diff options
author | Oleksandr Suvorov <oleksandr.suvorov@toradex.com> | 2019-07-19 06:05:31 -0400 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2019-07-22 08:26:24 -0400 |
commit | b1f373a11d25fc9a5f7679c9b85799fe09b0dc4a (patch) | |
tree | 55e8e898d61adf159854dc0d72d0ec2d08c4672e | |
parent | cfc8f568aada98f9608a0a62511ca18d647613e2 (diff) |
ASoC: sgtl5000: Improve VAG power and mute control
VAG power control is improved to fit the manual [1]. This patch fixes as
minimum one bug: if customer muxes Headphone to Line-In right after boot,
the VAG power remains off that leads to poor sound quality from line-in.
I.e. after boot:
- Connect sound source to Line-In jack;
- Connect headphone to HP jack;
- Run following commands:
$ amixer set 'Headphone' 80%
$ amixer set 'Headphone Mux' LINE_IN
Change VAG power on/off control according to the following algorithm:
- turn VAG power ON on the 1st incoming event.
- keep it ON if there is any active VAG consumer (ADC/DAC/HP/Line-In).
- turn VAG power OFF when there is the latest consumer's pre-down event
come.
- always delay after VAG power OFF to avoid pop.
- delay after VAG power ON if the initiative consumer is Line-In, this
prevents pop during line-in muxing.
According to the data sheet [1], to avoid any pops/clicks,
the outputs should be muted during input/output
routing changes.
[1] https://www.nxp.com/docs/en/data-sheet/SGTL5000.pdf
Cc: stable@vger.kernel.org
Fixes: 9b34e6cc3bc2 ("ASoC: Add Freescale SGTL5000 codec support")
Signed-off-by: Oleksandr Suvorov <oleksandr.suvorov@toradex.com>
Reviewed-by: Marcel Ziswiler <marcel.ziswiler@toradex.com>
Reviewed-by: Fabio Estevam <festevam@gmail.com>
Reviewed-by: Cezary Rojewski <cezary.rojewski@intel.com>
Link: https://lore.kernel.org/r/20190719100524.23300-3-oleksandr.suvorov@toradex.com
Signed-off-by: Mark Brown <broonie@kernel.org>
-rw-r--r-- | sound/soc/codecs/sgtl5000.c | 224 |
1 files changed, 194 insertions, 30 deletions
diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index a6a4748c97f9..34cc85e49003 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c | |||
@@ -31,6 +31,13 @@ | |||
31 | #define SGTL5000_DAP_REG_OFFSET 0x0100 | 31 | #define SGTL5000_DAP_REG_OFFSET 0x0100 |
32 | #define SGTL5000_MAX_REG_OFFSET 0x013A | 32 | #define SGTL5000_MAX_REG_OFFSET 0x013A |
33 | 33 | ||
34 | /* Delay for the VAG ramp up */ | ||
35 | #define SGTL5000_VAG_POWERUP_DELAY 500 /* ms */ | ||
36 | /* Delay for the VAG ramp down */ | ||
37 | #define SGTL5000_VAG_POWERDOWN_DELAY 500 /* ms */ | ||
38 | |||
39 | #define SGTL5000_OUTPUTS_MUTE (SGTL5000_HP_MUTE | SGTL5000_LINE_OUT_MUTE) | ||
40 | |||
34 | /* default value of sgtl5000 registers */ | 41 | /* default value of sgtl5000 registers */ |
35 | static const struct reg_default sgtl5000_reg_defaults[] = { | 42 | static const struct reg_default sgtl5000_reg_defaults[] = { |
36 | { SGTL5000_CHIP_DIG_POWER, 0x0000 }, | 43 | { SGTL5000_CHIP_DIG_POWER, 0x0000 }, |
@@ -123,6 +130,13 @@ enum { | |||
123 | I2S_SCLK_STRENGTH_HIGH, | 130 | I2S_SCLK_STRENGTH_HIGH, |
124 | }; | 131 | }; |
125 | 132 | ||
133 | enum { | ||
134 | HP_POWER_EVENT, | ||
135 | DAC_POWER_EVENT, | ||
136 | ADC_POWER_EVENT, | ||
137 | LAST_POWER_EVENT = ADC_POWER_EVENT | ||
138 | }; | ||
139 | |||
126 | /* sgtl5000 private structure in codec */ | 140 | /* sgtl5000 private structure in codec */ |
127 | struct sgtl5000_priv { | 141 | struct sgtl5000_priv { |
128 | int sysclk; /* sysclk rate */ | 142 | int sysclk; /* sysclk rate */ |
@@ -137,8 +151,109 @@ struct sgtl5000_priv { | |||
137 | u8 micbias_voltage; | 151 | u8 micbias_voltage; |
138 | u8 lrclk_strength; | 152 | u8 lrclk_strength; |
139 | u8 sclk_strength; | 153 | u8 sclk_strength; |
154 | u16 mute_state[LAST_POWER_EVENT + 1]; | ||
140 | }; | 155 | }; |
141 | 156 | ||
157 | static inline int hp_sel_input(struct snd_soc_component *component) | ||
158 | { | ||
159 | return (snd_soc_component_read32(component, SGTL5000_CHIP_ANA_CTRL) & | ||
160 | SGTL5000_HP_SEL_MASK) >> SGTL5000_HP_SEL_SHIFT; | ||
161 | } | ||
162 | |||
163 | static inline u16 mute_output(struct snd_soc_component *component, | ||
164 | u16 mute_mask) | ||
165 | { | ||
166 | u16 mute_reg = snd_soc_component_read32(component, | ||
167 | SGTL5000_CHIP_ANA_CTRL); | ||
168 | |||
169 | snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_CTRL, | ||
170 | mute_mask, mute_mask); | ||
171 | return mute_reg; | ||
172 | } | ||
173 | |||
174 | static inline void restore_output(struct snd_soc_component *component, | ||
175 | u16 mute_mask, u16 mute_reg) | ||
176 | { | ||
177 | snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_CTRL, | ||
178 | mute_mask, mute_reg); | ||
179 | } | ||
180 | |||
181 | static void vag_power_on(struct snd_soc_component *component, u32 source) | ||
182 | { | ||
183 | if (snd_soc_component_read32(component, SGTL5000_CHIP_ANA_POWER) & | ||
184 | SGTL5000_VAG_POWERUP) | ||
185 | return; | ||
186 | |||
187 | snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, | ||
188 | SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP); | ||
189 | |||
190 | /* When VAG powering on to get local loop from Line-In, the sleep | ||
191 | * is required to avoid loud pop. | ||
192 | */ | ||
193 | if (hp_sel_input(component) == SGTL5000_HP_SEL_LINE_IN && | ||
194 | source == HP_POWER_EVENT) | ||
195 | msleep(SGTL5000_VAG_POWERUP_DELAY); | ||
196 | } | ||
197 | |||
198 | static int vag_power_consumers(struct snd_soc_component *component, | ||
199 | u16 ana_pwr_reg, u32 source) | ||
200 | { | ||
201 | int consumers = 0; | ||
202 | |||
203 | /* count dac/adc consumers unconditional */ | ||
204 | if (ana_pwr_reg & SGTL5000_DAC_POWERUP) | ||
205 | consumers++; | ||
206 | if (ana_pwr_reg & SGTL5000_ADC_POWERUP) | ||
207 | consumers++; | ||
208 | |||
209 | /* | ||
210 | * If the event comes from HP and Line-In is selected, | ||
211 | * current action is 'DAC to be powered down'. | ||
212 | * As HP_POWERUP is not set when HP muxed to line-in, | ||
213 | * we need to keep VAG power ON. | ||
214 | */ | ||
215 | if (source == HP_POWER_EVENT) { | ||
216 | if (hp_sel_input(component) == SGTL5000_HP_SEL_LINE_IN) | ||
217 | consumers++; | ||
218 | } else { | ||
219 | if (ana_pwr_reg & SGTL5000_HP_POWERUP) | ||
220 | consumers++; | ||
221 | } | ||
222 | |||
223 | return consumers; | ||
224 | } | ||
225 | |||
226 | static void vag_power_off(struct snd_soc_component *component, u32 source) | ||
227 | { | ||
228 | u16 ana_pwr = snd_soc_component_read32(component, | ||
229 | SGTL5000_CHIP_ANA_POWER); | ||
230 | |||
231 | if (!(ana_pwr & SGTL5000_VAG_POWERUP)) | ||
232 | return; | ||
233 | |||
234 | /* | ||
235 | * This function calls when any of VAG power consumers is disappearing. | ||
236 | * Thus, if there is more than one consumer at the moment, as minimum | ||
237 | * one consumer will definitely stay after the end of the current | ||
238 | * event. | ||
239 | * Don't clear VAG_POWERUP if 2 or more consumers of VAG present: | ||
240 | * - LINE_IN (for HP events) / HP (for DAC/ADC events) | ||
241 | * - DAC | ||
242 | * - ADC | ||
243 | * (the current consumer is disappearing right now) | ||
244 | */ | ||
245 | if (vag_power_consumers(component, ana_pwr, source) >= 2) | ||
246 | return; | ||
247 | |||
248 | snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, | ||
249 | SGTL5000_VAG_POWERUP, 0); | ||
250 | /* In power down case, we need wait 400-1000 ms | ||
251 | * when VAG fully ramped down. | ||
252 | * As longer we wait, as smaller pop we've got. | ||
253 | */ | ||
254 | msleep(SGTL5000_VAG_POWERDOWN_DELAY); | ||
255 | } | ||
256 | |||
142 | /* | 257 | /* |
143 | * mic_bias power on/off share the same register bits with | 258 | * mic_bias power on/off share the same register bits with |
144 | * output impedance of mic bias, when power on mic bias, we | 259 | * output impedance of mic bias, when power on mic bias, we |
@@ -170,36 +285,46 @@ static int mic_bias_event(struct snd_soc_dapm_widget *w, | |||
170 | return 0; | 285 | return 0; |
171 | } | 286 | } |
172 | 287 | ||
173 | /* | 288 | static int vag_and_mute_control(struct snd_soc_component *component, |
174 | * As manual described, ADC/DAC only works when VAG powerup, | 289 | int event, int event_source) |
175 | * So enabled VAG before ADC/DAC up. | ||
176 | * In power down case, we need wait 400ms when vag fully ramped down. | ||
177 | */ | ||
178 | static int power_vag_event(struct snd_soc_dapm_widget *w, | ||
179 | struct snd_kcontrol *kcontrol, int event) | ||
180 | { | 290 | { |
181 | struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); | 291 | static const u16 mute_mask[] = { |
182 | const u32 mask = SGTL5000_DAC_POWERUP | SGTL5000_ADC_POWERUP; | 292 | /* |
293 | * Mask for HP_POWER_EVENT. | ||
294 | * Muxing Headphones have to be wrapped with mute/unmute | ||
295 | * headphones only. | ||
296 | */ | ||
297 | SGTL5000_HP_MUTE, | ||
298 | /* | ||
299 | * Masks for DAC_POWER_EVENT/ADC_POWER_EVENT. | ||
300 | * Muxing DAC or ADC block have to wrapped with mute/unmute | ||
301 | * both headphones and line-out. | ||
302 | */ | ||
303 | SGTL5000_OUTPUTS_MUTE, | ||
304 | SGTL5000_OUTPUTS_MUTE | ||
305 | }; | ||
306 | |||
307 | struct sgtl5000_priv *sgtl5000 = | ||
308 | snd_soc_component_get_drvdata(component); | ||
183 | 309 | ||
184 | switch (event) { | 310 | switch (event) { |
311 | case SND_SOC_DAPM_PRE_PMU: | ||
312 | sgtl5000->mute_state[event_source] = | ||
313 | mute_output(component, mute_mask[event_source]); | ||
314 | break; | ||
185 | case SND_SOC_DAPM_POST_PMU: | 315 | case SND_SOC_DAPM_POST_PMU: |
186 | snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, | 316 | vag_power_on(component, event_source); |
187 | SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP); | 317 | restore_output(component, mute_mask[event_source], |
188 | msleep(400); | 318 | sgtl5000->mute_state[event_source]); |
189 | break; | 319 | break; |
190 | |||
191 | case SND_SOC_DAPM_PRE_PMD: | 320 | case SND_SOC_DAPM_PRE_PMD: |
192 | /* | 321 | sgtl5000->mute_state[event_source] = |
193 | * Don't clear VAG_POWERUP, when both DAC and ADC are | 322 | mute_output(component, mute_mask[event_source]); |
194 | * operational to prevent inadvertently starving the | 323 | vag_power_off(component, event_source); |
195 | * other one of them. | 324 | break; |
196 | */ | 325 | case SND_SOC_DAPM_POST_PMD: |
197 | if ((snd_soc_component_read32(component, SGTL5000_CHIP_ANA_POWER) & | 326 | restore_output(component, mute_mask[event_source], |
198 | mask) != mask) { | 327 | sgtl5000->mute_state[event_source]); |
199 | snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, | ||
200 | SGTL5000_VAG_POWERUP, 0); | ||
201 | msleep(400); | ||
202 | } | ||
203 | break; | 328 | break; |
204 | default: | 329 | default: |
205 | break; | 330 | break; |
@@ -208,6 +333,41 @@ static int power_vag_event(struct snd_soc_dapm_widget *w, | |||
208 | return 0; | 333 | return 0; |
209 | } | 334 | } |
210 | 335 | ||
336 | /* | ||
337 | * Mute Headphone when power it up/down. | ||
338 | * Control VAG power on HP power path. | ||
339 | */ | ||
340 | static int headphone_pga_event(struct snd_soc_dapm_widget *w, | ||
341 | struct snd_kcontrol *kcontrol, int event) | ||
342 | { | ||
343 | struct snd_soc_component *component = | ||
344 | snd_soc_dapm_to_component(w->dapm); | ||
345 | |||
346 | return vag_and_mute_control(component, event, HP_POWER_EVENT); | ||
347 | } | ||
348 | |||
349 | /* As manual describes, ADC/DAC powering up/down requires | ||
350 | * to mute outputs to avoid pops. | ||
351 | * Control VAG power on ADC/DAC power path. | ||
352 | */ | ||
353 | static int adc_updown_depop(struct snd_soc_dapm_widget *w, | ||
354 | struct snd_kcontrol *kcontrol, int event) | ||
355 | { | ||
356 | struct snd_soc_component *component = | ||
357 | snd_soc_dapm_to_component(w->dapm); | ||
358 | |||
359 | return vag_and_mute_control(component, event, ADC_POWER_EVENT); | ||
360 | } | ||
361 | |||
362 | static int dac_updown_depop(struct snd_soc_dapm_widget *w, | ||
363 | struct snd_kcontrol *kcontrol, int event) | ||
364 | { | ||
365 | struct snd_soc_component *component = | ||
366 | snd_soc_dapm_to_component(w->dapm); | ||
367 | |||
368 | return vag_and_mute_control(component, event, DAC_POWER_EVENT); | ||
369 | } | ||
370 | |||
211 | /* input sources for ADC */ | 371 | /* input sources for ADC */ |
212 | static const char *adc_mux_text[] = { | 372 | static const char *adc_mux_text[] = { |
213 | "MIC_IN", "LINE_IN" | 373 | "MIC_IN", "LINE_IN" |
@@ -280,7 +440,10 @@ static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { | |||
280 | mic_bias_event, | 440 | mic_bias_event, |
281 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), | 441 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), |
282 | 442 | ||
283 | SND_SOC_DAPM_PGA("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0), | 443 | SND_SOC_DAPM_PGA_E("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0, |
444 | headphone_pga_event, | ||
445 | SND_SOC_DAPM_PRE_POST_PMU | | ||
446 | SND_SOC_DAPM_PRE_POST_PMD), | ||
284 | SND_SOC_DAPM_PGA("LO", SGTL5000_CHIP_ANA_POWER, 0, 0, NULL, 0), | 447 | SND_SOC_DAPM_PGA("LO", SGTL5000_CHIP_ANA_POWER, 0, 0, NULL, 0), |
285 | 448 | ||
286 | SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux), | 449 | SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux), |
@@ -301,11 +464,12 @@ static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { | |||
301 | 0, SGTL5000_CHIP_DIG_POWER, | 464 | 0, SGTL5000_CHIP_DIG_POWER, |
302 | 1, 0), | 465 | 1, 0), |
303 | 466 | ||
304 | SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_ANA_POWER, 1, 0), | 467 | SND_SOC_DAPM_ADC_E("ADC", "Capture", SGTL5000_CHIP_ANA_POWER, 1, 0, |
305 | SND_SOC_DAPM_DAC("DAC", "Playback", SGTL5000_CHIP_ANA_POWER, 3, 0), | 468 | adc_updown_depop, SND_SOC_DAPM_PRE_POST_PMU | |
306 | 469 | SND_SOC_DAPM_PRE_POST_PMD), | |
307 | SND_SOC_DAPM_PRE("VAG_POWER_PRE", power_vag_event), | 470 | SND_SOC_DAPM_DAC_E("DAC", "Playback", SGTL5000_CHIP_ANA_POWER, 3, 0, |
308 | SND_SOC_DAPM_POST("VAG_POWER_POST", power_vag_event), | 471 | dac_updown_depop, SND_SOC_DAPM_PRE_POST_PMU | |
472 | SND_SOC_DAPM_PRE_POST_PMD), | ||
309 | }; | 473 | }; |
310 | 474 | ||
311 | /* routes for sgtl5000 */ | 475 | /* routes for sgtl5000 */ |