sound/soc/codecs/rt722-sdca.c | 77 ++++++++++++++++++++++++++++------- sound/soc/codecs/rt722-sdca.h | 4 ++ 2 files changed, 67 insertions(+), 14 deletions(-)
The rt722-sdca codec driver exposes FU06 Playback Volume but no
corresponding mute switch. Without a user-facing ALSA switch, UCM
cannot attach the speaker mute LED via snd_ctl_led, and PipeWire
cannot drive hardware mute.
Signed-off-by: Aaron Ma <aaron.ma@canonical.com>
---
sound/soc/codecs/rt722-sdca.c | 77 ++++++++++++++++++++++++++++-------
sound/soc/codecs/rt722-sdca.h | 4 ++
2 files changed, 67 insertions(+), 14 deletions(-)
diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c
index 79b8b7e70a334..23d2f63d68ef3 100644
--- a/sound/soc/codecs/rt722-sdca.c
+++ b/sound/soc/codecs/rt722-sdca.c
@@ -517,6 +517,61 @@ static int rt722_sdca_fu1e_capture_put(struct snd_kcontrol *kcontrol,
return changed;
}
+static int rt722_sdca_set_fu06_playback_ctl(struct rt722_sdca_priv *rt722)
+{
+ int err;
+ unsigned int ch_l, ch_r;
+
+ ch_l = (rt722->fu06_dapm_mute || rt722->fu06_mixer_l_mute) ? 0x01 : 0x00;
+ ch_r = (rt722->fu06_dapm_mute || rt722->fu06_mixer_r_mute) ? 0x01 : 0x00;
+
+ err = regmap_write(rt722->regmap,
+ SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
+ RT722_SDCA_CTL_FU_MUTE, CH_L), ch_l);
+ if (err < 0)
+ return err;
+
+ err = regmap_write(rt722->regmap,
+ SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
+ RT722_SDCA_CTL_FU_MUTE, CH_R), ch_r);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int rt722_sdca_fu06_playback_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.integer.value[0] = !rt722->fu06_mixer_l_mute;
+ ucontrol->value.integer.value[1] = !rt722->fu06_mixer_r_mute;
+ return 0;
+}
+
+static int rt722_sdca_fu06_playback_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component);
+ int err, changed = 0;
+
+ if (rt722->fu06_mixer_l_mute != !ucontrol->value.integer.value[0] ||
+ rt722->fu06_mixer_r_mute != !ucontrol->value.integer.value[1])
+ changed = 1;
+
+ rt722->fu06_mixer_l_mute = !ucontrol->value.integer.value[0];
+ rt722->fu06_mixer_r_mute = !ucontrol->value.integer.value[1];
+
+ err = rt722_sdca_set_fu06_playback_ctl(rt722);
+ if (err < 0)
+ return err;
+
+ return changed;
+}
+
static int rt722_sdca_set_fu0f_capture_ctl(struct rt722_sdca_priv *rt722)
{
int err;
@@ -718,6 +773,8 @@ static const struct snd_kcontrol_new rt722_sdca_controls[] = {
RT722_SDCA_CTL_FU_CH_GAIN, CH_R), 8, 3, 0,
rt722_sdca_set_gain_get, rt722_sdca_set_gain_put, boost_vol_tlv),
/* AMP playback settings */
+ SOC_DOUBLE_EXT("FU06 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0,
+ rt722_sdca_fu06_playback_get, rt722_sdca_fu06_playback_put),
SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume",
SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
RT722_SDCA_CTL_FU_VOLUME, CH_L),
@@ -807,27 +864,17 @@ static int rt722_sdca_fu21_event(struct snd_soc_dapm_widget *w,
struct snd_soc_component *component =
snd_soc_dapm_to_component(w->dapm);
struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component);
- unsigned char unmute = 0x0, mute = 0x1;
switch (event) {
case SND_SOC_DAPM_POST_PMU:
- regmap_write(rt722->regmap,
- SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
- RT722_SDCA_CTL_FU_MUTE, CH_L), unmute);
- regmap_write(rt722->regmap,
- SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
- RT722_SDCA_CTL_FU_MUTE, CH_R), unmute);
+ rt722->fu06_dapm_mute = false;
break;
case SND_SOC_DAPM_PRE_PMD:
- regmap_write(rt722->regmap,
- SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
- RT722_SDCA_CTL_FU_MUTE, CH_L), mute);
- regmap_write(rt722->regmap,
- SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
- RT722_SDCA_CTL_FU_MUTE, CH_R), mute);
+ rt722->fu06_dapm_mute = true;
break;
}
- return 0;
+
+ return rt722_sdca_set_fu06_playback_ctl(rt722);
}
static int rt722_sdca_fu113_event(struct snd_soc_dapm_widget *w,
@@ -1324,6 +1371,8 @@ int rt722_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave
rt722->first_hw_init = false;
rt722->fu1e_dapm_mute = true;
rt722->fu0f_dapm_mute = true;
+ rt722->fu06_dapm_mute = true;
+ rt722->fu06_mixer_l_mute = rt722->fu06_mixer_r_mute = false;
rt722->fu0f_mixer_l_mute = rt722->fu0f_mixer_r_mute = true;
rt722->fu1e_mixer_mute[0] = rt722->fu1e_mixer_mute[1] =
rt722->fu1e_mixer_mute[2] = rt722->fu1e_mixer_mute[3] = true;
diff --git a/sound/soc/codecs/rt722-sdca.h b/sound/soc/codecs/rt722-sdca.h
index 823abee9ab76c..fc50beff94245 100644
--- a/sound/soc/codecs/rt722-sdca.h
+++ b/sound/soc/codecs/rt722-sdca.h
@@ -36,6 +36,10 @@ struct rt722_sdca_priv {
bool fu0f_dapm_mute;
bool fu0f_mixer_l_mute;
bool fu0f_mixer_r_mute;
+ /* For AMP */
+ bool fu06_dapm_mute;
+ bool fu06_mixer_l_mute;
+ bool fu06_mixer_r_mute;
/* For DMIC */
bool fu1e_dapm_mute;
bool fu1e_mixer_mute[4];
--
2.43.0
On Thu, 23 Apr 2026 18:13:38 +0800, Aaron Ma wrote:
> ASoC: rt722-sdca: add FU06 Playback Switch for speaker mute control
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-7.2
Thanks!
[1/1] ASoC: rt722-sdca: add FU06 Playback Switch for speaker mute control
https://git.kernel.org/broonie/sound/c/bda41dd5e1dd
All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying
to this mail.
Thanks,
Mark
On 4/23/26 12:13, Aaron Ma wrote:
> The rt722-sdca codec driver exposes FU06 Playback Volume but no
> corresponding mute switch. Without a user-facing ALSA switch, UCM
> cannot attach the speaker mute LED via snd_ctl_led, and PipeWire
> cannot drive hardware mute.
>
> Signed-off-by: Aaron Ma <aaron.ma@canonical.com>
> ---
> sound/soc/codecs/rt722-sdca.c | 77 ++++++++++++++++++++++++++++-------
> sound/soc/codecs/rt722-sdca.h | 4 ++
> 2 files changed, 67 insertions(+), 14 deletions(-)
>
> diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c
> index 79b8b7e70a334..23d2f63d68ef3 100644
> --- a/sound/soc/codecs/rt722-sdca.c
> +++ b/sound/soc/codecs/rt722-sdca.c
> @@ -517,6 +517,61 @@ static int rt722_sdca_fu1e_capture_put(struct snd_kcontrol *kcontrol,
> return changed;
> }
>
> +static int rt722_sdca_set_fu06_playback_ctl(struct rt722_sdca_priv *rt722)
> +{
> + int err;
> + unsigned int ch_l, ch_r;
> +
> + ch_l = (rt722->fu06_dapm_mute || rt722->fu06_mixer_l_mute) ? 0x01 : 0x00;
> + ch_r = (rt722->fu06_dapm_mute || rt722->fu06_mixer_r_mute) ? 0x01 : 0x00;
> +
> + err = regmap_write(rt722->regmap,
> + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
> + RT722_SDCA_CTL_FU_MUTE, CH_L), ch_l);
> + if (err < 0)
> + return err;
> +
> + err = regmap_write(rt722->regmap,
> + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
> + RT722_SDCA_CTL_FU_MUTE, CH_R), ch_r);
IIRC you can mute all channels at once in a FU (Feature Unit) using Control Number 0.
If both channels need to be muted/unmuted it's better to do so in a single command.
On Thu, Apr 23, 2026 at 9:01 PM Pierre-Louis Bossart
<pierre-louis.bossart@linux.dev> wrote:
>
> On 4/23/26 12:13, Aaron Ma wrote:
> > The rt722-sdca codec driver exposes FU06 Playback Volume but no
> > corresponding mute switch. Without a user-facing ALSA switch, UCM
> > cannot attach the speaker mute LED via snd_ctl_led, and PipeWire
> > cannot drive hardware mute.
> >
> > Signed-off-by: Aaron Ma <aaron.ma@canonical.com>
> > ---
> > sound/soc/codecs/rt722-sdca.c | 77 ++++++++++++++++++++++++++++-------
> > sound/soc/codecs/rt722-sdca.h | 4 ++
> > 2 files changed, 67 insertions(+), 14 deletions(-)
> >
> > diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c
> > index 79b8b7e70a334..23d2f63d68ef3 100644
> > --- a/sound/soc/codecs/rt722-sdca.c
> > +++ b/sound/soc/codecs/rt722-sdca.c
> > @@ -517,6 +517,61 @@ static int rt722_sdca_fu1e_capture_put(struct snd_kcontrol *kcontrol,
> > return changed;
> > }
> >
> > +static int rt722_sdca_set_fu06_playback_ctl(struct rt722_sdca_priv *rt722)
> > +{
> > + int err;
> > + unsigned int ch_l, ch_r;
> > +
> > + ch_l = (rt722->fu06_dapm_mute || rt722->fu06_mixer_l_mute) ? 0x01 : 0x00;
> > + ch_r = (rt722->fu06_dapm_mute || rt722->fu06_mixer_r_mute) ? 0x01 : 0x00;
> > +
> > + err = regmap_write(rt722->regmap,
> > + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
> > + RT722_SDCA_CTL_FU_MUTE, CH_L), ch_l);
> > + if (err < 0)
> > + return err;
> > +
> > + err = regmap_write(rt722->regmap,
> > + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
> > + RT722_SDCA_CTL_FU_MUTE, CH_R), ch_r);
>
> IIRC you can mute all channels at once in a FU (Feature Unit) using Control Number 0.
>
> If both channels need to be muted/unmuted it's better to do so in a single command.
>
Thanks for the review.
The per-channel mute writes are unchanged from the existing DAPM
handler, just moved into a helper.
All rt711/rt712/rt722 drivers use per-channel writes for FU_MUTE — no
CN0 precedent exists.
Aaron
On 4/24/26 05:52, Aaron Ma wrote:
> On Thu, Apr 23, 2026 at 9:01 PM Pierre-Louis Bossart
> <pierre-louis.bossart@linux.dev> wrote:
>>
>> On 4/23/26 12:13, Aaron Ma wrote:
>>> The rt722-sdca codec driver exposes FU06 Playback Volume but no
>>> corresponding mute switch. Without a user-facing ALSA switch, UCM
>>> cannot attach the speaker mute LED via snd_ctl_led, and PipeWire
>>> cannot drive hardware mute.
>>>
>>> Signed-off-by: Aaron Ma <aaron.ma@canonical.com>
>>> ---
>>> sound/soc/codecs/rt722-sdca.c | 77 ++++++++++++++++++++++++++++-------
>>> sound/soc/codecs/rt722-sdca.h | 4 ++
>>> 2 files changed, 67 insertions(+), 14 deletions(-)
>>>
>>> diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c
>>> index 79b8b7e70a334..23d2f63d68ef3 100644
>>> --- a/sound/soc/codecs/rt722-sdca.c
>>> +++ b/sound/soc/codecs/rt722-sdca.c
>>> @@ -517,6 +517,61 @@ static int rt722_sdca_fu1e_capture_put(struct snd_kcontrol *kcontrol,
>>> return changed;
>>> }
>>>
>>> +static int rt722_sdca_set_fu06_playback_ctl(struct rt722_sdca_priv *rt722)
>>> +{
>>> + int err;
>>> + unsigned int ch_l, ch_r;
>>> +
>>> + ch_l = (rt722->fu06_dapm_mute || rt722->fu06_mixer_l_mute) ? 0x01 : 0x00;
>>> + ch_r = (rt722->fu06_dapm_mute || rt722->fu06_mixer_r_mute) ? 0x01 : 0x00;
>>> +
>>> + err = regmap_write(rt722->regmap,
>>> + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
>>> + RT722_SDCA_CTL_FU_MUTE, CH_L), ch_l);
>>> + if (err < 0)
>>> + return err;
>>> +
>>> + err = regmap_write(rt722->regmap,
>>> + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
>>> + RT722_SDCA_CTL_FU_MUTE, CH_R), ch_r);
>>
>> IIRC you can mute all channels at once in a FU (Feature Unit) using Control Number 0.
>>
>> If both channels need to be muted/unmuted it's better to do so in a single command.
>>
>
> Thanks for the review.
>
> The per-channel mute writes are unchanged from the existing DAPM
> handler, just moved into a helper.
> All rt711/rt712/rt722 drivers use per-channel writes for FU_MUTE — no
> CN0 precedent exists.
Fair enough, something to do in the future...
While I am at it, in theory those registers are Dual-Ranked, with a 'commit' mechanism to deal with all volume changes at the same time. We'll need to add support for Dual-Ranked registers at some point, not sure how this can be bolted in regmap support... Cc: Charles for future SDCA improvements...
For now, here's a
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.dev>
On Fri, Apr 24, 2026 at 03:09:10PM +0200, Pierre-Louis Bossart wrote: > On 4/24/26 05:52, Aaron Ma wrote: > > On Thu, Apr 23, 2026 at 9:01 PM Pierre-Louis Bossart > > <pierre-louis.bossart@linux.dev> wrote: > >> On 4/23/26 12:13, Aaron Ma wrote: > While I am at it, in theory those registers are Dual-Ranked, > with a 'commit' mechanism to deal with all volume changes > at the same time. We'll need to add support for Dual-Ranked > registers at some point, not sure how this can be bolted in > regmap support... Cc: Charles for future SDCA improvements... Eek... *hides in dark corner*. Yeah at some point we do need to do some thinking about dual ranked controls, at the moment there is no support. As far as my thinking got was really it is in a lot of situations hard to tell which writes you want to group. There is some low hanging fruit, for example stereo volumes would make sense to group if dual ranked. Although in those cases I tend to see user-space changing the volumes one channel at a time anyway. And that is even before you get to the regmap implementation details which is foggy at best in my mind. Sorry I don't have more, it is loosely on my virtual todo list, but definitely not something I have got around to thinking through yet. Thanks, Charles
On 4/24/26 17:53, Charles Keepax wrote: > On Fri, Apr 24, 2026 at 03:09:10PM +0200, Pierre-Louis Bossart wrote: >> On 4/24/26 05:52, Aaron Ma wrote: >>> On Thu, Apr 23, 2026 at 9:01 PM Pierre-Louis Bossart >>> <pierre-louis.bossart@linux.dev> wrote: >>>> On 4/23/26 12:13, Aaron Ma wrote: >> While I am at it, in theory those registers are Dual-Ranked, >> with a 'commit' mechanism to deal with all volume changes >> at the same time. We'll need to add support for Dual-Ranked >> registers at some point, not sure how this can be bolted in >> regmap support... Cc: Charles for future SDCA improvements... > > Eek... *hides in dark corner*. Yeah at some point we do need > to do some thinking about dual ranked controls, at the moment > there is no support. As far as my thinking got was really it > is in a lot of situations hard to tell which writes you want > to group. There is some low hanging fruit, for example stereo > volumes would make sense to group if dual ranked. Although in > those cases I tend to see user-space changing the volumes one > channel at a time anyway. And that is even before you get to the > regmap implementation details which is foggy at best in my mind. > > Sorry I don't have more, it is loosely on my virtual todo list, > but definitely not something I have got around to thinking > through yet. Yeah I can't say I have a good view either... In theory at the Function level there is a Commit Group mask, a Function can be a member of multiple groups. The fun part is this statement in the spec "This Control selects the Commit Group(s) which contain every dual-ranked Control within this Function." but then the SoundWire spec says "Every Dual-Ranked Register is associated with one or more of 7 Commit Groups" interesting wording, I am in the dark on how we would know which controls are part of which Commit Group. And then IIRC there was a desire to update volume controls on multiple devices, e.g. if there are multiple amps the volumes would be updated across devices. Not sure how to achieve this...
On Mon, Apr 27, 2026 at 04:53:20PM +0200, Pierre-Louis Bossart wrote: > On 4/24/26 17:53, Charles Keepax wrote: > > On Fri, Apr 24, 2026 at 03:09:10PM +0200, Pierre-Louis Bossart wrote: > >> On 4/24/26 05:52, Aaron Ma wrote: > >>> On Thu, Apr 23, 2026 at 9:01 PM Pierre-Louis Bossart > >>> <pierre-louis.bossart@linux.dev> wrote: > >>>> On 4/23/26 12:13, Aaron Ma wrote: > Yeah I can't say I have a good view either... > > In theory at the Function level there is a Commit Group mask, > a Function can be a member of multiple groups. The fun part > is this statement in the spec > > "This Control selects the Commit Group(s) which contain every > dual-ranked Control within this Function." > > but then the SoundWire spec says > > "Every Dual-Ranked Register is associated with one or more of > 7 Commit Groups" > > interesting wording, I am in the dark on how we would know > which controls are part of which Commit Group. There is "Atomic Behaviors Using Dual-Ranked Controls and Commit" 10.3.8 (in 1.0), 13.4 (1.1), which gives some examples of how one could group stuff. Although its unclear to me how much these are "recommendations" and how much they are an exhaustive list. > And then IIRC there was a desire to update volume controls on > multiple devices, e.g. if there are multiple amps the volumes > would be updated across devices. Not sure how to achieve > this... phew... yeah I hadn't even remotely considered commit groups spanning functions or indeed even devices. That is indeed some scary stuff. Thanks, Charles
© 2016 - 2026 Red Hat, Inc.