aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2010-03-03 08:47:03 -0500
committerMark Brown <broonie@opensource.wolfsonmicro.com>2010-03-03 12:08:43 -0500
commit913d7b4cc0d958df9f2e4bc0e6926c037d96d07e (patch)
tree002e053fcda7dd2fb41e90706963b2366e83e29c
parentb6877a477d356a7c07a6c173d58c34a0a6abb086 (diff)
ASoC: Add support for WM8960 capless mode
The WM8960 headphone outputs can be run in capless mode with OUT3 used to drive a pseudo ground for the headphone drivers. In this mode the mono mixer is not used, the mixer should be turned on in concert with the headphone output drivers and the device bias levels are managed differently. Also tweak the existing bias management to remove the use of active discharge while we're at it since that's often audible. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
-rw-r--r--include/sound/wm8960.h2
-rw-r--r--sound/soc/codecs/wm8960.c206
2 files changed, 179 insertions, 29 deletions
diff --git a/include/sound/wm8960.h b/include/sound/wm8960.h
index 808a258e696c..74e9a95529c5 100644
--- a/include/sound/wm8960.h
+++ b/include/sound/wm8960.h
@@ -16,6 +16,8 @@
16#define WM8960_DRES_MAX 3 16#define WM8960_DRES_MAX 3
17 17
18struct wm8960_data { 18struct wm8960_data {
19 bool capless; /* Headphone outputs configured in capless mode */
20
19 int dres; /* Discharge resistance for headphone outputs */ 21 int dres; /* Discharge resistance for headphone outputs */
20}; 22};
21 23
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c
index cf5cb3f73b69..c2960d3ec6df 100644
--- a/sound/soc/codecs/wm8960.c
+++ b/sound/soc/codecs/wm8960.c
@@ -31,8 +31,14 @@
31struct snd_soc_codec_device soc_codec_dev_wm8960; 31struct snd_soc_codec_device soc_codec_dev_wm8960;
32 32
33/* R25 - Power 1 */ 33/* R25 - Power 1 */
34#define WM8960_VMID_MASK 0x180
34#define WM8960_VREF 0x40 35#define WM8960_VREF 0x40
35 36
37/* R26 - Power 2 */
38#define WM8960_PWR2_LOUT1 0x40
39#define WM8960_PWR2_ROUT1 0x20
40#define WM8960_PWR2_OUT3 0x02
41
36/* R28 - Anti-pop 1 */ 42/* R28 - Anti-pop 1 */
37#define WM8960_POBCTRL 0x80 43#define WM8960_POBCTRL 0x80
38#define WM8960_BUFDCOPEN 0x10 44#define WM8960_BUFDCOPEN 0x10
@@ -42,6 +48,7 @@ struct snd_soc_codec_device soc_codec_dev_wm8960;
42 48
43/* R29 - Anti-pop 2 */ 49/* R29 - Anti-pop 2 */
44#define WM8960_DISOP 0x40 50#define WM8960_DISOP 0x40
51#define WM8960_DRES_MASK 0x30
45 52
46/* 53/*
47 * wm8960 register cache 54 * wm8960 register cache
@@ -68,6 +75,9 @@ static const u16 wm8960_reg[WM8960_CACHEREGNUM] = {
68struct wm8960_priv { 75struct wm8960_priv {
69 u16 reg_cache[WM8960_CACHEREGNUM]; 76 u16 reg_cache[WM8960_CACHEREGNUM];
70 struct snd_soc_codec codec; 77 struct snd_soc_codec codec;
78 struct snd_soc_dapm_widget *lout1;
79 struct snd_soc_dapm_widget *rout1;
80 struct snd_soc_dapm_widget *out3;
71}; 81};
72 82
73#define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) 83#define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0)
@@ -226,10 +236,6 @@ SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0,
226 &wm8960_routput_mixer[0], 236 &wm8960_routput_mixer[0],
227 ARRAY_SIZE(wm8960_routput_mixer)), 237 ARRAY_SIZE(wm8960_routput_mixer)),
228 238
229SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
230 &wm8960_mono_out[0],
231 ARRAY_SIZE(wm8960_mono_out)),
232
233SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), 239SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),
234SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), 240SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),
235 241
@@ -248,6 +254,17 @@ SND_SOC_DAPM_OUTPUT("SPK_RN"),
248SND_SOC_DAPM_OUTPUT("OUT3"), 254SND_SOC_DAPM_OUTPUT("OUT3"),
249}; 255};
250 256
257static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = {
258SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
259 &wm8960_mono_out[0],
260 ARRAY_SIZE(wm8960_mono_out)),
261};
262
263/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */
264static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = {
265SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0),
266};
267
251static const struct snd_soc_dapm_route audio_paths[] = { 268static const struct snd_soc_dapm_route audio_paths[] = {
252 { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, 269 { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
253 { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, 270 { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
@@ -278,9 +295,6 @@ static const struct snd_soc_dapm_route audio_paths[] = {
278 { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , 295 { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } ,
279 { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, 296 { "Right Output Mixer", "PCM Playback Switch", "Right DAC" },
280 297
281 { "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
282 { "Mono Output Mixer", "Right Switch", "Right Output Mixer" },
283
284 { "LOUT1 PGA", NULL, "Left Output Mixer" }, 298 { "LOUT1 PGA", NULL, "Left Output Mixer" },
285 { "ROUT1 PGA", NULL, "Right Output Mixer" }, 299 { "ROUT1 PGA", NULL, "Right Output Mixer" },
286 300
@@ -297,17 +311,65 @@ static const struct snd_soc_dapm_route audio_paths[] = {
297 { "SPK_LP", NULL, "Left Speaker Output" }, 311 { "SPK_LP", NULL, "Left Speaker Output" },
298 { "SPK_RN", NULL, "Right Speaker Output" }, 312 { "SPK_RN", NULL, "Right Speaker Output" },
299 { "SPK_RP", NULL, "Right Speaker Output" }, 313 { "SPK_RP", NULL, "Right Speaker Output" },
314};
315
316static const struct snd_soc_dapm_route audio_paths_out3[] = {
317 { "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
318 { "Mono Output Mixer", "Right Switch", "Right Output Mixer" },
300 319
301 { "OUT3", NULL, "Mono Output Mixer", } 320 { "OUT3", NULL, "Mono Output Mixer", }
302}; 321};
303 322
323static const struct snd_soc_dapm_route audio_paths_capless[] = {
324 { "HP_L", NULL, "OUT3 VMID" },
325 { "HP_R", NULL, "OUT3 VMID" },
326
327 { "OUT3 VMID", NULL, "Left Output Mixer" },
328 { "OUT3 VMID", NULL, "Right Output Mixer" },
329};
330
304static int wm8960_add_widgets(struct snd_soc_codec *codec) 331static int wm8960_add_widgets(struct snd_soc_codec *codec)
305{ 332{
333 struct wm8960_data *pdata = codec->dev->platform_data;
334 struct wm8960_priv *wm8960 = codec->private_data;
335 struct snd_soc_dapm_widget *w;
336
306 snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, 337 snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets,
307 ARRAY_SIZE(wm8960_dapm_widgets)); 338 ARRAY_SIZE(wm8960_dapm_widgets));
308 339
309 snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); 340 snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
310 341
342 /* In capless mode OUT3 is used to provide VMID for the
343 * headphone outputs, otherwise it is used as a mono mixer.
344 */
345 if (pdata && pdata->capless) {
346 snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_capless,
347 ARRAY_SIZE(wm8960_dapm_widgets_capless));
348
349 snd_soc_dapm_add_routes(codec, audio_paths_capless,
350 ARRAY_SIZE(audio_paths_capless));
351 } else {
352 snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_out3,
353 ARRAY_SIZE(wm8960_dapm_widgets_out3));
354
355 snd_soc_dapm_add_routes(codec, audio_paths_out3,
356 ARRAY_SIZE(audio_paths_out3));
357 }
358
359 /* We need to power up the headphone output stage out of
360 * sequence for capless mode. To save scanning the widget
361 * list each time to find the desired power state do so now
362 * and save the result.
363 */
364 list_for_each_entry(w, &codec->dapm_widgets, list) {
365 if (strcmp(w->name, "LOUT1 PGA") == 0)
366 wm8960->lout1 = w;
367 if (strcmp(w->name, "ROUT1 PGA") == 0)
368 wm8960->rout1 = w;
369 if (strcmp(w->name, "OUT3 VMID") == 0)
370 wm8960->out3 = w;
371 }
372
311 return 0; 373 return 0;
312} 374}
313 375
@@ -408,10 +470,9 @@ static int wm8960_mute(struct snd_soc_dai *dai, int mute)
408 return 0; 470 return 0;
409} 471}
410 472
411static int wm8960_set_bias_level(struct snd_soc_codec *codec, 473static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
412 enum snd_soc_bias_level level) 474 enum snd_soc_bias_level level)
413{ 475{
414 struct wm8960_data *pdata = codec->dev->platform_data;
415 u16 reg; 476 u16 reg;
416 477
417 switch (level) { 478 switch (level) {
@@ -430,18 +491,8 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec,
430 if (codec->bias_level == SND_SOC_BIAS_OFF) { 491 if (codec->bias_level == SND_SOC_BIAS_OFF) {
431 /* Enable anti-pop features */ 492 /* Enable anti-pop features */
432 snd_soc_write(codec, WM8960_APOP1, 493 snd_soc_write(codec, WM8960_APOP1,
433 WM8960_POBCTRL | WM8960_SOFT_ST | 494 WM8960_POBCTRL | WM8960_SOFT_ST |
434 WM8960_BUFDCOPEN | WM8960_BUFIOEN); 495 WM8960_BUFDCOPEN | WM8960_BUFIOEN);
435
436 /* Discharge HP output */
437 reg = WM8960_DISOP;
438 if (pdata)
439 reg |= pdata->dres << 4;
440 snd_soc_write(codec, WM8960_APOP2, reg);
441
442 msleep(400);
443
444 snd_soc_write(codec, WM8960_APOP2, 0);
445 496
446 /* Enable & ramp VMID at 2x50k */ 497 /* Enable & ramp VMID at 2x50k */
447 reg = snd_soc_read(codec, WM8960_POWER1); 498 reg = snd_soc_read(codec, WM8960_POWER1);
@@ -472,8 +523,101 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec,
472 /* Disable VMID and VREF, let them discharge */ 523 /* Disable VMID and VREF, let them discharge */
473 snd_soc_write(codec, WM8960_POWER1, 0); 524 snd_soc_write(codec, WM8960_POWER1, 0);
474 msleep(600); 525 msleep(600);
526 break;
527 }
528
529 codec->bias_level = level;
530
531 return 0;
532}
533
534static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
535 enum snd_soc_bias_level level)
536{
537 struct wm8960_priv *wm8960 = codec->private_data;
538 int reg;
539
540 switch (level) {
541 case SND_SOC_BIAS_ON:
542 break;
543
544 case SND_SOC_BIAS_PREPARE:
545 switch (codec->bias_level) {
546 case SND_SOC_BIAS_STANDBY:
547 /* Enable anti pop mode */
548 snd_soc_update_bits(codec, WM8960_APOP1,
549 WM8960_POBCTRL | WM8960_SOFT_ST |
550 WM8960_BUFDCOPEN,
551 WM8960_POBCTRL | WM8960_SOFT_ST |
552 WM8960_BUFDCOPEN);
553
554 /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */
555 reg = 0;
556 if (wm8960->lout1 && wm8960->lout1->power)
557 reg |= WM8960_PWR2_LOUT1;
558 if (wm8960->rout1 && wm8960->rout1->power)
559 reg |= WM8960_PWR2_ROUT1;
560 if (wm8960->out3 && wm8960->out3->power)
561 reg |= WM8960_PWR2_OUT3;
562 snd_soc_update_bits(codec, WM8960_POWER2,
563 WM8960_PWR2_LOUT1 |
564 WM8960_PWR2_ROUT1 |
565 WM8960_PWR2_OUT3, reg);
566
567 /* Enable VMID at 2*50k */
568 snd_soc_update_bits(codec, WM8960_POWER1,
569 WM8960_VMID_MASK, 0x80);
570
571 /* Ramp */
572 msleep(100);
573
574 /* Enable VREF */
575 snd_soc_update_bits(codec, WM8960_POWER1,
576 WM8960_VREF, WM8960_VREF);
577
578 msleep(100);
579 break;
580
581 case SND_SOC_BIAS_ON:
582 /* Enable anti-pop mode */
583 snd_soc_update_bits(codec, WM8960_APOP1,
584 WM8960_POBCTRL | WM8960_SOFT_ST |
585 WM8960_BUFDCOPEN,
586 WM8960_POBCTRL | WM8960_SOFT_ST |
587 WM8960_BUFDCOPEN);
588
589 /* Disable VMID and VREF */
590 snd_soc_update_bits(codec, WM8960_POWER1,
591 WM8960_VREF | WM8960_VMID_MASK, 0);
592 break;
593
594 default:
595 break;
596 }
597 break;
598
599 case SND_SOC_BIAS_STANDBY:
600 switch (codec->bias_level) {
601 case SND_SOC_BIAS_PREPARE:
602 /* Disable HP discharge */
603 snd_soc_update_bits(codec, WM8960_APOP2,
604 WM8960_DISOP | WM8960_DRES_MASK,
605 0);
606
607 /* Disable anti-pop features */
608 snd_soc_update_bits(codec, WM8960_APOP1,
609 WM8960_POBCTRL | WM8960_SOFT_ST |
610 WM8960_BUFDCOPEN,
611 WM8960_POBCTRL | WM8960_SOFT_ST |
612 WM8960_BUFDCOPEN);
613 break;
614
615 default:
616 break;
617 }
618 break;
475 619
476 snd_soc_write(codec, WM8960_APOP1, 0); 620 case SND_SOC_BIAS_OFF:
477 break; 621 break;
478 } 622 }
479 623
@@ -663,7 +807,7 @@ static int wm8960_suspend(struct platform_device *pdev, pm_message_t state)
663 struct snd_soc_device *socdev = platform_get_drvdata(pdev); 807 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
664 struct snd_soc_codec *codec = socdev->card->codec; 808 struct snd_soc_codec *codec = socdev->card->codec;
665 809
666 wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF); 810 codec->set_bias_level(codec, SND_SOC_BIAS_OFF);
667 return 0; 811 return 0;
668} 812}
669 813
@@ -682,8 +826,8 @@ static int wm8960_resume(struct platform_device *pdev)
682 codec->hw_write(codec->control_data, data, 2); 826 codec->hw_write(codec->control_data, data, 2);
683 } 827 }
684 828
685 wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 829 codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
686 wm8960_set_bias_level(codec, codec->suspend_bias_level); 830 codec->set_bias_level(codec, codec->suspend_bias_level);
687 return 0; 831 return 0;
688} 832}
689 833
@@ -753,6 +897,8 @@ static int wm8960_register(struct wm8960_priv *wm8960,
753 goto err; 897 goto err;
754 } 898 }
755 899
900 codec->set_bias_level = wm8960_set_bias_level_out3;
901
756 if (!pdata) { 902 if (!pdata) {
757 dev_warn(codec->dev, "No platform data supplied\n"); 903 dev_warn(codec->dev, "No platform data supplied\n");
758 } else { 904 } else {
@@ -760,6 +906,9 @@ static int wm8960_register(struct wm8960_priv *wm8960,
760 dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); 906 dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres);
761 pdata->dres = 0; 907 pdata->dres = 0;
762 } 908 }
909
910 if (pdata->capless)
911 codec->set_bias_level = wm8960_set_bias_level_capless;
763 } 912 }
764 913
765 mutex_init(&codec->mutex); 914 mutex_init(&codec->mutex);
@@ -770,7 +919,6 @@ static int wm8960_register(struct wm8960_priv *wm8960,
770 codec->name = "WM8960"; 919 codec->name = "WM8960";
771 codec->owner = THIS_MODULE; 920 codec->owner = THIS_MODULE;
772 codec->bias_level = SND_SOC_BIAS_OFF; 921 codec->bias_level = SND_SOC_BIAS_OFF;
773 codec->set_bias_level = wm8960_set_bias_level;
774 codec->dai = &wm8960_dai; 922 codec->dai = &wm8960_dai;
775 codec->num_dai = 1; 923 codec->num_dai = 1;
776 codec->reg_cache_size = WM8960_CACHEREGNUM; 924 codec->reg_cache_size = WM8960_CACHEREGNUM;
@@ -792,7 +940,7 @@ static int wm8960_register(struct wm8960_priv *wm8960,
792 940
793 wm8960_dai.dev = codec->dev; 941 wm8960_dai.dev = codec->dev;
794 942
795 wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 943 codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
796 944
797 /* Latch the update bits */ 945 /* Latch the update bits */
798 reg = snd_soc_read(codec, WM8960_LINVOL); 946 reg = snd_soc_read(codec, WM8960_LINVOL);
@@ -841,7 +989,7 @@ err:
841 989
842static void wm8960_unregister(struct wm8960_priv *wm8960) 990static void wm8960_unregister(struct wm8960_priv *wm8960)
843{ 991{
844 wm8960_set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); 992 wm8960->codec.set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF);
845 snd_soc_unregister_dai(&wm8960_dai); 993 snd_soc_unregister_dai(&wm8960_dai);
846 snd_soc_unregister_codec(&wm8960->codec); 994 snd_soc_unregister_codec(&wm8960->codec);
847 kfree(wm8960); 995 kfree(wm8960);