ASoC: Actively manage WM8903 DC servo configuration
[deliverable/linux.git] / sound / soc / codecs / wm8903.c
index 389d2e8088f2f97342eba21516eb6213678dd8c1..2f912276a8f5b5c4614099418a5ce86b612071c1 100644 (file)
@@ -223,6 +223,9 @@ struct wm8903_priv {
        int fs;
        int deemph;
 
+       int dcs_pending;
+       int dcs_cache[4];
+
        /* Reference count */
        int class_w_users;
 
@@ -248,6 +251,10 @@ static int wm8903_volatile_register(struct snd_soc_codec *codec, unsigned int re
        case WM8903_WRITE_SEQUENCER_4:
        case WM8903_POWER_MANAGEMENT_3:
        case WM8903_POWER_MANAGEMENT_2:
+       case WM8903_DC_SERVO_READBACK_1:
+       case WM8903_DC_SERVO_READBACK_2:
+       case WM8903_DC_SERVO_READBACK_3:
+       case WM8903_DC_SERVO_READBACK_4:
                return 1;
 
        default:
@@ -315,6 +322,103 @@ static int wm8903_cp_event(struct snd_soc_dapm_widget *w,
        return 0;
 }
 
+static int wm8903_dcs_event(struct snd_soc_dapm_widget *w,
+                           struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               wm8903->dcs_pending |= 1 << w->shift;
+               break;
+       case SND_SOC_DAPM_PRE_PMD:
+               snd_soc_update_bits(codec, WM8903_DC_SERVO_0,
+                                   1 << w->shift, 0);
+               break;
+       }
+
+       return 0;
+}
+
+#define WM8903_DCS_MODE_WRITE_STOP 0
+#define WM8903_DCS_MODE_START_STOP 2
+
+static void wm8903_seq_notifier(struct snd_soc_dapm_context *dapm,
+                               enum snd_soc_dapm_type event, int subseq)
+{
+       struct snd_soc_codec *codec = container_of(dapm,
+                                                  struct snd_soc_codec, dapm);
+       struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+       int dcs_mode = WM8903_DCS_MODE_WRITE_STOP;
+       int i, val;
+
+       /* Complete any pending DC servo starts */
+       if (wm8903->dcs_pending) {
+               dev_dbg(codec->dev, "Starting DC servo for %x\n",
+                       wm8903->dcs_pending);
+
+               /* If we've no cached values then we need to do startup */
+               for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) {
+                       if (!(wm8903->dcs_pending & (1 << i)))
+                               continue;
+
+                       if (wm8903->dcs_cache[i]) {
+                               dev_dbg(codec->dev,
+                                       "Restore DC servo %d value %x\n",
+                                       3 - i, wm8903->dcs_cache[i]);
+
+                               snd_soc_write(codec, WM8903_DC_SERVO_4 + i,
+                                             wm8903->dcs_cache[i] & 0xff);
+                       } else {
+                               dev_dbg(codec->dev,
+                                       "Calibrate DC servo %d\n", 3 - i);
+                               dcs_mode = WM8903_DCS_MODE_START_STOP;
+                       }
+               }
+
+               /* Don't trust the cache for analogue */
+               if (wm8903->class_w_users)
+                       dcs_mode = WM8903_DCS_MODE_START_STOP;
+
+               snd_soc_update_bits(codec, WM8903_DC_SERVO_2,
+                                   WM8903_DCS_MODE_MASK, dcs_mode);
+
+               snd_soc_update_bits(codec, WM8903_DC_SERVO_0,
+                                   WM8903_DCS_ENA_MASK, wm8903->dcs_pending);
+
+               switch (dcs_mode) {
+               case WM8903_DCS_MODE_WRITE_STOP:
+                       break;
+
+               case WM8903_DCS_MODE_START_STOP:
+                       msleep(270);
+
+                       /* Cache the measured offsets for digital */
+                       if (wm8903->class_w_users)
+                               break;
+
+                       for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) {
+                               if (!(wm8903->dcs_pending & (1 << i)))
+                                       continue;
+
+                               val = snd_soc_read(codec,
+                                                  WM8903_DC_SERVO_READBACK_1 + i);
+                               dev_dbg(codec->dev, "DC servo %d: %x\n",
+                                       3 - i, val);
+                               wm8903->dcs_cache[i] = val;
+                       }
+                       break;
+
+               default:
+                       pr_warn("DCS mode %d delay not set\n", dcs_mode);
+                       break;
+               }
+
+               wm8903->dcs_pending = 0;
+       }
+}
+
 /*
  * When used with DAC outputs only the WM8903 charge pump supports
  * operation in class W mode, providing very low power consumption
@@ -847,10 +951,15 @@ SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 2, 0,
 SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 1, 0,
                   NULL, 0),
 
-SND_SOC_DAPM_PGA_S("HPL_DCS", 3, WM8903_DC_SERVO_0, 3, 0, NULL, 0),
-SND_SOC_DAPM_PGA_S("HPR_DCS", 3, WM8903_DC_SERVO_0, 2, 0, NULL, 0),
-SND_SOC_DAPM_PGA_S("LINEOUTL_DCS", 3, WM8903_DC_SERVO_0, 1, 0, NULL, 0),
-SND_SOC_DAPM_PGA_S("LINEOUTR_DCS", 3, WM8903_DC_SERVO_0, 0, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("DCS Master", WM8903_DC_SERVO_0, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_DCS", 3, SND_SOC_NOPM, 3, 0, wm8903_dcs_event,
+                  SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("HPR_DCS", 3, SND_SOC_NOPM, 2, 0, wm8903_dcs_event,
+                  SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("LINEOUTL_DCS", 3, SND_SOC_NOPM, 1, 0, wm8903_dcs_event,
+                  SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("LINEOUTR_DCS", 3, SND_SOC_NOPM, 0, 0, wm8903_dcs_event,
+                  SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
 
 SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0,
                 NULL, 0),
@@ -979,6 +1088,11 @@ static const struct snd_soc_dapm_route intercon[] = {
        { "LINEOUTL_ENA_DLY", NULL, "Left Line Output PGA" },
        { "LINEOUTR_ENA_DLY", NULL, "Right Line Output PGA" },
 
+       { "HPL_DCS", NULL, "DCS Master" },
+       { "HPR_DCS", NULL, "DCS Master" },
+       { "LINEOUTL_DCS", NULL, "DCS Master" },
+       { "LINEOUTR_DCS", NULL, "DCS Master" },
+
        { "HPL_DCS", NULL, "HPL_ENA_DLY" },
        { "HPR_DCS", NULL, "HPR_ENA_DLY" },
        { "LINEOUTL_DCS", NULL, "LINEOUTL_ENA_DLY" },
@@ -1870,9 +1984,9 @@ static int wm8903_probe(struct snd_soc_codec *codec)
        snd_soc_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val);
 
        /* Enable DAC soft mute by default */
-       val = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
-       val |= WM8903_DAC_MUTEMODE;
-       snd_soc_write(codec, WM8903_DAC_DIGITAL_1, val);
+       snd_soc_update_bits(codec, WM8903_DAC_DIGITAL_1,
+                           WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE,
+                           WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE);
 
        snd_soc_add_controls(codec, wm8903_snd_controls,
                                ARRAY_SIZE(wm8903_snd_controls));
@@ -1901,6 +2015,7 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8903 = {
        .reg_word_size = sizeof(u16),
        .reg_cache_default = wm8903_reg_defaults,
        .volatile_register = wm8903_volatile_register,
+       .seq_notifier = wm8903_seq_notifier,
 };
 
 #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
This page took 0.027385 seconds and 5 git commands to generate.