diff options
-rw-r--r-- | include/sound/wm8960.h | 2 | ||||
-rw-r--r-- | sound/soc/codecs/wm8960.c | 206 |
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 | ||
18 | struct wm8960_data { | 18 | struct 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 @@ | |||
31 | struct snd_soc_codec_device soc_codec_dev_wm8960; | 31 | struct 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] = { | |||
68 | struct wm8960_priv { | 75 | struct 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 | ||
229 | SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, | ||
230 | &wm8960_mono_out[0], | ||
231 | ARRAY_SIZE(wm8960_mono_out)), | ||
232 | |||
233 | SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), | 239 | SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), |
234 | SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), | 240 | SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), |
235 | 241 | ||
@@ -248,6 +254,17 @@ SND_SOC_DAPM_OUTPUT("SPK_RN"), | |||
248 | SND_SOC_DAPM_OUTPUT("OUT3"), | 254 | SND_SOC_DAPM_OUTPUT("OUT3"), |
249 | }; | 255 | }; |
250 | 256 | ||
257 | static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = { | ||
258 | SND_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 */ | ||
264 | static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { | ||
265 | SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0), | ||
266 | }; | ||
267 | |||
251 | static const struct snd_soc_dapm_route audio_paths[] = { | 268 | static 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 | |||
316 | static 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 | ||
323 | static 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 | |||
304 | static int wm8960_add_widgets(struct snd_soc_codec *codec) | 331 | static 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 | ||
411 | static int wm8960_set_bias_level(struct snd_soc_codec *codec, | 473 | static 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 | |||
534 | static 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 | ||
842 | static void wm8960_unregister(struct wm8960_priv *wm8960) | 990 | static 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); |