mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] USB Audio Class 2 Mixer unit support for GET_CUR, SET_CUR and RANGE
@ 2026-09-15  8:54 Zipdox
  2026-09-15 11:08 ` Takashi Iwai
  0 siblings, 1 reply; 8+ messages in thread
From: Zipdox @ 2026-09-15  8:54 UTC (permalink / raw)
  To: linux-sound; +Cc: perex, linux-kernel, tiwai

Hello,

The USB Audio Mixer Unit driver (sound/usb/mixer.c) sends audio class
1.0 format control messages to devices, regardless of the actual audio
class version. This means audio class 2.0 devices will receive incorrect
messages, stall, and cause the driver to error (e.g. alsamixer exits
with a broken pipe error). This patch implements the GET_CUR, SET_CUR,
and RANGE control requests for mixer units according to the Audio Class
2.0 specification.

I have one question though, which has to do with the get_cur_mix_raw
function. It states "channel = 0: master, 1 = first channel", but as far
as I know the spec doesn't specify a master control for mixer units. I
implemented it so that channel 0 results the channel 1 value, but maybe
some other solution is desirable.

From: Zipdox <zipdox@zipdox.net>
Date: Tue, 15 Sep 2026 10:12:01 +0200
Subject: [PATCH] USB Audio Class 2 Mixer unit support for GET_CUR, SET_CUR and
  RANGE

Signed-off-by: Zipdox <zipdox@zipdox.net>
---
  sound/usb/mixer.c | 191 ++++++++++++++++++++++++++++++++--------------
  1 file changed, 133 insertions(+), 58 deletions(-)

diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index ecaa8bc08d7c..99270731d409 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -360,12 +360,12 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,
  	{
  		CLASS(snd_usb_lock, pm)(chip);
  		if (pm.err)
-			return -EIO;
+		return -EIO;
  
-		idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8);
-		ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
-				      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
-				      validx, idx, buf, size);
+	idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8);
+	ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
+			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+			      validx, idx, buf, size);
  	}
  
  	if (ret < 0) {
@@ -410,6 +410,53 @@ static int get_ctl_value(struct usb_mixer_elem_info *cval, int request,
  		get_ctl_value_v2(cval, request, validx, value_ret);
  }
  
+static int get_ctl_range_16(struct usb_mixer_elem_info *cval)
+{
+	struct snd_usb_audio *chip = cval->head.mixer->chip;
+	int idx;
+	int ret;
+	__u16 wNumSubRanges;
+	__u8 range_res[8] = {0};
+
+	__u8 mcn = (cval->control - 1) * cval->channels;
+	__u16 wValue = (UAC2_MU_MIXER << 8) | mcn;
+
+	if (snd_usb_lock_shutdown(chip))
+		return -EIO;
+
+	idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8);
+	ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_RANGE,
+			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+			      wValue, idx, range_res, sizeof(range_res));
+	snd_usb_unlock_shutdown(chip);
+
+	if (ret < 0) {
+		usb_audio_dbg(chip,
+			"cannot get ctl range: req = %#x, wValue = %#x, wIndex = %#x\n",
+			UAC2_CS_RANGE, wValue, idx);
+		return ret;
+	}
+
+	wNumSubRanges = combine_word(&range_res[0]);
+	if(wNumSubRanges != 1) {
+		usb_audio_dbg(chip,
+			"unexpected wNumSubRanges (%u) for ctl range: req = %#x, wValue = %#x, wIndex = %#x\n",
+			(unsigned int)wNumSubRanges, UAC2_CS_RANGE, wValue, idx);
+		return -EIO;
+	}
+
+	cval->min = combine_word(&range_res[2]);
+	if (cval->min >= 0x8000) cval->min -= 0x10000;
+	cval->max = combine_word(&range_res[4]);
+	if (cval->max >= 0x8000) cval->max -= 0x10000;
+	cval->res = combine_word(&range_res[6]);
+	usb_audio_dbg(chip,
+		"range for mcn = %u: min = %d, max = %d, res = %d\n",
+		(unsigned int)mcn, cval->min, cval->max, cval->res);
+
+	return 0;
+}
+
  static int get_cur_ctl_value(struct usb_mixer_elem_info *cval,
  			     int validx, int *value)
  {
@@ -420,9 +467,18 @@ static int get_cur_ctl_value(struct usb_mixer_elem_info *cval,
  static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval,
  				  int channel, int *value)
  {
-	return get_ctl_value(cval, UAC_GET_CUR,
+	if(cval->head.mixer->protocol == UAC_VERSION_1){
+		return get_ctl_value(cval, UAC_GET_CUR,
  			     (cval->control << 8) | channel,
  			     value);
+	}else{
+		__u8 mcn = (cval->control - 1) * cval->channels + ((channel == 0 ? 1 : channel) - 1);
+		__u16 wValue = (UAC2_MU_MIXER << 8) | mcn;
+		return get_ctl_value(cval, UAC_GET_CUR,
+			     wValue,
+			     value);
+	}
+	
  }
  
  int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval,
@@ -527,9 +583,18 @@ int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel,
  		return 0;
  	}
  
-	err = snd_usb_mixer_set_ctl_value(cval,
-					  UAC_SET_CUR, (cval->control << 8) | channel,
-					  value);
+	if(cval->head.mixer->protocol == UAC_VERSION_1){
+		err = snd_usb_mixer_set_ctl_value(cval,
+						UAC_SET_CUR, (cval->control << 8) | channel,
+						value);
+	}else{
+		__u8 mcn = (cval->control - 1) * cval->channels + ((channel == 0 ? 1 : channel) - 1);
+		__u16 wValue = (UAC2_MU_MIXER << 8) | mcn;
+		err = snd_usb_mixer_set_ctl_value(cval,
+						UAC_SET_CUR, wValue,
+						value);
+	}
+
  	if (err < 0)
  		return err;
  	cval->cached |= BIT(channel);
@@ -1336,59 +1401,69 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
  	    cval->val_type == USB_MIXER_INV_BOOLEAN) {
  		cval->initialized = 1;
  	} else {
-		int minchn = 0;
-		if (cval->cmask) {
-			for (i = 0; i < MAX_CHANNELS; i++)
-				if (cval->cmask & BIT(i)) {
-					minchn = i + 1;
-					break;
-				}
-		}
-		if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 ||
-		    get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) {
-			usb_audio_err(cval->head.mixer->chip,
-				      "%d:%d: cannot get min/max values for control %d (id %d)\n",
-				   cval->head.id, mixer_ctrl_intf(cval->head.mixer),
-							       cval->control, cval->head.id);
-			return -EAGAIN;
-		}
-		if (get_ctl_value(cval, UAC_GET_RES,
-				  (cval->control << 8) | minchn,
-				  &cval->res) < 0) {
-			cval->res = 1;
-		} else if (cval->head.mixer->protocol == UAC_VERSION_1) {
-			int last_valid_res = cval->res;
-
-			while (cval->res > 1) {
-				if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES,
-								(cval->control << 8) | minchn,
-								cval->res / 2) < 0)
-					break;
-				cval->res /= 2;
+		if(cval->head.mixer->protocol == UAC_VERSION_1){
+			int minchn = 0;
+			if (cval->cmask) {
+				for (i = 0; i < MAX_CHANNELS; i++)
+					if (cval->cmask & BIT(i)) {
+						minchn = i + 1;
+						break;
+					}
+			}
+			if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 ||
+				get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) {
+				usb_audio_err(cval->head.mixer->chip,
+						"%d:%d: cannot get min/max values for control %d (id %d)\n",
+					cval->head.id, mixer_ctrl_intf(cval->head.mixer),
+									cval->control, cval->head.id);
+				return -EAGAIN;
  			}
  			if (get_ctl_value(cval, UAC_GET_RES,
-					  (cval->control << 8) | minchn, &cval->res) < 0)
-				cval->res = last_valid_res;
-		}
-		if (cval->res == 0)
-			cval->res = 1;
+					(cval->control << 8) | minchn,
+					&cval->res) < 0) {
+				cval->res = 1;
+			} else if (cval->head.mixer->protocol == UAC_VERSION_1) {
+				int last_valid_res = cval->res;
+
+				while (cval->res > 1) {
+					if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES,
+									(cval->control << 8) | minchn,
+									cval->res / 2) < 0)
+						break;
+					cval->res /= 2;
+				}
+				if (get_ctl_value(cval, UAC_GET_RES,
+						(cval->control << 8) | minchn, &cval->res) < 0)
+					cval->res = last_valid_res;
+			}
+			if (cval->res == 0)
+				cval->res = 1;
  
-		if (cval->min < cval->max) {
-			int saved;
+			if (cval->min < cval->max) {
+				int saved;
  
-			if (get_cur_mix_raw(cval, minchn, &saved) < 0)
-				goto no_checks;
+				if (get_cur_mix_raw(cval, minchn, &saved) < 0)
+					goto no_checks;
  
-			ret = check_sticky_volume_control(cval, minchn, saved);
-			if (ret)
-				goto no_checks;
+				ret = check_sticky_volume_control(cval, minchn, saved);
+				if (ret)
+					goto no_checks;
  
-			if (cval->min + cval->res < cval->max)
-				check_volume_control_res(cval, minchn, saved);
+				if (cval->min + cval->res < cval->max)
+					check_volume_control_res(cval, minchn, saved);
  
-			snd_usb_set_cur_mix_value(cval, minchn, 0, saved);
-		}
+				snd_usb_set_cur_mix_value(cval, minchn, 0, saved);
+			}
  
+		}else{
+			if(get_ctl_range_16(cval) < 0){
+				usb_audio_err(cval->head.mixer->chip,
+				"%d:%d: cannot get min/max values for control %d (id %d)\n",
+					cval->head.id, mixer_ctrl_intf(cval->head.mixer),
+									cval->control, cval->head.id);
+				return -EINVAL;
+			}
+		}
  no_checks:
  		/*
  		 * Got a non-fatal failure during sanity checks.
@@ -1983,9 +2058,9 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer,
  			       cval->head.id, kctl->id.name, cval->channels,
  			       cval->min, cval->max, cval->res);
  	} else {
-		usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
-			      cval->head.id, kctl->id.name, cval->channels,
-			      cval->min, cval->max, cval->res);
+	usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
+		      cval->head.id, kctl->id.name, cval->channels,
+		      cval->min, cval->max, cval->res);
  	}
  
  	snd_usb_mixer_add_control(&cval->head, kctl);
@@ -3550,7 +3625,7 @@ void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid)
  		info = mixer_elem_list_to_info(list);
  		/* invalidate cache, so the value is read from the device */
  		if (!info->get_cur_broken)
-			info->cached = 0;
+		info->cached = 0;
  		snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
  			       &list->kctl->id);
  	}
-- 
2.47.3

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-09-16 18:22 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-15  8:54 [PATCH] USB Audio Class 2 Mixer unit support for GET_CUR, SET_CUR and RANGE Zipdox
2026-09-15 11:08 ` Takashi Iwai
2026-09-15 12:54   ` Zipdox
2026-09-15 14:19     ` Takashi Iwai
2026-09-16  7:43       ` Zipdox
2026-09-16 15:40         ` Takashi Iwai
2026-09-16 16:58           ` Takashi Iwai
2026-09-16 18:22             ` Takashi Iwai

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®