public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: crwulff@gmail.com
To: linux-usb@vger.kernel.org
Cc: Pavel Hofman <pavel.hofman@ivitera.com>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	James Gruber <jimmyjgruber@gmail.com>,
	Jeff Johnson <quic_jjohnson@quicinc.com>,
	John Keeping <jkeeping@inmusicbrands.com>,
	Jonathan Corbet <corbet@lwn.net>, Lee Jones <lee@kernel.org>,
	Perr Zhang <perr@usb7.net>,
	linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
	Chris Wulff <crwulff@gmail.com>
Subject: [PATCH RFC 14/14] usb: gadget: f_uac2: support ganged volume/mute controls
Date: Sat, 28 Sep 2024 11:09:05 -0400	[thread overview]
Message-ID: <20240928150905.2616313-15-crwulff@gmail.com> (raw)
In-Reply-To: <20240928150905.2616313-1-crwulff@gmail.com>

From: Chris Wulff <crwulff@gmail.com>

When multiple feature units exist due to differences
in other terminal descriptors, they still represent
the same volume/mute controls.

Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
 drivers/usb/gadget/function/f_uac2.c | 171 +++++++++++++++++++++------
 1 file changed, 133 insertions(+), 38 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index c30fbd062793..427a95b043db 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -35,8 +35,8 @@
  */
 #define USB_OUT_CLK_ID	(out_clk_src_desc.bClockID)
 #define USB_IN_CLK_ID	(in_clk_src_desc.bClockID)
-#define USB_OUT_FU_ID(_opts)	(_opts->c_alt_1_opts.fu_id)
-#define USB_IN_FU_ID(_opts)	(_opts->p_alt_1_opts.fu_id)
+#define USB_FU_ID(_alt_opts) ((_alt_opts) ? (_alt_opts)->fu_id : 0)
+
 
 #define CONTROL_ABSENT	0
 #define CONTROL_RDONLY	1
@@ -54,7 +54,6 @@
 #define UNFLW_CTRL	8
 #define OVFLW_CTRL	10
 
-
 #define EP_EN(_alt_opts) ((_alt_opts) && ((_alt_opts)->chmask != 0))
 #define FUIN_EN(_opts) (EP_EN(&_opts->p_alt_1_opts) \
 				&& ((_opts)->p_mute_present \
@@ -112,6 +111,59 @@ struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev)
 
 static int afunc_notify(struct g_audio *agdev, int unit_id, int cs);
 
