summaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs/wm_hubs.c
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2010-01-20 17:39:45 +0000
committerMark Brown <broonie@opensource.wolfsonmicro.com>2010-02-01 18:35:46 +0000
commit3ed7074c4cc0de5ba77e180e5d96c23ef96859f0 (patch)
treef9a08d09b5ccc96664f385c5c74860802c40e213 /sound/soc/codecs/wm_hubs.c
parent2f1ff6614cb5938e5c5760358752d92deb67fb63 (diff)
downloadlinux-3ed7074c4cc0de5ba77e180e5d96c23ef96859f0.tar.bz2
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 <broonie@opensource.wolfsonmicro.com> Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
Diffstat (limited to 'sound/soc/codecs/wm_hubs.c')
-rw-r--r--sound/soc/codecs/wm_hubs.c142
1 files changed, 116 insertions, 26 deletions
diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c
index a67319d9ca7e..0ad9f5d536c6 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" },