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.
This commit is contained in:
Daniel Nouri 2025-10-06 08:17:06 +02:00
parent 87d34335f3
commit b7a2fcf27e

View file

@ -1615,6 +1615,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;
@ -1681,6 +1734,9 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device
}
}
/* Enable IEC958 switches for digital outputs */
sync_iec958_controls(dev);
return 0;
}
@ -2179,6 +2235,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);