[PATCH v2] ALSA: hda: Notify IEC958 Default PCM switch state changes

From: Cássio Gabriel

Date: Thu Apr 02 2026 - 10:48:45 EST


The "IEC958 Default PCM Playback Switch" control is backed directly by
mout->share_spdif. The share-switch callbacks currently access that state
without serialization, and spdif_share_sw_put() always returns 0, so
normal userspace writes never emit the standard ALSA control value
notification.

snd_hda_multi_out_analog_open() may also clear mout->share_spdif when the
analog PCM capabilities and the SPDIF capabilities no longer intersect.
That fallback is still needed to avoid creating an impossible hw
constraint set, but it changes the mixer backing value without notifying
subscribers.

Protect the share-switch callbacks with spdif_mutex like the other SPDIF
control handlers, return the actual change value from spdif_share_sw_put(),
and notify the cached control when the open path forcibly disables
shared SPDIF mode after dropping spdif_mutex.

This keeps the existing auto-disable behavior while making switch state
changes visible to userspace.

Fixes: 9a08160bdbe3 ("[ALSA] hda-codec - Add "IEC958 Default PCM" switch")
Fixes: 022b466fc353 ("ALSA: hda - Avoid invalid formats and rates with shared SPDIF")
Suggested-by: Takashi Iwai <tiwai@xxxxxxx>
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@xxxxxxxxx>
---
Changes in v2:
- Reworked the share-switch callbacks to use codec in private_data and
mout in private_value.
- Replaced the mixer-list lookup helper with a cached shared SPDIF
kcontrol pointer for forced-disable notifications.
- Kept the functional change otherwise unchanged.
- Link to v1: https://patch.msgid.link/20260401-hda-spdif-share-notify-v1-1-707e422ed9d1@xxxxxxxxx
---
sound/hda/common/codec.c | 46 ++++++++++++++++++++++++++++++++++++--------
sound/hda/common/hda_local.h | 3 +++
2 files changed, 41 insertions(+), 8 deletions(-)

diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c
index 09b1329bb8f3..5123df32ad89 100644
--- a/sound/hda/common/codec.c
+++ b/sound/hda/common/codec.c
@@ -2529,7 +2529,10 @@ EXPORT_SYMBOL_GPL(snd_hda_spdif_ctls_assign);
static int spdif_share_sw_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_multi_out *mout = (void *)kcontrol->private_value;
+
+ guard(mutex)(&codec->spdif_mutex);
ucontrol->value.integer.value[0] = mout->share_spdif;
return 0;
}
@@ -2537,9 +2540,15 @@ static int spdif_share_sw_get(struct snd_kcontrol *kcontrol,
static int spdif_share_sw_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
- mout->share_spdif = !!ucontrol->value.integer.value[0];
- return 0;
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_multi_out *mout = (void *)kcontrol->private_value;
+ bool val = !!ucontrol->value.integer.value[0];
+ int change;
+
+ guard(mutex)(&codec->spdif_mutex);
+ change = mout->share_spdif != val;
+ mout->share_spdif = val;
+ return change;
}

static const struct snd_kcontrol_new spdif_share_sw = {
@@ -2550,6 +2559,14 @@ static const struct snd_kcontrol_new spdif_share_sw = {
.put = spdif_share_sw_put,
};

+static void notify_spdif_share_sw(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ if (mout->share_spdif_kctl)
+ snd_ctl_notify_one(codec->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ mout->share_spdif_kctl, 0);
+}
+
/**
* snd_hda_create_spdif_share_sw - create Default PCM switch
* @codec: the HDA codec
@@ -2559,15 +2576,24 @@ int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
struct hda_multi_out *mout)
{
struct snd_kcontrol *kctl;
+ int err;

if (!mout->dig_out_nid)
return 0;

- kctl = snd_ctl_new1(&spdif_share_sw, mout);
+ kctl = snd_ctl_new1(&spdif_share_sw, codec);
if (!kctl)
return -ENOMEM;
- /* ATTENTION: here mout is passed as private_data, instead of codec */
- return snd_hda_ctl_add(codec, mout->dig_out_nid, kctl);
+ /* snd_ctl_new1() stores @codec in private_data; stash @mout in
+ * private_value for the share-switch callbacks and cache the
+ * assigned control for forced-disable notifications.
+ */
+ kctl->private_value = (unsigned long)mout;
+ err = snd_hda_ctl_add(codec, mout->dig_out_nid, kctl);
+ if (err < 0)
+ return err;
+ mout->share_spdif_kctl = kctl;
+ return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_create_spdif_share_sw);

@@ -3701,6 +3727,8 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec,
struct hda_pcm_stream *hinfo)
{
struct snd_pcm_runtime *runtime = substream->runtime;
+ bool notify_share_sw = false;
+
runtime->hw.channels_max = mout->max_channels;
if (mout->dig_out_nid) {
if (!mout->analog_rates) {
@@ -3729,10 +3757,12 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec,
hinfo->maxbps = mout->spdif_maxbps;
} else {
mout->share_spdif = 0;
- /* FIXME: need notify? */
+ notify_share_sw = true;
}
}
}
+ if (notify_share_sw)
+ notify_spdif_share_sw(codec, mout);
return snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS, 2);
}
diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h
index ab423f1cef54..490bed97cbda 100644
--- a/sound/hda/common/hda_local.h
+++ b/sound/hda/common/hda_local.h
@@ -12,6 +12,8 @@

#include <sound/pcm_drm_eld.h>

+struct snd_kcontrol;
+
/* We abuse kcontrol_new.subdev field to pass the NID corresponding to
* the given new control. If id.subdev has a bit flag HDA_SUBDEV_NID_FLAG,
* snd_hda_ctl_add() takes the lower-bit subdev value as a valid NID.
@@ -203,6 +205,7 @@ enum { HDA_DIG_NONE, HDA_DIG_EXCLUSIVE, HDA_DIG_ANALOG_DUP }; /* dig_out_used */
#define HDA_MAX_OUTS 5

struct hda_multi_out {
+ struct snd_kcontrol *share_spdif_kctl; /* cached shared SPDIF switch */
int num_dacs; /* # of DACs, must be more than 1 */
const hda_nid_t *dac_nids; /* DAC list */
hda_nid_t hp_nid; /* optional DAC for HP, 0 when not exists */

---
base-commit: f16695c0dc9db64f0a5a9871a10b70daee2653e3
change-id: 20260330-hda-spdif-share-notify-22fa4ac00a14

Best regards,
--
Cássio Gabriel <cassiogabrielcontato@xxxxxxxxx>