From: Sasha Levin Date: Wed, 9 Oct 2019 16:32:03 +0000 (-0400) Subject: fixes for 4.14 X-Git-Tag: v4.14.149~4^2~2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=65e0dfd9898b9240b7265c42b4f7b65fb7e117fb;p=thirdparty%2Fkernel%2Fstable-queue.git fixes for 4.14 Signed-off-by: Sasha Levin --- diff --git a/queue-4.14/asoc-sgtl5000-improve-vag-power-and-mute-control.patch b/queue-4.14/asoc-sgtl5000-improve-vag-power-and-mute-control.patch new file mode 100644 index 00000000000..9aa437f44b0 --- /dev/null +++ b/queue-4.14/asoc-sgtl5000-improve-vag-power-and-mute-control.patch @@ -0,0 +1,344 @@ +From 40ae1c411e68aa92e8adf774591a6739eb08eb8f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 9 Oct 2019 14:38:41 +0000 +Subject: ASoC: sgtl5000: Improve VAG power and mute control + +From: Oleksandr Suvorov + +[ Upstream commit b1f373a11d25fc9a5f7679c9b85799fe09b0dc4a ] + +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 +Reviewed-by: Marcel Ziswiler +Reviewed-by: Fabio Estevam +Reviewed-by: Cezary Rojewski +Link: https://lore.kernel.org/r/20190719100524.23300-3-oleksandr.suvorov@toradex.com +Signed-off-by: Mark Brown +Signed-off-by: Sasha Levin +--- + sound/soc/codecs/sgtl5000.c | 232 +++++++++++++++++++++++++++++++----- + 1 file changed, 202 insertions(+), 30 deletions(-) + +diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c +index b649675d190d2..10764c1e854e2 100644 +--- a/sound/soc/codecs/sgtl5000.c ++++ b/sound/soc/codecs/sgtl5000.c +@@ -35,6 +35,13 @@ + #define SGTL5000_DAP_REG_OFFSET 0x0100 + #define SGTL5000_MAX_REG_OFFSET 0x013A + ++/* Delay for the VAG ramp up */ ++#define SGTL5000_VAG_POWERUP_DELAY 500 /* ms */ ++/* Delay for the VAG ramp down */ ++#define SGTL5000_VAG_POWERDOWN_DELAY 500 /* ms */ ++ ++#define SGTL5000_OUTPUTS_MUTE (SGTL5000_HP_MUTE | SGTL5000_LINE_OUT_MUTE) ++ + /* default value of sgtl5000 registers */ + static const struct reg_default sgtl5000_reg_defaults[] = { + { SGTL5000_CHIP_DIG_POWER, 0x0000 }, +@@ -120,6 +127,13 @@ enum { + I2S_LRCLK_STRENGTH_HIGH, + }; + ++enum { ++ HP_POWER_EVENT, ++ DAC_POWER_EVENT, ++ ADC_POWER_EVENT, ++ LAST_POWER_EVENT = ADC_POWER_EVENT ++}; ++ + /* sgtl5000 private structure in codec */ + struct sgtl5000_priv { + int sysclk; /* sysclk rate */ +@@ -133,8 +147,117 @@ struct sgtl5000_priv { + u8 micbias_resistor; + u8 micbias_voltage; + u8 lrclk_strength; ++ u16 mute_state[LAST_POWER_EVENT + 1]; + }; + ++static inline int hp_sel_input(struct snd_soc_component *component) ++{ ++ unsigned int ana_reg = 0; ++ ++ snd_soc_component_read(component, SGTL5000_CHIP_ANA_CTRL, &ana_reg); ++ ++ return (ana_reg & SGTL5000_HP_SEL_MASK) >> SGTL5000_HP_SEL_SHIFT; ++} ++ ++static inline u16 mute_output(struct snd_soc_component *component, ++ u16 mute_mask) ++{ ++ unsigned int mute_reg = 0; ++ ++ snd_soc_component_read(component, SGTL5000_CHIP_ANA_CTRL, &mute_reg); ++ ++ snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_CTRL, ++ mute_mask, mute_mask); ++ return mute_reg; ++} ++ ++static inline void restore_output(struct snd_soc_component *component, ++ u16 mute_mask, u16 mute_reg) ++{ ++ snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_CTRL, ++ mute_mask, mute_reg); ++} ++ ++static void vag_power_on(struct snd_soc_component *component, u32 source) ++{ ++ unsigned int ana_reg = 0; ++ ++ snd_soc_component_read(component, SGTL5000_CHIP_ANA_POWER, &ana_reg); ++ ++ if (ana_reg & SGTL5000_VAG_POWERUP) ++ return; ++ ++ snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, ++ SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP); ++ ++ /* When VAG powering on to get local loop from Line-In, the sleep ++ * is required to avoid loud pop. ++ */ ++ if (hp_sel_input(component) == SGTL5000_HP_SEL_LINE_IN && ++ source == HP_POWER_EVENT) ++ msleep(SGTL5000_VAG_POWERUP_DELAY); ++} ++ ++static int vag_power_consumers(struct snd_soc_component *component, ++ u16 ana_pwr_reg, u32 source) ++{ ++ int consumers = 0; ++ ++ /* count dac/adc consumers unconditional */ ++ if (ana_pwr_reg & SGTL5000_DAC_POWERUP) ++ consumers++; ++ if (ana_pwr_reg & SGTL5000_ADC_POWERUP) ++ consumers++; ++ ++ /* ++ * If the event comes from HP and Line-In is selected, ++ * current action is 'DAC to be powered down'. ++ * As HP_POWERUP is not set when HP muxed to line-in, ++ * we need to keep VAG power ON. ++ */ ++ if (source == HP_POWER_EVENT) { ++ if (hp_sel_input(component) == SGTL5000_HP_SEL_LINE_IN) ++ consumers++; ++ } else { ++ if (ana_pwr_reg & SGTL5000_HP_POWERUP) ++ consumers++; ++ } ++ ++ return consumers; ++} ++ ++static void vag_power_off(struct snd_soc_component *component, u32 source) ++{ ++ unsigned int ana_pwr = SGTL5000_VAG_POWERUP; ++ ++ snd_soc_component_read(component, SGTL5000_CHIP_ANA_POWER, &ana_pwr); ++ ++ if (!(ana_pwr & SGTL5000_VAG_POWERUP)) ++ return; ++ ++ /* ++ * This function calls when any of VAG power consumers is disappearing. ++ * Thus, if there is more than one consumer at the moment, as minimum ++ * one consumer will definitely stay after the end of the current ++ * event. ++ * Don't clear VAG_POWERUP if 2 or more consumers of VAG present: ++ * - LINE_IN (for HP events) / HP (for DAC/ADC events) ++ * - DAC ++ * - ADC ++ * (the current consumer is disappearing right now) ++ */ ++ if (vag_power_consumers(component, ana_pwr, source) >= 2) ++ return; ++ ++ snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, ++ SGTL5000_VAG_POWERUP, 0); ++ /* In power down case, we need wait 400-1000 ms ++ * when VAG fully ramped down. ++ * As longer we wait, as smaller pop we've got. ++ */ ++ msleep(SGTL5000_VAG_POWERDOWN_DELAY); ++} ++ + /* + * mic_bias power on/off share the same register bits with + * output impedance of mic bias, when power on mic bias, we +@@ -166,36 +289,46 @@ static int mic_bias_event(struct snd_soc_dapm_widget *w, + return 0; + } + +-/* +- * As manual described, ADC/DAC only works when VAG powerup, +- * So enabled VAG before ADC/DAC up. +- * In power down case, we need wait 400ms when vag fully ramped down. +- */ +-static int power_vag_event(struct snd_soc_dapm_widget *w, +- struct snd_kcontrol *kcontrol, int event) ++static int vag_and_mute_control(struct snd_soc_component *component, ++ int event, int event_source) + { +- struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); +- const u32 mask = SGTL5000_DAC_POWERUP | SGTL5000_ADC_POWERUP; ++ static const u16 mute_mask[] = { ++ /* ++ * Mask for HP_POWER_EVENT. ++ * Muxing Headphones have to be wrapped with mute/unmute ++ * headphones only. ++ */ ++ SGTL5000_HP_MUTE, ++ /* ++ * Masks for DAC_POWER_EVENT/ADC_POWER_EVENT. ++ * Muxing DAC or ADC block have to be wrapped with mute/unmute ++ * both headphones and line-out. ++ */ ++ SGTL5000_OUTPUTS_MUTE, ++ SGTL5000_OUTPUTS_MUTE ++ }; ++ ++ struct sgtl5000_priv *sgtl5000 = ++ snd_soc_component_get_drvdata(component); + + switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ sgtl5000->mute_state[event_source] = ++ mute_output(component, mute_mask[event_source]); ++ break; + case SND_SOC_DAPM_POST_PMU: +- snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, +- SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP); +- msleep(400); ++ vag_power_on(component, event_source); ++ restore_output(component, mute_mask[event_source], ++ sgtl5000->mute_state[event_source]); + break; +- + case SND_SOC_DAPM_PRE_PMD: +- /* +- * Don't clear VAG_POWERUP, when both DAC and ADC are +- * operational to prevent inadvertently starving the +- * other one of them. +- */ +- if ((snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER) & +- mask) != mask) { +- snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, +- SGTL5000_VAG_POWERUP, 0); +- msleep(400); +- } ++ sgtl5000->mute_state[event_source] = ++ mute_output(component, mute_mask[event_source]); ++ vag_power_off(component, event_source); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ restore_output(component, mute_mask[event_source], ++ sgtl5000->mute_state[event_source]); + break; + default: + break; +@@ -204,6 +337,41 @@ static int power_vag_event(struct snd_soc_dapm_widget *w, + return 0; + } + ++/* ++ * Mute Headphone when power it up/down. ++ * Control VAG power on HP power path. ++ */ ++static int headphone_pga_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ ++ return vag_and_mute_control(component, event, HP_POWER_EVENT); ++} ++ ++/* As manual describes, ADC/DAC powering up/down requires ++ * to mute outputs to avoid pops. ++ * Control VAG power on ADC/DAC power path. ++ */ ++static int adc_updown_depop(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ ++ return vag_and_mute_control(component, event, ADC_POWER_EVENT); ++} ++ ++static int dac_updown_depop(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ ++ return vag_and_mute_control(component, event, DAC_POWER_EVENT); ++} ++ + /* input sources for ADC */ + static const char *adc_mux_text[] = { + "MIC_IN", "LINE_IN" +@@ -239,7 +407,10 @@ static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { + mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +- SND_SOC_DAPM_PGA("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0, ++ headphone_pga_event, ++ SND_SOC_DAPM_PRE_POST_PMU | ++ SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_PGA("LO", SGTL5000_CHIP_ANA_POWER, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux), +@@ -255,11 +426,12 @@ static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { + 0, SGTL5000_CHIP_DIG_POWER, + 1, 0), + +- SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_ANA_POWER, 1, 0), +- SND_SOC_DAPM_DAC("DAC", "Playback", SGTL5000_CHIP_ANA_POWER, 3, 0), +- +- SND_SOC_DAPM_PRE("VAG_POWER_PRE", power_vag_event), +- SND_SOC_DAPM_POST("VAG_POWER_POST", power_vag_event), ++ SND_SOC_DAPM_ADC_E("ADC", "Capture", SGTL5000_CHIP_ANA_POWER, 1, 0, ++ adc_updown_depop, SND_SOC_DAPM_PRE_POST_PMU | ++ SND_SOC_DAPM_PRE_POST_PMD), ++ SND_SOC_DAPM_DAC_E("DAC", "Playback", SGTL5000_CHIP_ANA_POWER, 3, 0, ++ dac_updown_depop, SND_SOC_DAPM_PRE_POST_PMU | ++ SND_SOC_DAPM_PRE_POST_PMD), + }; + + /* routes for sgtl5000 */ +-- +2.20.1 + diff --git a/queue-4.14/series b/queue-4.14/series index 557a38dee03..09d19fc6484 100644 --- a/queue-4.14/series +++ b/queue-4.14/series @@ -58,3 +58,4 @@ coresight-etm4x-use-explicit-barriers-on-enable-disable.patch cfg80211-add-and-use-strongly-typed-element-iteration-macros.patch cfg80211-use-const-more-consistently-in-for_each_element-macros.patch nl80211-validate-beacon-head.patch +asoc-sgtl5000-improve-vag-power-and-mute-control.patch