diff options
Diffstat (limited to 'sound/soc')
-rw-r--r-- | sound/soc/codecs/twl4030.c | 116 |
1 files changed, 91 insertions, 25 deletions
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 99fe44f70507..f554672f67c1 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c | |||
@@ -130,6 +130,12 @@ struct twl4030_priv { | |||
130 | unsigned int rate; | 130 | unsigned int rate; |
131 | unsigned int sample_bits; | 131 | unsigned int sample_bits; |
132 | unsigned int channels; | 132 | unsigned int channels; |
133 | |||
134 | unsigned int sysclk; | ||
135 | |||
136 | /* Headset output state handling */ | ||
137 | unsigned int hsl_enabled; | ||
138 | unsigned int hsr_enabled; | ||
133 | }; | 139 | }; |
134 | 140 | ||
135 | /* | 141 | /* |
@@ -564,39 +570,85 @@ static int handsfree_event(struct snd_soc_dapm_widget *w, | |||
564 | return 0; | 570 | return 0; |
565 | } | 571 | } |
566 | 572 | ||
567 | static int headsetl_event(struct snd_soc_dapm_widget *w, | 573 | static void headset_ramp(struct snd_soc_codec *codec, int ramp) |
568 | struct snd_kcontrol *kcontrol, int event) | ||
569 | { | 574 | { |
570 | unsigned char hs_gain, hs_pop; | 575 | unsigned char hs_gain, hs_pop; |
576 | struct twl4030_priv *twl4030 = codec->private_data; | ||
577 | /* Base values for ramp delay calculation: 2^19 - 2^26 */ | ||
578 | unsigned int ramp_base[] = {524288, 1048576, 2097152, 4194304, | ||
579 | 8388608, 16777216, 33554432, 67108864}; | ||
571 | 580 | ||
572 | /* Save the current volume */ | 581 | hs_gain = twl4030_read_reg_cache(codec, TWL4030_REG_HS_GAIN_SET); |
573 | hs_gain = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_GAIN_SET); | 582 | hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); |
574 | hs_pop = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_POPN_SET); | ||
575 | 583 | ||
576 | switch (event) { | 584 | if (ramp) { |
577 | case SND_SOC_DAPM_POST_PMU: | 585 | /* Headset ramp-up according to the TRM */ |
578 | /* Do the anti-pop/bias ramp enable according to the TRM */ | ||
579 | hs_pop |= TWL4030_VMID_EN; | 586 | hs_pop |= TWL4030_VMID_EN; |
580 | twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); | 587 | twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); |
581 | /* Is this needed? Can we just use whatever gain here? */ | 588 | twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hs_gain); |
582 | twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET, | ||
583 | (hs_gain & (~0x0f)) | 0x0a); | ||
584 | hs_pop |= TWL4030_RAMP_EN; | 589 | hs_pop |= TWL4030_RAMP_EN; |
585 | twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); | 590 | twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); |
586 | 591 | } else { | |
587 | /* Restore the original volume */ | 592 | /* Headset ramp-down _not_ according to |
588 | twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET, hs_gain); | 593 | * the TRM, but in a way that it is working */ |
589 | break; | ||
590 | case SND_SOC_DAPM_POST_PMD: | ||
591 | /* Do the anti-pop/bias ramp disable according to the TRM */ | ||
592 | hs_pop &= ~TWL4030_RAMP_EN; | 594 | hs_pop &= ~TWL4030_RAMP_EN; |
593 | twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); | 595 | twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); |
596 | /* Wait ramp delay time + 1, so the VMID can settle */ | ||
597 | mdelay((ramp_base[(hs_pop & TWL4030_RAMP_DELAY) >> 2] / | ||
598 | twl4030->sysclk) + 1); | ||
594 | /* Bypass the reg_cache to mute the headset */ | 599 | /* Bypass the reg_cache to mute the headset */ |
595 | twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, | 600 | twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, |
596 | hs_gain & (~0x0f), | 601 | hs_gain & (~0x0f), |
597 | TWL4030_REG_HS_GAIN_SET); | 602 | TWL4030_REG_HS_GAIN_SET); |
603 | |||
598 | hs_pop &= ~TWL4030_VMID_EN; | 604 | hs_pop &= ~TWL4030_VMID_EN; |
599 | twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); | 605 | twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); |
606 | } | ||
607 | } | ||
608 | |||
609 | static int headsetlpga_event(struct snd_soc_dapm_widget *w, | ||
610 | struct snd_kcontrol *kcontrol, int event) | ||
611 | { | ||
612 | struct twl4030_priv *twl4030 = w->codec->private_data; | ||
613 | |||
614 | switch (event) { | ||
615 | case SND_SOC_DAPM_POST_PMU: | ||
616 | /* Do the ramp-up only once */ | ||
617 | if (!twl4030->hsr_enabled) | ||
618 | headset_ramp(w->codec, 1); | ||
619 | |||
620 | twl4030->hsl_enabled = 1; | ||
621 | break; | ||
622 | case SND_SOC_DAPM_POST_PMD: | ||
623 | /* Do the ramp-down only if both headsetL/R is disabled */ | ||
624 | if (!twl4030->hsr_enabled) | ||
625 | headset_ramp(w->codec, 0); | ||
626 | |||
627 | twl4030->hsl_enabled = 0; | ||
628 | break; | ||
629 | } | ||
630 | return 0; | ||
631 | } | ||
632 | |||
633 | static int headsetrpga_event(struct snd_soc_dapm_widget *w, | ||
634 | struct snd_kcontrol *kcontrol, int event) | ||
635 | { | ||
636 | struct twl4030_priv *twl4030 = w->codec->private_data; | ||
637 | |||
638 | switch (event) { | ||
639 | case SND_SOC_DAPM_POST_PMU: | ||
640 | /* Do the ramp-up only once */ | ||
641 | if (!twl4030->hsl_enabled) | ||
642 | headset_ramp(w->codec, 1); | ||
643 | |||
644 | twl4030->hsr_enabled = 1; | ||
645 | break; | ||
646 | case SND_SOC_DAPM_POST_PMD: | ||
647 | /* Do the ramp-down only if both headsetL/R is disabled */ | ||
648 | if (!twl4030->hsl_enabled) | ||
649 | headset_ramp(w->codec, 0); | ||
650 | |||
651 | twl4030->hsr_enabled = 0; | ||
600 | break; | 652 | break; |
601 | } | 653 | } |
602 | return 0; | 654 | return 0; |
@@ -1116,13 +1168,18 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { | |||
1116 | &twl4030_dapm_predriver_controls[0], | 1168 | &twl4030_dapm_predriver_controls[0], |
1117 | ARRAY_SIZE(twl4030_dapm_predriver_controls)), | 1169 | ARRAY_SIZE(twl4030_dapm_predriver_controls)), |
1118 | /* HeadsetL/R */ | 1170 | /* HeadsetL/R */ |
1119 | SND_SOC_DAPM_MIXER_E("HeadsetL Mixer", SND_SOC_NOPM, 0, 0, | 1171 | SND_SOC_DAPM_MIXER("HeadsetL Mixer", SND_SOC_NOPM, 0, 0, |
1120 | &twl4030_dapm_hsol_controls[0], | 1172 | &twl4030_dapm_hsol_controls[0], |
1121 | ARRAY_SIZE(twl4030_dapm_hsol_controls), headsetl_event, | 1173 | ARRAY_SIZE(twl4030_dapm_hsol_controls)), |
1174 | SND_SOC_DAPM_PGA_E("HeadsetL PGA", SND_SOC_NOPM, | ||
1175 | 0, 0, NULL, 0, headsetlpga_event, | ||
1122 | SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), | 1176 | SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), |
1123 | SND_SOC_DAPM_MIXER("HeadsetR Mixer", SND_SOC_NOPM, 0, 0, | 1177 | SND_SOC_DAPM_MIXER("HeadsetR Mixer", SND_SOC_NOPM, 0, 0, |
1124 | &twl4030_dapm_hsor_controls[0], | 1178 | &twl4030_dapm_hsor_controls[0], |
1125 | ARRAY_SIZE(twl4030_dapm_hsor_controls)), | 1179 | ARRAY_SIZE(twl4030_dapm_hsor_controls)), |
1180 | SND_SOC_DAPM_PGA_E("HeadsetR PGA", SND_SOC_NOPM, | ||
1181 | 0, 0, NULL, 0, headsetrpga_event, | ||
1182 | SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), | ||
1126 | /* CarkitL/R */ | 1183 | /* CarkitL/R */ |
1127 | SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0, | 1184 | SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0, |
1128 | &twl4030_dapm_carkitl_controls[0], | 1185 | &twl4030_dapm_carkitl_controls[0], |
@@ -1227,10 +1284,12 @@ static const struct snd_soc_dapm_route intercon[] = { | |||
1227 | {"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"}, | 1284 | {"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"}, |
1228 | {"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, | 1285 | {"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, |
1229 | {"HeadsetL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, | 1286 | {"HeadsetL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, |
1287 | {"HeadsetL PGA", NULL, "HeadsetL Mixer"}, | ||
1230 | /* HeadsetR */ | 1288 | /* HeadsetR */ |
1231 | {"HeadsetR Mixer", "Voice", "Analog Voice Playback Mixer"}, | 1289 | {"HeadsetR Mixer", "Voice", "Analog Voice Playback Mixer"}, |
1232 | {"HeadsetR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, | 1290 | {"HeadsetR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, |
1233 | {"HeadsetR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, | 1291 | {"HeadsetR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, |
1292 | {"HeadsetR PGA", NULL, "HeadsetR Mixer"}, | ||
1234 | /* CarkitL */ | 1293 | /* CarkitL */ |
1235 | {"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"}, | 1294 | {"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"}, |
1236 | {"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, | 1295 | {"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, |
@@ -1261,8 +1320,8 @@ static const struct snd_soc_dapm_route intercon[] = { | |||
1261 | {"EARPIECE", NULL, "Earpiece Mixer"}, | 1320 | {"EARPIECE", NULL, "Earpiece Mixer"}, |
1262 | {"PREDRIVEL", NULL, "PredriveL Mixer"}, | 1321 | {"PREDRIVEL", NULL, "PredriveL Mixer"}, |
1263 | {"PREDRIVER", NULL, "PredriveR Mixer"}, | 1322 | {"PREDRIVER", NULL, "PredriveR Mixer"}, |
1264 | {"HSOL", NULL, "HeadsetL Mixer"}, | 1323 | {"HSOL", NULL, "HeadsetL PGA"}, |
1265 | {"HSOR", NULL, "HeadsetR Mixer"}, | 1324 | {"HSOR", NULL, "HeadsetR PGA"}, |
1266 | {"CARKITL", NULL, "CarkitL Mixer"}, | 1325 | {"CARKITL", NULL, "CarkitL Mixer"}, |
1267 | {"CARKITR", NULL, "CarkitR Mixer"}, | 1326 | {"CARKITR", NULL, "CarkitR Mixer"}, |
1268 | {"HFL", NULL, "HandsfreeL Mux"}, | 1327 | {"HFL", NULL, "HandsfreeL Mux"}, |
@@ -1601,17 +1660,21 @@ static int twl4030_set_dai_sysclk(struct snd_soc_dai *codec_dai, | |||
1601 | int clk_id, unsigned int freq, int dir) | 1660 | int clk_id, unsigned int freq, int dir) |
1602 | { | 1661 | { |
1603 | struct snd_soc_codec *codec = codec_dai->codec; | 1662 | struct snd_soc_codec *codec = codec_dai->codec; |
1663 | struct twl4030_priv *twl4030 = codec->private_data; | ||
1604 | u8 infreq; | 1664 | u8 infreq; |
1605 | 1665 | ||
1606 | switch (freq) { | 1666 | switch (freq) { |
1607 | case 19200000: | 1667 | case 19200000: |
1608 | infreq = TWL4030_APLL_INFREQ_19200KHZ; | 1668 | infreq = TWL4030_APLL_INFREQ_19200KHZ; |
1669 | twl4030->sysclk = 19200; | ||
1609 | break; | 1670 | break; |
1610 | case 26000000: | 1671 | case 26000000: |
1611 | infreq = TWL4030_APLL_INFREQ_26000KHZ; | 1672 | infreq = TWL4030_APLL_INFREQ_26000KHZ; |
1673 | twl4030->sysclk = 26000; | ||
1612 | break; | 1674 | break; |
1613 | case 38400000: | 1675 | case 38400000: |
1614 | infreq = TWL4030_APLL_INFREQ_38400KHZ; | 1676 | infreq = TWL4030_APLL_INFREQ_38400KHZ; |
1677 | twl4030->sysclk = 38400; | ||
1615 | break; | 1678 | break; |
1616 | default: | 1679 | default: |
1617 | printk(KERN_ERR "TWL4030 set sysclk: unknown rate %d\n", | 1680 | printk(KERN_ERR "TWL4030 set sysclk: unknown rate %d\n", |
@@ -2000,6 +2063,9 @@ static int twl4030_probe(struct platform_device *pdev) | |||
2000 | kfree(codec); | 2063 | kfree(codec); |
2001 | return -ENOMEM; | 2064 | return -ENOMEM; |
2002 | } | 2065 | } |
2066 | /* Set default sysclk (used by the headsetl/rpga_event callback for | ||
2067 | * pop-attenuation) */ | ||
2068 | twl4030->sysclk = 26000; | ||
2003 | 2069 | ||
2004 | codec->private_data = twl4030; | 2070 | codec->private_data = twl4030; |
2005 | socdev->card->codec = codec; | 2071 | socdev->card->codec = codec; |