diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index 4b30cd63c5a5..78fb484e8b04 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2389,6 +2389,18 @@ quirk_flags from snd_usb_handle_sync_urb. Instead fall through and enqueue a packet_info containing only size-0 packets, so the OUT ring keeps moving (emits silence). Needed by Behringer Flow 8 (1397:050c). + * bit 30: ``mixer_get_cur_broken`` + Some mixers are sticky, which means that setting their current volume + is a no-op, and reading the current volume returns a constant value. + The sticky check disables these mixers to prevent confusing userspace. + However, some devices do have a tunable volume despite the reported + current volume being constant. As the sticky check can't distinguish + between the two categories, setting this flag tells that the device + should fall into the second category when GET_CUR returns a constant + value, resulting in the sticky check being non-fatal and only + disabling GET_CUR instead of the whole mixer. The current volume will + then be provided by the internal cache that stores the last set + volume This module supports multiple devices, autoprobe and hotplugging. diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index b30af79f716c..b4c855c25eef 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -434,6 +434,11 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, *value = cval->cache_val[index]; return 0; } + + /* The current value is always provided by the cache after initialization. */ + if (cval->get_cur_broken) + return -ENXIO; + err = get_cur_mix_raw(cval, channel, value); if (err < 0) { if (!cval->head.mixer->ignore_ctl_error) @@ -1223,7 +1228,7 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) err = snd_usb_get_cur_mix_value(cval, ch, idx, &val); if (!err) return; - if (!cval->head.mixer->ignore_ctl_error) + if (!cval->head.mixer->ignore_ctl_error && !cval->get_cur_broken) usb_audio_warn(cval->head.mixer->chip, "%d:%d: failed to get current value for ch %d (%d)\n", cval->head.id, mixer_ctrl_intf(cval->head.mixer), @@ -1237,8 +1242,16 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) * Some devices' volume control mixers are sticky, which accept SET_CUR but * do absolutely nothing. * - * Prevent sticky mixers from being registered, otherwise they confuses - * userspace and results in ineffective volume control. + * Check the return values of GET_CUR with different SET_CUR values. Consider + * the mixer as sticky if GET_CUR always returns a constant value. + * + * Some devices have effective SET_CUR despite GET_CUR being constant. Do not + * consider the mixer as sticky if a quirk flag indicates that. + * + * Gate the registration of sticky mixers to prevent confusing userspace, so + * that they won't cause ineffective volume control. However, for mixers with + * effective SET_CUR but broken GET_CUR, the registration can continue normally + * but further GET_CUR requests will be gated. */ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, int channel, int saved) @@ -1258,10 +1271,22 @@ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, return 0; } + if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) { + usb_audio_info(cval->head.mixer->chip, + "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->min, cval->max, cval->res, saved); + + cval->get_cur_broken = 1; + return -ENXIO; + } + usb_audio_err(cval->head.mixer->chip, "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", cval->head.id, mixer_ctrl_intf(cval->head.mixer), cval->min, cval->max, cval->res, saved); + usb_audio_info(cval->head.mixer->chip, + "check MIXER_GET_CUR_BROKEN if you believe the mixer is non-sticky"); return -ENODEV; } @@ -1304,7 +1329,7 @@ static void check_volume_control_res(struct usb_mixer_elem_info *cval, static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, int default_min, struct snd_kcontrol *kctl) { - int i, idx, ret; + int i, idx, ret = 0; /* for failsafe */ cval->min = default_min; @@ -1360,8 +1385,10 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, goto no_checks; ret = check_sticky_volume_control(cval, minchn, saved); - if (ret < 0) + if (ret == -ENODEV) goto sticky; + if (ret) + goto no_checks; if (cval->min + cval->res < cval->max) check_volume_control_res(cval, minchn, saved); @@ -1370,6 +1397,16 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, } no_checks: + /* + * Got a non-fatal failure during sanity checks. + * + * Do not propagate mixer values written by sanity checks. + * Instead, rely on init_cur_mix_raw() to initialize the mixer + * properly. + */ + if (ret) + cval->cached = 0; + cval->initialized = 1; } @@ -3538,7 +3575,8 @@ void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) continue; info = mixer_elem_list_to_info(list); /* invalidate cache, so the value is read from the device */ - info->cached = 0; + if (!info->get_cur_broken) + info->cached = 0; snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &list->kctl->id); } @@ -3635,10 +3673,12 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, switch (attribute) { case UAC2_CS_CUR: /* invalidate cache, so the value is read from the device */ - if (channel) - info->cached &= ~BIT(channel); - else /* master channel */ - info->cached = 0; + if (!info->get_cur_broken) { + if (channel) + info->cached &= ~BIT(channel); + else /* master channel */ + info->cached = 0; + } snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &info->head.kctl->id); diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index afbb3dd9f177..3fa1bd96f858 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -94,6 +94,7 @@ struct usb_mixer_elem_info { int cache_val[MAX_CHANNELS]; u8 initialized; u8 min_mute; + u8 get_cur_broken; void *private_data; }; diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 410f5a92c0cf..10c32e633786 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2607,6 +2607,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), + QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), NULL }; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 9afcad8f143a..e472aef6eb87 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -242,6 +242,17 @@ extern bool snd_usb_skip_validation; * from snd_usb_handle_sync_urb. Instead fall through and enqueue a * packet_info containing only size-0 packets, so the OUT ring keeps * moving (emits silence). Needed by Behringer Flow 8 (1397:050c). + * QUIRK_FLAG_MIXER_GET_CUR_BROKEN + * Some mixers are sticky, which means that setting their current volume is a + * no-op, and reading the current volume returns a constant value. The sticky + * check disables these mixers to prevent confusing userspace. However, some + * devices do have a tunable volume despite the reported current volume being + * constant. As the sticky check can't distinguish between the two categories, + * setting this flag tells that the device should fall into the second + * category when GET_CUR returns a constant value, resulting in the sticky + * check being non-fatal and only disabling GET_CUR instead of the whole mixer. + * The current volume will then be provided by the internal cache that stores + * the last set volume */ enum { @@ -275,6 +286,7 @@ enum { QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, + QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, /* Please also edit snd_usb_audio_quirk_flag_names */ }; @@ -310,5 +322,6 @@ enum { #define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) +#define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) #endif /* __USBAUDIO_H */