From 821dd91ec7838e1313d783384ea9ce43510d4013 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 21 Jan 2010 11:33:20 +0000 Subject: ASoC: Use BIAS_OFF when idle for wm_hubs devices This provides a small power saving when audio is inactive. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm_hubs.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'sound/soc/codecs/wm_hubs.c') diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index d73c30536a2..a67319d9ca7 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -753,6 +753,12 @@ int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *codec, WM8993_LINEOUT2_MODE, WM8993_LINEOUT2_MODE); + /* If the line outputs are differential then we aren't presenting + * VMID as an output and can disable it. + */ + if (lineout1_diff && lineout2_diff) + codec->idle_bias_off = 1; + if (lineout1fb) snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL, WM8993_LINEOUT1_FB, WM8993_LINEOUT1_FB); -- cgit v1.2.3-70-g09d2 From 3ed7074c4cc0de5ba77e180e5d96c23ef96859f0 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 20 Jan 2010 17:39:45 +0000 Subject: ASoC: Improved wm_hubs headphone handling Perform DC servo offset calibration using a series update sequence rather than startup update sequence, tuning the configuration of the WM8993 DC servo to make best use of this. Also introduce currently unused data allowing us to correct for any systematic errors in the DC servo calibration results and an alternative startup path for the headphone output which performs better with some chip revisions. The alternative setup sequence is enabled for WM8993. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8993.c | 8 +++ sound/soc/codecs/wm_hubs.c | 142 ++++++++++++++++++++++++++++++++++++--------- sound/soc/codecs/wm_hubs.h | 6 ++ 3 files changed, 130 insertions(+), 26 deletions(-) (limited to 'sound/soc/codecs/wm_hubs.c') diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 828d8174d5b..bacfc2f20d7 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -213,6 +213,7 @@ static struct { }; struct wm8993_priv { + struct wm_hubs_data hubs_data; u16 reg_cache[WM8993_REGISTER_COUNT]; struct wm8993_platform_data pdata; struct snd_soc_codec codec; @@ -997,6 +998,11 @@ static int wm8993_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Tune DC servo configuration */ + snd_soc_write(codec, 0x44, 3); + snd_soc_write(codec, 0x56, 3); + snd_soc_write(codec, 0x44, 0); + /* Bring up VMID with fast soft start */ snd_soc_update_bits(codec, WM8993_ANTIPOP2, WM8993_STARTUP_BIAS_ENA | @@ -1591,6 +1597,8 @@ static int wm8993_i2c_probe(struct i2c_client *i2c, codec->num_dai = 1; codec->private_data = wm8993; + wm8993->hubs_data.hp_startup_mode = 1; + memcpy(wm8993->reg_cache, wm8993_reg_defaults, sizeof(wm8993->reg_cache)); diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index a67319d9ca7..0ad9f5d536c 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -68,24 +68,77 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec) int count = 0; dev_dbg(codec->dev, "Waiting for DC servo...\n"); + do { count++; msleep(1); reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_0); - dev_dbg(codec->dev, "DC servo status: %x\n", reg); - } while ((reg & WM8993_DCS_CAL_COMPLETE_MASK) - != WM8993_DCS_CAL_COMPLETE_MASK && count < 1000); + dev_dbg(codec->dev, "DC servo: %x\n", reg); + } while (reg & WM8993_DCS_DATAPATH_BUSY); - if ((reg & WM8993_DCS_CAL_COMPLETE_MASK) - != WM8993_DCS_CAL_COMPLETE_MASK) + if (reg & WM8993_DCS_DATAPATH_BUSY) dev_err(codec->dev, "Timed out waiting for DC Servo\n"); } +/* + * Startup calibration of the DC servo + */ +static void calibrate_dc_servo(struct snd_soc_codec *codec) +{ + struct wm_hubs_data *hubs = codec->private_data; + u16 reg, dcs_cfg; + + /* Set for 32 series updates */ + snd_soc_update_bits(codec, WM8993_DC_SERVO_1, + WM8993_DCS_SERIES_NO_01_MASK, + 32 << WM8993_DCS_SERIES_NO_01_SHIFT); + + /* Enable the DC servo. Write all bits to avoid triggering startup + * or write calibration. + */ + snd_soc_update_bits(codec, WM8993_DC_SERVO_0, + 0xFFFF, + WM8993_DCS_ENA_CHAN_0 | + WM8993_DCS_ENA_CHAN_1 | + WM8993_DCS_TRIG_SERIES_1 | + WM8993_DCS_TRIG_SERIES_0); + + wait_for_dc_servo(codec); + + /* Apply correction to DC servo result */ + if (hubs->dcs_codes) { + dev_dbg(codec->dev, "Applying %d code DC servo correction\n", + hubs->dcs_codes); + + /* HPOUT1L */ + reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1) & + WM8993_DCS_INTEG_CHAN_0_MASK;; + reg += hubs->dcs_codes; + dcs_cfg = reg << WM8993_DCS_DAC_WR_VAL_1_SHIFT; + + /* HPOUT1R */ + reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2) & + WM8993_DCS_INTEG_CHAN_1_MASK; + reg += hubs->dcs_codes; + dcs_cfg |= reg; + + /* Do it */ + snd_soc_write(codec, WM8993_DC_SERVO_3, dcs_cfg); + snd_soc_update_bits(codec, WM8993_DC_SERVO_0, + WM8993_DCS_TRIG_DAC_WR_0 | + WM8993_DCS_TRIG_DAC_WR_1, + WM8993_DCS_TRIG_DAC_WR_0 | + WM8993_DCS_TRIG_DAC_WR_1); + + wait_for_dc_servo(codec); + } +} + /* * Update the DC servo calibration on gain changes */ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); int ret; @@ -251,6 +304,47 @@ SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1, line_tlv), }; +static int hp_supply_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm_hubs_data *hubs = codec->private_data; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switch (hubs->hp_startup_mode) { + case 0: + break; + case 1: + /* Enable the headphone amp */ + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, + WM8993_HPOUT1L_ENA | + WM8993_HPOUT1R_ENA, + WM8993_HPOUT1L_ENA | + WM8993_HPOUT1R_ENA); + + /* Enable the second stage */ + snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0, + WM8993_HPOUT1L_DLY | + WM8993_HPOUT1R_DLY, + WM8993_HPOUT1L_DLY | + WM8993_HPOUT1R_DLY); + break; + default: + dev_err(codec->dev, "Unknown HP startup mode %d\n", + hubs->hp_startup_mode); + break; + } + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1, + WM8993_CP_ENA, 0); + break; + } + + return 0; +} + static int hp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -271,14 +365,11 @@ static int hp_event(struct snd_soc_dapm_widget *w, reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY; snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg); - /* Start the DC servo */ - snd_soc_update_bits(codec, WM8993_DC_SERVO_0, - 0xFFFF, - WM8993_DCS_ENA_CHAN_0 | - WM8993_DCS_ENA_CHAN_1 | - WM8993_DCS_TRIG_STARTUP_1 | - WM8993_DCS_TRIG_STARTUP_0); - wait_for_dc_servo(codec); + /* Smallest supported update interval */ + snd_soc_update_bits(codec, WM8993_DC_SERVO_1, + WM8993_DCS_TIMER_PERIOD_01_MASK, 1); + + calibrate_dc_servo(codec); reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT | WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT; @@ -286,23 +377,19 @@ static int hp_event(struct snd_soc_dapm_widget *w, break; case SND_SOC_DAPM_PRE_PMD: - reg &= ~(WM8993_HPOUT1L_RMV_SHORT | - WM8993_HPOUT1L_DLY | - WM8993_HPOUT1L_OUTP | - WM8993_HPOUT1R_RMV_SHORT | - WM8993_HPOUT1R_DLY | - WM8993_HPOUT1R_OUTP); + snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0, + WM8993_HPOUT1L_DLY | + WM8993_HPOUT1R_DLY | + WM8993_HPOUT1L_RMV_SHORT | + WM8993_HPOUT1R_RMV_SHORT, 0); - snd_soc_update_bits(codec, WM8993_DC_SERVO_0, - 0xffff, 0); + snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0, + WM8993_HPOUT1L_OUTP | + WM8993_HPOUT1R_OUTP, 0); - snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg); snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, 0); - - snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1, - WM8993_CP_ENA, 0); break; } @@ -473,6 +560,8 @@ SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0, SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0), SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("Headphone Supply", SND_SOC_NOPM, 0, 0, hp_supply_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0, NULL, 0, hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), @@ -626,6 +715,7 @@ static const struct snd_soc_dapm_route analogue_routes[] = { { "Headphone PGA", NULL, "Left Headphone Mux" }, { "Headphone PGA", NULL, "Right Headphone Mux" }, { "Headphone PGA", NULL, "CLK_SYS" }, + { "Headphone PGA", NULL, "Headphone Supply" }, { "HPOUT1L", NULL, "Headphone PGA" }, { "HPOUT1R", NULL, "Headphone PGA" }, diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h index 36d3fba1de8..420104fe9c9 100644 --- a/sound/soc/codecs/wm_hubs.h +++ b/sound/soc/codecs/wm_hubs.h @@ -18,6 +18,12 @@ struct snd_soc_codec; extern const unsigned int wm_hubs_spkmix_tlv[]; +/* This *must* be the first element of the codec->private_data struct */ +struct wm_hubs_data { + int dcs_codes; + int hp_startup_mode; +}; + extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *); extern int wm_hubs_add_analogue_routes(struct snd_soc_codec *, int, int); extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *, -- cgit v1.2.3-70-g09d2