ASoC: rt5670: Add emulated 'DAC1 Playback Switch' control
authorHans de Goede <hdegoede@redhat.com>
Mon, 15 Feb 2021 14:21:18 +0000 (15:21 +0100)
committerMark Brown <broonie@kernel.org>
Wed, 10 Mar 2021 12:48:35 +0000 (12:48 +0000)
For reliable output-mute LED control we need a "DAC1 Playback Switch"
control. The "DAC Playback volume" control is the only control in the
path from the DAC1 data input to the speaker output, so the UCM profile
for the speaker output will have its PlaybackMixerElem set to "DAC1".

But userspace (pulseaudio) will set the "DAC1 Playback Volume" control to
its softest setting (which is not fully muted) while still showing the
speaker as being enabled at a low volume in the UI.

If we were to set the SNDRV_CTL_ELEM_ACCESS_SPK_LED on the "DAC1 Playback
Volume" control, this would mean then what pressing KEY_VOLUMEDOWN the
speaker-mute LED (embedded in the volume-mute toggle key) would light
while the UI is still showing the speaker as being enabled at a low
volume, meaning that the UI and the LED are out of sync.

Only after an _extra_ KEY_VOLUMEDOWN press would the UI show the
speaker as being muted.

The path from DAC1 data input to the speaker output does have
a digital mixer with DAC1's 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 that mixers DAC1 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 DAC1 input of that mixer if the
stored values indicate both controls are enabled.

This is a preparation patch for adding "audio-mute" LED trigger support.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20210215142118.308516-5-hdegoede@redhat.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/rt5670.c
sound/soc/codecs/rt5670.h

