aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2010-01-20 12:39:45 -0500
committerMark Brown <broonie@opensource.wolfsonmicro.com>2010-02-01 13:35:46 -0500
commit3ed7074c4cc0de5ba77e180e5d96c23ef96859f0 (patch)
treef9a08d09b5ccc96664f385c5c74860802c40e213
parent2f1ff6614cb5938e5c5760358752d92deb67fb63 (diff)
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>
-rw-r--r--sound/soc/codecs/wm8993.c8
-rw-r--r--sound/soc/codecs/wm_hubs.c142
-rw-r--r--sound/soc/codecs/wm_hubs.h6
3 files changed, 130 insertions, 26 deletions
diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c
index 828d8174d5b7..bacfc2f20d70 100644
--- a/sound/soc/codecs/wm8993.c
+++ b/sound/soc/codecs/wm8993.c
@@ -213,6 +213,7 @@ static struct {
213}; 213};
214 214
215struct wm8993_priv { 215struct wm8993_priv {
216 struct wm_hubs_data hubs_data;
216 u16 reg_cache[WM8993_REGISTER_COUNT]; 217 u16 reg_cache[WM8993_REGISTER_COUNT];
217 struct wm8993_platform_data pdata; 218 struct wm8993_platform_data pdata;
218 struct snd_soc_codec codec; 219 struct snd_soc_codec codec;
@@ -997,6 +998,11 @@ static int wm8993_set_bias_level(struct snd_soc_codec *codec,
997 998
998 case SND_SOC_BIAS_STANDBY: 999 case SND_SOC_BIAS_STANDBY:
999 if (codec->bias_level == SND_SOC_BIAS_OFF) { 1000 if (codec->bias_level == SND_SOC_BIAS_OFF) {
1001 /* Tune DC servo configuration */
1002 snd_soc_write(codec, 0x44, 3);
1003 snd_soc_write(codec, 0x56, 3);
1004 snd_soc_write(codec, 0x44, 0);
1005
1000 /* Bring up VMID with fast soft start */ 1006 /* Bring up VMID with fast soft start */
1001 snd_soc_update_bits(codec, WM8993_ANTIPOP2, 1007 snd_soc_update_bits(codec, WM8993_ANTIPOP2,
1002 WM8993_STARTUP_BIAS_ENA | 1008 WM8993_STARTUP_BIAS_ENA |
@@ -1591,6 +1597,8 @@ static int wm8993_i2c_probe(struct i2c_client *i2c,
1591 codec->num_dai = 1; 1597 codec->num_dai = 1;
1592 codec->private_data = wm8993; 1598 codec->private_data = wm8993;
1593 1599
1600 wm8993->hubs_data.hp_startup_mode = 1;
1601
1594 memcpy(wm8993->reg_cache, wm8993_reg_defaults, 1602 memcpy(wm8993->reg_cache, wm8993_reg_defaults,
1595 sizeof(wm8993->reg_cache)); 1603 sizeof(wm8993->reg_cache));
1596 1604
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)
68 int count = 0; 68 int count = 0;
69 69
70 dev_dbg(codec->dev, "Waiting for DC servo...\n"); 70 dev_dbg(codec->dev, "Waiting for DC servo...\n");
71
71 do { 72 do {
72 count++; 73 count++;
73 msleep(1); 74 msleep(1);
74 reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_0); 75 reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_0);
75 dev_dbg(codec->dev, "DC servo status: %x\n", reg); 76 dev_dbg(codec->dev, "DC servo: %x\n", reg);
76 } while ((reg & WM8993_DCS_CAL_COMPLETE_MASK) 77 } while (reg & WM8993_DCS_DATAPATH_BUSY);
77 != WM8993_DCS_CAL_COMPLETE_MASK && count < 1000);
78 78
79 if ((reg & WM8993_DCS_CAL_COMPLETE_MASK) 79 if (reg & WM8993_DCS_DATAPATH_BUSY)
80 != WM8993_DCS_CAL_COMPLETE_MASK)
81 dev_err(codec->dev, "Timed out waiting for DC Servo\n"); 80 dev_err(codec->dev, "Timed out waiting for DC Servo\n");
82} 81}
83 82
84/* 83/*
84 * Startup calibration of the DC servo
85 */
86static void calibrate_dc_servo(struct snd_soc_codec *codec)
87{
88 struct wm_hubs_data *hubs = codec->private_data;
89 u16 reg, dcs_cfg;
90
91 /* Set for 32 series updates */
92 snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
93 WM8993_DCS_SERIES_NO_01_MASK,
94 32 << WM8993_DCS_SERIES_NO_01_SHIFT);
95
96 /* Enable the DC servo. Write all bits to avoid triggering startup
97 * or write calibration.
98 */
99 snd_soc_update_bits(codec, WM8993_DC_SERVO_0,
100 0xFFFF,
101 WM8993_DCS_ENA_CHAN_0 |
102 WM8993_DCS_ENA_CHAN_1 |
103 WM8993_DCS_TRIG_SERIES_1 |
104 WM8993_DCS_TRIG_SERIES_0);
105
106 wait_for_dc_servo(codec);
107
108 /* Apply correction to DC servo result */
109 if (hubs->dcs_codes) {
110 dev_dbg(codec->dev, "Applying %d code DC servo correction\n",
111 hubs->dcs_codes);
112
113 /* HPOUT1L */
114 reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1) &
115 WM8993_DCS_INTEG_CHAN_0_MASK;;
116 reg += hubs->dcs_codes;
117 dcs_cfg = reg << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
118
119 /* HPOUT1R */
120 reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2) &
121 WM8993_DCS_INTEG_CHAN_1_MASK;
122 reg += hubs->dcs_codes;
123 dcs_cfg |= reg;
124
125 /* Do it */
126 snd_soc_write(codec, WM8993_DC_SERVO_3, dcs_cfg);
127 snd_soc_update_bits(codec, WM8993_DC_SERVO_0,
128 WM8993_DCS_TRIG_DAC_WR_0 |
129 WM8993_DCS_TRIG_DAC_WR_1,
130 WM8993_DCS_TRIG_DAC_WR_0 |
131 WM8993_DCS_TRIG_DAC_WR_1);
132
133 wait_for_dc_servo(codec);
134 }
135}
136
137/*
85 * Update the DC servo calibration on gain changes 138 * Update the DC servo calibration on gain changes
86 */ 139 */
87static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, 140static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,
88 struct snd_ctl_elem_value *ucontrol) 141 struct snd_ctl_elem_value *ucontrol)
89{ 142{
90 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 143 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
91 int ret; 144 int ret;
@@ -251,6 +304,47 @@ SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1,
251 line_tlv), 304 line_tlv),
252}; 305};
253 306
307static int hp_supply_event(struct snd_soc_dapm_widget *w,
308 struct snd_kcontrol *kcontrol, int event)
309{
310 struct snd_soc_codec *codec = w->codec;
311 struct wm_hubs_data *hubs = codec->private_data;
312
313 switch (event) {
314 case SND_SOC_DAPM_PRE_PMU:
315 switch (hubs->hp_startup_mode) {
316 case 0:
317 break;
318 case 1:
319 /* Enable the headphone amp */
320 snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
321 WM8993_HPOUT1L_ENA |
322 WM8993_HPOUT1R_ENA,
323 WM8993_HPOUT1L_ENA |
324 WM8993_HPOUT1R_ENA);
325
326 /* Enable the second stage */
327 snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
328 WM8993_HPOUT1L_DLY |
329 WM8993_HPOUT1R_DLY,
330 WM8993_HPOUT1L_DLY |
331 WM8993_HPOUT1R_DLY);
332 break;
333 default:
334 dev_err(codec->dev, "Unknown HP startup mode %d\n",
335 hubs->hp_startup_mode);
336 break;
337 }
338
339 case SND_SOC_DAPM_PRE_PMD:
340 snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
341 WM8993_CP_ENA, 0);
342 break;
343 }
344
345 return 0;
346}
347
254static int hp_event(struct snd_soc_dapm_widget *w, 348static int hp_event(struct snd_soc_dapm_widget *w,
255 struct snd_kcontrol *kcontrol, int event) 349 struct snd_kcontrol *kcontrol, int event)
256{ 350{
@@ -271,14 +365,11 @@ static int hp_event(struct snd_soc_dapm_widget *w,
271 reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY; 365 reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY;
272 snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg); 366 snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
273 367
274 /* Start the DC servo */ 368 /* Smallest supported update interval */
275 snd_soc_update_bits(codec, WM8993_DC_SERVO_0, 369 snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
276 0xFFFF, 370 WM8993_DCS_TIMER_PERIOD_01_MASK, 1);
277 WM8993_DCS_ENA_CHAN_0 | 371
278 WM8993_DCS_ENA_CHAN_1 | 372 calibrate_dc_servo(codec);
279 WM8993_DCS_TRIG_STARTUP_1 |
280 WM8993_DCS_TRIG_STARTUP_0);
281 wait_for_dc_servo(codec);
282 373
283 reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT | 374 reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT |
284 WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT; 375 WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT;
@@ -286,23 +377,19 @@ static int hp_event(struct snd_soc_dapm_widget *w,
286 break; 377 break;
287 378
288 case SND_SOC_DAPM_PRE_PMD: 379 case SND_SOC_DAPM_PRE_PMD:
289 reg &= ~(WM8993_HPOUT1L_RMV_SHORT | 380 snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
290 WM8993_HPOUT1L_DLY | 381 WM8993_HPOUT1L_DLY |
291 WM8993_HPOUT1L_OUTP | 382 WM8993_HPOUT1R_DLY |
292 WM8993_HPOUT1R_RMV_SHORT | 383 WM8993_HPOUT1L_RMV_SHORT |
293 WM8993_HPOUT1R_DLY | 384 WM8993_HPOUT1R_RMV_SHORT, 0);
294 WM8993_HPOUT1R_OUTP);
295 385
296 snd_soc_update_bits(codec, WM8993_DC_SERVO_0, 386 snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
297 0xffff, 0); 387 WM8993_HPOUT1L_OUTP |
388 WM8993_HPOUT1R_OUTP, 0);
298 389
299 snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
300 snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, 390 snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
301 WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, 391 WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA,
302 0); 392 0);
303
304 snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
305 WM8993_CP_ENA, 0);
306 break; 393 break;
307 } 394 }
308 395
@@ -473,6 +560,8 @@ SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0,
473SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0), 560SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0),
474SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0), 561SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0),
475 562
563SND_SOC_DAPM_SUPPLY("Headphone Supply", SND_SOC_NOPM, 0, 0, hp_supply_event,
564 SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
476SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0, 565SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0,
477 NULL, 0, 566 NULL, 0,
478 hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), 567 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[] = {
626 { "Headphone PGA", NULL, "Left Headphone Mux" }, 715 { "Headphone PGA", NULL, "Left Headphone Mux" },
627 { "Headphone PGA", NULL, "Right Headphone Mux" }, 716 { "Headphone PGA", NULL, "Right Headphone Mux" },
628 { "Headphone PGA", NULL, "CLK_SYS" }, 717 { "Headphone PGA", NULL, "CLK_SYS" },
718 { "Headphone PGA", NULL, "Headphone Supply" },
629 719
630 { "HPOUT1L", NULL, "Headphone PGA" }, 720 { "HPOUT1L", NULL, "Headphone PGA" },
631 { "HPOUT1R", NULL, "Headphone PGA" }, 721 { "HPOUT1R", NULL, "Headphone PGA" },
diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h
index 36d3fba1de8b..420104fe9c90 100644
--- a/sound/soc/codecs/wm_hubs.h
+++ b/sound/soc/codecs/wm_hubs.h
@@ -18,6 +18,12 @@ struct snd_soc_codec;
18 18
19extern const unsigned int wm_hubs_spkmix_tlv[]; 19extern const unsigned int wm_hubs_spkmix_tlv[];
20 20
21/* This *must* be the first element of the codec->private_data struct */
22struct wm_hubs_data {
23 int dcs_codes;
24 int hp_startup_mode;
25};
26
21extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *); 27extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *);
22extern int wm_hubs_add_analogue_routes(struct snd_soc_codec *, int, int); 28extern int wm_hubs_add_analogue_routes(struct snd_soc_codec *, int, int);
23extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *, 29extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *,