diff mbox series

[04/11] usb: gadget: f_uac1: Support multiple sampling rates

Message ID 20211220082542.13750-5-pavel.hofman@ivitera.com
State Superseded
Headers show
Series usb: gadget: audio: Multiple rates, dyn. bInterval | expand

Commit Message

Pavel Hofman Dec. 20, 2021, 8:25 a.m. UTC
From: Julian Scheel <julian@jusst.de>

A list of sampling rates can be specified via configfs. All enabled
sampling rates are sent to the USB host on request. When the host
selects a sampling rate the internal active rate is updated.

Config strings with single value stay compatible with the previous version.

Multiple samplerates passed as configuration arrays to g_audio module
when built for f_uac1.

Signed-off-by: Julian Scheel <julian@jusst.de>
Signed-off-by: Pavel Hofman <pavel.hofman@ivitera.com>
---
 .../ABI/testing/configfs-usb-gadget-uac1      |   4 +-
 Documentation/usb/gadget-testing.rst          |   4 +-
 drivers/usb/gadget/function/f_uac1.c          | 114 ++++++++++++++----
 drivers/usb/gadget/function/u_uac1.h          |  63 +++++++++-
 drivers/usb/gadget/legacy/audio.c             |  12 +-
 5 files changed, 168 insertions(+), 29 deletions(-)

Comments

