When using AIF1 the 'DAC1 Playback Volume' control will be used as the PlaybackMasterElem in UCM. We need a matching 'DAC1 Playback Switch' for 2 reasons: 1. To be able to truely fully mute the output (the softest volume setting is not fully muted). 2. For reliable output-mute LED control. The path from the IF1_DAC data input to the 'Stereo DAC MIXL' / 'Stereo DAC MIXR' digital mixer has a 'DAC MIXL' / 'DAC MIXR' digital mixer with IF1_DAC data as one of its inputs direclty after the 'DAC1 Playback Volume' control. This commit adds an emulated "DAC1 Playback Switch" control by: 1. Declaring the enable flag for the mixers IF1_DAC input as well as the "DAC1 Playback Switch" control both as SND_SOC_NOPM controls. 2. Storing the settings of both controls as driver-private data. 3. Only clearing the mute flag for the IF1_DAC input of that mixer if the stored values indicate both controls are enabled. Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx> --- sound/soc/codecs/rt5640.c | 96 +++++++++++++++++++++++++++++++++++++-- sound/soc/codecs/rt5640.h | 4 ++ 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index a5674c227b3a..c143ca174921 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -378,6 +378,56 @@ static const char * const rt5640_clsd_spk_ratio[] = {"1.66x", "1.83x", "1.94x", static SOC_ENUM_SINGLE_DECL(rt5640_clsd_spk_ratio_enum, RT5640_CLS_D_OUT, RT5640_CLSD_RATIO_SFT, rt5640_clsd_spk_ratio); +/* + * For reliable output-mute LED control we need a "DAC1 Playback Switch" control. + * We emulate this by only clearing the RT5640_M_IF1_DAC_L/_R AD_DA_MIXER register + * bits when both our emulated DAC1 Playback Switch control and the DAC1 MIXL/R + * DAPM-mixer DAC1 input are enabled. + */ +static void rt5640_update_ad_da_mixer_if1_dac_m_bits(struct rt5640_priv *rt5640) +{ + int val = RT5640_M_IF1_DAC_L | RT5640_M_IF1_DAC_R; + + if (rt5640->dac1_mixl_if1_switch && rt5640->dac1_playback_switch_l) + val &= ~RT5640_M_IF1_DAC_L; + + if (rt5640->dac1_mixr_if1_switch && rt5640->dac1_playback_switch_r) + val &= ~RT5640_M_IF1_DAC_R; + + regmap_update_bits(rt5640->regmap, RT5640_AD_DA_MIXER, + RT5640_M_IF1_DAC_L | RT5640_M_IF1_DAC_R, val); +} + +static int rt5640_dac1_playback_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = rt5640->dac1_playback_switch_l; + ucontrol->value.integer.value[1] = rt5640->dac1_playback_switch_r; + + return 0; +} + +static int rt5640_dac1_playback_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + if (rt5640->dac1_playback_switch_l == ucontrol->value.integer.value[0] && + rt5640->dac1_playback_switch_r == ucontrol->value.integer.value[1]) + return 0; + + rt5640->dac1_playback_switch_l = ucontrol->value.integer.value[0]; + rt5640->dac1_playback_switch_r = ucontrol->value.integer.value[1]; + + rt5640_update_ad_da_mixer_if1_dac_m_bits(rt5640); + + return 1; +} + static const struct snd_kcontrol_new rt5640_snd_controls[] = { /* Speaker Output Volume */ SOC_DOUBLE("Speaker Channel Switch", RT5640_SPK_VOL, @@ -400,6 +450,8 @@ static const struct snd_kcontrol_new rt5640_snd_controls[] = { /* DAC Digital Volume */ SOC_DOUBLE("DAC2 Playback Switch", RT5640_DAC2_CTRL, RT5640_M_DAC_L2_VOL_SFT, RT5640_M_DAC_R2_VOL_SFT, 1, 1), + SOC_DOUBLE_EXT("DAC1 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, + rt5640_dac1_playback_switch_get, rt5640_dac1_playback_switch_put), SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5640_DAC1_DIG_VOL, RT5640_L_VOL_SFT, RT5640_R_VOL_SFT, 175, 0, dac_vol_tlv), @@ -515,18 +567,44 @@ static const struct snd_kcontrol_new rt5640_mono_adc_r_mix[] = { RT5640_M_MONO_ADC_R2_SFT, 1, 1), }; +/* See comment above rt5640_update_ad_da_mixer_if1_dac_m_bits() */ +static int rt5640_put_dac1_mix_if1_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + int ret; + + if (mc->shift == 0) + rt5640->dac1_mixl_if1_switch = ucontrol->value.integer.value[0]; + else + rt5640->dac1_mixr_if1_switch = ucontrol->value.integer.value[0]; + + /* Apply the update (if any) */ + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + if (ret == 0) + return 0; + + rt5640_update_ad_da_mixer_if1_dac_m_bits(rt5640); + + return 1; +} + +#define SOC_DAPM_SINGLE_RT5640_IF1_SW(name, shift) \ + SOC_SINGLE_EXT(name, SND_SOC_NOPM, shift, 1, 0, \ + snd_soc_dapm_get_volsw, rt5640_put_dac1_mix_if1_switch) + static const struct snd_kcontrol_new rt5640_dac_l_mix[] = { SOC_DAPM_SINGLE("Stereo ADC Switch", RT5640_AD_DA_MIXER, RT5640_M_ADCMIX_L_SFT, 1, 1), - SOC_DAPM_SINGLE("INF1 Switch", RT5640_AD_DA_MIXER, - RT5640_M_IF1_DAC_L_SFT, 1, 1), + SOC_DAPM_SINGLE_RT5640_IF1_SW("INF1 Switch", 0), }; static const struct snd_kcontrol_new rt5640_dac_r_mix[] = { SOC_DAPM_SINGLE("Stereo ADC Switch", RT5640_AD_DA_MIXER, RT5640_M_ADCMIX_R_SFT, 1, 1), - SOC_DAPM_SINGLE("INF1 Switch", RT5640_AD_DA_MIXER, - RT5640_M_IF1_DAC_R_SFT, 1, 1), + SOC_DAPM_SINGLE_RT5640_IF1_SW("INF1 Switch", 1), }; static const struct snd_kcontrol_new rt5640_sto_dac_l_mix[] = { @@ -2831,6 +2909,16 @@ static int rt5640_i2c_probe(struct i2c_client *i2c, INIT_DELAYED_WORK(&rt5640->bp_work, rt5640_button_press_work); INIT_WORK(&rt5640->jack_work, rt5640_jack_work); + /* + * Enable the emulated "DAC1 Playback Switch" by default to avoid + * muting the output with older UCM profiles. + */ + rt5640->dac1_playback_switch_l = true; + rt5640->dac1_playback_switch_r = true; + /* The Power-On-Reset values for the DAC1 mixer have the INF1 input enabled. */ + rt5640->dac1_mixl_if1_switch = true; + rt5640->dac1_mixr_if1_switch = true; + /* Make sure work is stopped on probe-error / remove */ ret = devm_add_action_or_reset(&i2c->dev, rt5640_cancel_work, rt5640); if (ret) diff --git a/sound/soc/codecs/rt5640.h b/sound/soc/codecs/rt5640.h index 4fd47f2b936b..0d029f5dbb61 100644 --- a/sound/soc/codecs/rt5640.h +++ b/sound/soc/codecs/rt5640.h @@ -2135,6 +2135,10 @@ struct rt5640_priv { bool hp_mute; bool asrc_en; + bool dac1_mixl_if1_switch; + bool dac1_mixr_if1_switch; + bool dac1_playback_switch_l; + bool dac1_playback_switch_r; /* Jack and button detect data */ bool ovcd_irq_enabled; -- 2.30.1