index 2f015c2..4063aac 100644 (file)
@@ -629,6 +629,56 @@ static SOC_ENUM_SINGLE_DECL(rt5670_if2_dac_enum, RT5670_DIG_INF1_DATA,
 static SOC_ENUM_SINGLE_DECL(rt5670_if2_adc_enum, RT5670_DIG_INF1_DATA,
                                RT5670_IF2_ADC_SEL_SFT, rt5670_data_select);
 
+/*
+ * For reliable output-mute LED control we need a "DAC1 Playback Switch" control.
+ * We emulate this by only clearing the RT5670_M_DAC1_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 rt5670_update_ad_da_mixer_dac1_m_bits(struct rt5670_priv *rt5670)
+{
+       int val = RT5670_M_DAC1_L | RT5670_M_DAC1_R;
+
+       if (rt5670->dac1_mixl_dac1_switch && rt5670->dac1_playback_switch_l)
+               val &= ~RT5670_M_DAC1_L;
+
+       if (rt5670->dac1_mixr_dac1_switch && rt5670->dac1_playback_switch_r)
+               val &= ~RT5670_M_DAC1_R;
+
+       regmap_update_bits(rt5670->regmap, RT5670_AD_DA_MIXER,
+                          RT5670_M_DAC1_L | RT5670_M_DAC1_R, val);
+}
+
+static int rt5670_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 rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component);
+
+       ucontrol->value.integer.value[0] = rt5670->dac1_playback_switch_l;
+       ucontrol->value.integer.value[1] = rt5670->dac1_playback_switch_r;
+
+       return 0;
+}
+
+static int rt5670_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 rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component);
+
+       if (rt5670->dac1_playback_switch_l == ucontrol->value.integer.value[0] &&
+           rt5670->dac1_playback_switch_r == ucontrol->value.integer.value[1])
+               return 0;
+
+       rt5670->dac1_playback_switch_l = ucontrol->value.integer.value[0];
+       rt5670->dac1_playback_switch_r = ucontrol->value.integer.value[1];
+
+       rt5670_update_ad_da_mixer_dac1_m_bits(rt5670);
+
+       return 1;
+}
+
 static const struct snd_kcontrol_new rt5670_snd_controls[] = {
        /* Headphone Output Volume */
        SOC_DOUBLE_TLV("HP Playback Volume", RT5670_HP_VOL,
@@ -640,6 +690,8 @@ static const struct snd_kcontrol_new rt5670_snd_controls[] = {
        /* DAC Digital Volume */
        SOC_DOUBLE("DAC2 Playback Switch", RT5670_DAC_CTRL,
                RT5670_M_DAC_L2_VOL_SFT, RT5670_M_DAC_R2_VOL_SFT, 1, 1),
+       SOC_DOUBLE_EXT("DAC1 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0,
+                       rt5670_dac1_playback_switch_get, rt5670_dac1_playback_switch_put),
        SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5670_DAC1_DIG_VOL,
                        RT5670_L_VOL_SFT, RT5670_R_VOL_SFT,
                        175, 0, dac_vol_tlv),
@@ -909,18 +961,44 @@ static const struct snd_kcontrol_new rt5670_mono_adc_r_mix[] = {
                        RT5670_M_MONO_ADC_R2_SFT, 1, 1),
 };
 
+/* See comment above rt5670_update_ad_da_mixer_dac1_m_bits() */
+static int rt5670_put_dac1_mix_dac1_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 rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component);
+       int ret;
+
+       if (mc->shift == 0)
+               rt5670->dac1_mixl_dac1_switch = ucontrol->value.integer.value[0];
+       else
+               rt5670->dac1_mixr_dac1_switch = ucontrol->value.integer.value[0];
+
+       /* Apply the update (if any) */
+       ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+       if (ret == 0)
+               return 0;
+
+       rt5670_update_ad_da_mixer_dac1_m_bits(rt5670);
+
+       return 1;
+}
+
+#define SOC_DAPM_SINGLE_RT5670_DAC1_SW(name, shift) \
+       SOC_SINGLE_EXT(name, SND_SOC_NOPM, shift, 1, 0, \
+                      snd_soc_dapm_get_volsw, rt5670_put_dac1_mix_dac1_switch)
+
 static const struct snd_kcontrol_new rt5670_dac_l_mix[] = {
        SOC_DAPM_SINGLE("Stereo ADC Switch", RT5670_AD_DA_MIXER,
                        RT5670_M_ADCMIX_L_SFT, 1, 1),
-       SOC_DAPM_SINGLE("DAC1 Switch", RT5670_AD_DA_MIXER,
-                       RT5670_M_DAC1_L_SFT, 1, 1),
+       SOC_DAPM_SINGLE_RT5670_DAC1_SW("DAC1 Switch", 0),
 };
 
 static const struct snd_kcontrol_new rt5670_dac_r_mix[] = {
        SOC_DAPM_SINGLE("Stereo ADC Switch", RT5670_AD_DA_MIXER,
                        RT5670_M_ADCMIX_R_SFT, 1, 1),
-       SOC_DAPM_SINGLE("DAC1 Switch", RT5670_AD_DA_MIXER,
-                       RT5670_M_DAC1_R_SFT, 1, 1),
+       SOC_DAPM_SINGLE_RT5670_DAC1_SW("DAC1 Switch", 1),
 };
 
 static const struct snd_kcontrol_new rt5670_sto_dac_l_mix[] = {
@@ -2993,6 +3071,16 @@ static int rt5670_i2c_probe(struct i2c_client *i2c,
                dev_info(&i2c->dev, "quirk JD mode 3\n");
        }
 
+       /*
+        * Enable the emulated "DAC1 Playback Switch" by default to avoid
+        * muting the output with older UCM profiles.
+        */
+       rt5670->dac1_playback_switch_l = true;
+       rt5670->dac1_playback_switch_r = true;
+       /* The Power-On-Reset values for the DAC1 mixer have the DAC1 input enabled. */
+       rt5670->dac1_mixl_dac1_switch = true;
+       rt5670->dac1_mixr_dac1_switch = true;
+
        rt5670->regmap = devm_regmap_init_i2c(i2c, &rt5670_regmap);
        if (IS_ERR(rt5670->regmap)) {
                ret = PTR_ERR(rt5670->regmap);
index f9c4db1..6fb3c36 100644 (file)
@@ -2013,6 +2013,11 @@ struct rt5670_priv {
        int dsp_rate;
        int jack_type;
        int jack_type_saved;
+
+       bool dac1_mixl_dac1_switch;
+       bool dac1_mixr_dac1_switch;
+       bool dac1_playback_switch_l;
+       bool dac1_playback_switch_r;
 };
 
 void rt5670_jack_suspend(struct snd_soc_component *component);