ALSA: hda - Fix D3 clock stop check for codecs with own set_power_state op
authorTakashi Iwai <tiwai@suse.de>
Tue, 28 Aug 2012 16:59:20 +0000 (09:59 -0700)
committerTakashi Iwai <tiwai@suse.de>
Thu, 30 Aug 2012 14:48:55 +0000 (07:48 -0700)
When a codec provides its own set_power_state op, the D3-clock-stop
isn't checked correctly.  And the recent changes for repeating the
state-setting operation isn't applied to such a codec, too.

This patch fixes these issues by moving the call of codec's own op to
the place where the generic power-set operation is done, and move the
power-state synchronization code out of
snd_hda_set_power_state_to_all() so that it can be called always at
the end of power-up/down sequence, and updates the D3 clock-stop flag
properly.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/hda_codec.c

index 90b34e8..409f5ec 100644 (file)
@@ -3518,20 +3518,6 @@ void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg,
                snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE,
                                    power_state);
        }
-
-       if (power_state == AC_PWRST_D0) {
-               unsigned long end_time;
-               int state;
-               /* wait until the codec reachs to D0 */
-               end_time = jiffies + msecs_to_jiffies(500);
-               do {
-                       state = snd_hda_codec_read(codec, fg, 0,
-                                                  AC_VERB_GET_POWER_STATE, 0);
-                       if (state == power_state)
-                               break;
-                       msleep(1);
-               } while (time_after_eq(end_time, jiffies));
-       }
 }
 EXPORT_SYMBOL_HDA(snd_hda_codec_set_power_to_all);
 
@@ -3552,6 +3538,32 @@ static bool snd_hda_codec_get_supported_ps(struct hda_codec *codec, hda_nid_t fg
 }
 
 /*
+ * wait until the state is reached, returns the current state
+ */
+static unsigned int hda_sync_power_state(struct hda_codec *codec,
+                                        hda_nid_t fg,
+                                        unsigned int power_state)
+{
+       unsigned long end_time = jiffies + msecs_to_jiffies(500);
+       unsigned int state, actual_state;
+
+       for (;;) {
+               state = snd_hda_codec_read(codec, fg, 0,
+                                          AC_VERB_GET_POWER_STATE, 0);
+               if (state & AC_PWRST_ERROR)
+                       break;
+               actual_state = (state >> 4) & 0x0f;
+               if (actual_state == power_state)
+                       break;
+               if (time_after_eq(jiffies, end_time))
+                       break;
+               /* wait until the codec reachs to the target state */
+               msleep(1);
+       }
+       return state;
+}
+
+/*
  * set power state of the codec
  */
 static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
@@ -3564,11 +3576,6 @@ static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
        codec->d3_stop_clk_ok = 0;
 #endif
 
-       if (codec->patch_ops.set_power_state) {
-               codec->patch_ops.set_power_state(codec, fg, power_state);
-               return;
-       }
-
        /* this delay seems necessary to avoid click noise at power-down */
        if (power_state == AC_PWRST_D3) {
                /* transition time less than 10ms for power down */
@@ -3577,11 +3584,17 @@ static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
 
        /* repeat power states setting at most 10 times*/
        for (count = 0; count < 10; count++) {
-               snd_hda_codec_read(codec, fg, 0, AC_VERB_SET_POWER_STATE,
-                                   power_state);
-               snd_hda_codec_set_power_to_all(codec, fg, power_state, true);
-               state = snd_hda_codec_read(codec, fg, 0,
-                                          AC_VERB_GET_POWER_STATE, 0);
+               if (codec->patch_ops.set_power_state)
+                       codec->patch_ops.set_power_state(codec, fg,
+                                                        power_state);
+               else {
+                       snd_hda_codec_read(codec, fg, 0,
+                                          AC_VERB_SET_POWER_STATE,
+                                          power_state);
+                       snd_hda_codec_set_power_to_all(codec, fg, power_state,
+                                                      true);
+               }
+               state = hda_sync_power_state(codec, fg, power_state);
                if (!(state & AC_PWRST_ERROR))
                        break;
        }