From f030e1dc032a5f800cce9948ea952c2301805cd1 Mon Sep 17 00:00:00 2001 From: Daniel Nouri Date: Mon, 6 Oct 2025 08:17:06 +0200 Subject: [PATCH] alsa: Enable IEC958 switches on device activation IEC958 (S/PDIF, HDMI, DisplayPort) switches default to muted in ALSA drivers, causing no audio output on digital devices. While UCM configurations and mixer paths can handle IEC958 unmuting, several scenarios lack coverage: - Pro-audio profiles (bypass UCM and mixer paths by design) - Devices without UCM configurations - Devices with incomplete mixer path definitions - Cards with multiple HDMI/DP outputs (indexed switches) This ensures IEC958 switches are enabled during device activation and port changes. The implementation uses the device mixer when available, falls back to the card mixer for pro-audio profiles, and enables all IEC958 switches regardless of index. Safe for all configurations: the operation is idempotent and provides defense-in-depth even when UCM or mixer paths handle it correctly. Tested on AMD Rembrandt GPU with 3 HDMI outputs in pro-audio mode. --- spa/plugins/alsa/acp/acp.c | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index f6f03a5ff..c2197c145 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -1686,6 +1686,59 @@ static int device_disable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_devic return 0; } +/* Synchronize IEC958 digital output/input switch states. + * + * IEC958 switches default to muted in ALSA drivers. Cards with multiple + * HDMI/DP outputs have indexed switches (IEC958,0 IEC958,1 etc). We enable + * all switches since we cannot reliably map device numbers to indices. + */ +static void sync_iec958_controls(pa_alsa_device *d) +{ + snd_mixer_t *mixer_handle; + snd_mixer_elem_t *elem; + pa_card *impl; + int r; + + mixer_handle = d->mixer_handle; + + /* Pro-audio profiles don't have per-device mixers, use card mixer */ + if (!mixer_handle) { + impl = d->card; + if (!impl || impl->card.index == ACP_INVALID_INDEX) + return; + + mixer_handle = pa_alsa_open_mixer(impl->ucm.mixers, impl->card.index, true); + if (!mixer_handle) + return; + } + + /* Enable all IEC958 switches */ + for (elem = snd_mixer_first_elem(mixer_handle); elem; + elem = snd_mixer_elem_next(elem)) { + + if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE) + continue; + + const char *name = snd_mixer_selem_get_name(elem); + if (!name || !pa_startswith(name, "IEC958")) + continue; + + if (snd_mixer_selem_has_playback_switch(elem)) { + r = snd_mixer_selem_set_playback_switch_all(elem, 1); + if (r < 0) + pa_log_warn("Failed to enable IEC958 playback switch: %s", + pa_alsa_strerror(r)); + } + + if (snd_mixer_selem_has_capture_switch(elem)) { + r = snd_mixer_selem_set_capture_switch_all(elem, 1); + if (r < 0) + pa_log_warn("Failed to enable IEC958 capture switch: %s", + pa_alsa_strerror(r)); + } + } +} + static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev) { const char *mod_name; @@ -1777,6 +1830,9 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device break; } + /* Enable IEC958 switches for digital outputs */ + sync_iec958_controls(dev); + return 0; } @@ -2277,6 +2333,7 @@ int acp_device_set_port(struct acp_device *dev, uint32_t port_index, uint32_t fl pa_sink_suspend(s, false, PA_SUSPEND_UNAVAILABLE); #endif } + sync_iec958_controls(d); if (impl->events && impl->events->port_changed) impl->events->port_changed(impl->user_data, old ? old->port.index : 0, p->port.index);