[PATCH 3/4] ALSA: usb-audio: Reverse MIXER_GET_CUR_BROKEN as MIXER_GET_CUR_OK

From: Rong Zhang

Date: Sat Aug 22 2026 - 14:55:57 EST


The default behavior of sticky check now becomes what
QUIRK_FLAG_MIXER_GET_CUR_BROKEN originally does, so the quirk flag is no
longer needed.

On some devices, whether their GET_CUR being sticky depends on whether
hotpluggable components are present. When the hotpluggable components
are missing on probe, their GET_CUR behavior is classified as broken.
Therefore, reverse QUIRK_FLAG_MIXER_GET_CUR_BROKEN as
QUIRK_FLAG_MIXER_GET_CUR_OK, so that it can be set to prevent the
heuristics from gating GET_CUR.

Note that even if the quirk flag is set, init_cur_mix_raw() should still
initialize the mixer value to cval->min, otherwise restoring the bogus
saved value on the first channel could lead to unbalanced channels.

Signed-off-by: Rong Zhang <i@xxxxxxxx>
---
Documentation/sound/alsa-configuration.rst | 20 ++++++++------------
sound/usb/mixer.c | 8 ++++++++
sound/usb/quirks.c | 16 +++-------------
sound/usb/usbaudio.h | 20 +++++++-------------
4 files changed, 26 insertions(+), 38 deletions(-)

diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst
index 71733e807a7c..e500ec84e3ef 100644
--- a/Documentation/sound/alsa-configuration.rst
+++ b/Documentation/sound/alsa-configuration.rst
@@ -2316,6 +2316,9 @@ quirk_flags
applies the ``mixer_playback_min_mute`` flag and clears the
``ignore_ctl_error`` flag for the device 1234:abcd, and applies the
``skip_sample_rate`` flag for all devices.
+ New quirk flags may replace old ones by reusing the latter's bits, so the
+ new usage is preferred. Despite that, depending on the order of probing is
+ fragile, so it'd better migrate to the new usage anyway.

* bit 0: ``get_sample_rate``
Skip reading sample rate for devices
@@ -2389,18 +2392,11 @@ 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
+ * bit 30: ``mixer_get_cur_ok``
+ On some devices, whether their GET_CUR being sticky depends on whether
+ hotpluggable components are present. When the hotpluggable components
+ are missing on probe, their GET_CUR behavior is classified as broken.
+ Set the flag to prevent the heuristics from gating GET_CUR.
* bit 31: ``playback_urb_fixup``
Some devices show the stuttering at playback, and this quirk
works around it by enforcing the fixed max URBs (12) instead of
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index 3aa9a9c12557..ecaa8bc08d7c 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -1447,6 +1447,14 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
}
}

+ /*
+ * When GET_CUR is sticky, the saved value is bogus, so mixer values set
+ * by the sanity checks must be discarded through init_cur_mix_raw().
+ * After that, we can clear the flag as per QUIRK_FLAG_MIXER_GET_CUR_OK.
+ */
+ if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_OK)
+ cval->get_cur_broken = 0;
+
return 0;
}

diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index eb1750def067..b62168a11e32 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -2215,8 +2215,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */
QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16),
- DEVICE_FLG(0x040b, 0x0897, /* Weltrend Semiconductor, sold as Redragon H510-PRO Wireless headset */
- QUIRK_FLAG_MIXER_GET_CUR_BROKEN),
DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */
QUIRK_FLAG_IGNORE_CTL_ERROR),
DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */
@@ -2256,8 +2254,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */
QUIRK_FLAG_CTL_MSG_DELAY_1M |
QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
- DEVICE_FLG(0x046d, 0x0af7, /* Logitech PRO X 2 LIGHTSPEED */
- QUIRK_FLAG_MIXER_GET_CUR_BROKEN),
DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */
QUIRK_FLAG_GENERIC_IMPLICIT_FB),
DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */
@@ -2329,9 +2325,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
DEVICE_FLG(0x0763, 0x2031, /* M-Audio Fast Track C600 */
QUIRK_FLAG_GENERIC_IMPLICIT_FB),
DEVICE_FLG(0x0763, 0x2080, /* M-Audio Fast Track Ultra */
- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB),
+ QUIRK_FLAG_GENERIC_IMPLICIT_FB),
DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */
- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB),
+ QUIRK_FLAG_GENERIC_IMPLICIT_FB),
DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */
QUIRK_FLAG_CTL_MSG_DELAY_1M),
DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */
@@ -2370,8 +2366,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER),
DEVICE_FLG(0x1038, 0x1294, /* SteelSeries Arctis Pro Wireless */
QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
- DEVICE_FLG(0x1038, 0x2232, /* SteelSeries Arctis Nova 5 */
- QUIRK_FLAG_MIXER_GET_CUR_BROKEN),
DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */
QUIRK_FLAG_GET_SAMPLE_RATE),
DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */
@@ -2379,8 +2373,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */
QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16),
- DEVICE_FLG(0x1377, 0x6004, /* Sennheiser MOMENTUM 3 */
- QUIRK_FLAG_MIXER_GET_CUR_BROKEN),
DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */
QUIRK_FLAG_GET_SAMPLE_RATE),
DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */
@@ -2505,8 +2497,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_CTL_MSG_DELAY_1M),
DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */
QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
- DEVICE_FLG(0x2d99, 0xa024, /* Edifier MF200 */
- QUIRK_FLAG_MIXER_GET_CUR_BROKEN),
DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */
QUIRK_FLAG_CTL_MSG_DELAY),
DEVICE_FLG(0x2fc6, 0xf0b5, /* iBasso DC-Elite */
@@ -2647,7 +2637,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),
+ QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK),
QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP),
QUIRK_STRING_ENTRY(ALWAYS_SET_RATE),
NULL
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index abc2d7e579de..24436473931d 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -243,17 +243,11 @@ 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
+ * QUIRK_FLAG_MIXER_GET_CUR_OK
+ * On some devices, whether their GET_CUR being sticky depends on whether
+ * hotpluggable components are present. When the hotpluggable components are
+ * missing on probe, their GET_CUR behavior is classified as broken. Set the
+ * flag to prevent the heuristics from gating GET_CUR.
* QUIRK_FLAG_PLAYBACK_URB_FIXUP
* Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS.
* This is needed for devices that exhibit boot-time audio stuttering due
@@ -297,7 +291,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,
+ QUIRK_TYPE_MIXER_GET_CUR_OK = 30,
QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31,
QUIRK_TYPE_ALWAYS_SET_RATE = 32,
/* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */
@@ -335,7 +329,7 @@ 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)
+#define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK)
#define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP)
#define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE)


--
2.55.0