kernel test robot Dec. 20, 2021, 8:26 p.m. UTC | #1
Hi Pavel,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on usb/usb-testing]
[also build test ERROR on linus/master v5.16-rc6 next-20211220]
[cannot apply to balbi-usb/testing/next peter-chen-usb/for-usb-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Pavel-Hofman/usb-gadget-audio-Multiple-rates-dyn-bInterval/20211220-162736
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
config: riscv-randconfig-r003-20211220 (https://download.01.org/0day-ci/archive/20211221/202112210422.0fC1uDBA-lkp@intel.com/config)
compiler: riscv32-linux-gcc (GCC) 11.2.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/2b868f914e557058c0b5e749db604db56b77e353
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Pavel-Hofman/usb-gadget-audio-Multiple-rates-dyn-bInterval/20211220-162736
        git checkout 2b868f914e557058c0b5e749db604db56b77e353
        # save the config file to linux build tree
        mkdir build_dir
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.2.0 make.cross O=build_dir ARCH=riscv SHELL=/bin/bash drivers/usb/gadget/legacy/

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   drivers/usb/gadget/legacy/audio.c: In function 'audio_bind':
>> drivers/usb/gadget/legacy/audio.c:287:25: error: 'p_srates_cnt' undeclared (first use in this function)
     287 |         for (i = 0; i < p_srates_cnt; ++i)
         |                         ^~~~~~~~~~~~
   drivers/usb/gadget/legacy/audio.c:287:25: note: each undeclared identifier is reported only once for each function it appears in
>> drivers/usb/gadget/legacy/audio.c:288:42: error: 'p_srates' undeclared (first use in this function); did you mean 'p_srate'?
     288 |                 uac1_opts->p_srates[i] = p_srates[i];
         |                                          ^~~~~~~~
         |                                          p_srate
>> drivers/usb/gadget/legacy/audio.c:294:25: error: 'c_srates_cnt' undeclared (first use in this function)
     294 |         for (i = 0; i < c_srates_cnt; ++i)
         |                         ^~~~~~~~~~~~
>> drivers/usb/gadget/legacy/audio.c:295:42: error: 'c_srates' undeclared (first use in this function); did you mean 'c_srate'?
     295 |                 uac1_opts->c_srates[i] = c_srates[i];
         |                                          ^~~~~~~~
         |                                          c_srate


vim +/p_srates_cnt +287 drivers/usb/gadget/legacy/audio.c

   264	
   265	#ifndef CONFIG_GADGET_UAC1
   266		uac2_opts = container_of(fi_uac2, struct f_uac2_opts, func_inst);
   267		uac2_opts->p_chmask = p_chmask;
   268	
   269		for (i = 0; i < p_srates_cnt; ++i)
   270			uac2_opts->p_srates[i] = p_srates[i];
   271		uac2_opts->p_srate = p_srates[0];
   272	
   273		uac2_opts->p_ssize = p_ssize;
   274		uac2_opts->c_chmask = c_chmask;
   275	
   276		for (i = 0; i < c_srates_cnt; ++i)
   277			uac2_opts->c_srates[i] = c_srates[i];
   278		uac2_opts->c_srate = c_srates[0];
   279	
   280		uac2_opts->c_ssize = c_ssize;
   281		uac2_opts->req_number = UAC2_DEF_REQ_NUM;
   282	#else
   283	#ifndef CONFIG_GADGET_UAC1_LEGACY
   284		uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst);
   285		uac1_opts->p_chmask = p_chmask;
   286	
 > 287		for (i = 0; i < p_srates_cnt; ++i)
 > 288			uac1_opts->p_srates[i] = p_srates[i];
   289		uac1_opts->p_srate = p_srates[0];
   290	
   291		uac1_opts->p_ssize = p_ssize;
   292		uac1_opts->c_chmask = c_chmask;
   293	
 > 294		for (i = 0; i < c_srates_cnt; ++i)
 > 295			uac1_opts->c_srates[i] = c_srates[i];
   296		uac1_opts->c_srate = c_srates[0];
   297	
   298		uac1_opts->c_ssize = c_ssize;
   299		uac1_opts->req_number = UAC1_DEF_REQ_NUM;
   300	#else /* CONFIG_GADGET_UAC1_LEGACY */
   301		uac1_opts = container_of(fi_uac1, struct f_uac1_legacy_opts, func_inst);
   302		uac1_opts->fn_play = fn_play;
   303		uac1_opts->fn_cap = fn_cap;
   304		uac1_opts->fn_cntl = fn_cntl;
   305		uac1_opts->req_buf_size = req_buf_size;
   306		uac1_opts->req_count = req_count;
   307		uac1_opts->audio_buf_size = audio_buf_size;
   308	#endif /* CONFIG_GADGET_UAC1_LEGACY */
   309	#endif
   310	
   311		status = usb_string_ids_tab(cdev, strings_dev);
   312		if (status < 0)
   313			goto fail;
   314		device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id;
   315		device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;
   316	
   317		if (gadget_is_otg(cdev->gadget) && !otg_desc[0]) {
   318			struct usb_descriptor_header *usb_desc;
   319	
   320			usb_desc = usb_otg_descriptor_alloc(cdev->gadget);
   321			if (!usb_desc) {
   322				status = -ENOMEM;
   323				goto fail;
   324			}
   325			usb_otg_descriptor_init(cdev->gadget, usb_desc);
   326			otg_desc[0] = usb_desc;
   327			otg_desc[1] = NULL;
   328		}
   329	
   330		status = usb_add_config(cdev, &audio_config_driver, audio_do_config);
   331		if (status < 0)
   332			goto fail_otg_desc;
   333		usb_composite_overwrite_options(cdev, &coverwrite);
   334	
   335		INFO(cdev, "%s, version: %s\n", DRIVER_DESC, DRIVER_VERSION);
   336		return 0;
   337	
   338	fail_otg_desc:
   339		kfree(otg_desc[0]);
   340		otg_desc[0] = NULL;
   341	fail:
   342	#ifndef CONFIG_GADGET_UAC1
   343		usb_put_function_instance(fi_uac2);
   344	#else
   345		usb_put_function_instance(fi_uac1);
   346	#endif
   347		return status;
   348	}
   349	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1
index b576b3d6ea6d..a8ecf17f688b 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget-uac1
+++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1
@@ -6,7 +6,7 @@  Description:
 
 		=====================	=======================================
 		c_chmask		capture channel mask
-		c_srate			capture sampling rate
+		c_srate			list of capture sampling rates (comma-separated)
 		c_ssize			capture sample size (bytes)
 		c_mute_present		capture mute control enable
 		c_volume_present	capture volume control enable
@@ -17,7 +17,7 @@  Description:
 		c_volume_res		capture volume control resolution
 					(in 1/256 dB)
 		p_chmask		playback channel mask
-		p_srate			playback sampling rate
+		p_srate			list of playback sampling rates (comma-separated)
 		p_ssize			playback sample size (bytes)
 		p_mute_present		playback mute control enable
 		p_volume_present	playback volume control enable
diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index 928f60a31544..f21cc21d2d7b 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -916,7 +916,7 @@  The uac1 function provides these attributes in its function directory:
 
 	================ ====================================================
 	c_chmask         capture channel mask
-	c_srate          capture sampling rate
+	c_srate          list of capture sampling rates (comma-separated)
 	c_ssize          capture sample size (bytes)
 	c_mute_present   capture mute control enable
 	c_volume_present capture volume control enable
@@ -924,7 +924,7 @@  The uac1 function provides these attributes in its function directory:
 	c_volume_max     capture volume control max value (in 1/256 dB)
 	c_volume_res     capture volume control resolution (in 1/256 dB)
 	p_chmask         playback channel mask
-	p_srate          playback sampling rate
+	p_srate          list of playback sampling rates (comma-separated)
 	p_ssize          playback sample size (bytes)
 	p_mute_present   playback mute control enable
 	p_volume_present playback volume control enable
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index ccb0e4f41e5d..7fd2b5580374 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -3,6 +3,7 @@ 
  * f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API)
  *
  * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ * Copyright (C) 2021 Julian Scheel <julian@jusst.de>
  *
  * This driver doesn't expect any real Audio codec to be present
  * on the device - the audio streams are simply sinked to and
@@ -42,6 +43,7 @@  struct f_uac1 {
 	/* Interrupt IN endpoint of AC interface */
 	struct usb_ep	*int_ep;
 	atomic_t	int_count;
+	int ctl_id;
 };
 
 static inline struct f_uac1 *func_to_uac1(struct usb_function *f)
@@ -188,16 +190,18 @@  static struct uac1_as_header_descriptor as_in_header_desc = {
 	.wFormatTag =		cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
 };
 
-DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
+DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(UAC_MAX_RATES);
+#define uac_format_type_i_discrete_descriptor \
+	uac_format_type_i_discrete_descriptor_##UAC_MAX_RATES
 
-static struct uac_format_type_i_discrete_descriptor_1 as_out_type_i_desc = {
-	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+static struct uac_format_type_i_discrete_descriptor as_out_type_i_desc = {
+	.bLength =		0, /* filled on rate setup */
 	.bDescriptorType =	USB_DT_CS_INTERFACE,
 	.bDescriptorSubtype =	UAC_FORMAT_TYPE,
 	.bFormatType =		UAC_FORMAT_TYPE_I,
 	.bSubframeSize =	2,
 	.bBitResolution =	16,
-	.bSamFreqType =		1,
+	.bSamFreqType =		0, /* filled on rate setup */
 };
 
 /* Standard ISO OUT Endpoint Descriptor */
@@ -221,14 +225,14 @@  static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
 	.wLockDelay =		cpu_to_le16(1),
 };
 
-static struct uac_format_type_i_discrete_descriptor_1 as_in_type_i_desc = {
-	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+static struct uac_format_type_i_discrete_descriptor as_in_type_i_desc = {
+	.bLength =		0, /* filled on rate setup */
 	.bDescriptorType =	USB_DT_CS_INTERFACE,
 	.bDescriptorSubtype =	UAC_FORMAT_TYPE,
 	.bFormatType =		UAC_FORMAT_TYPE_I,
 	.bSubframeSize =	2,
 	.bBitResolution =	16,
-	.bSamFreqType =		1,
+	.bSamFreqType =		0, /* filled on rate setup */
 };
 
 /* Standard ISO OUT Endpoint Descriptor */
@@ -333,6 +337,31 @@  static struct usb_gadget_strings *uac1_strings[] = {
  * This function is an ALSA sound card following USB Audio Class Spec 1.0.
  */
 
+static void uac_cs_attr_sample_rate(struct usb_ep *ep, struct usb_request *req)
+{
+	struct usb_function *fn = ep->driver_data;
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct g_audio *agdev = func_to_g_audio(fn);
+	struct f_uac1 *uac1 = func_to_uac1(fn);
+	struct f_uac1_opts *opts = g_audio_to_uac1_opts(agdev);
+	u8 *buf = (u8 *)req->buf;
+	u32 val = 0;
+
+	if (req->actual != 3) {
+		WARN(cdev, "Invalid data size for UAC_EP_CS_ATTR_SAMPLE_RATE.\n");
+		return;
+	}
+
+	val = buf[0] | (buf[1] << 8) | (buf[2] << 16);
+	if (uac1->ctl_id == (USB_DIR_IN | 2)) {
+		opts->p_srate = val;
+		u_audio_set_playback_srate(agdev, opts->p_srate);
+	} else if (uac1->ctl_id == (USB_DIR_OUT | 1)) {
+		opts->c_srate = val;
+		u_audio_set_capture_srate(agdev, opts->c_srate);
+	}
+}
+
 static void audio_notify_complete(struct usb_ep *_ep, struct usb_request *req)
 {
 	struct g_audio *audio = req->context;
@@ -707,18 +736,27 @@  static int audio_set_endpoint_req(struct usb_function *f,
 		const struct usb_ctrlrequest *ctrl)
 {
 	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = f->config->cdev->req;
+	struct f_uac1		*uac1 = func_to_uac1(f);
 	int			value = -EOPNOTSUPP;
 	u16			ep = le16_to_cpu(ctrl->wIndex);
 	u16			len = le16_to_cpu(ctrl->wLength);
 	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u8			cs = w_value >> 8;
 
 	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
 			ctrl->bRequest, w_value, len, ep);
 
 	switch (ctrl->bRequest) {
-	case UAC_SET_CUR:
+	case UAC_SET_CUR: {
+		if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) {
+			cdev->gadget->ep0->driver_data = f;
+			uac1->ctl_id = ep;
+			req->complete = uac_cs_attr_sample_rate;
+		}
 		value = len;
 		break;
+	}
 
 	case UAC_SET_MIN:
 		break;
@@ -743,16 +781,34 @@  static int audio_get_endpoint_req(struct usb_function *f,
 		const struct usb_ctrlrequest *ctrl)
 {
 	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request *req = f->config->cdev->req;
+	struct g_audio *agdev = func_to_g_audio(f);
+	struct f_uac1_opts *opts = g_audio_to_uac1_opts(agdev);
+	u8 *buf = (u8 *)req->buf;
 	int value = -EOPNOTSUPP;
-	u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+	u8 ep = le16_to_cpu(ctrl->wIndex);
 	u16 len = le16_to_cpu(ctrl->wLength);
 	u16 w_value = le16_to_cpu(ctrl->wValue);
+	u8 cs = w_value >> 8;
+	u32 val = 0;
 
 	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
 			ctrl->bRequest, w_value, len, ep);
 
 	switch (ctrl->bRequest) {
-	case UAC_GET_CUR:
+	case UAC_GET_CUR: {
+		if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) {
+			if (ep == (USB_DIR_IN | 2))
+				val = opts->p_srate;
+			else if (ep == (USB_DIR_OUT | 1))
+				val = opts->c_srate;
+			buf[2] = (val >> 16) & 0xff;
+			buf[1] = (val >> 8) & 0xff;
+			buf[0] = val & 0xff;
+		}
+		value = len;
+		break;
+	}
 	case UAC_GET_MIN:
 	case UAC_GET_MAX:
 	case UAC_GET_RES:
@@ -1118,10 +1174,9 @@  static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 	struct f_uac1_opts		*audio_opts;
 	struct usb_ep			*ep = NULL;
 	struct usb_string		*us;
-	u8				*sam_freq;
-	int				rate;
 	int				ba_iface_id;
 	int				status;
+	int				idx, i;
 
 	status = f_audio_validate_opts(audio, dev);
 	if (status)
@@ -1213,12 +1268,23 @@  static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 	}
 
 	/* Set sample rates */
-	rate = audio_opts->c_srate;
-	sam_freq = as_out_type_i_desc.tSamFreq[0];
-	memcpy(sam_freq, &rate, 3);
-	rate = audio_opts->p_srate;
-	sam_freq = as_in_type_i_desc.tSamFreq[0];
-	memcpy(sam_freq, &rate, 3);
+	for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+		if (audio_opts->c_srates[i] == 0)
+			break;
+		memcpy(as_out_type_i_desc.tSamFreq[idx++],
+				&audio_opts->c_srates[i], 3);
+	}
+	as_out_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+	as_out_type_i_desc.bSamFreqType = idx;
+
+	for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+		if (audio_opts->p_srates[i] == 0)
+			break;
+		memcpy(as_in_type_i_desc.tSamFreq[idx++],
+				&audio_opts->p_srates[i], 3);
+	}
+	as_in_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+	as_in_type_i_desc.bSamFreqType = idx;
 
 	/* allocate instance-specific interface IDs, and patch descriptors */
 	status = usb_interface_id(c, f);
@@ -1298,7 +1364,8 @@  static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 	audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize);
 	audio->params.c_chmask = audio_opts->c_chmask;
 	audio->params.c_srate = audio_opts->c_srate;
-	audio->params.c_srates[0] = audio_opts->c_srate;
+	memcpy(audio->params.c_srates, audio_opts->c_srates,
+			sizeof(audio->params.c_srates));
 	audio->params.c_ssize = audio_opts->c_ssize;
 	if (FUIN_EN(audio_opts)) {
 		audio->params.p_fu.id = USB_IN_FU_ID;
@@ -1311,7 +1378,8 @@  static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 	}
 	audio->params.p_chmask = audio_opts->p_chmask;
 	audio->params.p_srate = audio_opts->p_srate;
-	audio->params.p_srates[0] = audio_opts->p_srate;
+	memcpy(audio->params.p_srates, audio_opts->p_srates,
+			sizeof(audio->params.p_srates));
 	audio->params.p_ssize = audio_opts->p_ssize;
 	if (FUOUT_EN(audio_opts)) {
 		audio->params.c_fu.id = USB_OUT_FU_ID;
@@ -1417,10 +1485,10 @@  end:									\
 CONFIGFS_ATTR(f_uac1_opts_, name)
 
 UAC1_ATTRIBUTE(u32, c_chmask);
-UAC1_ATTRIBUTE(u32, c_srate);
+UAC_RATE1_ATTRIBUTE(c_srate);
 UAC1_ATTRIBUTE(u32, c_ssize);
 UAC1_ATTRIBUTE(u32, p_chmask);
-UAC1_ATTRIBUTE(u32, p_srate);
+UAC_RATE1_ATTRIBUTE(p_srate);
 UAC1_ATTRIBUTE(u32, p_ssize);
 UAC1_ATTRIBUTE(u32, req_number);
 
@@ -1490,9 +1558,11 @@  static struct usb_function_instance *f_audio_alloc_inst(void)
 
 	opts->c_chmask = UAC1_DEF_CCHMASK;
 	opts->c_srate = UAC1_DEF_CSRATE;
+	opts->c_srates[0] = UAC1_DEF_CSRATE;
 	opts->c_ssize = UAC1_DEF_CSSIZE;
 	opts->p_chmask = UAC1_DEF_PCHMASK;
 	opts->p_srate = UAC1_DEF_PSRATE;
+	opts->p_srates[0] = UAC1_DEF_PSRATE;
 	opts->p_ssize = UAC1_DEF_PSSIZE;
 
 	opts->p_mute_present = UAC1_DEF_MUTE_PRESENT;
diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h
index 589fae861141..57dce469b46d 100644
--- a/drivers/usb/gadget/function/u_uac1.h
+++ b/drivers/usb/gadget/function/u_uac1.h
@@ -9,6 +9,7 @@ 
 #define __U_UAC1_H
 
 #include <linux/usb/composite.h>
+#include "uac_common.h"
 
 #define UAC1_OUT_EP_MAX_PACKET_SIZE	200
 #define UAC1_DEF_CCHMASK	0x3
@@ -30,9 +31,11 @@ 
 struct f_uac1_opts {
 	struct usb_function_instance	func_inst;
 	int				c_chmask;
+	int				c_srates[UAC_MAX_RATES];
 	int				c_srate;
 	int				c_ssize;
 	int				p_chmask;
+	int				p_srates[UAC_MAX_RATES];
 	int				p_srate;
 	int				p_ssize;
 
@@ -54,5 +57,63 @@  struct f_uac1_opts {
 	struct mutex			lock;
 	int				refcnt;
 };
-
+#define UAC_RATE1_ATTRIBUTE(name)					\
+static ssize_t f_uac1_opts_##name##_show(struct config_item *item,	\
+					 char *page)			\
+{									\
+	struct f_uac1_opts *opts = to_f_uac1_opts(item);			\
+	int result = 0;							\
+	int i;								\
+									\
+	mutex_lock(&opts->lock);					\
+	page[0] = '\0';							\
+	for (i = 0; i < UAC_MAX_RATES; i++) {				\
+		if (opts->name##s[i] == 0)					\
+			break;					\
+		result += sprintf(page + strlen(page), "%u,",		\
+				opts->name##s[i]);				\
+	}								\
+	if (strlen(page) > 0)						\
+		page[strlen(page) - 1] = '\n';				\
+	mutex_unlock(&opts->lock);					\
+									\
+	return result;							\
+}									\
+									\
+static ssize_t f_uac1_opts_##name##_store(struct config_item *item,	\
+					  const char *page, size_t len)	\
+{									\
+	struct f_uac1_opts *opts = to_f_uac1_opts(item);			\
+	char *split_page = NULL;					\
+	int ret = -EINVAL;						\
+	char *token;							\
+	u32 num;							\
+	int i;								\
+									\
+	mutex_lock(&opts->lock);					\
+	if (opts->refcnt) {						\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	i = 0;								\
+	memset(opts->name##s, 0x00, sizeof(opts->name##s));			\
+	split_page = kstrdup(page, GFP_KERNEL);				\
+	while ((token = strsep(&split_page, ",")) != NULL) {		\
+		ret = kstrtou32(token, 0, &num);			\
+		if (ret)						\
+			goto end;					\
+									\
+		opts->name##s[i++] = num;					\
+		opts->name = num;				\
+		ret = len;						\
+	};								\
+									\
+end:									\
+	kfree(split_page);						\
+	mutex_unlock(&opts->lock);					\
+	return ret;							\
+}									\
+									\
+CONFIGFS_ATTR(f_uac1_opts_, name)
 #endif /* __U_UAC1_H */
diff --git a/drivers/usb/gadget/legacy/audio.c b/drivers/usb/gadget/legacy/audio.c
index 58bcb26c7854..f3cae72bcd4c 100644
--- a/drivers/usb/gadget/legacy/audio.c
+++ b/drivers/usb/gadget/legacy/audio.c
@@ -283,10 +283,18 @@  static int audio_bind(struct usb_composite_dev *cdev)
 #ifndef CONFIG_GADGET_UAC1_LEGACY
 	uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst);
 	uac1_opts->p_chmask = p_chmask;
-	uac1_opts->p_srate = p_srate;
+
+	for (i = 0; i < p_srates_cnt; ++i)
+		uac1_opts->p_srates[i] = p_srates[i];
+	uac1_opts->p_srate = p_srates[0];
+
 	uac1_opts->p_ssize = p_ssize;
 	uac1_opts->c_chmask = c_chmask;
-	uac1_opts->c_srate = c_srate;
+
+	for (i = 0; i < c_srates_cnt; ++i)
+		uac1_opts->c_srates[i] = c_srates[i];
+	uac1_opts->c_srate = c_srates[0];
+
 	uac1_opts->c_ssize = c_ssize;
 	uac1_opts->req_number = UAC1_DEF_REQ_NUM;
 #else /* CONFIG_GADGET_UAC1_LEGACY */