aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs/twl4030.c
diff options
context:
space:
mode:
authorPeter Ujfalusi <peter.ujfalusi@nokia.com>2010-03-22 11:46:37 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2010-03-22 12:47:12 -0400
commitc96907f21f26a1f8c1a1a9096a22500e4d158c4f (patch)
tree37ab90241186038b37863847a61f61e712a4ecb5 /sound/soc/codecs/twl4030.c
parent4ca612ebdb795d15714487576cfb2e0f7173f0a4 (diff)
ASoC: TWL4030: PM fix for output amplifiers
Gain controls on outputs affect the power consumption when the gain is set to non 0 value. Outputs with amps have one register to configure the routing and the gain: PREDL_CTL (0x25): bit 0: Voice enable bit 1: Audio L1 enable bit 2: Audio L2 enable bit 3: Audio R2 enable bit 4-5: Gain (0x0 - power down, 0x1 - 6dB, 0x2 - 0dB, 0x3 - -6dB) bit 0 - 3: is handled in DAPM domain (DAPM_MIXER) bit 4 - 5: has simple volume control If there is no audio activity (BIAS_STANDBY), and user changes the volume, than the output amplifier will be enabled. If the user changes the routing (but the codec remains in BIAS_STANDBY), than the cached gain value also be written to the register, which enables the amplifier. The existing workaround for this is to have virtual PGAs associated with the outputs, and whit DAPM PMD the gain on the output will be forced to 0 (off) by bypassing the regcache. This failed to disable the amplifiers in several scenario (as mentioned above). Also if the codec is in BIAS_ON state, and user modifies a volume control, which path is actually not enabled, than that amplifier will be enabled as well, but it will be not turned off, since there is no DAPM path, which would make mute it. To prevent amps being enabled, when they are not needed, introduce the following workaround: Track the state of each of this type of output. In twl4030_write only allow actual write, when the given output is enabled, otherwise only update the reg_cache. The PGA event handlers on power up will write the cached value to the chip (restoring gain, routing selection). On power down 0 is written to the register (disabling the amp, and also just in case clearing the routing). Signed-off-by: Peter Ujfalusi <peter.ujfalusi@nokia.com> Acked-by: Liam Girdwood <lrg@slimlogic.co.uk> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/codecs/twl4030.c')
-rw-r--r--sound/soc/codecs/twl4030.c72
1 files changed, 56 insertions, 16 deletions
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c
index 6f5d4af20052..bf59b8a4d1d7 100644
--- a/sound/soc/codecs/twl4030.c
+++ b/sound/soc/codecs/twl4030.c
@@ -135,9 +135,11 @@ struct twl4030_priv {
135 135
136 unsigned int sysclk; 136 unsigned int sysclk;
137 137
138 /* Headset output state handling */ 138 /* Output (with associated amp) states */
139 unsigned int hsl_enabled; 139 u8 hsl_enabled, hsr_enabled;
140 unsigned int hsr_enabled; 140 u8 earpiece_enabled;
141 u8 predrivel_enabled, predriver_enabled;
142 u8 carkitl_enabled, carkitr_enabled;
141}; 143};
142 144
143/* 145/*
@@ -173,12 +175,47 @@ static inline void twl4030_write_reg_cache(struct snd_soc_codec *codec,
173static int twl4030_write(struct snd_soc_codec *codec, 175static int twl4030_write(struct snd_soc_codec *codec,
174 unsigned int reg, unsigned int value) 176 unsigned int reg, unsigned int value)
175{ 177{
178 struct twl4030_priv *twl4030 = codec->private_data;
179 int write_to_reg = 0;
180
176 twl4030_write_reg_cache(codec, reg, value); 181 twl4030_write_reg_cache(codec, reg, value);
177 if (likely(reg < TWL4030_REG_SW_SHADOW)) 182 if (likely(reg < TWL4030_REG_SW_SHADOW)) {
178 return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, 183 /* Decide if the given register can be written */
179 reg); 184 switch (reg) {
180 else 185 case TWL4030_REG_EAR_CTL:
181 return 0; 186 if (twl4030->earpiece_enabled)
187 write_to_reg = 1;
188 break;
189 case TWL4030_REG_PREDL_CTL:
190 if (twl4030->predrivel_enabled)
191 write_to_reg = 1;
192 break;
193 case TWL4030_REG_PREDR_CTL:
194 if (twl4030->predriver_enabled)
195 write_to_reg = 1;
196 break;
197 case TWL4030_REG_PRECKL_CTL:
198 if (twl4030->carkitl_enabled)
199 write_to_reg = 1;
200 break;
201 case TWL4030_REG_PRECKR_CTL:
202 if (twl4030->carkitr_enabled)
203 write_to_reg = 1;
204 break;
205 case TWL4030_REG_HS_GAIN_SET:
206 if (twl4030->hsl_enabled || twl4030->hsr_enabled)
207 write_to_reg = 1;
208 break;
209 default:
210 /* All other register can be written */
211 write_to_reg = 1;
212 break;
213 }
214 if (write_to_reg)
215 return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
216 value, reg);
217 }
218 return 0;
182} 219}
183 220
184static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable) 221static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable)
@@ -525,26 +562,26 @@ static int micpath_event(struct snd_soc_dapm_widget *w,
525 * Output PGA builder: 562 * Output PGA builder:
526 * Handle the muting and unmuting of the given output (turning off the 563 * Handle the muting and unmuting of the given output (turning off the
527 * amplifier associated with the output pin) 564 * amplifier associated with the output pin)
528 * On mute bypass the reg_cache and mute the volume 565 * On mute bypass the reg_cache and write 0 to the register
529 * On unmute: restore the register content 566 * On unmute: restore the register content from the reg_cache
530 * Outputs handled in this way: Earpiece, PreDrivL/R, CarkitL/R 567 * Outputs handled in this way: Earpiece, PreDrivL/R, CarkitL/R
531 */ 568 */
532#define TWL4030_OUTPUT_PGA(pin_name, reg, mask) \ 569#define TWL4030_OUTPUT_PGA(pin_name, reg, mask) \
533static int pin_name##pga_event(struct snd_soc_dapm_widget *w, \ 570static int pin_name##pga_event(struct snd_soc_dapm_widget *w, \
534 struct snd_kcontrol *kcontrol, int event) \ 571 struct snd_kcontrol *kcontrol, int event) \
535{ \ 572{ \
536 u8 reg_val; \ 573 struct twl4030_priv *twl4030 = w->codec->private_data; \
537 \ 574 \
538 switch (event) { \ 575 switch (event) { \
539 case SND_SOC_DAPM_POST_PMU: \ 576 case SND_SOC_DAPM_POST_PMU: \
577 twl4030->pin_name##_enabled = 1; \
540 twl4030_write(w->codec, reg, \ 578 twl4030_write(w->codec, reg, \
541 twl4030_read_reg_cache(w->codec, reg)); \ 579 twl4030_read_reg_cache(w->codec, reg)); \
542 break; \ 580 break; \
543 case SND_SOC_DAPM_POST_PMD: \ 581 case SND_SOC_DAPM_POST_PMD: \
544 reg_val = twl4030_read_reg_cache(w->codec, reg); \ 582 twl4030->pin_name##_enabled = 0; \
545 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, \ 583 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, \
546 reg_val & (~mask), \ 584 0, reg); \
547 reg); \
548 break; \ 585 break; \
549 } \ 586 } \
550 return 0; \ 587 return 0; \
@@ -664,7 +701,10 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp)
664 /* Headset ramp-up according to the TRM */ 701 /* Headset ramp-up according to the TRM */
665 hs_pop |= TWL4030_VMID_EN; 702 hs_pop |= TWL4030_VMID_EN;
666 twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); 703 twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
667 twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hs_gain); 704 /* Actually write to the register */
705 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
706 hs_gain,
707 TWL4030_REG_HS_GAIN_SET);
668 hs_pop |= TWL4030_RAMP_EN; 708 hs_pop |= TWL4030_RAMP_EN;
669 twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); 709 twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
670 /* Wait ramp delay time + 1, so the VMID can settle */ 710 /* Wait ramp delay time + 1, so the VMID can settle */