+static inline struct f_uac2_alt_opts *get_alt_opts(struct f_uac2_opts *opts, int alt, int dir)
+{
+	struct f_uac2_alt_opts *alt_opts;
+
+	if (alt == 0)
+		return NULL;
+
+	if (alt == 1)
+		return (dir == HOST_TO_DEVICE) ? &opts->c_alt_1_opts : &opts->p_alt_1_opts;
+
+	list_for_each_entry(alt_opts, (dir == HOST_TO_DEVICE) ? &opts->c_alt_opts
+							      : &opts->p_alt_opts, list) {
+		if (alt_opts->c.alt_num == alt)
+			return alt_opts;
+	}
+
+	return NULL;
+}
+
+static struct f_uac2_alt_opts *
+find_feature_unit(struct f_uac2_opts *opts, u8 entity_id, int *is_playback)
+{
+	struct f_uac2_alt_opts *alt_opts;
+
+	if (FUOUT_EN(opts)) {
+		if (is_playback)
+			*is_playback = 0;
+
+		if (entity_id == USB_FU_ID(&opts->c_alt_1_opts))
+			return &opts->c_alt_1_opts;
+
+		list_for_each_entry(alt_opts, &opts->c_alt_opts, list) {
+			if (entity_id == USB_FU_ID(alt_opts))
+				return alt_opts;
+		}
+	}
+
+	if (FUIN_EN(opts)) {
+		if (is_playback)
+			*is_playback = 1;
+
+		if (entity_id == USB_FU_ID(&opts->p_alt_1_opts))
+			return &opts->p_alt_1_opts;
+
+		list_for_each_entry(alt_opts, &opts->p_alt_opts, list) {
+			if (entity_id == USB_FU_ID(alt_opts))
+				return alt_opts;
+		}
+	}
+
+	return NULL;
+}
+
 /* --------- USB Function Interface ------------- */
 
 static struct usb_interface_assoc_descriptor iad_desc = {
@@ -1243,7 +1295,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
 	agdev->params.p_ssize = uac2_opts->p_ssize;
 
 	if (FUIN_EN(uac2_opts)) {
-		agdev->params.p_fu.id = USB_IN_FU_ID(uac2_opts);
+		agdev->params.p_fu.id = USB_FU_ID(&uac2_opts->p_alt_1_opts);
 		agdev->params.p_fu.mute_present = uac2_opts->p_mute_present;
 		agdev->params.p_fu.volume_present = uac2_opts->p_volume_present;
 		agdev->params.p_fu.volume_min = uac2_opts->p_volume_min;
@@ -1258,7 +1310,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
 	agdev->params.c_ssize = uac2_opts->c_ssize;
 
 	if (FUOUT_EN(uac2_opts)) {
-		agdev->params.c_fu.id = USB_OUT_FU_ID(uac2_opts);
+		agdev->params.c_fu.id = USB_FU_ID(&uac2_opts->c_alt_1_opts);
 		agdev->params.c_fu.mute_present = uac2_opts->c_mute_present;
 		agdev->params.c_fu.volume_present = uac2_opts->c_volume_present;
 		agdev->params.c_fu.volume_min = uac2_opts->c_volume_min;
@@ -1295,8 +1347,49 @@ afunc_notify_complete(struct usb_ep *_ep, struct usb_request *req)
 	usb_ep_free_request(_ep, req);
 }
 
+static int afunc_notify_one(struct g_audio *agdev, int unit_id, int cs);
+
+static int afunc_notify_multiple(struct g_audio *agdev, int unit_id, int cs, int source_id)
+{
+	struct f_uac2 *uac2 = func_to_uac2(&agdev->func);
+	struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
+	int ret;
+	struct f_uac2_alt_opts *alt_opts;
+	struct list_head *alt_opts_list;
+
+	if (!uac2->int_ep->enabled)
+		return 0;
+
+	if (unit_id != source_id) {
+		ret = afunc_notify_one(agdev, unit_id, cs);
+		if (ret)
+			return ret;
+	}
+
+	alt_opts_list = (unit_id == USB_FU_ID(&opts->c_alt_1_opts)) ? &opts->c_alt_opts
+								    : &opts->p_alt_opts;
+
+	/* Notify all other ganged controls */
+	list_for_each_entry(alt_opts, alt_opts_list, list) {
+		if ((USB_FU_ID(alt_opts) > unit_id) && (USB_FU_ID(alt_opts) != source_id)) {
+			unit_id = USB_FU_ID(alt_opts);
+			ret = afunc_notify_one(agdev, unit_id, cs);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
 static int
 afunc_notify(struct g_audio *agdev, int unit_id, int cs)
+{
+	return afunc_notify_multiple(agdev, unit_id, cs, 0);
+}
+
+static int
+afunc_notify_one(struct g_audio *agdev, int unit_id, int cs)
 {
 	struct f_uac2 *uac2 = func_to_uac2(&agdev->func);
 	struct usb_request *req;
@@ -1304,9 +1397,6 @@ afunc_notify(struct g_audio *agdev, int unit_id, int cs)
 	u16 w_index, w_value;
 	int ret;
 
-	if (!uac2->int_ep->enabled)
-		return 0;
-
 	if (atomic_inc_return(&uac2->int_count) > UAC2_DEF_INT_REQ_NUM) {
 		atomic_dec(&uac2->int_count);
 		return 0;
@@ -1360,16 +1450,12 @@ afunc_set_alt(struct usb_function *fn, unsigned int intf, unsigned int alt)
 	struct usb_composite_dev *cdev = fn->config->cdev;
 	struct f_uac2 *uac2 = func_to_uac2(fn);
 	struct g_audio *agdev = func_to_g_audio(fn);
+	struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
+	struct f_uac2_alt_opts *alt_opts = NULL;
 	struct usb_gadget *gadget = cdev->gadget;
 	struct device *dev = &gadget->dev;
 	int ret = 0;
 
-	/* No i/f has more than 2 alt settings */
-	if (alt > 1) {
-		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
-		return -EINVAL;
-	}
-
 	if (intf == uac2->ac_intf) {
 		/* Control I/f has only 1 AltSetting - 0 */
 		if (alt) {
@@ -1388,6 +1474,12 @@ afunc_set_alt(struct usb_function *fn, unsigned int intf, unsigned int alt)
 	}
 
 	if (intf == uac2->as_out_intf) {
+		alt_opts = get_alt_opts(opts, alt, HOST_TO_DEVICE);
+		if (alt && !alt_opts) {
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+			return -EINVAL;
+		}
+
 		uac2->as_out_alt = alt;
 
 		if (alt)
@@ -1395,6 +1487,12 @@ afunc_set_alt(struct usb_function *fn, unsigned int intf, unsigned int alt)
 		else
 			u_audio_stop_capture(&uac2->g_audio);
 	} else if (intf == uac2->as_in_intf) {
+		alt_opts = get_alt_opts(opts, alt, DEVICE_TO_HOST);
+		if (alt && !alt_opts) {
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+			return -EINVAL;
+		}
+
 		uac2->as_in_alt = alt;
 
 		if (alt)
@@ -1463,6 +1561,8 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 	u8 control_selector = w_value >> 8;
 	int value = -EOPNOTSUPP;
 	u32 p_srate, c_srate;
+	unsigned int is_playback = 0;
+	struct f_uac2_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
 
 	u_audio_get_playback_srate(agdev, &p_srate);
 	u_audio_get_capture_srate(agdev, &c_srate);
@@ -1488,13 +1588,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 				"%s:%d control_selector=%d TODO!\n",
 				__func__, __LINE__, control_selector);
 		}
-	} else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
-			(FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
-		unsigned int is_playback = 0;
-
-		if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
-			is_playback = 1;
-
+	} else if (alt_opts) {
 		if (control_selector == UAC_FU_MUTE) {
 			unsigned int mute;
 
@@ -1539,6 +1633,8 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 	u8 entity_id = (w_index >> 8) & 0xff;
 	u8 control_selector = w_value >> 8;
 	int value = -EOPNOTSUPP;
+	unsigned int is_playback = 0;
+	struct f_uac2_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
 
 	if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) {
 		if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
@@ -1577,13 +1673,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 				"%s:%d control_selector=%d TODO!\n",
 				__func__, __LINE__, control_selector);
 		}
-	} else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
-			(FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
-		unsigned int is_playback = 0;
-
-		if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
-			is_playback = 1;
-
+	} else if (alt_opts) {
 		if (control_selector == UAC_FU_VOLUME) {
 			struct cntrl_range_lay2 r;
 			s16 max_db, min_db, res_db;
@@ -1654,31 +1744,32 @@ out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
 {
 	struct g_audio *agdev = req->context;
 	struct usb_composite_dev *cdev = agdev->func.config->cdev;
-	struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
 	struct f_uac2 *uac2 = func_to_uac2(&agdev->func);
+	struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
 	struct usb_ctrlrequest *cr = &uac2->setup_cr;
 	u16 w_index = le16_to_cpu(cr->wIndex);
 	u16 w_value = le16_to_cpu(cr->wValue);
 	u8 entity_id = (w_index >> 8) & 0xff;
 	u8 control_selector = w_value >> 8;
+	unsigned int is_playback;
+	struct f_uac2_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
 
 	if (req->status != 0) {
 		dev_dbg(&cdev->gadget->dev, "completion err %d\n", req->status);
 		return;
 	}
 
-	if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
-		(FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
-		unsigned int is_playback = 0;
-
-		if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
-			is_playback = 1;
-
+	if (alt_opts) {
 		if (control_selector == UAC_FU_MUTE) {
 			u8 mute = *(u8 *)req->buf;
 
 			u_audio_set_mute(agdev, is_playback, mute);
 
+			/* We also need to send notify for ganged controls */
+			afunc_notify_multiple(agdev, USB_FU_ID(is_playback ? &opts->p_alt_1_opts
+									   : &opts->c_alt_1_opts),
+					      control_selector, entity_id);
+
 			return;
 		} else if (control_selector == UAC_FU_VOLUME) {
 			struct cntrl_cur_lay2 *c = req->buf;
@@ -1687,6 +1778,11 @@ out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
 			volume = le16_to_cpu(c->wCUR);
 			u_audio_set_volume(agdev, is_playback, volume);
 
+			/* We also need to send notify for ganged controls */
+			afunc_notify_multiple(agdev, USB_FU_ID(is_playback ? &opts->p_alt_1_opts
+									   : &opts->c_alt_1_opts),
+					      control_selector, entity_id);
+
 			return;
 		} else {
 			dev_err(&agdev->gadget->dev,
@@ -1721,8 +1817,7 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 			req->complete = uac2_cs_control_sam_freq;
 			return w_length;
 		}
-	} else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
-			(FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
+	} else if (find_feature_unit(opts, entity_id, NULL)) {
 		memcpy(&uac2->setup_cr, cr, sizeof(*cr));
 		req->context = agdev;
 		req->complete = out_rq_cur_complete;
-- 
2.43.0


      parent reply	other threads:[~2024-09-28 15:09 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
2024-09-28 15:08 ` [PATCH RFC 01/14] usb: gadget: f_uac: Refactor common configfs attribute defines used in UAC1/2 crwulff
2024-09-28 15:08 ` [PATCH RFC 02/14] usb: gadget: f_uac1: Fix fs/hs/ss descriptors to have correct values crwulff
2024-09-28 15:08 ` [PATCH RFC 03/14] usb: gadget: f_uac1: Add adaptive sync support for capture crwulff
2024-09-28 15:08 ` [PATCH RFC 04/14] usb: gadget: f_uac2: Move max packet size code to a common header crwulff
2024-09-28 15:08 ` [PATCH RFC 05/14] usb: gadget: f_uac1: Add hs_bint to configfs crwulff
2024-09-28 15:08 ` [PATCH RFC 06/14] usb: gadget: f_uac1: Add terminal type attributes crwulff
2024-09-28 15:08 ` [PATCH RFC 07/14] usb: gadget: f_uac1: Add alt mode settings interface crwulff
2024-09-28 15:08 ` [PATCH RFC 08/14] usb: gadget: f_uac2: " crwulff
2024-09-28 15:09 ` [PATCH RFC 09/14] usb: gadget: f_uac1: Make string table dynamic with strings from all alt modes crwulff
2024-09-28 15:09 ` [PATCH RFC 10/14] usb: gadget: f_uac2: " crwulff
2024-09-28 15:09 ` [PATCH RFC 11/14] usb: gadget: f_uac1: Generate dynamic descriptors based on alt opts crwulff
2024-09-28 15:09 ` [PATCH RFC 12/14] usb: gadget: f_uac2: " crwulff
2024-09-28 15:09 ` [PATCH RFC 13/14] usb: gadget: f_uac1: support ganged volume/mute controls crwulff
2024-09-28 15:09 ` crwulff [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240928150905.2616313-15-crwulff@gmail.com \
    --to=crwulff@gmail.com \
    --cc=corbet@lwn.net \
    --cc=gregkh@linuxfoundation.org \
    --cc=jimmyjgruber@gmail.com \
    --cc=jkeeping@inmusicbrands.com \
    --cc=lee@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=pavel.hofman@ivitera.com \
    --cc=perr@usb7.net \
    --cc=quic_jjohnson@quicinc.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox