Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

+179 -29
+2
include/sound/wm8960.h
··· 16 16 #define WM8960_DRES_MAX 3 17 17 18 18 struct wm8960_data { 19 + bool capless; /* Headphone outputs configured in capless mode */ 20 + 19 21 int dres; /* Discharge resistance for headphone outputs */ 20 22 }; 21 23
+177 -29
sound/soc/codecs/wm8960.c
··· 31 31 struct snd_soc_codec_device soc_codec_dev_wm8960; 32 32 33 33 /* R25 - Power 1 */ 34 + #define WM8960_VMID_MASK 0x180 34 35 #define WM8960_VREF 0x40 36 + 37 + /* R26 - Power 2 */ 38 + #define WM8960_PWR2_LOUT1 0x40 39 + #define WM8960_PWR2_ROUT1 0x20 40 + #define WM8960_PWR2_OUT3 0x02 35 41 36 42 /* R28 - Anti-pop 1 */ 37 43 #define WM8960_POBCTRL 0x80 ··· 48 42 49 43 /* R29 - Anti-pop 2 */ 50 44 #define WM8960_DISOP 0x40 45 + #define WM8960_DRES_MASK 0x30 51 46 52 47 /* 53 48 * wm8960 register cache ··· 75 68 struct wm8960_priv { 76 69 u16 reg_cache[WM8960_CACHEREGNUM]; 77 70 struct snd_soc_codec codec; 71 + struct snd_soc_dapm_widget *lout1; 72 + struct snd_soc_dapm_widget *rout1; 73 + struct snd_soc_dapm_widget *out3; 78 74 }; 79 75 80 76 #define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) ··· 236 226 &wm8960_routput_mixer[0], 237 227 ARRAY_SIZE(wm8960_routput_mixer)), 238 228 239 - SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, 240 - &wm8960_mono_out[0], 241 - ARRAY_SIZE(wm8960_mono_out)), 242 - 243 229 SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), 244 230 SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), 245 231 ··· 252 246 SND_SOC_DAPM_OUTPUT("SPK_RP"), 253 247 SND_SOC_DAPM_OUTPUT("SPK_RN"), 254 248 SND_SOC_DAPM_OUTPUT("OUT3"), 249 + }; 250 + 251 + static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = { 252 + SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, 253 + &wm8960_mono_out[0], 254 + ARRAY_SIZE(wm8960_mono_out)), 255 + }; 256 + 257 + /* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */ 258 + static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { 259 + SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0), 255 260 }; 256 261 257 262 static const struct snd_soc_dapm_route audio_paths[] = { ··· 295 278 { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , 296 279 { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, 297 280 298 - { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, 299 - { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, 300 - 301 281 { "LOUT1 PGA", NULL, "Left Output Mixer" }, 302 282 { "ROUT1 PGA", NULL, "Right Output Mixer" }, 303 283 ··· 311 297 { "SPK_LP", NULL, "Left Speaker Output" }, 312 298 { "SPK_RN", NULL, "Right Speaker Output" }, 313 299 { "SPK_RP", NULL, "Right Speaker Output" }, 300 + }; 301 + 302 + static const struct snd_soc_dapm_route audio_paths_out3[] = { 303 + { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, 304 + { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, 314 305 315 306 { "OUT3", NULL, "Mono Output Mixer", } 316 307 }; 317 308 309 + static const struct snd_soc_dapm_route audio_paths_capless[] = { 310 + { "HP_L", NULL, "OUT3 VMID" }, 311 + { "HP_R", NULL, "OUT3 VMID" }, 312 + 313 + { "OUT3 VMID", NULL, "Left Output Mixer" }, 314 + { "OUT3 VMID", NULL, "Right Output Mixer" }, 315 + }; 316 + 318 317 static int wm8960_add_widgets(struct snd_soc_codec *codec) 319 318 { 319 + struct wm8960_data *pdata = codec->dev->platform_data; 320 + struct wm8960_priv *wm8960 = codec->private_data; 321 + struct snd_soc_dapm_widget *w; 322 + 320 323 snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, 321 324 ARRAY_SIZE(wm8960_dapm_widgets)); 322 325 323 326 snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); 324 327 328 + /* In capless mode OUT3 is used to provide VMID for the 329 + * headphone outputs, otherwise it is used as a mono mixer. 330 + */ 331 + if (pdata && pdata->capless) { 332 + snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_capless, 333 + ARRAY_SIZE(wm8960_dapm_widgets_capless)); 334 + 335 + snd_soc_dapm_add_routes(codec, audio_paths_capless, 336 + ARRAY_SIZE(audio_paths_capless)); 337 + } else { 338 + snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_out3, 339 + ARRAY_SIZE(wm8960_dapm_widgets_out3)); 340 + 341 + snd_soc_dapm_add_routes(codec, audio_paths_out3, 342 + ARRAY_SIZE(audio_paths_out3)); 343 + } 344 + 345 + /* We need to power up the headphone output stage out of 346 + * sequence for capless mode. To save scanning the widget 347 + * list each time to find the desired power state do so now 348 + * and save the result. 349 + */ 350 + list_for_each_entry(w, &codec->dapm_widgets, list) { 351 + if (strcmp(w->name, "LOUT1 PGA") == 0) 352 + wm8960->lout1 = w; 353 + if (strcmp(w->name, "ROUT1 PGA") == 0) 354 + wm8960->rout1 = w; 355 + if (strcmp(w->name, "OUT3 VMID") == 0) 356 + wm8960->out3 = w; 357 + } 358 + 325 359 return 0; 326 360 } 327 361 ··· 470 408 return 0; 471 409 } 472 410 473 - static int wm8960_set_bias_level(struct snd_soc_codec *codec, 474 - enum snd_soc_bias_level level) 411 + static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, 412 + enum snd_soc_bias_level level) 475 413 { 476 - struct wm8960_data *pdata = codec->dev->platform_data; 477 414 u16 reg; 478 415 479 416 switch (level) { ··· 491 430 if (codec->bias_level == SND_SOC_BIAS_OFF) { 492 431 /* Enable anti-pop features */ 493 432 snd_soc_write(codec, WM8960_APOP1, 494 - WM8960_POBCTRL | WM8960_SOFT_ST | 495 - WM8960_BUFDCOPEN | WM8960_BUFIOEN); 496 - 497 - /* Discharge HP output */ 498 - reg = WM8960_DISOP; 499 - if (pdata) 500 - reg |= pdata->dres << 4; 501 - snd_soc_write(codec, WM8960_APOP2, reg); 502 - 503 - msleep(400); 504 - 505 - snd_soc_write(codec, WM8960_APOP2, 0); 433 + WM8960_POBCTRL | WM8960_SOFT_ST | 434 + WM8960_BUFDCOPEN | WM8960_BUFIOEN); 506 435 507 436 /* Enable & ramp VMID at 2x50k */ 508 437 reg = snd_soc_read(codec, WM8960_POWER1); ··· 523 472 /* Disable VMID and VREF, let them discharge */ 524 473 snd_soc_write(codec, WM8960_POWER1, 0); 525 474 msleep(600); 475 + break; 476 + } 526 477 527 - snd_soc_write(codec, WM8960_APOP1, 0); 478 + codec->bias_level = level; 479 + 480 + return 0; 481 + } 482 + 483 + static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, 484 + enum snd_soc_bias_level level) 485 + { 486 + struct wm8960_priv *wm8960 = codec->private_data; 487 + int reg; 488 + 489 + switch (level) { 490 + case SND_SOC_BIAS_ON: 491 + break; 492 + 493 + case SND_SOC_BIAS_PREPARE: 494 + switch (codec->bias_level) { 495 + case SND_SOC_BIAS_STANDBY: 496 + /* Enable anti pop mode */ 497 + snd_soc_update_bits(codec, WM8960_APOP1, 498 + WM8960_POBCTRL | WM8960_SOFT_ST | 499 + WM8960_BUFDCOPEN, 500 + WM8960_POBCTRL | WM8960_SOFT_ST | 501 + WM8960_BUFDCOPEN); 502 + 503 + /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */ 504 + reg = 0; 505 + if (wm8960->lout1 && wm8960->lout1->power) 506 + reg |= WM8960_PWR2_LOUT1; 507 + if (wm8960->rout1 && wm8960->rout1->power) 508 + reg |= WM8960_PWR2_ROUT1; 509 + if (wm8960->out3 && wm8960->out3->power) 510 + reg |= WM8960_PWR2_OUT3; 511 + snd_soc_update_bits(codec, WM8960_POWER2, 512 + WM8960_PWR2_LOUT1 | 513 + WM8960_PWR2_ROUT1 | 514 + WM8960_PWR2_OUT3, reg); 515 + 516 + /* Enable VMID at 2*50k */ 517 + snd_soc_update_bits(codec, WM8960_POWER1, 518 + WM8960_VMID_MASK, 0x80); 519 + 520 + /* Ramp */ 521 + msleep(100); 522 + 523 + /* Enable VREF */ 524 + snd_soc_update_bits(codec, WM8960_POWER1, 525 + WM8960_VREF, WM8960_VREF); 526 + 527 + msleep(100); 528 + break; 529 + 530 + case SND_SOC_BIAS_ON: 531 + /* Enable anti-pop mode */ 532 + snd_soc_update_bits(codec, WM8960_APOP1, 533 + WM8960_POBCTRL | WM8960_SOFT_ST | 534 + WM8960_BUFDCOPEN, 535 + WM8960_POBCTRL | WM8960_SOFT_ST | 536 + WM8960_BUFDCOPEN); 537 + 538 + /* Disable VMID and VREF */ 539 + snd_soc_update_bits(codec, WM8960_POWER1, 540 + WM8960_VREF | WM8960_VMID_MASK, 0); 541 + break; 542 + 543 + default: 544 + break; 545 + } 546 + break; 547 + 548 + case SND_SOC_BIAS_STANDBY: 549 + switch (codec->bias_level) { 550 + case SND_SOC_BIAS_PREPARE: 551 + /* Disable HP discharge */ 552 + snd_soc_update_bits(codec, WM8960_APOP2, 553 + WM8960_DISOP | WM8960_DRES_MASK, 554 + 0); 555 + 556 + /* Disable anti-pop features */ 557 + snd_soc_update_bits(codec, WM8960_APOP1, 558 + WM8960_POBCTRL | WM8960_SOFT_ST | 559 + WM8960_BUFDCOPEN, 560 + WM8960_POBCTRL | WM8960_SOFT_ST | 561 + WM8960_BUFDCOPEN); 562 + break; 563 + 564 + default: 565 + break; 566 + } 567 + break; 568 + 569 + case SND_SOC_BIAS_OFF: 528 570 break; 529 571 } 530 572 ··· 807 663 struct snd_soc_device *socdev = platform_get_drvdata(pdev); 808 664 struct snd_soc_codec *codec = socdev->card->codec; 809 665 810 - wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF); 666 + codec->set_bias_level(codec, SND_SOC_BIAS_OFF); 811 667 return 0; 812 668 } 813 669 ··· 826 682 codec->hw_write(codec->control_data, data, 2); 827 683 } 828 684 829 - wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 830 - wm8960_set_bias_level(codec, codec->suspend_bias_level); 685 + codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); 686 + codec->set_bias_level(codec, codec->suspend_bias_level); 831 687 return 0; 832 688 } 833 689 ··· 897 753 goto err; 898 754 } 899 755 756 + codec->set_bias_level = wm8960_set_bias_level_out3; 757 + 900 758 if (!pdata) { 901 759 dev_warn(codec->dev, "No platform data supplied\n"); 902 760 } else { ··· 906 760 dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); 907 761 pdata->dres = 0; 908 762 } 763 + 764 + if (pdata->capless) 765 + codec->set_bias_level = wm8960_set_bias_level_capless; 909 766 } 910 767 911 768 mutex_init(&codec->mutex); ··· 919 770 codec->name = "WM8960"; 920 771 codec->owner = THIS_MODULE; 921 772 codec->bias_level = SND_SOC_BIAS_OFF; 922 - codec->set_bias_level = wm8960_set_bias_level; 923 773 codec->dai = &wm8960_dai; 924 774 codec->num_dai = 1; 925 775 codec->reg_cache_size = WM8960_CACHEREGNUM; ··· 940 792 941 793 wm8960_dai.dev = codec->dev; 942 794 943 - wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 795 + codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); 944 796 945 797 /* Latch the update bits */ 946 798 reg = snd_soc_read(codec, WM8960_LINVOL); ··· 989 841 990 842 static void wm8960_unregister(struct wm8960_priv *wm8960) 991 843 { 992 - wm8960_set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); 844 + wm8960->codec.set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); 993 845 snd_soc_unregister_dai(&wm8960_dai); 994 846 snd_soc_unregister_codec(&wm8960->codec); 995 847 kfree(wm8960);