summaryrefslogtreecommitdiff
path: root/sound/soc/codecs/wm8903.c
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2011-02-09 20:14:42 +0000
committerMark Brown <broonie@opensource.wolfsonmicro.com>2011-02-10 10:45:05 +0000
commitc5b6a9feaeb0fa0e39e3fc10f9bf8cc8de498739 (patch)
treec8f8fb62bfec14bc807d97c93441097d2db3c42b /sound/soc/codecs/wm8903.c
parentfa9879edebdaad4cfcd2dbe3eaa2ba0dc4f0a262 (diff)
ASoC: Actively manage WM8903 DC servo configuration
Explicitly cache the DC servo offsets for digital paths in the driver, allowing them to be preserved over suspend and resume, and ensure that we recalibrate analogue outputs paths when they are in use so that we cover any changes in the input offset. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
Diffstat (limited to 'sound/soc/codecs/wm8903.c')
-rw-r--r--sound/soc/codecs/wm8903.c123
1 files changed, 119 insertions, 4 deletions
diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c
index 119abceb1d85..2f912276a8f5 100644
--- a/sound/soc/codecs/wm8903.c
+++ b/sound/soc/codecs/wm8903.c
@@ -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" },
@@ -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)