All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
@ 2011-08-19 11:22 Jassi Brar
       [not found] ` <1313752934-20081-1-git-send-email-jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
  0 siblings, 1 reply; 17+ messages in thread
From: Jassi Brar @ 2011-08-19 11:22 UTC (permalink / raw)
  To: linux-usb-u79uwXL29TY76Z2rM5mHXA, balbi-l0cyMroinI0, gregkh-l3A5Bk7waGM
  Cc: alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw, zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w, Jassi Brar

This is a flexible USB Audio Class 2.0 compliant gadget driver that
implements a simple topology with a virtual sound card exposed at
the function side.

The driver doesn't expect any real audio codec to be present on the
function - the audio streams are simply sinked to and sourced from a
virtual ALSA sound card created. The user-space application may choose
to do whatever it wants with the data received from the USB Host and
choose to provide whatever it wants as audio data to the USB Host.

Capture(USB-Out) and Playback(USB-In) can be run at independent
configurations specified via module parameters while loading the driver.

Signed-off-by: Yadi Brar <yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Jassi Brar <jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---

===========
How To Test
===========
  USB_DEV$ insmod g_audio2.ko c_srate=64000 p_srate=48000 ; say
    gadget: high speed config #1: UAC2

  USB_DEV$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: uac2 [uac2], device 0: UAC2 PCM [UAC2 PCM]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

  USB_DEV$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: uac2 [uac2], device 0: UAC2 PCM [UAC2 PCM]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

  USB_HOST$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC665 Analog [ALC665 Analog]
  Subdevices: 0/1
  Subdevice #0: subdevice #0
card 1: Gadget [UAC2 Gadget], device 0: USB Audio [USB Audio]  <<<<<<
  Subdevices: 1/1
  Subdevice #0: subdevice #0

  USB_HOST$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC665 Analog [ALC665 Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: Gadget [UAC2 Gadget], device 0: USB Audio [USB Audio] <<<<<<<
  Subdevices: 1/1
  Subdevice #0: subdevice #0

 After this confirmation, you should be able to use it just like another
sound card in your system. That is, a new sound card should appear in the
sound preferences which you can select to route your default audio to and
from.

============
Test Results
============
 The driver has been tested for Half as well as Full Duplex at
Stereo, S16_LE at 44.1KHz, 48KHz and 64KHz. It should also
work for other configurations as well, except for different
sample-size(TODO).

Obviously, much depends upon the underlying UDC driver.

While testing with OMAP's implementation of Inventra HDRC, I observed
some noise for configurations for which the USB-OUT packet size chosen
by the Host is not a power of 2. USB-IN works just fine.
For ex, on both Beagle/Panda board, 48KHz USB-OUT has some noise while
64KHz is smooth. Also, with full duplex, it occasioanally shows noise.

Taking benefit of doubt, I assume this driver has unmasked some subtle
bug in the underlying UDC's ISOCH handling code ;)
Please feel free to find issue with this driver by testing it over
some other UDC.

 drivers/usb/gadget/Kconfig    |   20 +
 drivers/usb/gadget/Makefile   |    2 +
 drivers/usb/gadget/audio2.c   |  155 +++++
 drivers/usb/gadget/f_audio2.c | 1448 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1625 insertions(+), 0 deletions(-)
 create mode 100644 drivers/usb/gadget/audio2.c
 create mode 100644 drivers/usb/gadget/f_audio2.c

diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index e35dfef..95c65d8 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -599,6 +599,26 @@ config USB_ZERO_HNPTEST
 	  the "B-Peripheral" role, that device will use HNP to let this
 	  one serve as the USB host instead (in the "B-Host" role).
 
+config USB_AUDIO_2
+	tristate "Audio Gadget Class 2.0 (EXPERIMENTAL)"
+	depends on SND && EXPERIMENTAL
+	select SND_PCM
+	help
+	  This Gadget Audio driver is compatible with USB Audio Class
+	  specification 2.0. It implements 1 AudioControl interface,
+	  1 AudioStreaming Interface each for USB-OUT and USB-IN.
+	  Number of channels, sample rate and sample size can be
+	  specified as module parameters.
+	  This driver doesn't expect any real Audio codec to be present
+	  on the device - the audio streams are simply sinked to and
+	  sourced from a virtual ALSA sound card created. The user-space
+	  application may choose to do whatever it wants with the data
+	  received from the USB Host and choose to provide whatever it
+	  wants as audio data to the USB Host.
+
+	  Say "y" to link the driver statically, or "m" to build a
+	  dynamically linked module called "g_audio2".
+
 config USB_AUDIO
 	tristate "Audio Gadget (EXPERIMENTAL)"
 	depends on SND
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 7409338..fa8472e 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_USB_COMPOSITE)	+= usb-composite.o
 #
 g_zero-y			:= zero.o
 g_audio-y			:= audio.o
+g_audio2-y			:= audio2.o
 g_ether-y			:= ether.o
 g_serial-y			:= serial.o
 g_midi-y			:= gmidi.o
@@ -57,6 +58,7 @@ g_ncm-y				:= ncm.o
 
 obj-$(CONFIG_USB_ZERO)		+= g_zero.o
 obj-$(CONFIG_USB_AUDIO)		+= g_audio.o
+obj-$(CONFIG_USB_AUDIO_2)	+= g_audio2.o
 obj-$(CONFIG_USB_ETH)		+= g_ether.o
 obj-$(CONFIG_USB_GADGETFS)	+= gadgetfs.o
 obj-$(CONFIG_USB_FUNCTIONFS)	+= g_ffs.o
diff --git a/drivers/usb/gadget/audio2.c b/drivers/usb/gadget/audio2.c
new file mode 100644
index 0000000..92b6d64
--- /dev/null
+++ b/drivers/usb/gadget/audio2.c
@@ -0,0 +1,155 @@
+/*
+ * audio2.c -- USB Audio Class 2.0 gadget driver
+ *
+ * Copyright (C) 2011
+ *    Yadwinder Singh (yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org)
+ *    Jaswinder Singh (jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/utsname.h>
+#include <linux/platform_device.h>
+#include <linux/usb/composite.h>
+
+/*
+ * Kbuild is not very cooperative with respect to linking separately
+ * compiled library objects into one module.  So for now we won't use
+ * separate compilation ... ensuring init/exit sections work to shrink
+ * the runtime footprint, and giving us at least some parts of what
+ * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
+ */
+#include "f_audio2.c"
+
+enum {
+	STR_MANUFACTURER = 0,
+	STR_PRODUCT,
+	STR_CONFIG,
+};
+
+/* Thanks to Linux Foundation for donating this product ID. */
+#define UAC2_VENDOR	0x1d6b	/* Linux Foundation */
+#define UAC2_PRODUCT	0x0102	/* Linux-USB Audio Gadget */
+
+static const char manufacturer[] = "Linux Foundation";
+static const char product_desc[] = "UAC2 Gadget";
+static const char config[] = "Simple Source/Sink";
+
+static struct usb_string strings_cdev[] = {
+	[STR_MANUFACTURER].s = manufacturer,
+	[STR_PRODUCT].s = product_desc,
+	[STR_CONFIG].s = config,
+	{},
+};
+
+static struct usb_gadget_strings stringtab = {
+	.language = 0x0409,	/* en-us */
+	.strings = strings_cdev,
+};
+
+static struct usb_gadget_strings *cdev_strings[] = {
+	&stringtab,
+	NULL,
+};
+
+static struct usb_device_descriptor device_desc = {
+	.bLength = sizeof device_desc,
+	.bDescriptorType = USB_DT_DEVICE,
+
+	.bcdUSB = cpu_to_le16(0x200),
+	.bDeviceClass = USB_CLASS_MISC,
+	.bDeviceSubClass = 0x02,
+	.bDeviceProtocol = 0x01,
+	.idVendor = cpu_to_le16(UAC2_VENDOR),
+	.idProduct = cpu_to_le16(UAC2_PRODUCT),
+	/*
+	 * Implemented features of USB Audio Device class solely
+	 * depend on this driver, so this is where we decide the
+	 * version of the device.
+	 * Currently, the lowest version of simplest driver - 00.01
+	 */
+	.bcdDevice = cpu_to_le16(0x0001),
+	.bNumConfigurations = 1,
+};
+
+static struct usb_otg_descriptor otg_desc = {
+	.bLength = sizeof otg_desc,
+	.bDescriptorType = USB_DT_OTG,
+
+	.bmAttributes = USB_OTG_SRP,
+};
+
+const struct usb_descriptor_header *otgd[] = {
+	(struct usb_descriptor_header *) &otg_desc,
+	NULL,
+};
+
+static struct usb_configuration audio_config = {
+	.label = "UAC2",
+	.unbind = uac2_unbind_config,
+	.bConfigurationValue = 1,
+	.bmAttributes = USB_CONFIG_ATT_SELFPOWER,
+};
+
+static int __init
+audio_bind(struct usb_composite_dev *cdev)
+{
+	int  id;
+
+	/* Allocate string descriptor numbers */
+	id = usb_string_id(cdev);
+	if (id < 0)
+		return id;
+
+	strings_cdev[STR_MANUFACTURER].id = id;
+	device_desc.iManufacturer = id;
+
+	id = usb_string_id(cdev);
+	if (id < 0)
+		return id;
+
+	strings_cdev[STR_PRODUCT].id = id;
+	device_desc.iProduct = id;
+
+	id = usb_string_id(cdev);
+	if (id < 0)
+		return id;
+
+	strings_cdev[STR_CONFIG].id = id;
+	audio_config.iConfiguration = id;
+
+	if (gadget_is_otg(cdev->gadget)) {
+		audio_config.descriptors = otgd;
+		otg_desc.bmAttributes |= USB_OTG_HNP;
+		audio_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
+	}
+
+	return usb_add_config(cdev, &audio_config, uac2_bind_config);
+}
+
+static struct usb_composite_driver audio_driver = {
+	.name = "USB Audio Class 2.0",
+	.dev = &device_desc,
+	.strings = cdev_strings,
+	.max_speed = USB_SPEED_HIGH,
+};
+
+static int __init init(void)
+{
+	return usb_composite_probe(&audio_driver, audio_bind);
+}
+module_init(init);
+
+static void __exit cleanup(void)
+{
+	usb_composite_unregister(&audio_driver);
+}
+module_exit(cleanup);
+
+MODULE_DESCRIPTION("USB Audio Class 2.0 Gadget Driver");
+MODULE_AUTHOR("Yadi Brar, Jassi Brar");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/gadget/f_audio2.c b/drivers/usb/gadget/f_audio2.c
new file mode 100644
index 0000000..261d587
--- /dev/null
+++ b/drivers/usb/gadget/f_audio2.c
@@ -0,0 +1,1448 @@
+/*
+ * f_audio2.c -- USB Audio Class 2.0 Function
+ *
+ * Copyright (C) 2011
+ *    Yadwinder Singh (yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org)
+ *    Jaswinder Singh (jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+/* Playback(USB-IN) Default Stereo - Fl/Fr */
+static int p_chmask = 0x3;
+module_param(p_chmask, uint, S_IRUGO);
+MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
+
+/* Playback Default 48 KHz */
+static int p_srate = 48000;
+module_param(p_srate, uint, S_IRUGO);
+MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
+
+/* Playback Default 16bits/sample */
+static int p_ssize = 2;
+module_param(p_ssize, uint, S_IRUGO);
+MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
+
+/* Capture(USB-OUT) Default Stereo - Fl/Fr */
+static int c_chmask = 0x3;
+module_param(c_chmask, uint, S_IRUGO);
+MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
+
+/* Capture Default 48 KHz */
+static int c_srate = 48000;
+module_param(c_srate, uint, S_IRUGO);
+MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
+
+/* Capture Default 16bits/sample */
+static int c_ssize = 2;
+module_param(c_ssize, uint, S_IRUGO);
+MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
+
+#define DMA_ADDR_INVALID	(~(dma_addr_t)0)
+
+#define ALT_SET(x, a)	do {(x) &= ~0xff; (x) |= (a); } while (0)
+#define ALT_GET(x)	((x) & 0xff)
+#define INTF_SET(x, i)	do {(x) &= 0xff; (x) |= ((i) << 8); } while (0)
+#define INTF_GET(x)	((x >> 8) & 0xff)
+
+/* Keep everyone on toes */
+#define USB_XFERS	2
+
+/*
+ * The driver implements a simple UAC_2 topology.
+ * USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture
+ * ALSA_Playback -> IT_2 -> OT_4 -> USB-IN
+ * Capture and Playback sampling rates are independently
+ *  controlled by two clock sources :
+ *    CLK_5 := c_srate, and CLK_6 := p_srate
+ */
+#define USB_OUT_IT_ID	1
+#define IO_IN_IT_ID	2
+#define IO_OUT_OT_ID	3
+#define USB_IN_OT_ID	4
+#define USB_OUT_CLK_ID	5
+#define USB_IN_CLK_ID	6
+
+#define CONTROL_ABSENT	0
+#define CONTROL_RDONLY	1
+#define CONTROL_RDWR	3
+
+#define CLK_FREQ_CTRL	0
+#define CLK_VLD_CTRL	2
+
+#define COPY_CTRL	0
+#define CONN_CTRL	2
+#define OVRLD_CTRL	4
+#define CLSTR_CTRL	6
+#define UNFLW_CTRL	8
+#define OVFLW_CTRL	10
+
+const char *uac2_name = "snd_uac2";
+
+struct uac2_req {
+	struct uac2_rtd_params *pp; /* parent param */
+	struct usb_request *req;
+};
+
+struct uac2_rtd_params {
+	bool ep_enabled; /* if the ep is enabled */
+	/* Size of the ring buffer */
+	size_t dma_bytes;
+	unsigned char *dma_area;
+
+	struct snd_pcm_substream *ss;
+
+	/* Ring buffer */
+	ssize_t hw_ptr;
+
+	void *rbuf;
+
+	size_t period_size;
+
+	unsigned max_psize;
+	struct uac2_req ureq[USB_XFERS];
+
+	spinlock_t lock;
+};
+
+struct snd_uac2_chip {
+	struct platform_device pdev;
+	struct platform_driver pdrv;
+
+	struct uac2_rtd_params p_prm;
+	struct uac2_rtd_params c_prm;
+
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+};
+
+#define BUFF_SIZE_MAX	(PAGE_SIZE * 16)
+#define PRD_SIZE_MAX	PAGE_SIZE
+#define MIN_PERIODS	4
+
+static struct snd_pcm_hardware uac2_pcm_hardware = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER
+		 | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
+		 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
+	.rates = SNDRV_PCM_RATE_CONTINUOUS,
+	.periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX,
+	.buffer_bytes_max = BUFF_SIZE_MAX,
+	.period_bytes_max = PRD_SIZE_MAX,
+	.periods_min = MIN_PERIODS,
+};
+
+struct audio_dev {
+	/* Currently active {Interface[15:8] | AltSettings[7:0]} */
+	__u16 ac_alt, as_out_alt, as_in_alt;
+
+	struct usb_ep *in_ep, *out_ep;
+	struct usb_function func;
+
+	/* The ALSA Sound Card it represents on the USB-Client side */
+	struct snd_uac2_chip uac2;
+};
+
+static struct audio_dev *agdev_g;
+
+static inline
+struct audio_dev *func_to_agdev(struct usb_function *f)
+{
+	return container_of(f, struct audio_dev, func);
+}
+
+static inline
+struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u)
+{
+	return container_of(u, struct audio_dev, uac2);
+}
+
+static inline
+struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p)
+{
+	return container_of(p, struct snd_uac2_chip, pdev);
+}
+
+static inline
+struct snd_uac2_chip *prm_to_uac2(struct uac2_rtd_params *r)
+{
+	struct snd_uac2_chip *uac2 = container_of(r,
+					struct snd_uac2_chip, c_prm);
+
+	if (&uac2->c_prm != r)
+		uac2 = container_of(r, struct snd_uac2_chip, p_prm);
+
+	return uac2;
+}
+
+static inline
+uint num_channels(uint chanmask)
+{
+	uint num = 0;
+
+	while (chanmask) {
+		num += (chanmask & 1);
+		chanmask >>= 1;
+	}
+
+	return num;
+}
+
+static void
+agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	unsigned pending;
+	unsigned long flags;
+	bool update_alsa = false;
+	unsigned char *src, *dst;
+	int status = req->status;
+	struct uac2_req *ur = req->context;
+	struct snd_pcm_substream *substream;
+	struct uac2_rtd_params *prm = ur->pp;
+	struct snd_uac2_chip *uac2 = prm_to_uac2(prm);
+
+	/* i/f shutting down */
+	if (!prm->ep_enabled)
+		return;
+
+	/*
+	 * We can't really do much about bad xfers.
+	 * Afterall, the ISOCH xfers could fail legitimately.
+	 */
+	if (status)
+		pr_debug("%s: iso_complete status(%d) %d/%d\n",
+			__func__, status, req->actual, req->length);
+
+	substream = prm->ss;
+
+	/* Do nothing if ALSA isn't active */
+	if (!substream)
+		goto exit;
+
+	spin_lock_irqsave(&prm->lock, flags);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		src = prm->dma_area + prm->hw_ptr;
+		req->actual = req->length;
+		dst = req->buf;
+	} else {
+		dst = prm->dma_area + prm->hw_ptr;
+		src = req->buf;
+	}
+
+	pending = prm->hw_ptr % prm->period_size;
+	pending += req->actual;
+	if (pending >= prm->period_size)
+		update_alsa = true;
+
+	prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes;
+
+	spin_unlock_irqrestore(&prm->lock, flags);
+
+	/* Pack USB load in ALSA ring buffer */
+	memcpy(dst, src, req->actual);
+exit:
+	if (usb_ep_queue(ep, req, GFP_ATOMIC))
+		dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
+
+	if (update_alsa)
+		snd_pcm_period_elapsed(substream);
+
+	return;
+}
+
+static int
+uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
+	struct audio_dev *agdev = uac2_to_agdev(uac2);
+	struct uac2_rtd_params *prm;
+	unsigned long flags;
+	struct usb_ep *ep;
+	int err = 0;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		ep = agdev->in_ep;
+		prm = &uac2->p_prm;
+	} else {
+		ep = agdev->out_ep;
+		prm = &uac2->c_prm;
+	}
+
+	spin_lock_irqsave(&prm->lock, flags);
+
+	/* Reset */
+	prm->hw_ptr = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		prm->ss = substream;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		prm->ss = NULL;
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+	spin_unlock_irqrestore(&prm->lock, flags);
+
+	/* Clear buffer after Play stops */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss)
+		memset(prm->rbuf, 0, prm->max_psize * USB_XFERS);
+
+	return err;
+}
+
+static snd_pcm_uframes_t uac2_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
+	struct uac2_rtd_params *prm;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		prm = &uac2->p_prm;
+	else
+		prm = &uac2->c_prm;
+
+	return bytes_to_frames(substream->runtime, prm->hw_ptr);
+}
+
+static int uac2_pcm_hw_params(struct snd_pcm_substream *substream,
+			       struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
+	struct uac2_rtd_params *prm;
+	int err;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		prm = &uac2->p_prm;
+	else
+		prm = &uac2->c_prm;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	if (err >= 0) {
+		prm->dma_bytes = substream->runtime->dma_bytes;
+		prm->dma_area = substream->runtime->dma_area;
+		prm->period_size = params_period_bytes(hw_params);
+	}
+
+	return err;
+}
+
+static int uac2_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
+	struct uac2_rtd_params *prm;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		prm = &uac2->p_prm;
+	else
+		prm = &uac2->c_prm;
+
+	prm->dma_area = NULL;
+	prm->dma_bytes = 0;
+	prm->period_size = 0;
+
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int uac2_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = uac2_pcm_hardware;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		spin_lock_init(&uac2->p_prm.lock);
+		runtime->hw.rate_min = p_srate;
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; /* ! p_ssize ! */
+		runtime->hw.channels_min = num_channels(p_chmask);
+		runtime->hw.period_bytes_min = 2 * uac2->p_prm.max_psize
+						/ runtime->hw.periods_min;
+	} else {
+		spin_lock_init(&uac2->c_prm.lock);
+		runtime->hw.rate_min = c_srate;
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; /* ! c_ssize ! */
+		runtime->hw.channels_min = num_channels(c_chmask);
+		runtime->hw.period_bytes_min = 2 * uac2->c_prm.max_psize
+						/ runtime->hw.periods_min;
+	}
+
+	runtime->hw.rate_max = runtime->hw.rate_min;
+	runtime->hw.channels_max = runtime->hw.channels_min;
+
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+	return 0;
+}
+
+/* ALSA cries without these function pointers */
+static int uac2_pcm_null(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static struct snd_pcm_ops uac2_pcm_ops = {
+	.open = uac2_pcm_open,
+	.close = uac2_pcm_null,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = uac2_pcm_hw_params,
+	.hw_free = uac2_pcm_hw_free,
+	.trigger = uac2_pcm_trigger,
+	.pointer = uac2_pcm_pointer,
+	.prepare = uac2_pcm_null,
+};
+
+static int __devinit snd_uac2_probe(struct platform_device *pdev)
+{
+	struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev);
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	int err;
+
+	/* Choose any slot, with no id */
+	err = snd_card_create(-1, NULL, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	uac2->card = card;
+
+	/*
+	 * Create first PCM device
+	 * Create a substream only for non-zero channel streams
+	 */
+	err = snd_pcm_new(uac2->card, "UAC2 PCM", 0,
+			       p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm);
+	if (err < 0)
+		goto snd_fail;
+
+	strcpy(pcm->name, "UAC2 PCM");
+	pcm->private_data = uac2;
+
+	uac2->pcm = pcm;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops);
+
+	strcpy(card->driver, "uac2");
+	strcpy(card->shortname, "uac2");
+	sprintf(card->longname, "uac2 %i", pdev->id);
+
+	snd_card_set_dev(card, &pdev->dev);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+		snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
+
+	err = snd_card_register(card);
+	if (!err) {
+		platform_set_drvdata(pdev, card);
+		return 0;
+	}
+
+snd_fail:
+	snd_card_free(card);
+
+	uac2->pcm = NULL;
+	uac2->card = NULL;
+
+	return err;
+}
+
+static int __devexit snd_uac2_remove(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+
+	platform_set_drvdata(pdev, NULL);
+
+	if (card)
+		return snd_card_free(card);
+
+	return 0;
+}
+
+static int alsa_uac2_init(struct audio_dev *agdev)
+{
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	int err;
+
+	uac2->pdrv.probe = snd_uac2_probe;
+	uac2->pdrv.remove = snd_uac2_remove;
+	uac2->pdrv.driver.name = uac2_name;
+
+	uac2->pdev.id = 0;
+	uac2->pdev.name = uac2_name;
+
+	/* Register snd_uac2 driver */
+	err = platform_driver_register(&uac2->pdrv);
+	if (err)
+		return err;
+
+	/* Register snd_uac2 device */
+	err = platform_device_register(&uac2->pdev);
+	if (err)
+		platform_driver_unregister(&uac2->pdrv);
+
+	return err;
+}
+
+static void alsa_uac2_exit(struct audio_dev *agdev)
+{
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+
+	platform_driver_unregister(&uac2->pdrv);
+	platform_device_unregister(&uac2->pdev);
+}
+
+
+/* --------- USB Function Interface ------------- */
+
+enum {
+	STR_ASSOC,
+	STR_IF_CTRL,
+	STR_CLKSRC_IN,
+	STR_CLKSRC_OUT,
+	STR_USB_IT,
+	STR_IO_IT,
+	STR_USB_OT,
+	STR_IO_OT,
+	STR_AS_OUT_ALT0,
+	STR_AS_OUT_ALT1,
+	STR_AS_IN_ALT0,
+	STR_AS_IN_ALT1,
+};
+
+static const char ifassoc[] = "Source/Sink";
+static const char ifctrl[] = "Topology Control";
+static char clksrc_in[8];
+static char clksrc_out[8];
+static const char usb_it[] = "USBH Out";
+static const char io_it[] = "USBD Out";
+static const char usb_ot[] = "USBH In";
+static const char io_ot[] = "USBD In";
+static const char out_alt0[] = "Playback Inactive";
+static const char out_alt1[] = "Playback Active";
+static const char in_alt0[] = "Capture Inactive";
+static const char in_alt1[] = "Capture Active";
+
+static struct usb_string strings_fn[] = {
+	[STR_ASSOC].s = ifassoc,
+	[STR_IF_CTRL].s = ifctrl,
+	[STR_CLKSRC_IN].s = clksrc_in,
+	[STR_CLKSRC_OUT].s = clksrc_out,
+	[STR_USB_IT].s = usb_it,
+	[STR_IO_IT].s = io_it,
+	[STR_USB_OT].s = usb_ot,
+	[STR_IO_OT].s = io_ot,
+	[STR_AS_OUT_ALT0].s = out_alt0,
+	[STR_AS_OUT_ALT1].s = out_alt1,
+	[STR_AS_IN_ALT0].s = in_alt0,
+	[STR_AS_IN_ALT1].s = in_alt1,
+	{ },
+};
+
+static struct usb_gadget_strings str_fn = {
+	.language = 0x0409,	/* en-us */
+	.strings = strings_fn,
+};
+
+static struct usb_gadget_strings *fn_strings[] = {
+	&str_fn,
+	NULL,
+};
+
+static struct usb_qualifier_descriptor devqual_desc = {
+	.bLength = sizeof devqual_desc,
+	.bDescriptorType = USB_DT_DEVICE_QUALIFIER,
+
+	.bcdUSB = cpu_to_le16(0x200),
+	.bDeviceClass = USB_CLASS_MISC,
+	.bDeviceSubClass = 0x02,
+	.bDeviceProtocol = 0x01,
+	.bNumConfigurations = 1,
+	.bRESERVED = 0,
+};
+
+static struct usb_interface_assoc_descriptor iad_desc = {
+	.bLength = sizeof iad_desc,
+	.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
+
+	.bFirstInterface = 0,
+	.bInterfaceCount = 3,
+	.bFunctionClass = USB_CLASS_AUDIO,
+	.bFunctionSubClass = UAC2_FUNCTION_SUBCLASS_UNDEFINED,
+	.bFunctionProtocol = UAC_VERSION_2,
+};
+
+/* Audio Control Interface */
+static struct usb_interface_descriptor std_ac_if_desc = {
+	.bLength = sizeof std_ac_if_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Clock source for IN traffic */
+struct uac_clock_source_descriptor in_clk_src_desc = {
+	.bLength = sizeof in_clk_src_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
+	.bClockID = USB_IN_CLK_ID,
+	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
+	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+	.bAssocTerminal = 0,
+};
+
+/* Clock source for OUT traffic */
+struct uac_clock_source_descriptor out_clk_src_desc = {
+	.bLength = sizeof out_clk_src_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
+	.bClockID = USB_OUT_CLK_ID,
+	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
+	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+	.bAssocTerminal = 0,
+};
+
+/* Input Terminal for USB_OUT */
+struct uac2_input_terminal_descriptor usb_out_it_desc = {
+	.bLength = sizeof usb_out_it_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_INPUT_TERMINAL,
+	.bTerminalID = USB_OUT_IT_ID,
+	.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
+	.bAssocTerminal = 0,
+	.bCSourceID = USB_OUT_CLK_ID,
+	.iChannelNames = 0,
+	.bmControls = (CONTROL_RDWR << COPY_CTRL),
+};
+
+/* Input Terminal for I/O-In */
+struct uac2_input_terminal_descriptor io_in_it_desc = {
+	.bLength = sizeof io_in_it_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_INPUT_TERMINAL,
+	.bTerminalID = IO_IN_IT_ID,
+	.wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_UNDEFINED),
+	.bAssocTerminal = 0,
+	.bCSourceID = USB_IN_CLK_ID,
+	.iChannelNames = 0,
+	.bmControls = (CONTROL_RDWR << COPY_CTRL),
+};
+
+/* Ouput Terminal for USB_IN */
+struct uac2_output_terminal_descriptor usb_in_ot_desc = {
+	.bLength = sizeof usb_in_ot_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
+	.bTerminalID = USB_IN_OT_ID,
+	.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
+	.bAssocTerminal = 0,
+	.bSourceID = IO_IN_IT_ID,
+	.bCSourceID = USB_IN_CLK_ID,
+	.bmControls = (CONTROL_RDWR << COPY_CTRL),
+};
+
+/* Ouput Terminal for I/O-Out */
+struct uac2_output_terminal_descriptor io_out_ot_desc = {
+	.bLength = sizeof io_out_ot_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
+	.bTerminalID = IO_OUT_OT_ID,
+	.wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_UNDEFINED),
+	.bAssocTerminal = 0,
+	.bSourceID = USB_OUT_IT_ID,
+	.bCSourceID = USB_OUT_CLK_ID,
+	.bmControls = (CONTROL_RDWR << COPY_CTRL),
+};
+
+struct uac2_ac_header_descriptor ac_hdr_desc = {
+	.bLength = sizeof ac_hdr_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_MS_HEADER,
+	.bcdADC = cpu_to_le16(0x200),
+	.bCategory = UAC2_FUNCTION_IO_BOX,
+	.wTotalLength = sizeof in_clk_src_desc + sizeof out_clk_src_desc
+			 + sizeof usb_out_it_desc + sizeof io_in_it_desc
+			+ sizeof usb_in_ot_desc + sizeof io_out_ot_desc,
+	.bmControls = 0,
+};
+
+/* Audio Streaming OUT Interface - Alt0 */
+static struct usb_interface_descriptor std_as_out_if0_desc = {
+	.bLength = sizeof std_as_out_if0_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Streaming OUT Interface - Alt1 */
+static struct usb_interface_descriptor std_as_out_if1_desc = {
+	.bLength = sizeof std_as_out_if1_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 1,
+	.bNumEndpoints = 1,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Stream OUT Intface Desc */
+struct uac2_as_header_descriptor as_out_hdr_desc = {
+	.bLength = sizeof as_out_hdr_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_AS_GENERAL,
+	.bTerminalLink = USB_OUT_IT_ID,
+	.bmControls = 0,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+	.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
+	.iChannelNames = 0,
+};
+
+/* Audio USB_OUT Format */
+struct uac2_format_type_i_descriptor as_out_fmt1_desc = {
+	.bLength = sizeof as_out_fmt1_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype = UAC_FORMAT_TYPE,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+};
+
+/* STD AS ISO OUT Endpoint */
+struct usb_endpoint_descriptor fs_epout_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bEndpointAddress = USB_DIR_OUT,
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	.bInterval = 1,
+};
+
+struct usb_endpoint_descriptor hs_epout_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	.bInterval = 4,
+};
+
+/* CS AS ISO OUT Endpoint */
+static struct uac2_iso_endpoint_descriptor as_iso_out_desc = {
+	.bLength = sizeof as_iso_out_desc,
+	.bDescriptorType = USB_DT_CS_ENDPOINT,
+
+	.bDescriptorSubtype = UAC_EP_GENERAL,
+	.bmAttributes = 0,
+	.bmControls = 0,
+	.bLockDelayUnits = 0,
+	.wLockDelay = 0,
+};
+
+/* Audio Streaming IN Interface - Alt0 */
+static struct usb_interface_descriptor std_as_in_if0_desc = {
+	.bLength = sizeof std_as_in_if0_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Streaming IN Interface - Alt1 */
+static struct usb_interface_descriptor std_as_in_if1_desc = {
+	.bLength = sizeof std_as_in_if1_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 1,
+	.bNumEndpoints = 1,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Stream IN Intface Desc */
+struct uac2_as_header_descriptor as_in_hdr_desc = {
+	.bLength = sizeof as_in_hdr_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_AS_GENERAL,
+	.bTerminalLink = USB_IN_OT_ID,
+	.bmControls = 0,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+	.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
+	.iChannelNames = 0,
+};
+
+/* Audio USB_IN Format */
+struct uac2_format_type_i_descriptor as_in_fmt1_desc = {
+	.bLength = sizeof as_in_fmt1_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype = UAC_FORMAT_TYPE,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+};
+
+/* STD AS ISO IN Endpoint */
+struct usb_endpoint_descriptor fs_epin_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bEndpointAddress = USB_DIR_IN,
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	.bInterval = 1,
+};
+
+struct usb_endpoint_descriptor hs_epin_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	.bInterval = 4,
+};
+
+/* CS AS ISO IN Endpoint */
+static struct uac2_iso_endpoint_descriptor as_iso_in_desc = {
+	.bLength = sizeof as_iso_in_desc,
+	.bDescriptorType = USB_DT_CS_ENDPOINT,
+
+	.bDescriptorSubtype = UAC_EP_GENERAL,
+	.bmAttributes = 0,
+	.bmControls = 0,
+	.bLockDelayUnits = 0,
+	.wLockDelay = 0,
+};
+
+static struct usb_descriptor_header *fs_audio_desc[] = {
+	(struct usb_descriptor_header *)&iad_desc,
+	(struct usb_descriptor_header *)&std_ac_if_desc,
+
+	(struct usb_descriptor_header *)&ac_hdr_desc,
+	(struct usb_descriptor_header *)&in_clk_src_desc,
+	(struct usb_descriptor_header *)&out_clk_src_desc,
+	(struct usb_descriptor_header *)&usb_out_it_desc,
+	(struct usb_descriptor_header *)&io_in_it_desc,
+	(struct usb_descriptor_header *)&usb_in_ot_desc,
+	(struct usb_descriptor_header *)&io_out_ot_desc,
+
+	(struct usb_descriptor_header *)&std_as_out_if0_desc,
+	(struct usb_descriptor_header *)&std_as_out_if1_desc,
+
+	(struct usb_descriptor_header *)&as_out_hdr_desc,
+	(struct usb_descriptor_header *)&as_out_fmt1_desc,
+	(struct usb_descriptor_header *)&fs_epout_desc,
+	(struct usb_descriptor_header *)&as_iso_out_desc,
+
+	(struct usb_descriptor_header *)&std_as_in_if0_desc,
+	(struct usb_descriptor_header *)&std_as_in_if1_desc,
+
+	(struct usb_descriptor_header *)&as_in_hdr_desc,
+	(struct usb_descriptor_header *)&as_in_fmt1_desc,
+	(struct usb_descriptor_header *)&fs_epin_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *hs_audio_desc[] = {
+	(struct usb_descriptor_header *)&iad_desc,
+	(struct usb_descriptor_header *)&std_ac_if_desc,
+
+	(struct usb_descriptor_header *)&ac_hdr_desc,
+	(struct usb_descriptor_header *)&in_clk_src_desc,
+	(struct usb_descriptor_header *)&out_clk_src_desc,
+	(struct usb_descriptor_header *)&usb_out_it_desc,
+	(struct usb_descriptor_header *)&io_in_it_desc,
+	(struct usb_descriptor_header *)&usb_in_ot_desc,
+	(struct usb_descriptor_header *)&io_out_ot_desc,
+
+	(struct usb_descriptor_header *)&std_as_out_if0_desc,
+	(struct usb_descriptor_header *)&std_as_out_if1_desc,
+
+	(struct usb_descriptor_header *)&as_out_hdr_desc,
+	(struct usb_descriptor_header *)&as_out_fmt1_desc,
+	(struct usb_descriptor_header *)&hs_epout_desc,
+	(struct usb_descriptor_header *)&as_iso_out_desc,
+
+	(struct usb_descriptor_header *)&std_as_in_if0_desc,
+	(struct usb_descriptor_header *)&std_as_in_if1_desc,
+
+	(struct usb_descriptor_header *)&as_in_hdr_desc,
+	(struct usb_descriptor_header *)&as_in_fmt1_desc,
+	(struct usb_descriptor_header *)&hs_epin_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+struct cntrl_cur_lay3 {
+	__u32	dCUR;
+};
+
+struct cntrl_range_lay3 {
+	__u16	wNumSubRanges;
+	__u32	dMIN;
+	__u32	dMAX;
+	__u32	dRES;
+} __packed;
+
+static inline void
+free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep)
+{
+	struct snd_uac2_chip *uac2 = prm_to_uac2(prm);
+	int i;
+
+	prm->ep_enabled = false;
+
+	for (i = 0; i < USB_XFERS; i++) {
+		if (prm->ureq[i].req) {
+			usb_ep_dequeue(ep, prm->ureq[i].req);
+			usb_ep_free_request(ep, prm->ureq[i].req);
+			prm->ureq[i].req = NULL;
+		}
+	}
+
+	if (usb_ep_disable(ep))
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+}
+
+static int __init
+afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
+{
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	struct usb_composite_dev *cdev = cfg->cdev;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct uac2_rtd_params *prm;
+	int ret;
+
+	ret = usb_interface_id(cfg, fn);
+	if (ret < 0) {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+	std_ac_if_desc.bInterfaceNumber = ret;
+	ALT_SET(agdev->ac_alt, 0);
+	INTF_SET(agdev->ac_alt, ret);
+
+	ret = usb_interface_id(cfg, fn);
+	if (ret < 0) {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+	std_as_out_if0_desc.bInterfaceNumber = ret;
+	std_as_out_if1_desc.bInterfaceNumber = ret;
+	ALT_SET(agdev->as_out_alt, 0);
+	INTF_SET(agdev->as_out_alt, ret);
+
+	ret = usb_interface_id(cfg, fn);
+	if (ret < 0) {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+	std_as_in_if0_desc.bInterfaceNumber = ret;
+	std_as_in_if1_desc.bInterfaceNumber = ret;
+	ALT_SET(agdev->as_in_alt, 0);
+	INTF_SET(agdev->as_in_alt, ret);
+
+	agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
+	if (!agdev->out_ep)
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+	agdev->out_ep->driver_data = agdev;
+
+	agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
+	if (!agdev->in_ep)
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+	agdev->in_ep->driver_data = agdev;
+
+	hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
+	hs_epout_desc.wMaxPacketSize = fs_epout_desc.wMaxPacketSize;
+	hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
+	hs_epin_desc.wMaxPacketSize = fs_epin_desc.wMaxPacketSize;
+
+	fn->descriptors = usb_copy_descriptors(fs_audio_desc);
+	if (gadget_is_dualspeed(gadget))
+		fn->hs_descriptors = usb_copy_descriptors(hs_audio_desc);
+
+	prm = &agdev->uac2.c_prm;
+	prm->max_psize = hs_epout_desc.wMaxPacketSize;
+	prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL);
+	if (!prm->rbuf) {
+		prm->max_psize = 0;
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+	}
+
+	prm = &agdev->uac2.p_prm;
+	prm->max_psize = hs_epin_desc.wMaxPacketSize;
+	prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL);
+	if (!prm->rbuf) {
+		prm->max_psize = 0;
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+	}
+
+	return alsa_uac2_init(agdev);
+}
+
+static void
+afunc_unbind(struct usb_configuration *cfg, struct usb_function *fn)
+{
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct usb_composite_dev *cdev = cfg->cdev;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct uac2_rtd_params *prm;
+
+	alsa_uac2_exit(agdev);
+
+	prm = &agdev->uac2.p_prm;
+	kfree(prm->rbuf);
+
+	prm = &agdev->uac2.c_prm;
+	kfree(prm->rbuf);
+
+	if (gadget_is_dualspeed(gadget))
+		usb_free_descriptors(fn->hs_descriptors);
+	usb_free_descriptors(fn->descriptors);
+
+	if (agdev->in_ep)
+		agdev->in_ep->driver_data = NULL;
+	if (agdev->out_ep)
+		agdev->out_ep->driver_data = NULL;
+}
+
+static int
+afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
+{
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct usb_request *req;
+	struct usb_ep *ep;
+	struct uac2_rtd_params *prm;
+	int i;
+
+	/* No i/f has more than 2 alt settings */
+	if (alt > 1) {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	if (intf == INTF_GET(agdev->ac_alt)) {
+		/* Control I/f has only 1 AltSetting - 0 */
+		if (alt) {
+			dev_err(&uac2->pdev.dev,
+				"%s:%d Error!\n", __func__, __LINE__);
+			return -EINVAL;
+		}
+		return 0;
+	}
+
+	if (intf == INTF_GET(agdev->as_out_alt)) {
+		ep = agdev->out_ep;
+		prm = &uac2->c_prm;
+		config_ep_by_speed(gadget, fn, ep);
+		ALT_SET(agdev->as_out_alt, alt);
+	} else if (intf == INTF_GET(agdev->as_in_alt)) {
+		ep = agdev->in_ep;
+		prm = &uac2->p_prm;
+		config_ep_by_speed(gadget, fn, ep);
+		ALT_SET(agdev->as_in_alt, alt);
+	} else {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	if (alt == 0) {
+		free_ep(prm, ep);
+		return 0;
+	}
+
+	prm->ep_enabled = true;
+	usb_ep_enable(ep);
+
+	for (i = 0; i < USB_XFERS; i++) {
+		if (prm->ureq[i].req) {
+			if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
+				dev_err(&uac2->pdev.dev, "%d Error!\n",
+					__LINE__);
+			continue;
+		}
+
+		req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+		if (req == NULL) {
+			dev_err(&uac2->pdev.dev,
+				"%s:%d Error!\n", __func__, __LINE__);
+			return -EINVAL;
+		}
+
+		prm->ureq[i].req = req;
+		prm->ureq[i].pp = prm;
+
+		req->zero = 0;
+		req->dma = DMA_ADDR_INVALID;
+		req->context = &prm->ureq[i];
+		req->length = prm->max_psize;
+		req->complete =	agdev_iso_complete;
+		req->buf = prm->rbuf + i * req->length;
+
+		if (usb_ep_queue(ep, req, GFP_ATOMIC))
+			dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
+	}
+
+	return 0;
+}
+
+static int
+afunc_get_alt(struct usb_function *fn, unsigned intf)
+{
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+
+	if (intf == INTF_GET(agdev->ac_alt))
+		return ALT_GET(agdev->ac_alt);
+	else if (intf == INTF_GET(agdev->as_out_alt))
+		return ALT_GET(agdev->as_out_alt);
+	else if (intf == INTF_GET(agdev->as_in_alt))
+		return ALT_GET(agdev->as_in_alt);
+	else
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Invalid Interface %d!\n",
+			__func__, __LINE__, intf);
+
+	return -EINVAL;
+}
+
+static void
+afunc_disable(struct usb_function *fn)
+{
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+
+	free_ep(&uac2->p_prm, agdev->in_ep);
+	ALT_SET(agdev->as_in_alt, 0);
+
+	free_ep(&uac2->c_prm, agdev->out_ep);
+	ALT_SET(agdev->as_out_alt, 0);
+}
+
+static int
+in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct usb_request *req = fn->config->cdev->req;
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	u16 w_length = le16_to_cpu(cr->wLength);
+	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;
+	int value = -EOPNOTSUPP;
+
+	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+		struct cntrl_cur_lay3 c;
+
+		if (entity_id == USB_IN_CLK_ID)
+			c.dCUR = p_srate;
+		else if (entity_id == USB_OUT_CLK_ID)
+			c.dCUR = c_srate;
+
+		value = min_t(unsigned, w_length, sizeof c);
+		memcpy(req->buf, &c, value);
+	} else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) {
+		*(u8 *)req->buf = 1;
+		value = min(w_length, (u16) 1);
+	} else {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d control_selector=%d TODO!\n",
+			__func__, __LINE__, control_selector);
+	}
+
+	return value;
+}
+
+static int
+in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct usb_request *req = fn->config->cdev->req;
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	u16 w_length = le16_to_cpu(cr->wLength);
+	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;
+	struct cntrl_range_lay3 r;
+	int value = -EOPNOTSUPP;
+
+	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+		if (entity_id == USB_IN_CLK_ID)
+			r.dMIN = p_srate;
+		else if (entity_id == USB_OUT_CLK_ID)
+			r.dMIN = c_srate;
+		else
+			return -EOPNOTSUPP;
+
+		r.dMAX = r.dMIN;
+		r.dRES = 0;
+		r.wNumSubRanges = 1;
+
+		value = min_t(unsigned, w_length, sizeof r);
+		memcpy(req->buf, &r, value);
+	} else {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d control_selector=%d TODO!\n",
+			__func__, __LINE__, control_selector);
+	}
+
+	return value;
+}
+
+static int
+ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	if (cr->bRequest == UAC2_CS_CUR)
+		return in_rq_cur(fn, cr);
+	else if (cr->bRequest == UAC2_CS_RANGE)
+		return in_rq_range(fn, cr);
+	else
+		return -EOPNOTSUPP;
+}
+
+static int
+out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	u16 w_length = le16_to_cpu(cr->wLength);
+	u16 w_value = le16_to_cpu(cr->wValue);
+	u8 control_selector = w_value >> 8;
+
+	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
+		return w_length;
+
+	return -EOPNOTSUPP;
+}
+
+static int
+setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	u16 w_index = le16_to_cpu(cr->wIndex);
+	u8 intf = w_index & 0xff;
+
+	if (intf != INTF_GET(agdev->ac_alt)) {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return -EOPNOTSUPP;
+	}
+
+	if (cr->bRequestType & USB_DIR_IN)
+		return ac_rq_in(fn, cr);
+	else if (cr->bRequest == UAC2_CS_CUR)
+		return out_rq_cur(fn, cr);
+
+	return -EOPNOTSUPP;
+}
+
+static int
+afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	struct usb_request *req = cdev->req;
+	u16 w_length = le16_to_cpu(cr->wLength);
+	int value = -EOPNOTSUPP;
+
+	/* Only Class specific requests are supposed to reach here */
+	if ((cr->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS)
+		return -EOPNOTSUPP;
+
+	if ((cr->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE)
+		value = setup_rq_inf(fn, cr);
+	else
+		dev_err(&uac2->pdev.dev, "%s:%d Error!\n", __func__, __LINE__);
+
+	if (value >= 0) {
+		req->length = value;
+		req->zero = value < w_length;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0) {
+			dev_err(&uac2->pdev.dev,
+				"%s:%d Error!\n", __func__, __LINE__);
+			req->status = 0;
+		}
+	}
+
+	return value;
+}
+
+static int
+uac2_bind_config(struct usb_configuration *cfg)
+{
+	int id, res;
+
+	agdev_g = kzalloc(sizeof *agdev_g, GFP_KERNEL);
+	if (agdev_g == NULL) {
+		printk(KERN_ERR "Unable to allocate audio gadget\n");
+		return -ENOMEM;
+	}
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_ASSOC].id = id;
+	iad_desc.iFunction = id,
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_IF_CTRL].id = id;
+	std_ac_if_desc.iInterface = id,
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_CLKSRC_IN].id = id;
+	in_clk_src_desc.iClockSource = id,
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_CLKSRC_OUT].id = id;
+	out_clk_src_desc.iClockSource = id,
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_USB_IT].id = id;
+	usb_out_it_desc.iTerminal = id,
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_IO_IT].id = id;
+	io_in_it_desc.iTerminal = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_USB_OT].id = id;
+	usb_in_ot_desc.iTerminal = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_IO_OT].id = id;
+	io_out_ot_desc.iTerminal = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_AS_OUT_ALT0].id = id;
+	std_as_out_if0_desc.iInterface = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_AS_OUT_ALT1].id = id;
+	std_as_out_if1_desc.iInterface = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_AS_IN_ALT0].id = id;
+	std_as_in_if0_desc.iInterface = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_AS_IN_ALT1].id = id;
+	std_as_in_if1_desc.iInterface = id;
+
+	agdev_g->func.name = "uac2_func";
+	agdev_g->func.strings = fn_strings;
+	agdev_g->func.bind = afunc_bind;
+	agdev_g->func.unbind = afunc_unbind;
+	agdev_g->func.set_alt = afunc_set_alt;
+	agdev_g->func.get_alt = afunc_get_alt;
+	agdev_g->func.disable = afunc_disable;
+	agdev_g->func.setup = afunc_setup;
+
+	/* Initialize the configurable parameters */
+	usb_out_it_desc.bNrChannels = num_channels(c_chmask);
+	usb_out_it_desc.bmChannelConfig = cpu_to_le32(c_chmask);
+	io_in_it_desc.bNrChannels = num_channels(p_chmask);
+	io_in_it_desc.bmChannelConfig = cpu_to_le32(p_chmask);
+	as_out_hdr_desc.bNrChannels = num_channels(c_chmask);
+	as_out_hdr_desc.bmChannelConfig = cpu_to_le32(c_chmask);
+	as_in_hdr_desc.bNrChannels = num_channels(p_chmask);
+	as_in_hdr_desc.bmChannelConfig = cpu_to_le32(p_chmask);
+	as_out_fmt1_desc.bSubslotSize = c_ssize;
+	as_out_fmt1_desc.bBitResolution = c_ssize * 8;
+	as_in_fmt1_desc.bSubslotSize = p_ssize;
+	as_in_fmt1_desc.bBitResolution = p_ssize * 8;
+
+	snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", p_srate);
+	snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", c_srate);
+
+	res = usb_add_function(cfg, &agdev_g->func);
+	if (res < 0)
+		kfree(agdev_g);
+
+	return res;
+}
+
+static void
+uac2_unbind_config(struct usb_configuration *cfg)
+{
+	kfree(agdev_g);
+	agdev_g = NULL;
+}
-- 
1.7.4.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
       [not found] ` <1313752934-20081-1-git-send-email-jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
@ 2011-08-22 11:30   ` Felipe Balbi
       [not found]     ` <20110822113002.GD30398-UiBtZHVXSwEVvW8u9ZQWYwjfymiNCTlR@public.gmane.org>
  2011-08-24  2:00   ` [PATCH 2/2] " Chen Peter-B29397
  1 sibling, 1 reply; 17+ messages in thread
From: Felipe Balbi @ 2011-08-22 11:30 UTC (permalink / raw)
  To: Jassi Brar
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA, balbi-l0cyMroinI0,
	gregkh-l3A5Bk7waGM, alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw,
	zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w, Jassi Brar

[-- Attachment #1: Type: text/plain, Size: 5476 bytes --]

Hi,

On Fri, Aug 19, 2011 at 04:52:14PM +0530, Jassi Brar wrote:
> This is a flexible USB Audio Class 2.0 compliant gadget driver that
> implements a simple topology with a virtual sound card exposed at
> the function side.
> 
> The driver doesn't expect any real audio codec to be present on the
> function - the audio streams are simply sinked to and sourced from a
> virtual ALSA sound card created. The user-space application may choose
> to do whatever it wants with the data received from the USB Host and
> choose to provide whatever it wants as audio data to the USB Host.
> 
> Capture(USB-Out) and Playback(USB-In) can be run at independent
> configurations specified via module parameters while loading the driver.
> 
> Signed-off-by: Yadi Brar <yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> Signed-off-by: Jassi Brar <jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> 
> ===========
> How To Test
> ===========
>   USB_DEV$ insmod g_audio2.ko c_srate=64000 p_srate=48000 ; say
>     gadget: high speed config #1: UAC2
> 
>   USB_DEV$ aplay -l
> **** List of PLAYBACK Hardware Devices ****
> card 0: uac2 [uac2], device 0: UAC2 PCM [UAC2 PCM]
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>   USB_DEV$ arecord -l
> **** List of CAPTURE Hardware Devices ****
> card 0: uac2 [uac2], device 0: UAC2 PCM [UAC2 PCM]
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>   USB_HOST$ aplay -l
> **** List of PLAYBACK Hardware Devices ****
> card 0: PCH [HDA Intel PCH], device 0: ALC665 Analog [ALC665 Analog]
>   Subdevices: 0/1
>   Subdevice #0: subdevice #0
> card 1: Gadget [UAC2 Gadget], device 0: USB Audio [USB Audio]  <<<<<<
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>   USB_HOST$ arecord -l
> **** List of CAPTURE Hardware Devices ****
> card 0: PCH [HDA Intel PCH], device 0: ALC665 Analog [ALC665 Analog]
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> card 1: Gadget [UAC2 Gadget], device 0: USB Audio [USB Audio] <<<<<<<
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>  After this confirmation, you should be able to use it just like another
> sound card in your system. That is, a new sound card should appear in the
> sound preferences which you can select to route your default audio to and
> from.
> 
> ============
> Test Results
> ============
>  The driver has been tested for Half as well as Full Duplex at
> Stereo, S16_LE at 44.1KHz, 48KHz and 64KHz. It should also
> work for other configurations as well, except for different
> sample-size(TODO).
> 
> Obviously, much depends upon the underlying UDC driver.
> 
> While testing with OMAP's implementation of Inventra HDRC, I observed
> some noise for configurations for which the USB-OUT packet size chosen
> by the Host is not a power of 2. USB-IN works just fine.
> For ex, on both Beagle/Panda board, 48KHz USB-OUT has some noise while
> 64KHz is smooth. Also, with full duplex, it occasioanally shows noise.
> 
> Taking benefit of doubt, I assume this driver has unmasked some subtle
> bug in the underlying UDC's ISOCH handling code ;)
> Please feel free to find issue with this driver by testing it over
> some other UDC.
> 
>  drivers/usb/gadget/Kconfig    |   20 +
>  drivers/usb/gadget/Makefile   |    2 +
>  drivers/usb/gadget/audio2.c   |  155 +++++
>  drivers/usb/gadget/f_audio2.c | 1448 +++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 1625 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/usb/gadget/audio2.c
>  create mode 100644 drivers/usb/gadget/f_audio2.c
> 
> diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
> index e35dfef..95c65d8 100644
> --- a/drivers/usb/gadget/Kconfig
> +++ b/drivers/usb/gadget/Kconfig
> @@ -599,6 +599,26 @@ config USB_ZERO_HNPTEST
>  	  the "B-Peripheral" role, that device will use HNP to let this
>  	  one serve as the USB host instead (in the "B-Host" role).
>  
> +config USB_AUDIO_2
> +	tristate "Audio Gadget Class 2.0 (EXPERIMENTAL)"
> +	depends on SND && EXPERIMENTAL
> +	select SND_PCM
> +	help
> +	  This Gadget Audio driver is compatible with USB Audio Class
> +	  specification 2.0. It implements 1 AudioControl interface,
> +	  1 AudioStreaming Interface each for USB-OUT and USB-IN.
> +	  Number of channels, sample rate and sample size can be
> +	  specified as module parameters.
> +	  This driver doesn't expect any real Audio codec to be present
> +	  on the device - the audio streams are simply sinked to and
> +	  sourced from a virtual ALSA sound card created. The user-space
> +	  application may choose to do whatever it wants with the data
> +	  received from the USB Host and choose to provide whatever it
> +	  wants as audio data to the USB Host.
> +
> +	  Say "y" to link the driver statically, or "m" to build a
> +	  dynamically linked module called "g_audio2".
> +
>  config USB_AUDIO
>  	tristate "Audio Gadget (EXPERIMENTAL)"
>  	depends on SND
> diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
> index 7409338..fa8472e 100644
> --- a/drivers/usb/gadget/Makefile
> +++ b/drivers/usb/gadget/Makefile
> @@ -40,6 +40,7 @@ obj-$(CONFIG_USB_COMPOSITE)	+= usb-composite.o
>  #
>  g_zero-y			:= zero.o
>  g_audio-y			:= audio.o
> +g_audio2-y			:= audio2.o

instead of adding a new gadget driver, can't you just re-use g_audio and
add what's missing ?

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 490 bytes --]

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

* Re: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
       [not found]     ` <20110822113002.GD30398-UiBtZHVXSwEVvW8u9ZQWYwjfymiNCTlR@public.gmane.org>
@ 2011-08-22 12:37       ` Jassi Brar
       [not found]         ` <CAJe_Zhdb7d23_x=1GWV2EnEjdn69XBKEz+hCeCuBx4v9XAmykg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 17+ messages in thread
From: Jassi Brar @ 2011-08-22 12:37 UTC (permalink / raw)
  To: balbi-l0cyMroinI0
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA, gregkh-l3A5Bk7waGM,
	alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw, zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w, Jassi Brar

On 22 August 2011 17:00, Felipe Balbi <balbi-l0cyMroinI0@public.gmane.org> wrote:
> Hi,
>
> On Fri, Aug 19, 2011 at 04:52:14PM +0530, Jassi Brar wrote:
>> This is a flexible USB Audio Class 2.0 compliant gadget driver that
>> implements a simple topology with a virtual sound card exposed at
>> the function side.
>>
>> The driver doesn't expect any real audio codec to be present on the
>> function - the audio streams are simply sinked to and sourced from a
>> virtual ALSA sound card created. The user-space application may choose
>> to do whatever it wants with the data received from the USB Host and
>> choose to provide whatever it wants as audio data to the USB Host.
>>
>> Capture(USB-Out) and Playback(USB-In) can be run at independent
>> configurations specified via module parameters while loading the driver.
>>
>> Signed-off-by: Yadi Brar <yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> Signed-off-by: Jassi Brar <jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> ---
>>
>> ===========
>> How To Test
>> ===========
>>   USB_DEV$ insmod g_audio2.ko c_srate=64000 p_srate=48000 ; say
>>     gadget: high speed config #1: UAC2
>>
>>   USB_DEV$ aplay -l
>> **** List of PLAYBACK Hardware Devices ****
>> card 0: uac2 [uac2], device 0: UAC2 PCM [UAC2 PCM]
>>   Subdevices: 1/1
>>   Subdevice #0: subdevice #0
>>
>>   USB_DEV$ arecord -l
>> **** List of CAPTURE Hardware Devices ****
>> card 0: uac2 [uac2], device 0: UAC2 PCM [UAC2 PCM]
>>   Subdevices: 1/1
>>   Subdevice #0: subdevice #0
>>
>>   USB_HOST$ aplay -l
>> **** List of PLAYBACK Hardware Devices ****
>> card 0: PCH [HDA Intel PCH], device 0: ALC665 Analog [ALC665 Analog]
>>   Subdevices: 0/1
>>   Subdevice #0: subdevice #0
>> card 1: Gadget [UAC2 Gadget], device 0: USB Audio [USB Audio]  <<<<<<
>>   Subdevices: 1/1
>>   Subdevice #0: subdevice #0
>>
>>   USB_HOST$ arecord -l
>> **** List of CAPTURE Hardware Devices ****
>> card 0: PCH [HDA Intel PCH], device 0: ALC665 Analog [ALC665 Analog]
>>   Subdevices: 1/1
>>   Subdevice #0: subdevice #0
>> card 1: Gadget [UAC2 Gadget], device 0: USB Audio [USB Audio] <<<<<<<
>>   Subdevices: 1/1
>>   Subdevice #0: subdevice #0
>>
>>  After this confirmation, you should be able to use it just like another
>> sound card in your system. That is, a new sound card should appear in the
>> sound preferences which you can select to route your default audio to and
>> from.
>>
>> ============
>> Test Results
>> ============
>>  The driver has been tested for Half as well as Full Duplex at
>> Stereo, S16_LE at 44.1KHz, 48KHz and 64KHz. It should also
>> work for other configurations as well, except for different
>> sample-size(TODO).
>>
>> Obviously, much depends upon the underlying UDC driver.
>>
>> While testing with OMAP's implementation of Inventra HDRC, I observed
>> some noise for configurations for which the USB-OUT packet size chosen
>> by the Host is not a power of 2. USB-IN works just fine.
>> For ex, on both Beagle/Panda board, 48KHz USB-OUT has some noise while
>> 64KHz is smooth. Also, with full duplex, it occasioanally shows noise.
>>
>> Taking benefit of doubt, I assume this driver has unmasked some subtle
>> bug in the underlying UDC's ISOCH handling code ;)
>> Please feel free to find issue with this driver by testing it over
>> some other UDC.
>>
>>  drivers/usb/gadget/Kconfig    |   20 +
>>  drivers/usb/gadget/Makefile   |    2 +
>>  drivers/usb/gadget/audio2.c   |  155 +++++
>>  drivers/usb/gadget/f_audio2.c | 1448 +++++++++++++++++++++++++++++++++++++++++
>>  4 files changed, 1625 insertions(+), 0 deletions(-)
>>  create mode 100644 drivers/usb/gadget/audio2.c
>>  create mode 100644 drivers/usb/gadget/f_audio2.c
>>
>> diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
>> index e35dfef..95c65d8 100644
>> --- a/drivers/usb/gadget/Kconfig
>> +++ b/drivers/usb/gadget/Kconfig
>> @@ -599,6 +599,26 @@ config USB_ZERO_HNPTEST
>>         the "B-Peripheral" role, that device will use HNP to let this
>>         one serve as the USB host instead (in the "B-Host" role).
>>
>> +config USB_AUDIO_2
>> +     tristate "Audio Gadget Class 2.0 (EXPERIMENTAL)"
>> +     depends on SND && EXPERIMENTAL
>> +     select SND_PCM
>> +     help
>> +       This Gadget Audio driver is compatible with USB Audio Class
>> +       specification 2.0. It implements 1 AudioControl interface,
>> +       1 AudioStreaming Interface each for USB-OUT and USB-IN.
>> +       Number of channels, sample rate and sample size can be
>> +       specified as module parameters.
>> +       This driver doesn't expect any real Audio codec to be present
>> +       on the device - the audio streams are simply sinked to and
>> +       sourced from a virtual ALSA sound card created. The user-space
>> +       application may choose to do whatever it wants with the data
>> +       received from the USB Host and choose to provide whatever it
>> +       wants as audio data to the USB Host.
>> +
>> +       Say "y" to link the driver statically, or "m" to build a
>> +       dynamically linked module called "g_audio2".
>> +
>>  config USB_AUDIO
>>       tristate "Audio Gadget (EXPERIMENTAL)"
>>       depends on SND
>> diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
>> index 7409338..fa8472e 100644
>> --- a/drivers/usb/gadget/Makefile
>> +++ b/drivers/usb/gadget/Makefile
>> @@ -40,6 +40,7 @@ obj-$(CONFIG_USB_COMPOSITE) += usb-composite.o
>>  #
>>  g_zero-y                     := zero.o
>>  g_audio-y                    := audio.o
>> +g_audio2-y                   := audio2.o
>
> instead of adding a new gadget driver, can't you just re-use g_audio and
> add what's missing ?
>
Hi Felipe,

Functionality wise, audio.c hardcodes the data-path to the physical
audio-codec onboard
whereas audio2.c provides user-space with a virtual sound-card to fool around
with audio-data the way the user wants. The functionality that has
lion's share of code.
Also, although the functionality of audio.c can be achieved by audio2.c (but not
vice-versa) we'll break user-space by changing the behavior of audio.c
to audio2.c

Besides fundamental differences in functionality, they differ by
Usb-Audio-Class
compliance as well.  audio.c is UAC_1 compliant, whereas audio2.c is UAC_2
complaint.
One can not replace the other. For ex, MS-Windows doesn't support
UAC_2 by default
yet, Linux does reasonably well. And by definition having
Linux-USB-Gadget support
UAC_2 also, is only better. So by having both we cover more ground than by
having just one.

Thanks
-jassi
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
       [not found]         ` <CAJe_Zhdb7d23_x=1GWV2EnEjdn69XBKEz+hCeCuBx4v9XAmykg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2011-08-22 12:44           ` Felipe Balbi
  2011-08-22 13:00             ` Jassi Brar
  0 siblings, 1 reply; 17+ messages in thread
From: Felipe Balbi @ 2011-08-22 12:44 UTC (permalink / raw)
  To: Jassi Brar
  Cc: balbi-l0cyMroinI0, linux-usb-u79uwXL29TY76Z2rM5mHXA,
	gregkh-l3A5Bk7waGM, alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw,
	zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w, Jassi Brar

[-- Attachment #1: Type: text/plain, Size: 1710 bytes --]

Hi,

On Mon, Aug 22, 2011 at 06:07:46PM +0530, Jassi Brar wrote:

[big snip]

> Hi Felipe,

Hi

> Functionality wise, audio.c hardcodes the data-path to the physical
> audio-codec onboard
> whereas audio2.c provides user-space with a virtual sound-card to fool around
> with audio-data the way the user wants. The functionality that has
> lion's share of code.
> Also, although the functionality of audio.c can be achieved by audio2.c (but not
> vice-versa) we'll break user-space by changing the behavior of audio.c
> to audio2.c
> 
> Besides fundamental differences in functionality, they differ by
> Usb-Audio-Class
> compliance as well.  audio.c is UAC_1 compliant, whereas audio2.c is UAC_2
> complaint.
> One can not replace the other. For ex, MS-Windows doesn't support
> UAC_2 by default
> yet, Linux does reasonably well. And by definition having
> Linux-USB-Gadget support
> UAC_2 also, is only better. So by having both we cover more ground than by
> having just one.

I understand your point, but then we will have two gadget drivers (and
two function drivers) for the same thing (USB Audio), it's the same
thing with the storage gadgets and we have decided on dropping one of
them recently, so I'm not keen on accepting another gadget driver which
will essentially do the same thing.

If audio2.c can achieve audio.c's functionality, then you can start by
re-factoring audio.c so that it come closer to what audio2.c is today.

WRT UAC_1/UAC_2 compliance, you can use a Kconfig entry, or two separate
USB configurations (one with UAC_1 and another UAC_2). So there are ways
to make this all work while still keeping backwards compatibility.

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 490 bytes --]

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

* Re: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
  2011-08-22 12:44           ` Felipe Balbi
@ 2011-08-22 13:00             ` Jassi Brar
       [not found]               ` <CABb+yY3QxvHf38-qfD5ig77nJK=0eVLiSPVpbTh_DCfHNGRyGQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 17+ messages in thread
From: Jassi Brar @ 2011-08-22 13:00 UTC (permalink / raw)
  To: balbi; +Cc: alsa-devel, gregkh, yadi.brar01, linux-usb, zonque, Jassi Brar

On Mon, Aug 22, 2011 at 6:14 PM, Felipe Balbi <balbi@ti.com> wrote:
>> Functionality wise, audio.c hardcodes the data-path to the physical
>> audio-codec onboard
>> whereas audio2.c provides user-space with a virtual sound-card to fool around
>> with audio-data the way the user wants. The functionality that has
>> lion's share of code.
>> Also, although the functionality of audio.c can be achieved by audio2.c (but not
>> vice-versa) we'll break user-space by changing the behavior of audio.c
>> to audio2.c
>>
>> Besides fundamental differences in functionality, they differ by
>> Usb-Audio-Class
>> compliance as well.  audio.c is UAC_1 compliant, whereas audio2.c is UAC_2
>> complaint.
>> One can not replace the other. For ex, MS-Windows doesn't support
>> UAC_2 by default
>> yet, Linux does reasonably well. And by definition having
>> Linux-USB-Gadget support
>> UAC_2 also, is only better. So by having both we cover more ground than by
>> having just one.
>
> I understand your point, but then we will have two gadget drivers (and
> two function drivers) for the same thing (USB Audio), it's the same
> thing with the storage gadgets and we have decided on dropping one of
> them recently, so I'm not keen on accepting another gadget driver which
> will essentially do the same thing.
No dear, not the same thing.
As I said, they differ by features as well as USB-Spec... only the
'audio' thing is common.

> If audio2.c can achieve audio.c's functionality, then you can start by
> re-factoring audio.c so that it come closer to what audio2.c is today.
audio.c and audio2.c have almost nothing in common. Please have a look
at the code.
Making audio.c closer to audio2.c would mean :-
  * Changing all of the descriptors of audio.c {uac_1 -> uac_2}
  * Removing all alsa related code and adding virtual alsa sound card.
 At the end, audio.c would have been changed completely and Linux-usb-gadget
lost the UAC_1 compliance.

> WRT UAC_1/UAC_2 compliance, you can use a Kconfig entry, or two separate
> USB configurations (one with UAC_1 and another UAC_2). So there are ways
> to make this all work while still keeping backwards compatibility.
IIUC, you mean adding UAC_2 compliance and virtual sound-card i/f to
audio.c, while
preserving the it's original functionality ?
But that would mean having two drivers in one file, because they are
so different.
Otherwise, please have a look at audio.c and audio2.c and suggest what
exactly you mean.

thnx
_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

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

* Re: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
       [not found]               ` <CABb+yY3QxvHf38-qfD5ig77nJK=0eVLiSPVpbTh_DCfHNGRyGQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2011-08-22 13:46                 ` Felipe Balbi
       [not found]                   ` <20110822134627.GP30398-UiBtZHVXSwEVvW8u9ZQWYwjfymiNCTlR@public.gmane.org>
  0 siblings, 1 reply; 17+ messages in thread
From: Felipe Balbi @ 2011-08-22 13:46 UTC (permalink / raw)
  To: Jassi Brar
  Cc: balbi-l0cyMroinI0, Jassi Brar, linux-usb-u79uwXL29TY76Z2rM5mHXA,
	gregkh-l3A5Bk7waGM, alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw,
	zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w

[-- Attachment #1: Type: text/plain, Size: 2336 bytes --]

Hi,

On Mon, Aug 22, 2011 at 06:30:36PM +0530, Jassi Brar wrote:
> > I understand your point, but then we will have two gadget drivers (and
> > two function drivers) for the same thing (USB Audio), it's the same
> > thing with the storage gadgets and we have decided on dropping one of
> > them recently, so I'm not keen on accepting another gadget driver which
> > will essentially do the same thing.
> No dear, not the same thing.
> As I said, they differ by features as well as USB-Spec... only the
> 'audio' thing is common.

and that's what I meant by same thing. How different is UAC_1 from
UAC_2.

> > If audio2.c can achieve audio.c's functionality, then you can start by
> > re-factoring audio.c so that it come closer to what audio2.c is today.
> audio.c and audio2.c have almost nothing in common. Please have a look
> at the code.
> Making audio.c closer to audio2.c would mean :-
>   * Changing all of the descriptors of audio.c {uac_1 -> uac_2}
>   * Removing all alsa related code and adding virtual alsa sound card.
>  At the end, audio.c would have been changed completely and Linux-usb-gadget
> lost the UAC_1 compliance.

it shouldn't loose compliance. If it does it was probably not done
right.

> > WRT UAC_1/UAC_2 compliance, you can use a Kconfig entry, or two separate
> > USB configurations (one with UAC_1 and another UAC_2). So there are ways
> > to make this all work while still keeping backwards compatibility.
> IIUC, you mean adding UAC_2 compliance and virtual sound-card i/f to
> audio.c, while
> preserving the it's original functionality ?

yes.

> But that would mean having two drivers in one file, because they are
> so different.

then make only a differnt function driver, f_uac2.c, which gets added as
another interface (or configuration) to audio.c.

> Otherwise, please have a look at audio.c and audio2.c and suggest what
> exactly you mean.

audio.c doesn't do anything audio related. It's just adding the
configuration to composite layer. The real specification is implemented
in f_audio.c (should probably be called f_uac1.c, though).

Then only thing different between audio.c and audio2.c is bDeviceClass,
bDeviceSubClass and bDeviceProtocol, but that can be easily handled with
Kconfig choice and/or module parameter.

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 490 bytes --]

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

* Re: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
       [not found]                   ` <20110822134627.GP30398-UiBtZHVXSwEVvW8u9ZQWYwjfymiNCTlR@public.gmane.org>
@ 2011-08-22 14:00                     ` Jassi Brar
       [not found]                       ` <CABb+yY2m2MfkFwf+XTdKzsCCeSFz_J2LoGxJricamhNtTcSAcg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 17+ messages in thread
From: Jassi Brar @ 2011-08-22 14:00 UTC (permalink / raw)
  To: balbi-l0cyMroinI0
  Cc: Jassi Brar, linux-usb-u79uwXL29TY76Z2rM5mHXA, gregkh-l3A5Bk7waGM,
	alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw, zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w

On Mon, Aug 22, 2011 at 7:16 PM, Felipe Balbi <balbi-l0cyMroinI0@public.gmane.org> wrote:
>
>> But that would mean having two drivers in one file, because they are
>> so different.
>
> then make only a differnt function driver, f_uac2.c, which gets added as
> another interface (or configuration) to audio.c.
Yup, by audio.c I meant {audio.c + f_audio.c} and audio2.c {audio2.c +
f_audio2.c}


>> Otherwise, please have a look at audio.c and audio2.c and suggest what
>> exactly you mean.
>
> audio.c doesn't do anything audio related. It's just adding the
> configuration to composite layer. The real specification is implemented
> in f_audio.c (should probably be called f_uac1.c, though).
Agreed. Reusing audio.c is quite reasonable.
So, I'll :-
* Merge audio2.c into audio.c
* Rename f_audio.c -> f_uac1.c
* Add new f_audio2.c as f_uac2.c
* Add Kconfig switch to compile UAC_1 (audio.c + f_uac1.c)  or  UAC_2
(audio.c + f_uac2.c)

I'll wait for other comments and revise the patch after a few days.

Thanks.
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
       [not found]                       ` <CABb+yY2m2MfkFwf+XTdKzsCCeSFz_J2LoGxJricamhNtTcSAcg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2011-08-22 14:28                         ` Felipe Balbi
       [not found]                           ` <20110822142805.GT30398-UiBtZHVXSwEVvW8u9ZQWYwjfymiNCTlR@public.gmane.org>
  0 siblings, 1 reply; 17+ messages in thread
From: Felipe Balbi @ 2011-08-22 14:28 UTC (permalink / raw)
  To: Jassi Brar
  Cc: balbi-l0cyMroinI0, Jassi Brar, linux-usb-u79uwXL29TY76Z2rM5mHXA,
	gregkh-l3A5Bk7waGM, alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw,
	zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w

[-- Attachment #1: Type: text/plain, Size: 1189 bytes --]

Hi,

On Mon, Aug 22, 2011 at 07:30:35PM +0530, Jassi Brar wrote:
> On Mon, Aug 22, 2011 at 7:16 PM, Felipe Balbi <balbi-l0cyMroinI0@public.gmane.org> wrote:
> >
> >> But that would mean having two drivers in one file, because they are
> >> so different.
> >
> > then make only a differnt function driver, f_uac2.c, which gets added as
> > another interface (or configuration) to audio.c.
> Yup, by audio.c I meant {audio.c + f_audio.c} and audio2.c {audio2.c +
> f_audio2.c}
> 
> 
> >> Otherwise, please have a look at audio.c and audio2.c and suggest what
> >> exactly you mean.
> >
> > audio.c doesn't do anything audio related. It's just adding the
> > configuration to composite layer. The real specification is implemented
> > in f_audio.c (should probably be called f_uac1.c, though).
> Agreed. Reusing audio.c is quite reasonable.
> So, I'll :-
> * Merge audio2.c into audio.c
> * Rename f_audio.c -> f_uac1.c
> * Add new f_audio2.c as f_uac2.c
> * Add Kconfig switch to compile UAC_1 (audio.c + f_uac1.c)  or  UAC_2
> (audio.c + f_uac2.c)
> 
> I'll wait for other comments and revise the patch after a few days.

ok, sounds reasonable.

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 490 bytes --]

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

* Re: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
       [not found]                           ` <20110822142805.GT30398-UiBtZHVXSwEVvW8u9ZQWYwjfymiNCTlR@public.gmane.org>
@ 2011-08-22 14:29                             ` Felipe Balbi
  2012-02-02 16:19                             ` Jassi Brar
  1 sibling, 0 replies; 17+ messages in thread
From: Felipe Balbi @ 2011-08-22 14:29 UTC (permalink / raw)
  To: Felipe Balbi
  Cc: Jassi Brar, Jassi Brar, linux-usb-u79uwXL29TY76Z2rM5mHXA,
	gregkh-l3A5Bk7waGM, alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw,
	zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w

[-- Attachment #1: Type: text/plain, Size: 691 bytes --]

Hi,

On Mon, Aug 22, 2011 at 05:28:05PM +0300, Felipe Balbi wrote:
> > >> Otherwise, please have a look at audio.c and audio2.c and suggest what
> > >> exactly you mean.
> > >
> > > audio.c doesn't do anything audio related. It's just adding the
> > > configuration to composite layer. The real specification is implemented
> > > in f_audio.c (should probably be called f_uac1.c, though).
> > Agreed. Reusing audio.c is quite reasonable.
> > So, I'll :-
> > * Merge audio2.c into audio.c
> > * Rename f_audio.c -> f_uac1.c

just another tip/request:

when generating your patches add -M -C switches to git format-patch.
This will let git detect the renaming.

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 490 bytes --]

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

* RE: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
       [not found] ` <1313752934-20081-1-git-send-email-jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
  2011-08-22 11:30   ` Felipe Balbi
@ 2011-08-24  2:00   ` Chen Peter-B29397
       [not found]     ` <35AB98346D25394A9354ED89C4D7658717FD0C-TcFNo7jSaXOLgTCmFNXF2K4g8xLGJsHaLnY5E4hWTkheoWH0uzbU5w@public.gmane.org>
  1 sibling, 1 reply; 17+ messages in thread
From: Chen Peter-B29397 @ 2011-08-24  2:00 UTC (permalink / raw)
  To: Jassi Brar, linux-usb-u79uwXL29TY76Z2rM5mHXA, balbi-l0cyMroinI0,
	gregkh-l3A5Bk7waGM
  Cc: alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw, zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w, Jassi Brar

 

> -----Original Message-----
> From: linux-usb-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org [mailto:linux-usb-
> owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org] On Behalf Of Jassi Brar
> Sent: Friday, August 19, 2011 7:22 PM
> To: linux-usb-u79uwXL29TY76Z2rM5mHXA@public.gmane.org; balbi-l0cyMroinI0@public.gmane.org; gregkh-l3A5Bk7waGM@public.gmane.org
> Cc: alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw@public.gmane.org; zonque-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org; yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org;
> Jassi Brar
> Subject: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
> 
> This is a flexible USB Audio Class 2.0 compliant gadget driver that
> implements a simple topology with a virtual sound card exposed at
> the function side.
> 
> The driver doesn't expect any real audio codec to be present on the
> function - the audio streams are simply sinked to and sourced from a
> virtual ALSA sound card created. The user-space application may choose
> to do whatever it wants with the data received from the USB Host and
> choose to provide whatever it wants as audio data to the USB Host.
> 
> Capture(USB-Out) and Playback(USB-In) can be run at independent
> configurations specified via module parameters while loading the driver.
> 

Have you a chance to test at Macbook (10.6.5+) which has USB Audio 2.0 support well?
 
> Signed-off-by: Yadi Brar <yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> Signed-off-by: Jassi Brar <jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> 
> ===========
> How To Test
> ===========
>   USB_DEV$ insmod g_audio2.ko c_srate=64000 p_srate=48000 ; say
>     gadget: high speed config #1: UAC2
> 
>   USB_DEV$ aplay -l
> **** List of PLAYBACK Hardware Devices ****
> card 0: uac2 [uac2], device 0: UAC2 PCM [UAC2 PCM]
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>   USB_DEV$ arecord -l
> **** List of CAPTURE Hardware Devices ****
> card 0: uac2 [uac2], device 0: UAC2 PCM [UAC2 PCM]
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>   USB_HOST$ aplay -l
> **** List of PLAYBACK Hardware Devices ****
> card 0: PCH [HDA Intel PCH], device 0: ALC665 Analog [ALC665 Analog]
>   Subdevices: 0/1
>   Subdevice #0: subdevice #0
> card 1: Gadget [UAC2 Gadget], device 0: USB Audio [USB Audio]  <<<<<<
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>   USB_HOST$ arecord -l
> **** List of CAPTURE Hardware Devices ****
> card 0: PCH [HDA Intel PCH], device 0: ALC665 Analog [ALC665 Analog]
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> card 1: Gadget [UAC2 Gadget], device 0: USB Audio [USB Audio] <<<<<<<
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>  After this confirmation, you should be able to use it just like another
> sound card in your system. That is, a new sound card should appear in the
> sound preferences which you can select to route your default audio to and
> from.
> 
> ============
> Test Results
> ============
>  The driver has been tested for Half as well as Full Duplex at
> Stereo, S16_LE at 44.1KHz, 48KHz and 64KHz. It should also
> work for other configurations as well, except for different
> sample-size(TODO).
> 
> Obviously, much depends upon the underlying UDC driver.
> 
> While testing with OMAP's implementation of Inventra HDRC, I observed
> some noise for configurations for which the USB-OUT packet size chosen
> by the Host is not a power of 2. USB-IN works just fine.
> For ex, on both Beagle/Panda board, 48KHz USB-OUT has some noise while
> 64KHz is smooth. Also, with full duplex, it occasioanally shows noise.
> 
> Taking benefit of doubt, I assume this driver has unmasked some subtle
> bug in the underlying UDC's ISOCH handling code ;)
> Please feel free to find issue with this driver by testing it over
> some other UDC.
> 
>  drivers/usb/gadget/Kconfig    |   20 +
>  drivers/usb/gadget/Makefile   |    2 +
>  drivers/usb/gadget/audio2.c   |  155 +++++
>  drivers/usb/gadget/f_audio2.c | 1448
> +++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 1625 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/usb/gadget/audio2.c
>  create mode 100644 drivers/usb/gadget/f_audio2.c
> 
> diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
> index e35dfef..95c65d8 100644
> --- a/drivers/usb/gadget/Kconfig
> +++ b/drivers/usb/gadget/Kconfig
> @@ -599,6 +599,26 @@ config USB_ZERO_HNPTEST
>  	  the "B-Peripheral" role, that device will use HNP to let this
>  	  one serve as the USB host instead (in the "B-Host" role).
> 
> +config USB_AUDIO_2
> +	tristate "Audio Gadget Class 2.0 (EXPERIMENTAL)"
> +	depends on SND && EXPERIMENTAL
> +	select SND_PCM
> +	help
> +	  This Gadget Audio driver is compatible with USB Audio Class
> +	  specification 2.0. It implements 1 AudioControl interface,
> +	  1 AudioStreaming Interface each for USB-OUT and USB-IN.
> +	  Number of channels, sample rate and sample size can be
> +	  specified as module parameters.
> +	  This driver doesn't expect any real Audio codec to be present
> +	  on the device - the audio streams are simply sinked to and
> +	  sourced from a virtual ALSA sound card created. The user-space
> +	  application may choose to do whatever it wants with the data
> +	  received from the USB Host and choose to provide whatever it
> +	  wants as audio data to the USB Host.
> +
> +	  Say "y" to link the driver statically, or "m" to build a
> +	  dynamically linked module called "g_audio2".
> +
>  config USB_AUDIO
>  	tristate "Audio Gadget (EXPERIMENTAL)"
>  	depends on SND
> diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
> index 7409338..fa8472e 100644
> --- a/drivers/usb/gadget/Makefile
> +++ b/drivers/usb/gadget/Makefile
> @@ -40,6 +40,7 @@ obj-$(CONFIG_USB_COMPOSITE)	+= usb-composite.o
>  #
>  g_zero-y			:= zero.o
>  g_audio-y			:= audio.o
> +g_audio2-y			:= audio2.o
>  g_ether-y			:= ether.o
>  g_serial-y			:= serial.o
>  g_midi-y			:= gmidi.o
> @@ -57,6 +58,7 @@ g_ncm-y				:= ncm.o
> 
>  obj-$(CONFIG_USB_ZERO)		+= g_zero.o
>  obj-$(CONFIG_USB_AUDIO)		+= g_audio.o
> +obj-$(CONFIG_USB_AUDIO_2)	+= g_audio2.o
>  obj-$(CONFIG_USB_ETH)		+= g_ether.o
>  obj-$(CONFIG_USB_GADGETFS)	+= gadgetfs.o
>  obj-$(CONFIG_USB_FUNCTIONFS)	+= g_ffs.o
> diff --git a/drivers/usb/gadget/audio2.c b/drivers/usb/gadget/audio2.c
> new file mode 100644
> index 0000000..92b6d64
> --- /dev/null
> +++ b/drivers/usb/gadget/audio2.c
> @@ -0,0 +1,155 @@
> +/*
> + * audio2.c -- USB Audio Class 2.0 gadget driver
> + *
> + * Copyright (C) 2011
> + *    Yadwinder Singh (yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org)
> + *    Jaswinder Singh (jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org)
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/utsname.h>
> +#include <linux/platform_device.h>
> +#include <linux/usb/composite.h>
> +
> +/*
> + * Kbuild is not very cooperative with respect to linking separately
> + * compiled library objects into one module.  So for now we won't use
> + * separate compilation ... ensuring init/exit sections work to shrink
> + * the runtime footprint, and giving us at least some parts of what
> + * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
> + */
> +#include "f_audio2.c"
> +
> +enum {
> +	STR_MANUFACTURER = 0,
> +	STR_PRODUCT,
> +	STR_CONFIG,
> +};
> +
> +/* Thanks to Linux Foundation for donating this product ID. */
> +#define UAC2_VENDOR	0x1d6b	/* Linux Foundation */
> +#define UAC2_PRODUCT	0x0102	/* Linux-USB Audio Gadget */
> +
> +static const char manufacturer[] = "Linux Foundation";
> +static const char product_desc[] = "UAC2 Gadget";
> +static const char config[] = "Simple Source/Sink";
> +
> +static struct usb_string strings_cdev[] = {
> +	[STR_MANUFACTURER].s = manufacturer,
> +	[STR_PRODUCT].s = product_desc,
> +	[STR_CONFIG].s = config,
> +	{},
> +};
> +
> +static struct usb_gadget_strings stringtab = {
> +	.language = 0x0409,	/* en-us */
> +	.strings = strings_cdev,
> +};
> +
> +static struct usb_gadget_strings *cdev_strings[] = {
> +	&stringtab,
> +	NULL,
> +};
> +
> +static struct usb_device_descriptor device_desc = {
> +	.bLength = sizeof device_desc,
> +	.bDescriptorType = USB_DT_DEVICE,
> +
> +	.bcdUSB = cpu_to_le16(0x200),
> +	.bDeviceClass = USB_CLASS_MISC,
> +	.bDeviceSubClass = 0x02,
> +	.bDeviceProtocol = 0x01,
> +	.idVendor = cpu_to_le16(UAC2_VENDOR),
> +	.idProduct = cpu_to_le16(UAC2_PRODUCT),
> +	/*
> +	 * Implemented features of USB Audio Device class solely
> +	 * depend on this driver, so this is where we decide the
> +	 * version of the device.
> +	 * Currently, the lowest version of simplest driver - 00.01
> +	 */
> +	.bcdDevice = cpu_to_le16(0x0001),
> +	.bNumConfigurations = 1,
> +};
> +
> +static struct usb_otg_descriptor otg_desc = {
> +	.bLength = sizeof otg_desc,
> +	.bDescriptorType = USB_DT_OTG,
> +
> +	.bmAttributes = USB_OTG_SRP,
> +};
> +
> +const struct usb_descriptor_header *otgd[] = {
> +	(struct usb_descriptor_header *) &otg_desc,
> +	NULL,
> +};
> +
> +static struct usb_configuration audio_config = {
> +	.label = "UAC2",
> +	.unbind = uac2_unbind_config,
> +	.bConfigurationValue = 1,
> +	.bmAttributes = USB_CONFIG_ATT_SELFPOWER,
> +};
> +
> +static int __init
> +audio_bind(struct usb_composite_dev *cdev)
> +{
> +	int  id;
> +
> +	/* Allocate string descriptor numbers */
> +	id = usb_string_id(cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_cdev[STR_MANUFACTURER].id = id;
> +	device_desc.iManufacturer = id;
> +
> +	id = usb_string_id(cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_cdev[STR_PRODUCT].id = id;
> +	device_desc.iProduct = id;
> +
> +	id = usb_string_id(cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_cdev[STR_CONFIG].id = id;
> +	audio_config.iConfiguration = id;
> +
> +	if (gadget_is_otg(cdev->gadget)) {
> +		audio_config.descriptors = otgd;
> +		otg_desc.bmAttributes |= USB_OTG_HNP;
> +		audio_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
> +	}
> +
> +	return usb_add_config(cdev, &audio_config, uac2_bind_config);
> +}
> +
> +static struct usb_composite_driver audio_driver = {
> +	.name = "USB Audio Class 2.0",
> +	.dev = &device_desc,
> +	.strings = cdev_strings,
> +	.max_speed = USB_SPEED_HIGH,
> +};
> +
> +static int __init init(void)
> +{
> +	return usb_composite_probe(&audio_driver, audio_bind);
> +}
> +module_init(init);
> +
> +static void __exit cleanup(void)
> +{
> +	usb_composite_unregister(&audio_driver);
> +}
> +module_exit(cleanup);
> +
> +MODULE_DESCRIPTION("USB Audio Class 2.0 Gadget Driver");
> +MODULE_AUTHOR("Yadi Brar, Jassi Brar");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/usb/gadget/f_audio2.c
> b/drivers/usb/gadget/f_audio2.c
> new file mode 100644
> index 0000000..261d587
> --- /dev/null
> +++ b/drivers/usb/gadget/f_audio2.c
> @@ -0,0 +1,1448 @@
> +/*
> + * f_audio2.c -- USB Audio Class 2.0 Function
> + *
> + * Copyright (C) 2011
> + *    Yadwinder Singh (yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org)
> + *    Jaswinder Singh (jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org)
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/usb/audio.h>
> +#include <linux/usb/audio-v2.h>
> +
> +#include <sound/core.h>
> +#include <sound/pcm.h>
> +#include <sound/pcm_params.h>
> +
> +/* Playback(USB-IN) Default Stereo - Fl/Fr */
> +static int p_chmask = 0x3;
> +module_param(p_chmask, uint, S_IRUGO);
> +MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
> +
> +/* Playback Default 48 KHz */
> +static int p_srate = 48000;
> +module_param(p_srate, uint, S_IRUGO);
> +MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
> +
> +/* Playback Default 16bits/sample */
> +static int p_ssize = 2;
> +module_param(p_ssize, uint, S_IRUGO);
> +MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
> +
> +/* Capture(USB-OUT) Default Stereo - Fl/Fr */
> +static int c_chmask = 0x3;
> +module_param(c_chmask, uint, S_IRUGO);
> +MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
> +
> +/* Capture Default 48 KHz */
> +static int c_srate = 48000;
> +module_param(c_srate, uint, S_IRUGO);
> +MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
> +
> +/* Capture Default 16bits/sample */
> +static int c_ssize = 2;
> +module_param(c_ssize, uint, S_IRUGO);
> +MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
> +
> +#define DMA_ADDR_INVALID	(~(dma_addr_t)0)
> +
> +#define ALT_SET(x, a)	do {(x) &= ~0xff; (x) |= (a); } while (0)
> +#define ALT_GET(x)	((x) & 0xff)
> +#define INTF_SET(x, i)	do {(x) &= 0xff; (x) |= ((i) << 8); } while (0)
> +#define INTF_GET(x)	((x >> 8) & 0xff)
> +
> +/* Keep everyone on toes */
> +#define USB_XFERS	2
> +
> +/*
> + * The driver implements a simple UAC_2 topology.
> + * USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture
> + * ALSA_Playback -> IT_2 -> OT_4 -> USB-IN
> + * Capture and Playback sampling rates are independently
> + *  controlled by two clock sources :
> + *    CLK_5 := c_srate, and CLK_6 := p_srate
> + */
> +#define USB_OUT_IT_ID	1
> +#define IO_IN_IT_ID	2
> +#define IO_OUT_OT_ID	3
> +#define USB_IN_OT_ID	4
> +#define USB_OUT_CLK_ID	5
> +#define USB_IN_CLK_ID	6
> +
> +#define CONTROL_ABSENT	0
> +#define CONTROL_RDONLY	1
> +#define CONTROL_RDWR	3
> +
> +#define CLK_FREQ_CTRL	0
> +#define CLK_VLD_CTRL	2
> +
> +#define COPY_CTRL	0
> +#define CONN_CTRL	2
> +#define OVRLD_CTRL	4
> +#define CLSTR_CTRL	6
> +#define UNFLW_CTRL	8
> +#define OVFLW_CTRL	10
> +
> +const char *uac2_name = "snd_uac2";
> +
> +struct uac2_req {
> +	struct uac2_rtd_params *pp; /* parent param */
> +	struct usb_request *req;
> +};
> +
> +struct uac2_rtd_params {
> +	bool ep_enabled; /* if the ep is enabled */
> +	/* Size of the ring buffer */
> +	size_t dma_bytes;
> +	unsigned char *dma_area;
> +
> +	struct snd_pcm_substream *ss;
> +
> +	/* Ring buffer */
> +	ssize_t hw_ptr;
> +
> +	void *rbuf;
> +
> +	size_t period_size;
> +
> +	unsigned max_psize;
> +	struct uac2_req ureq[USB_XFERS];
> +
> +	spinlock_t lock;
> +};
> +
> +struct snd_uac2_chip {
> +	struct platform_device pdev;
> +	struct platform_driver pdrv;
> +
> +	struct uac2_rtd_params p_prm;
> +	struct uac2_rtd_params c_prm;
> +
> +	struct snd_card *card;
> +	struct snd_pcm *pcm;
> +};
> +
> +#define BUFF_SIZE_MAX	(PAGE_SIZE * 16)
> +#define PRD_SIZE_MAX	PAGE_SIZE
> +#define MIN_PERIODS	4
> +
> +static struct snd_pcm_hardware uac2_pcm_hardware = {
> +	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER
> +		 | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
> +		 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
> +	.rates = SNDRV_PCM_RATE_CONTINUOUS,
> +	.periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX,
> +	.buffer_bytes_max = BUFF_SIZE_MAX,
> +	.period_bytes_max = PRD_SIZE_MAX,
> +	.periods_min = MIN_PERIODS,
> +};
> +
> +struct audio_dev {
> +	/* Currently active {Interface[15:8] | AltSettings[7:0]} */
> +	__u16 ac_alt, as_out_alt, as_in_alt;
> +
> +	struct usb_ep *in_ep, *out_ep;
> +	struct usb_function func;
> +
> +	/* The ALSA Sound Card it represents on the USB-Client side */
> +	struct snd_uac2_chip uac2;
> +};
> +
> +static struct audio_dev *agdev_g;
> +
> +static inline
> +struct audio_dev *func_to_agdev(struct usb_function *f)
> +{
> +	return container_of(f, struct audio_dev, func);
> +}
> +
> +static inline
> +struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u)
> +{
> +	return container_of(u, struct audio_dev, uac2);
> +}
> +
> +static inline
> +struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p)
> +{
> +	return container_of(p, struct snd_uac2_chip, pdev);
> +}
> +
> +static inline
> +struct snd_uac2_chip *prm_to_uac2(struct uac2_rtd_params *r)
> +{
> +	struct snd_uac2_chip *uac2 = container_of(r,
> +					struct snd_uac2_chip, c_prm);
> +
> +	if (&uac2->c_prm != r)
> +		uac2 = container_of(r, struct snd_uac2_chip, p_prm);
> +
> +	return uac2;
> +}
> +
> +static inline
> +uint num_channels(uint chanmask)
> +{
> +	uint num = 0;
> +
> +	while (chanmask) {
> +		num += (chanmask & 1);
> +		chanmask >>= 1;
> +	}
> +
> +	return num;
> +}
> +
> +static void
> +agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
> +{
> +	unsigned pending;
> +	unsigned long flags;
> +	bool update_alsa = false;
> +	unsigned char *src, *dst;
> +	int status = req->status;
> +	struct uac2_req *ur = req->context;
> +	struct snd_pcm_substream *substream;
> +	struct uac2_rtd_params *prm = ur->pp;
> +	struct snd_uac2_chip *uac2 = prm_to_uac2(prm);
> +
> +	/* i/f shutting down */
> +	if (!prm->ep_enabled)
> +		return;
> +
> +	/*
> +	 * We can't really do much about bad xfers.
> +	 * Afterall, the ISOCH xfers could fail legitimately.
> +	 */
> +	if (status)
> +		pr_debug("%s: iso_complete status(%d) %d/%d\n",
> +			__func__, status, req->actual, req->length);
> +
> +	substream = prm->ss;
> +
> +	/* Do nothing if ALSA isn't active */
> +	if (!substream)
> +		goto exit;
> +
> +	spin_lock_irqsave(&prm->lock, flags);
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> +		src = prm->dma_area + prm->hw_ptr;
> +		req->actual = req->length;
> +		dst = req->buf;
> +	} else {
> +		dst = prm->dma_area + prm->hw_ptr;
> +		src = req->buf;
> +	}
> +
> +	pending = prm->hw_ptr % prm->period_size;
> +	pending += req->actual;
> +	if (pending >= prm->period_size)
> +		update_alsa = true;
> +
> +	prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes;
> +
> +	spin_unlock_irqrestore(&prm->lock, flags);
> +
> +	/* Pack USB load in ALSA ring buffer */
> +	memcpy(dst, src, req->actual);
> +exit:
> +	if (usb_ep_queue(ep, req, GFP_ATOMIC))
> +		dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
> +
> +	if (update_alsa)
> +		snd_pcm_period_elapsed(substream);
> +
> +	return;
> +}
> +
> +static int
> +uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> +{
> +	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
> +	struct audio_dev *agdev = uac2_to_agdev(uac2);
> +	struct uac2_rtd_params *prm;
> +	unsigned long flags;
> +	struct usb_ep *ep;
> +	int err = 0;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> +		ep = agdev->in_ep;
> +		prm = &uac2->p_prm;
> +	} else {
> +		ep = agdev->out_ep;
> +		prm = &uac2->c_prm;
> +	}
> +
> +	spin_lock_irqsave(&prm->lock, flags);
> +
> +	/* Reset */
> +	prm->hw_ptr = 0;
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_RESUME:
> +		prm->ss = substream;
> +		break;
> +	case SNDRV_PCM_TRIGGER_STOP:
> +	case SNDRV_PCM_TRIGGER_SUSPEND:
> +		prm->ss = NULL;
> +		break;
> +	default:
> +		err = -EINVAL;
> +	}
> +
> +	spin_unlock_irqrestore(&prm->lock, flags);
> +
> +	/* Clear buffer after Play stops */
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss)
> +		memset(prm->rbuf, 0, prm->max_psize * USB_XFERS);
> +
> +	return err;
> +}
> +
> +static snd_pcm_uframes_t uac2_pcm_pointer(struct snd_pcm_substream
> *substream)
> +{
> +	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
> +	struct uac2_rtd_params *prm;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		prm = &uac2->p_prm;
> +	else
> +		prm = &uac2->c_prm;
> +
> +	return bytes_to_frames(substream->runtime, prm->hw_ptr);
> +}
> +
> +static int uac2_pcm_hw_params(struct snd_pcm_substream *substream,
> +			       struct snd_pcm_hw_params *hw_params)
> +{
> +	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
> +	struct uac2_rtd_params *prm;
> +	int err;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		prm = &uac2->p_prm;
> +	else
> +		prm = &uac2->c_prm;
> +
> +	err = snd_pcm_lib_malloc_pages(substream,
> +					params_buffer_bytes(hw_params));
> +	if (err >= 0) {
> +		prm->dma_bytes = substream->runtime->dma_bytes;
> +		prm->dma_area = substream->runtime->dma_area;
> +		prm->period_size = params_period_bytes(hw_params);
> +	}
> +
> +	return err;
> +}
> +
> +static int uac2_pcm_hw_free(struct snd_pcm_substream *substream)
> +{
> +	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
> +	struct uac2_rtd_params *prm;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		prm = &uac2->p_prm;
> +	else
> +		prm = &uac2->c_prm;
> +
> +	prm->dma_area = NULL;
> +	prm->dma_bytes = 0;
> +	prm->period_size = 0;
> +
> +	return snd_pcm_lib_free_pages(substream);
> +}
> +
> +static int uac2_pcm_open(struct snd_pcm_substream *substream)
> +{
> +	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
> +	struct snd_pcm_runtime *runtime = substream->runtime;
> +
> +	runtime->hw = uac2_pcm_hardware;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> +		spin_lock_init(&uac2->p_prm.lock);
> +		runtime->hw.rate_min = p_srate;
> +		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; /* ! p_ssize !
> */
> +		runtime->hw.channels_min = num_channels(p_chmask);
> +		runtime->hw.period_bytes_min = 2 * uac2->p_prm.max_psize
> +						/ runtime->hw.periods_min;
> +	} else {
> +		spin_lock_init(&uac2->c_prm.lock);
> +		runtime->hw.rate_min = c_srate;
> +		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; /* ! c_ssize !
> */
> +		runtime->hw.channels_min = num_channels(c_chmask);
> +		runtime->hw.period_bytes_min = 2 * uac2->c_prm.max_psize
> +						/ runtime->hw.periods_min;
> +	}
> +
> +	runtime->hw.rate_max = runtime->hw.rate_min;
> +	runtime->hw.channels_max = runtime->hw.channels_min;
> +
> +	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
> +
> +	return 0;
> +}
> +
> +/* ALSA cries without these function pointers */
> +static int uac2_pcm_null(struct snd_pcm_substream *substream)
> +{
> +	return 0;
> +}
> +
> +static struct snd_pcm_ops uac2_pcm_ops = {
> +	.open = uac2_pcm_open,
> +	.close = uac2_pcm_null,
> +	.ioctl = snd_pcm_lib_ioctl,
> +	.hw_params = uac2_pcm_hw_params,
> +	.hw_free = uac2_pcm_hw_free,
> +	.trigger = uac2_pcm_trigger,
> +	.pointer = uac2_pcm_pointer,
> +	.prepare = uac2_pcm_null,
> +};
> +
> +static int __devinit snd_uac2_probe(struct platform_device *pdev)
> +{
> +	struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev);
> +	struct snd_card *card;
> +	struct snd_pcm *pcm;
> +	int err;
> +
> +	/* Choose any slot, with no id */
> +	err = snd_card_create(-1, NULL, THIS_MODULE, 0, &card);
> +	if (err < 0)
> +		return err;
> +
> +	uac2->card = card;
> +
> +	/*
> +	 * Create first PCM device
> +	 * Create a substream only for non-zero channel streams
> +	 */
> +	err = snd_pcm_new(uac2->card, "UAC2 PCM", 0,
> +			       p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm);
> +	if (err < 0)
> +		goto snd_fail;
> +
> +	strcpy(pcm->name, "UAC2 PCM");
> +	pcm->private_data = uac2;
> +
> +	uac2->pcm = pcm;
> +
> +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops);
> +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops);
> +
> +	strcpy(card->driver, "uac2");
> +	strcpy(card->shortname, "uac2");
> +	sprintf(card->longname, "uac2 %i", pdev->id);
> +
> +	snd_card_set_dev(card, &pdev->dev);
> +
> +	snd_pcm_lib_preallocate_pages_for_all(pcm,
> SNDRV_DMA_TYPE_CONTINUOUS,
> +		snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
> +
> +	err = snd_card_register(card);
> +	if (!err) {
> +		platform_set_drvdata(pdev, card);
> +		return 0;
> +	}
> +
> +snd_fail:
> +	snd_card_free(card);
> +
> +	uac2->pcm = NULL;
> +	uac2->card = NULL;
> +
> +	return err;
> +}
> +
> +static int __devexit snd_uac2_remove(struct platform_device *pdev)
> +{
> +	struct snd_card *card = platform_get_drvdata(pdev);
> +
> +	platform_set_drvdata(pdev, NULL);
> +
> +	if (card)
> +		return snd_card_free(card);
> +
> +	return 0;
> +}
> +
> +static int alsa_uac2_init(struct audio_dev *agdev)
> +{
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	int err;
> +
> +	uac2->pdrv.probe = snd_uac2_probe;
> +	uac2->pdrv.remove = snd_uac2_remove;
> +	uac2->pdrv.driver.name = uac2_name;
> +
> +	uac2->pdev.id = 0;
> +	uac2->pdev.name = uac2_name;
> +
> +	/* Register snd_uac2 driver */
> +	err = platform_driver_register(&uac2->pdrv);
> +	if (err)
> +		return err;
> +
> +	/* Register snd_uac2 device */
> +	err = platform_device_register(&uac2->pdev);
> +	if (err)
> +		platform_driver_unregister(&uac2->pdrv);
> +
> +	return err;
> +}
> +
> +static void alsa_uac2_exit(struct audio_dev *agdev)
> +{
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +
> +	platform_driver_unregister(&uac2->pdrv);
> +	platform_device_unregister(&uac2->pdev);
> +}
> +
> +
> +/* --------- USB Function Interface ------------- */
> +
> +enum {
> +	STR_ASSOC,
> +	STR_IF_CTRL,
> +	STR_CLKSRC_IN,
> +	STR_CLKSRC_OUT,
> +	STR_USB_IT,
> +	STR_IO_IT,
> +	STR_USB_OT,
> +	STR_IO_OT,
> +	STR_AS_OUT_ALT0,
> +	STR_AS_OUT_ALT1,
> +	STR_AS_IN_ALT0,
> +	STR_AS_IN_ALT1,
> +};
> +
> +static const char ifassoc[] = "Source/Sink";
> +static const char ifctrl[] = "Topology Control";
> +static char clksrc_in[8];
> +static char clksrc_out[8];
> +static const char usb_it[] = "USBH Out";
> +static const char io_it[] = "USBD Out";
> +static const char usb_ot[] = "USBH In";
> +static const char io_ot[] = "USBD In";
> +static const char out_alt0[] = "Playback Inactive";
> +static const char out_alt1[] = "Playback Active";
> +static const char in_alt0[] = "Capture Inactive";
> +static const char in_alt1[] = "Capture Active";
> +
> +static struct usb_string strings_fn[] = {
> +	[STR_ASSOC].s = ifassoc,
> +	[STR_IF_CTRL].s = ifctrl,
> +	[STR_CLKSRC_IN].s = clksrc_in,
> +	[STR_CLKSRC_OUT].s = clksrc_out,
> +	[STR_USB_IT].s = usb_it,
> +	[STR_IO_IT].s = io_it,
> +	[STR_USB_OT].s = usb_ot,
> +	[STR_IO_OT].s = io_ot,
> +	[STR_AS_OUT_ALT0].s = out_alt0,
> +	[STR_AS_OUT_ALT1].s = out_alt1,
> +	[STR_AS_IN_ALT0].s = in_alt0,
> +	[STR_AS_IN_ALT1].s = in_alt1,
> +	{ },
> +};
> +
> +static struct usb_gadget_strings str_fn = {
> +	.language = 0x0409,	/* en-us */
> +	.strings = strings_fn,
> +};
> +
> +static struct usb_gadget_strings *fn_strings[] = {
> +	&str_fn,
> +	NULL,
> +};
> +
> +static struct usb_qualifier_descriptor devqual_desc = {
> +	.bLength = sizeof devqual_desc,
> +	.bDescriptorType = USB_DT_DEVICE_QUALIFIER,
> +
> +	.bcdUSB = cpu_to_le16(0x200),
> +	.bDeviceClass = USB_CLASS_MISC,
> +	.bDeviceSubClass = 0x02,
> +	.bDeviceProtocol = 0x01,
> +	.bNumConfigurations = 1,
> +	.bRESERVED = 0,
> +};
> +
> +static struct usb_interface_assoc_descriptor iad_desc = {
> +	.bLength = sizeof iad_desc,
> +	.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
> +
> +	.bFirstInterface = 0,
> +	.bInterfaceCount = 3,
> +	.bFunctionClass = USB_CLASS_AUDIO,
> +	.bFunctionSubClass = UAC2_FUNCTION_SUBCLASS_UNDEFINED,
> +	.bFunctionProtocol = UAC_VERSION_2,
> +};
> +
> +/* Audio Control Interface */
> +static struct usb_interface_descriptor std_ac_if_desc = {
> +	.bLength = sizeof std_ac_if_desc,
> +	.bDescriptorType = USB_DT_INTERFACE,
> +
> +	.bAlternateSetting = 0,
> +	.bNumEndpoints = 0,
> +	.bInterfaceClass = USB_CLASS_AUDIO,
> +	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
> +	.bInterfaceProtocol = UAC_VERSION_2,
> +};
> +
> +/* Clock source for IN traffic */
> +struct uac_clock_source_descriptor in_clk_src_desc = {
> +	.bLength = sizeof in_clk_src_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
> +	.bClockID = USB_IN_CLK_ID,
> +	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
> +	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
> +	.bAssocTerminal = 0,
> +};
> +
> +/* Clock source for OUT traffic */
> +struct uac_clock_source_descriptor out_clk_src_desc = {
> +	.bLength = sizeof out_clk_src_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
> +	.bClockID = USB_OUT_CLK_ID,
> +	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
> +	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
> +	.bAssocTerminal = 0,
> +};
> +
> +/* Input Terminal for USB_OUT */
> +struct uac2_input_terminal_descriptor usb_out_it_desc = {
> +	.bLength = sizeof usb_out_it_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_INPUT_TERMINAL,
> +	.bTerminalID = USB_OUT_IT_ID,
> +	.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
> +	.bAssocTerminal = 0,
> +	.bCSourceID = USB_OUT_CLK_ID,
> +	.iChannelNames = 0,
> +	.bmControls = (CONTROL_RDWR << COPY_CTRL),
> +};
> +
> +/* Input Terminal for I/O-In */
> +struct uac2_input_terminal_descriptor io_in_it_desc = {
> +	.bLength = sizeof io_in_it_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_INPUT_TERMINAL,
> +	.bTerminalID = IO_IN_IT_ID,
> +	.wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_UNDEFINED),
> +	.bAssocTerminal = 0,
> +	.bCSourceID = USB_IN_CLK_ID,
> +	.iChannelNames = 0,
> +	.bmControls = (CONTROL_RDWR << COPY_CTRL),
> +};
> +
> +/* Ouput Terminal for USB_IN */
> +struct uac2_output_terminal_descriptor usb_in_ot_desc = {
> +	.bLength = sizeof usb_in_ot_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
> +	.bTerminalID = USB_IN_OT_ID,
> +	.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
> +	.bAssocTerminal = 0,
> +	.bSourceID = IO_IN_IT_ID,
> +	.bCSourceID = USB_IN_CLK_ID,
> +	.bmControls = (CONTROL_RDWR << COPY_CTRL),
> +};
> +
> +/* Ouput Terminal for I/O-Out */
> +struct uac2_output_terminal_descriptor io_out_ot_desc = {
> +	.bLength = sizeof io_out_ot_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
> +	.bTerminalID = IO_OUT_OT_ID,
> +	.wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_UNDEFINED),
> +	.bAssocTerminal = 0,
> +	.bSourceID = USB_OUT_IT_ID,
> +	.bCSourceID = USB_OUT_CLK_ID,
> +	.bmControls = (CONTROL_RDWR << COPY_CTRL),
> +};
> +
> +struct uac2_ac_header_descriptor ac_hdr_desc = {
> +	.bLength = sizeof ac_hdr_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_MS_HEADER,
> +	.bcdADC = cpu_to_le16(0x200),
> +	.bCategory = UAC2_FUNCTION_IO_BOX,
> +	.wTotalLength = sizeof in_clk_src_desc + sizeof out_clk_src_desc
> +			 + sizeof usb_out_it_desc + sizeof io_in_it_desc
> +			+ sizeof usb_in_ot_desc + sizeof io_out_ot_desc,
> +	.bmControls = 0,
> +};
> +
> +/* Audio Streaming OUT Interface - Alt0 */
> +static struct usb_interface_descriptor std_as_out_if0_desc = {
> +	.bLength = sizeof std_as_out_if0_desc,
> +	.bDescriptorType = USB_DT_INTERFACE,
> +
> +	.bAlternateSetting = 0,
> +	.bNumEndpoints = 0,
> +	.bInterfaceClass = USB_CLASS_AUDIO,
> +	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
> +	.bInterfaceProtocol = UAC_VERSION_2,
> +};
> +
> +/* Audio Streaming OUT Interface - Alt1 */
> +static struct usb_interface_descriptor std_as_out_if1_desc = {
> +	.bLength = sizeof std_as_out_if1_desc,
> +	.bDescriptorType = USB_DT_INTERFACE,
> +
> +	.bAlternateSetting = 1,
> +	.bNumEndpoints = 1,
> +	.bInterfaceClass = USB_CLASS_AUDIO,
> +	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
> +	.bInterfaceProtocol = UAC_VERSION_2,
> +};
> +
> +/* Audio Stream OUT Intface Desc */
> +struct uac2_as_header_descriptor as_out_hdr_desc = {
> +	.bLength = sizeof as_out_hdr_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_AS_GENERAL,
> +	.bTerminalLink = USB_OUT_IT_ID,
> +	.bmControls = 0,
> +	.bFormatType = UAC_FORMAT_TYPE_I,
> +	.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
> +	.iChannelNames = 0,
> +};
> +
> +/* Audio USB_OUT Format */
> +struct uac2_format_type_i_descriptor as_out_fmt1_desc = {
> +	.bLength = sizeof as_out_fmt1_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +	.bDescriptorSubtype = UAC_FORMAT_TYPE,
> +	.bFormatType = UAC_FORMAT_TYPE_I,
> +};
> +
> +/* STD AS ISO OUT Endpoint */
> +struct usb_endpoint_descriptor fs_epout_desc = {
> +	.bLength = USB_DT_ENDPOINT_SIZE,
> +	.bDescriptorType = USB_DT_ENDPOINT,
> +
> +	.bEndpointAddress = USB_DIR_OUT,
> +	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
> +	.bInterval = 1,
> +};
> +
> +struct usb_endpoint_descriptor hs_epout_desc = {
> +	.bLength = USB_DT_ENDPOINT_SIZE,
> +	.bDescriptorType = USB_DT_ENDPOINT,
> +
> +	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
> +	.bInterval = 4,
> +};
The sync type is asynchronous, do you have plan to add feedback for handling
overflow and underflow?

> +
> +/* CS AS ISO OUT Endpoint */
> +static struct uac2_iso_endpoint_descriptor as_iso_out_desc = {
> +	.bLength = sizeof as_iso_out_desc,
> +	.bDescriptorType = USB_DT_CS_ENDPOINT,
> +
> +	.bDescriptorSubtype = UAC_EP_GENERAL,
> +	.bmAttributes = 0,
> +	.bmControls = 0,
> +	.bLockDelayUnits = 0,
> +	.wLockDelay = 0,
> +};
> +
> +/* Audio Streaming IN Interface - Alt0 */
> +static struct usb_interface_descriptor std_as_in_if0_desc = {
> +	.bLength = sizeof std_as_in_if0_desc,
> +	.bDescriptorType = USB_DT_INTERFACE,
> +
> +	.bAlternateSetting = 0,
> +	.bNumEndpoints = 0,
> +	.bInterfaceClass = USB_CLASS_AUDIO,
> +	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
> +	.bInterfaceProtocol = UAC_VERSION_2,
> +};
> +
> +/* Audio Streaming IN Interface - Alt1 */
> +static struct usb_interface_descriptor std_as_in_if1_desc = {
> +	.bLength = sizeof std_as_in_if1_desc,
> +	.bDescriptorType = USB_DT_INTERFACE,
> +
> +	.bAlternateSetting = 1,
> +	.bNumEndpoints = 1,
> +	.bInterfaceClass = USB_CLASS_AUDIO,
> +	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
> +	.bInterfaceProtocol = UAC_VERSION_2,
> +};
> +
> +/* Audio Stream IN Intface Desc */
> +struct uac2_as_header_descriptor as_in_hdr_desc = {
> +	.bLength = sizeof as_in_hdr_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_AS_GENERAL,
> +	.bTerminalLink = USB_IN_OT_ID,
> +	.bmControls = 0,
> +	.bFormatType = UAC_FORMAT_TYPE_I,
> +	.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
> +	.iChannelNames = 0,
> +};
> +
> +/* Audio USB_IN Format */
> +struct uac2_format_type_i_descriptor as_in_fmt1_desc = {
> +	.bLength = sizeof as_in_fmt1_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +	.bDescriptorSubtype = UAC_FORMAT_TYPE,
> +	.bFormatType = UAC_FORMAT_TYPE_I,
> +};
> +
> +/* STD AS ISO IN Endpoint */
> +struct usb_endpoint_descriptor fs_epin_desc = {
> +	.bLength = USB_DT_ENDPOINT_SIZE,
> +	.bDescriptorType = USB_DT_ENDPOINT,
> +
> +	.bEndpointAddress = USB_DIR_IN,
> +	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
> +	.bInterval = 1,
> +};
> +
> +struct usb_endpoint_descriptor hs_epin_desc = {
> +	.bLength = USB_DT_ENDPOINT_SIZE,
> +	.bDescriptorType = USB_DT_ENDPOINT,
> +
> +	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
> +	.bInterval = 4,
> +};
> +
> +/* CS AS ISO IN Endpoint */
> +static struct uac2_iso_endpoint_descriptor as_iso_in_desc = {
> +	.bLength = sizeof as_iso_in_desc,
> +	.bDescriptorType = USB_DT_CS_ENDPOINT,
> +
> +	.bDescriptorSubtype = UAC_EP_GENERAL,
> +	.bmAttributes = 0,
> +	.bmControls = 0,
> +	.bLockDelayUnits = 0,
> +	.wLockDelay = 0,
> +};
> +
> +static struct usb_descriptor_header *fs_audio_desc[] = {
> +	(struct usb_descriptor_header *)&iad_desc,
> +	(struct usb_descriptor_header *)&std_ac_if_desc,
> +
> +	(struct usb_descriptor_header *)&ac_hdr_desc,
> +	(struct usb_descriptor_header *)&in_clk_src_desc,
> +	(struct usb_descriptor_header *)&out_clk_src_desc,
> +	(struct usb_descriptor_header *)&usb_out_it_desc,
> +	(struct usb_descriptor_header *)&io_in_it_desc,
> +	(struct usb_descriptor_header *)&usb_in_ot_desc,
> +	(struct usb_descriptor_header *)&io_out_ot_desc,
> +
> +	(struct usb_descriptor_header *)&std_as_out_if0_desc,
> +	(struct usb_descriptor_header *)&std_as_out_if1_desc,
> +
> +	(struct usb_descriptor_header *)&as_out_hdr_desc,
> +	(struct usb_descriptor_header *)&as_out_fmt1_desc,
> +	(struct usb_descriptor_header *)&fs_epout_desc,
> +	(struct usb_descriptor_header *)&as_iso_out_desc,
> +
> +	(struct usb_descriptor_header *)&std_as_in_if0_desc,
> +	(struct usb_descriptor_header *)&std_as_in_if1_desc,
> +
> +	(struct usb_descriptor_header *)&as_in_hdr_desc,
> +	(struct usb_descriptor_header *)&as_in_fmt1_desc,
> +	(struct usb_descriptor_header *)&fs_epin_desc,
> +	(struct usb_descriptor_header *)&as_iso_in_desc,
> +	NULL,
> +};
> +
> +static struct usb_descriptor_header *hs_audio_desc[] = {
> +	(struct usb_descriptor_header *)&iad_desc,
> +	(struct usb_descriptor_header *)&std_ac_if_desc,
> +
> +	(struct usb_descriptor_header *)&ac_hdr_desc,
> +	(struct usb_descriptor_header *)&in_clk_src_desc,
> +	(struct usb_descriptor_header *)&out_clk_src_desc,
> +	(struct usb_descriptor_header *)&usb_out_it_desc,
> +	(struct usb_descriptor_header *)&io_in_it_desc,
> +	(struct usb_descriptor_header *)&usb_in_ot_desc,
> +	(struct usb_descriptor_header *)&io_out_ot_desc,
> +
> +	(struct usb_descriptor_header *)&std_as_out_if0_desc,
> +	(struct usb_descriptor_header *)&std_as_out_if1_desc,
> +
> +	(struct usb_descriptor_header *)&as_out_hdr_desc,
> +	(struct usb_descriptor_header *)&as_out_fmt1_desc,
> +	(struct usb_descriptor_header *)&hs_epout_desc,
> +	(struct usb_descriptor_header *)&as_iso_out_desc,
> +
> +	(struct usb_descriptor_header *)&std_as_in_if0_desc,
> +	(struct usb_descriptor_header *)&std_as_in_if1_desc,
> +
> +	(struct usb_descriptor_header *)&as_in_hdr_desc,
> +	(struct usb_descriptor_header *)&as_in_fmt1_desc,
> +	(struct usb_descriptor_header *)&hs_epin_desc,
> +	(struct usb_descriptor_header *)&as_iso_in_desc,
> +	NULL,
> +};
> +
> +struct cntrl_cur_lay3 {
> +	__u32	dCUR;
> +};
> +
> +struct cntrl_range_lay3 {
> +	__u16	wNumSubRanges;
> +	__u32	dMIN;
> +	__u32	dMAX;
> +	__u32	dRES;
> +} __packed;
> +
> +static inline void
> +free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep)
> +{
> +	struct snd_uac2_chip *uac2 = prm_to_uac2(prm);
> +	int i;
> +
> +	prm->ep_enabled = false;
> +
> +	for (i = 0; i < USB_XFERS; i++) {
> +		if (prm->ureq[i].req) {
> +			usb_ep_dequeue(ep, prm->ureq[i].req);
> +			usb_ep_free_request(ep, prm->ureq[i].req);
> +			prm->ureq[i].req = NULL;
> +		}
> +	}
> +
> +	if (usb_ep_disable(ep))
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +}
> +
> +static int __init
> +afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
> +{
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	struct usb_composite_dev *cdev = cfg->cdev;
> +	struct usb_gadget *gadget = cdev->gadget;
> +	struct uac2_rtd_params *prm;
> +	int ret;
> +
> +	ret = usb_interface_id(cfg, fn);
> +	if (ret < 0) {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return ret;
> +	}
> +	std_ac_if_desc.bInterfaceNumber = ret;
> +	ALT_SET(agdev->ac_alt, 0);
> +	INTF_SET(agdev->ac_alt, ret);
> +
> +	ret = usb_interface_id(cfg, fn);
> +	if (ret < 0) {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return ret;
> +	}
> +	std_as_out_if0_desc.bInterfaceNumber = ret;
> +	std_as_out_if1_desc.bInterfaceNumber = ret;
> +	ALT_SET(agdev->as_out_alt, 0);
> +	INTF_SET(agdev->as_out_alt, ret);
> +
> +	ret = usb_interface_id(cfg, fn);
> +	if (ret < 0) {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return ret;
> +	}
> +	std_as_in_if0_desc.bInterfaceNumber = ret;
> +	std_as_in_if1_desc.bInterfaceNumber = ret;
> +	ALT_SET(agdev->as_in_alt, 0);
> +	INTF_SET(agdev->as_in_alt, ret);
> +
> +	agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
> +	if (!agdev->out_ep)
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +	agdev->out_ep->driver_data = agdev;
> +
> +	agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
> +	if (!agdev->in_ep)
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +	agdev->in_ep->driver_data = agdev;
> +
> +	hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
> +	hs_epout_desc.wMaxPacketSize = fs_epout_desc.wMaxPacketSize;
> +	hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
> +	hs_epin_desc.wMaxPacketSize = fs_epin_desc.wMaxPacketSize;
> +
> +	fn->descriptors = usb_copy_descriptors(fs_audio_desc);
> +	if (gadget_is_dualspeed(gadget))
> +		fn->hs_descriptors = usb_copy_descriptors(hs_audio_desc);
> +
> +	prm = &agdev->uac2.c_prm;
> +	prm->max_psize = hs_epout_desc.wMaxPacketSize;
> +	prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL);
> +	if (!prm->rbuf) {
> +		prm->max_psize = 0;
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +	}
> +
> +	prm = &agdev->uac2.p_prm;
> +	prm->max_psize = hs_epin_desc.wMaxPacketSize;
> +	prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL);
> +	if (!prm->rbuf) {
> +		prm->max_psize = 0;
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +	}
> +
> +	return alsa_uac2_init(agdev);
> +}
> +
> +static void
> +afunc_unbind(struct usb_configuration *cfg, struct usb_function *fn)
> +{
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct usb_composite_dev *cdev = cfg->cdev;
> +	struct usb_gadget *gadget = cdev->gadget;
> +	struct uac2_rtd_params *prm;
> +
> +	alsa_uac2_exit(agdev);
> +
> +	prm = &agdev->uac2.p_prm;
> +	kfree(prm->rbuf);
> +
> +	prm = &agdev->uac2.c_prm;
> +	kfree(prm->rbuf);
> +
> +	if (gadget_is_dualspeed(gadget))
> +		usb_free_descriptors(fn->hs_descriptors);
> +	usb_free_descriptors(fn->descriptors);
> +
> +	if (agdev->in_ep)
> +		agdev->in_ep->driver_data = NULL;
> +	if (agdev->out_ep)
> +		agdev->out_ep->driver_data = NULL;
> +}
> +
> +static int
> +afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
> +{
> +	struct usb_composite_dev *cdev = fn->config->cdev;
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	struct usb_gadget *gadget = cdev->gadget;
> +	struct usb_request *req;
> +	struct usb_ep *ep;
> +	struct uac2_rtd_params *prm;
> +	int i;
> +
> +	/* No i/f has more than 2 alt settings */
> +	if (alt > 1) {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return -EINVAL;
> +	}
> +
> +	if (intf == INTF_GET(agdev->ac_alt)) {
> +		/* Control I/f has only 1 AltSetting - 0 */
> +		if (alt) {
> +			dev_err(&uac2->pdev.dev,
> +				"%s:%d Error!\n", __func__, __LINE__);
> +			return -EINVAL;
> +		}
> +		return 0;
> +	}
> +
> +	if (intf == INTF_GET(agdev->as_out_alt)) {
> +		ep = agdev->out_ep;
> +		prm = &uac2->c_prm;
> +		config_ep_by_speed(gadget, fn, ep);
> +		ALT_SET(agdev->as_out_alt, alt);
> +	} else if (intf == INTF_GET(agdev->as_in_alt)) {
> +		ep = agdev->in_ep;
> +		prm = &uac2->p_prm;
> +		config_ep_by_speed(gadget, fn, ep);
> +		ALT_SET(agdev->as_in_alt, alt);
> +	} else {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return -EINVAL;
> +	}
> +
> +	if (alt == 0) {
> +		free_ep(prm, ep);
> +		return 0;
> +	}
> +
> +	prm->ep_enabled = true;
> +	usb_ep_enable(ep);
> +
> +	for (i = 0; i < USB_XFERS; i++) {
> +		if (prm->ureq[i].req) {
> +			if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
> +				dev_err(&uac2->pdev.dev, "%d Error!\n",
> +					__LINE__);
> +			continue;
> +		}
> +
> +		req = usb_ep_alloc_request(ep, GFP_ATOMIC);
> +		if (req == NULL) {
> +			dev_err(&uac2->pdev.dev,
> +				"%s:%d Error!\n", __func__, __LINE__);
> +			return -EINVAL;
> +		}
> +
> +		prm->ureq[i].req = req;
> +		prm->ureq[i].pp = prm;
> +
> +		req->zero = 0;
> +		req->dma = DMA_ADDR_INVALID;
> +		req->context = &prm->ureq[i];
> +		req->length = prm->max_psize;
> +		req->complete =	agdev_iso_complete;
> +		req->buf = prm->rbuf + i * req->length;
> +
> +		if (usb_ep_queue(ep, req, GFP_ATOMIC))
> +			dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +afunc_get_alt(struct usb_function *fn, unsigned intf)
> +{
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +
> +	if (intf == INTF_GET(agdev->ac_alt))
> +		return ALT_GET(agdev->ac_alt);
> +	else if (intf == INTF_GET(agdev->as_out_alt))
> +		return ALT_GET(agdev->as_out_alt);
> +	else if (intf == INTF_GET(agdev->as_in_alt))
> +		return ALT_GET(agdev->as_in_alt);
> +	else
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Invalid Interface %d!\n",
> +			__func__, __LINE__, intf);
> +
> +	return -EINVAL;
> +}
> +
> +static void
> +afunc_disable(struct usb_function *fn)
> +{
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +
> +	free_ep(&uac2->p_prm, agdev->in_ep);
> +	ALT_SET(agdev->as_in_alt, 0);
> +
> +	free_ep(&uac2->c_prm, agdev->out_ep);
> +	ALT_SET(agdev->as_out_alt, 0);
> +}
> +
> +static int
> +in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	struct usb_request *req = fn->config->cdev->req;
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	u16 w_length = le16_to_cpu(cr->wLength);
> +	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;
> +	int value = -EOPNOTSUPP;
> +
> +	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
> +		struct cntrl_cur_lay3 c;
> +
> +		if (entity_id == USB_IN_CLK_ID)
> +			c.dCUR = p_srate;
> +		else if (entity_id == USB_OUT_CLK_ID)
> +			c.dCUR = c_srate;
> +
> +		value = min_t(unsigned, w_length, sizeof c);
> +		memcpy(req->buf, &c, value);
> +	} else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) {
> +		*(u8 *)req->buf = 1;
> +		value = min(w_length, (u16) 1);
> +	} else {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d control_selector=%d TODO!\n",
> +			__func__, __LINE__, control_selector);
> +	}
> +
> +	return value;
> +}
> +
> +static int
> +in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	struct usb_request *req = fn->config->cdev->req;
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	u16 w_length = le16_to_cpu(cr->wLength);
> +	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;
> +	struct cntrl_range_lay3 r;
> +	int value = -EOPNOTSUPP;
> +
> +	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
> +		if (entity_id == USB_IN_CLK_ID)
> +			r.dMIN = p_srate;
> +		else if (entity_id == USB_OUT_CLK_ID)
> +			r.dMIN = c_srate;
> +		else
> +			return -EOPNOTSUPP;
> +
> +		r.dMAX = r.dMIN;
> +		r.dRES = 0;
> +		r.wNumSubRanges = 1;
> +
> +		value = min_t(unsigned, w_length, sizeof r);
> +		memcpy(req->buf, &r, value);
> +	} else {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d control_selector=%d TODO!\n",
> +			__func__, __LINE__, control_selector);
> +	}
> +
> +	return value;
> +}
> +
> +static int
> +ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	if (cr->bRequest == UAC2_CS_CUR)
> +		return in_rq_cur(fn, cr);
> +	else if (cr->bRequest == UAC2_CS_RANGE)
> +		return in_rq_range(fn, cr);
> +	else
> +		return -EOPNOTSUPP;
> +}
> +
> +static int
> +out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	u16 w_length = le16_to_cpu(cr->wLength);
> +	u16 w_value = le16_to_cpu(cr->wValue);
> +	u8 control_selector = w_value >> 8;
> +
> +	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
> +		return w_length;
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static int
> +setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	u16 w_index = le16_to_cpu(cr->wIndex);
> +	u8 intf = w_index & 0xff;
> +
> +	if (intf != INTF_GET(agdev->ac_alt)) {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return -EOPNOTSUPP;
> +	}
> +
> +	if (cr->bRequestType & USB_DIR_IN)
> +		return ac_rq_in(fn, cr);
> +	else if (cr->bRequest == UAC2_CS_CUR)
> +		return out_rq_cur(fn, cr);
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static int
> +afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	struct usb_composite_dev *cdev = fn->config->cdev;
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	struct usb_request *req = cdev->req;
> +	u16 w_length = le16_to_cpu(cr->wLength);
> +	int value = -EOPNOTSUPP;
> +
> +	/* Only Class specific requests are supposed to reach here */
> +	if ((cr->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS)
> +		return -EOPNOTSUPP;
> +
> +	if ((cr->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE)
> +		value = setup_rq_inf(fn, cr);
> +	else
> +		dev_err(&uac2->pdev.dev, "%s:%d Error!\n", __func__,
> __LINE__);
> +
> +	if (value >= 0) {
> +		req->length = value;
> +		req->zero = value < w_length;
> +		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
> +		if (value < 0) {
> +			dev_err(&uac2->pdev.dev,
> +				"%s:%d Error!\n", __func__, __LINE__);
> +			req->status = 0;
> +		}
> +	}
> +
> +	return value;
> +}
> +
> +static int
> +uac2_bind_config(struct usb_configuration *cfg)
> +{
> +	int id, res;
> +
> +	agdev_g = kzalloc(sizeof *agdev_g, GFP_KERNEL);
> +	if (agdev_g == NULL) {
> +		printk(KERN_ERR "Unable to allocate audio gadget\n");
> +		return -ENOMEM;
> +	}
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_ASSOC].id = id;
> +	iad_desc.iFunction = id,
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_IF_CTRL].id = id;
> +	std_ac_if_desc.iInterface = id,
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_CLKSRC_IN].id = id;
> +	in_clk_src_desc.iClockSource = id,
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_CLKSRC_OUT].id = id;
> +	out_clk_src_desc.iClockSource = id,
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_USB_IT].id = id;
> +	usb_out_it_desc.iTerminal = id,
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_IO_IT].id = id;
> +	io_in_it_desc.iTerminal = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_USB_OT].id = id;
> +	usb_in_ot_desc.iTerminal = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_IO_OT].id = id;
> +	io_out_ot_desc.iTerminal = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_AS_OUT_ALT0].id = id;
> +	std_as_out_if0_desc.iInterface = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_AS_OUT_ALT1].id = id;
> +	std_as_out_if1_desc.iInterface = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_AS_IN_ALT0].id = id;
> +	std_as_in_if0_desc.iInterface = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_AS_IN_ALT1].id = id;
> +	std_as_in_if1_desc.iInterface = id;
> +
> +	agdev_g->func.name = "uac2_func";
> +	agdev_g->func.strings = fn_strings;
> +	agdev_g->func.bind = afunc_bind;
> +	agdev_g->func.unbind = afunc_unbind;
> +	agdev_g->func.set_alt = afunc_set_alt;
> +	agdev_g->func.get_alt = afunc_get_alt;
> +	agdev_g->func.disable = afunc_disable;
> +	agdev_g->func.setup = afunc_setup;
> +
> +	/* Initialize the configurable parameters */
> +	usb_out_it_desc.bNrChannels = num_channels(c_chmask);
> +	usb_out_it_desc.bmChannelConfig = cpu_to_le32(c_chmask);
> +	io_in_it_desc.bNrChannels = num_channels(p_chmask);
> +	io_in_it_desc.bmChannelConfig = cpu_to_le32(p_chmask);
> +	as_out_hdr_desc.bNrChannels = num_channels(c_chmask);
> +	as_out_hdr_desc.bmChannelConfig = cpu_to_le32(c_chmask);
> +	as_in_hdr_desc.bNrChannels = num_channels(p_chmask);
> +	as_in_hdr_desc.bmChannelConfig = cpu_to_le32(p_chmask);
> +	as_out_fmt1_desc.bSubslotSize = c_ssize;
> +	as_out_fmt1_desc.bBitResolution = c_ssize * 8;
> +	as_in_fmt1_desc.bSubslotSize = p_ssize;
> +	as_in_fmt1_desc.bBitResolution = p_ssize * 8;
> +
> +	snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", p_srate);
> +	snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", c_srate);
> +
> +	res = usb_add_function(cfg, &agdev_g->func);
> +	if (res < 0)
> +		kfree(agdev_g);
> +
> +	return res;
> +}
> +
> +static void
> +uac2_unbind_config(struct usb_configuration *cfg)
> +{
> +	kfree(agdev_g);
> +	agdev_g = NULL;
> +}
> --
> 1.7.4.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-usb" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
       [not found]     ` <35AB98346D25394A9354ED89C4D7658717FD0C-TcFNo7jSaXOLgTCmFNXF2K4g8xLGJsHaLnY5E4hWTkheoWH0uzbU5w@public.gmane.org>
@ 2011-08-24  4:22       ` Jassi Brar
  0 siblings, 0 replies; 17+ messages in thread
From: Jassi Brar @ 2011-08-24  4:22 UTC (permalink / raw)
  To: Chen Peter-B29397
  Cc: Jassi Brar, linux-usb-u79uwXL29TY76Z2rM5mHXA, balbi-l0cyMroinI0,
	gregkh-l3A5Bk7waGM, alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw,
	zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w

On Wed, Aug 24, 2011 at 7:30 AM, Chen Peter-B29397 <B29397-KZfg59tc24x4beGV+Eop3g@public.gmane.orgm> wrote:
>>
>> This is a flexible USB Audio Class 2.0 compliant gadget driver that
>> implements a simple topology with a virtual sound card exposed at
>> the function side.
>>
>> The driver doesn't expect any real audio codec to be present on the
>> function - the audio streams are simply sinked to and sourced from a
>> virtual ALSA sound card created. The user-space application may choose
>> to do whatever it wants with the data received from the USB Host and
>> choose to provide whatever it wants as audio data to the USB Host.
>>
>> Capture(USB-Out) and Playback(USB-In) can be run at independent
>> configurations specified via module parameters while loading the driver.
>>
>
> Have you a chance to test at Macbook (10.6.5+) which has USB Audio 2.0 support well?
>
Nopes, haven't had my hands on one yet.
It would help if someone could verify.

.....

>> +/* STD AS ISO OUT Endpoint */
>> +struct usb_endpoint_descriptor fs_epout_desc = {
>> +     .bLength = USB_DT_ENDPOINT_SIZE,
>> +     .bDescriptorType = USB_DT_ENDPOINT,
>> +
>> +     .bEndpointAddress = USB_DIR_OUT,
>> +     .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
>> +     .bInterval = 1,
>> +};
>> +
>> +struct usb_endpoint_descriptor hs_epout_desc = {
>> +     .bLength = USB_DT_ENDPOINT_SIZE,
>> +     .bDescriptorType = USB_DT_ENDPOINT,
>> +
>> +     .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
>> +     .bInterval = 4,
>> +};
> The sync type is asynchronous, do you have plan to add feedback for handling
> overflow and underflow?
>
umm... I was not sure if the feature is worth the overhead/complexity
of maintaining timers in the driver. Though I welcome hearing why not
so?
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
       [not found]                           ` <20110822142805.GT30398-UiBtZHVXSwEVvW8u9ZQWYwjfymiNCTlR@public.gmane.org>
  2011-08-22 14:29                             ` Felipe Balbi
@ 2012-02-02 16:19                             ` Jassi Brar
       [not found]                               ` <CABb+yY3X0XDk0zByZZcJOO0h2Kxu14UD4w-GS7p1HPAAWU3SVA-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  1 sibling, 1 reply; 17+ messages in thread
From: Jassi Brar @ 2012-02-02 16:19 UTC (permalink / raw)
  To: balbi-l0cyMroinI0
  Cc: Jassi Brar, linux-usb-u79uwXL29TY76Z2rM5mHXA, gregkh-l3A5Bk7waGM,
	alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw, zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w,
	jaredwiltshire-Re5JQEeQqe8AvxtiuMwx3w

On Mon, Aug 22, 2011 at 7:58 PM, Felipe Balbi <balbi-l0cyMroinI0@public.gmane.org> wrote:
>
>> > audio.c doesn't do anything audio related. It's just adding the
>> > configuration to composite layer. The real specification is implemented
>> > in f_audio.c (should probably be called f_uac1.c, though).
>> Agreed. Reusing audio.c is quite reasonable.
>> So, I'll :-
>> * Merge audio2.c into audio.c
>> * Rename f_audio.c -> f_uac1.c
>> * Add new f_audio2.c as f_uac2.c
>> * Add Kconfig switch to compile UAC_1 (audio.c + f_uac1.c)  or  UAC_2
>> (audio.c + f_uac2.c)
>>
>> I'll wait for other comments and revise the patch after a few days.
>
> ok, sounds reasonable.
>
OK, after a 'few days' on some distant giant planet, here is the revised
patchset accordingly.

Cheers
-jassi
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH 1/4] USB: Gadget: Rename audio function to uac1
       [not found]                               ` <CABb+yY3X0XDk0zByZZcJOO0h2Kxu14UD4w-GS7p1HPAAWU3SVA-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2012-02-02 16:29                                 ` Jassi Brar
  2012-02-15  8:12                                   ` Felipe Balbi
  2012-02-02 16:30                                 ` [PATCH 2/4] USB: UAC2: Add ACHeader and FormatType descriptor Jassi Brar
                                                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 17+ messages in thread
From: Jassi Brar @ 2012-02-02 16:29 UTC (permalink / raw)
  To: balbi-l0cyMroinI0
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA, greg-U8xfFu+wG4EAvxtiuMwx3w,
	alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw, zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w,
	jaredwiltshire-Re5JQEeQqe8AvxtiuMwx3w, Jassi Brar

The extant USB-Audio function driver complies to UAC_1 spec.
So name the files accordingly, paving way for inclusion of
a new UAC_2 specified driver.

Signed-off-by: Yadi Brar <yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Jassi Brar <jaswinder.singh-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
 drivers/usb/gadget/audio.c                 |    6 +++---
 drivers/usb/gadget/{f_audio.c => f_uac1.c} |    2 +-
 drivers/usb/gadget/{u_audio.c => u_uac1.c} |    4 ++--
 drivers/usb/gadget/{u_audio.h => u_uac1.h} |    2 +-
 4 files changed, 7 insertions(+), 7 deletions(-)
 rename drivers/usb/gadget/{f_audio.c => f_uac1.c} (99%)
 rename drivers/usb/gadget/{u_audio.c => u_uac1.c} (99%)
 rename drivers/usb/gadget/{u_audio.h => u_uac1.h} (94%)

diff --git a/drivers/usb/gadget/audio.c b/drivers/usb/gadget/audio.c
index 9d89ae47..056a2d9 100644
--- a/drivers/usb/gadget/audio.c
+++ b/drivers/usb/gadget/audio.c
@@ -14,7 +14,7 @@
 #include <linux/kernel.h>
 #include <linux/utsname.h>
 
-#include "u_audio.h"
+#include "u_uac1.h"
 
 #define DRIVER_DESC		"Linux USB Audio Gadget"
 #define DRIVER_VERSION		"Dec 18, 2008"
@@ -33,8 +33,8 @@
 #include "config.c"
 #include "epautoconf.c"
 
-#include "u_audio.c"
-#include "f_audio.c"
+#include "u_uac1.c"
+#include "f_uac1.c"
 
 /*-------------------------------------------------------------------------*/
 
diff --git a/drivers/usb/gadget/f_audio.c b/drivers/usb/gadget/f_uac1.c
similarity index 99%
rename from drivers/usb/gadget/f_audio.c
rename to drivers/usb/gadget/f_uac1.c
index ec7ffcd..e4c060f 100644
--- a/drivers/usb/gadget/f_audio.c
+++ b/drivers/usb/gadget/f_uac1.c
@@ -14,7 +14,7 @@
 #include <linux/device.h>
 #include <linux/atomic.h>
 
-#include "u_audio.h"
+#include "u_uac1.h"
 
 #define OUT_EP_MAX_PACKET_SIZE	200
 static int req_buf_size = OUT_EP_MAX_PACKET_SIZE;
diff --git a/drivers/usb/gadget/u_audio.c b/drivers/usb/gadget/u_uac1.c
similarity index 99%
rename from drivers/usb/gadget/u_audio.c
rename to drivers/usb/gadget/u_uac1.c
index 59ffe1e..af98989 100644
--- a/drivers/usb/gadget/u_audio.c
+++ b/drivers/usb/gadget/u_uac1.c
@@ -1,5 +1,5 @@
 /*
- * u_audio.c -- ALSA audio utilities for Gadget stack
+ * u_uac1.c -- ALSA audio utilities for Gadget stack
  *
  * Copyright (C) 2008 Bryan Wu <cooloney-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
  * Copyright (C) 2008 Analog Devices, Inc
@@ -17,7 +17,7 @@
 #include <linux/random.h>
 #include <linux/syscalls.h>
 
-#include "u_audio.h"
+#include "u_uac1.h"
 
 /*
  * This component encapsulates the ALSA devices for USB audio gadget
diff --git a/drivers/usb/gadget/u_audio.h b/drivers/usb/gadget/u_uac1.h
similarity index 94%
rename from drivers/usb/gadget/u_audio.h
rename to drivers/usb/gadget/u_uac1.h
index 08ffce3..18c2e72 100644
--- a/drivers/usb/gadget/u_audio.h
+++ b/drivers/usb/gadget/u_uac1.h
@@ -1,5 +1,5 @@
 /*
- * u_audio.h -- interface to USB gadget "ALSA AUDIO" utilities
+ * u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities
  *
  * Copyright (C) 2008 Bryan Wu <cooloney-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
  * Copyright (C) 2008 Analog Devices, Inc
-- 
1.7.4.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH 2/4] USB: UAC2: Add ACHeader and FormatType descriptor
       [not found]                               ` <CABb+yY3X0XDk0zByZZcJOO0h2Kxu14UD4w-GS7p1HPAAWU3SVA-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  2012-02-02 16:29                                 ` [PATCH 1/4] USB: Gadget: Rename audio function to uac1 Jassi Brar
@ 2012-02-02 16:30                                 ` Jassi Brar
  2012-02-02 16:31                                 ` [PATCH 3/4] USB: Gadget: Audio: Move string IDs to audio.c Jassi Brar
  2012-02-02 16:31                                 ` [PATCH 4/4] USB: Gadget: Add Audio Class 2.0 Driver Jassi Brar
  3 siblings, 0 replies; 17+ messages in thread
From: Jassi Brar @ 2012-02-02 16:30 UTC (permalink / raw)
  To: balbi-l0cyMroinI0
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA, greg-U8xfFu+wG4EAvxtiuMwx3w,
	alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw, zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w,
	jaredwiltshire-Re5JQEeQqe8AvxtiuMwx3w, Jassi Brar

Add missing but needed ACHeader and FormatType descriptor definitions.

Signed-off-by: Yadi Brar <yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Jassi Brar <jaswinder.singh-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
 include/linux/usb/audio-v2.h |   21 +++++++++++++++++++++
 1 files changed, 21 insertions(+), 0 deletions(-)

diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h
index 964cb60..ed13053 100644
--- a/include/linux/usb/audio-v2.h
+++ b/include/linux/usb/audio-v2.h
@@ -43,6 +43,27 @@ static inline bool uac2_control_is_writeable(u32 bmControls, u8 control)
 	return (bmControls >> (control * 2)) & 0x2;
 }
 
+/* 4.7.2 Class-Specific AC Interface Descriptor */
+struct uac2_ac_header_descriptor {
+	__u8  bLength;			/* 9 */
+	__u8  bDescriptorType;		/* USB_DT_CS_INTERFACE */
+	__u8  bDescriptorSubtype;	/* UAC_MS_HEADER */
+	__le16 bcdADC;			/* 0x0200 */
+	__u8  bCategory;
+	__le16 wTotalLength;		/* includes Unit and Terminal desc. */
+	__u8  bmControls;
+} __packed;
+
+/* 2.3.1.6 Type I Format Type Descriptor (Frmts20 final.pdf)*/
+struct uac2_format_type_i_descriptor {
+	__u8  bLength;			/* in bytes: 6 */
+	__u8  bDescriptorType;		/* USB_DT_CS_INTERFACE */
+	__u8  bDescriptorSubtype;	/* FORMAT_TYPE */
+	__u8  bFormatType;		/* FORMAT_TYPE_1 */
+	__u8  bSubslotSize;		/* {1,2,3,4} */
+	__u8  bBitResolution;
+} __packed;
+
 /* 4.7.2.1 Clock Source Descriptor */
 
 struct uac_clock_source_descriptor {
-- 
1.7.4.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH 3/4] USB: Gadget: Audio: Move string IDs to audio.c
       [not found]                               ` <CABb+yY3X0XDk0zByZZcJOO0h2Kxu14UD4w-GS7p1HPAAWU3SVA-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  2012-02-02 16:29                                 ` [PATCH 1/4] USB: Gadget: Rename audio function to uac1 Jassi Brar
  2012-02-02 16:30                                 ` [PATCH 2/4] USB: UAC2: Add ACHeader and FormatType descriptor Jassi Brar
@ 2012-02-02 16:31                                 ` Jassi Brar
  2012-02-02 16:31                                 ` [PATCH 4/4] USB: Gadget: Add Audio Class 2.0 Driver Jassi Brar
  3 siblings, 0 replies; 17+ messages in thread
From: Jassi Brar @ 2012-02-02 16:31 UTC (permalink / raw)
  To: balbi-l0cyMroinI0
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA, greg-U8xfFu+wG4EAvxtiuMwx3w,
	alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw, zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w,
	jaredwiltshire-Re5JQEeQqe8AvxtiuMwx3w, Jassi Brar

Move manufacturer and product string ids into audio.c so
as to be reusable by the new uac2 version of gadget driver.

Signed-off-by: Yadi Brar <yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Jassi Brar <jaswinder.singh-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
 drivers/usb/gadget/audio.c  |   23 +++++++++++++++++++++++
 drivers/usb/gadget/f_uac1.c |   23 -----------------------
 2 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/drivers/usb/gadget/audio.c b/drivers/usb/gadget/audio.c
index 056a2d9..33e9327 100644
--- a/drivers/usb/gadget/audio.c
+++ b/drivers/usb/gadget/audio.c
@@ -33,6 +33,29 @@
 #include "config.c"
 #include "epautoconf.c"
 
+/* string IDs are assigned dynamically */
+
+#define STRING_MANUFACTURER_IDX		0
+#define STRING_PRODUCT_IDX		1
+
+static char manufacturer[50];
+
+static struct usb_string strings_dev[] = {
+	[STRING_MANUFACTURER_IDX].s = manufacturer,
+	[STRING_PRODUCT_IDX].s = DRIVER_DESC,
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_dev = {
+	.language = 0x0409,	/* en-us */
+	.strings = strings_dev,
+};
+
+static struct usb_gadget_strings *audio_strings[] = {
+	&stringtab_dev,
+	NULL,
+};
+
 #include "u_uac1.c"
 #include "f_uac1.c"
 
diff --git a/drivers/usb/gadget/f_uac1.c b/drivers/usb/gadget/f_uac1.c
index e4c060f..1a5dcd5 100644
--- a/drivers/usb/gadget/f_uac1.c
+++ b/drivers/usb/gadget/f_uac1.c
@@ -216,29 +216,6 @@ static struct usb_descriptor_header *f_audio_desc[] __initdata = {
 	NULL,
 };
 
-/* string IDs are assigned dynamically */
-
-#define STRING_MANUFACTURER_IDX		0
-#define STRING_PRODUCT_IDX		1
-
-static char manufacturer[50];
-
-static struct usb_string strings_dev[] = {
-	[STRING_MANUFACTURER_IDX].s = manufacturer,
-	[STRING_PRODUCT_IDX].s = DRIVER_DESC,
-	{  } /* end of list */
-};
-
-static struct usb_gadget_strings stringtab_dev = {
-	.language	= 0x0409,	/* en-us */
-	.strings	= strings_dev,
-};
-
-static struct usb_gadget_strings *audio_strings[] = {
-	&stringtab_dev,
-	NULL,
-};

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

* [PATCH 4/4] USB: Gadget: Add Audio Class 2.0 Driver
       [not found]                               ` <CABb+yY3X0XDk0zByZZcJOO0h2Kxu14UD4w-GS7p1HPAAWU3SVA-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
                                                   ` (2 preceding siblings ...)
  2012-02-02 16:31                                 ` [PATCH 3/4] USB: Gadget: Audio: Move string IDs to audio.c Jassi Brar
@ 2012-02-02 16:31                                 ` Jassi Brar
  3 siblings, 0 replies; 17+ messages in thread
From: Jassi Brar @ 2012-02-02 16:31 UTC (permalink / raw)
  To: balbi-l0cyMroinI0
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA, greg-U8xfFu+wG4EAvxtiuMwx3w,
	alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw, zonque-Re5JQEeQqe8AvxtiuMwx3w,
	yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w,
	jaredwiltshire-Re5JQEeQqe8AvxtiuMwx3w, Jassi Brar

This is a flexible USB Audio Class 2.0 compliant gadget driver that
implements a simple topology with a virtual sound card exposed at
the function side.

The driver doesn't expect any real audio codec to be present on the
function - the audio streams are simply sinked to and sourced from a
virtual ALSA sound card created. The user-space application may choose
to do whatever it wants with the data received from the USB Host and
choose to provide whatever it wants as audio data to the USB Host.

Capture(USB-Out) and Playback(USB-In) can be run at independent
configurations specified via module parameters while loading the driver.

Make this new version as the default selection by a new Kconfig choice.

Signed-off-by: Yadi Brar <yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Jassi Brar <jaswinder.singh-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
 drivers/usb/gadget/Kconfig  |   25 +-
 drivers/usb/gadget/audio.c  |   20 +-
 drivers/usb/gadget/f_uac2.c | 1449 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1485 insertions(+), 9 deletions(-)
 create mode 100644 drivers/usb/gadget/f_uac2.c

diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 7ecb68a..1623b52 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -599,16 +599,29 @@ config USB_AUDIO
 	depends on SND
 	select SND_PCM
 	help
-	  Gadget Audio is compatible with USB Audio Class specification 1.0.
-	  It will include at least one AudioControl interface, zero or more
-	  AudioStream interface and zero or more MIDIStream interface.
-
-	  Gadget Audio will use on-board ALSA (CONFIG_SND) audio card to
-	  playback or capture audio stream.
+	  This Gadget Audio driver is compatible with USB Audio Class
+	  specification 2.0. It implements 1 AudioControl interface,
+	  1 AudioStreaming Interface each for USB-OUT and USB-IN.
+	  Number of channels, sample rate and sample size can be
+	  specified as module parameters.
+	  This driver doesn't expect any real Audio codec to be present
+	  on the device - the audio streams are simply sinked to and
+	  sourced from a virtual ALSA sound card created. The user-space
+	  application may choose to do whatever it wants with the data
+	  received from the USB Host and choose to provide whatever it
+	  wants as audio data to the USB Host.
 
 	  Say "y" to link the driver statically, or "m" to build a
 	  dynamically linked module called "g_audio".
 
+config GADGET_UAC1
+	bool "UAC 1.0 (Legacy)"
+	depends on USB_AUDIO
+	help
+	  If you instead want older UAC Spec-1.0 driver that also has audio
+	  paths hardwired to the Audio codec chip on-board and doesn't work
+	  without one.
+
 config USB_ETH
 	tristate "Ethernet Gadget (with CDC Ethernet support)"
 	depends on NET
diff --git a/drivers/usb/gadget/audio.c b/drivers/usb/gadget/audio.c
index 33e9327..9889924 100644
--- a/drivers/usb/gadget/audio.c
+++ b/drivers/usb/gadget/audio.c
@@ -14,10 +14,8 @@
 #include <linux/kernel.h>
 #include <linux/utsname.h>
 
-#include "u_uac1.h"
-
 #define DRIVER_DESC		"Linux USB Audio Gadget"
-#define DRIVER_VERSION		"Dec 18, 2008"
+#define DRIVER_VERSION		"Feb 2, 2012"
 
 /*-------------------------------------------------------------------------*/
 
@@ -56,8 +54,13 @@ static struct usb_gadget_strings *audio_strings[] = {
 	NULL,
 };
 
+#ifdef CONFIG_GADGET_UAC1
+#include "u_uac1.h"
 #include "u_uac1.c"
 #include "f_uac1.c"
+#else
+#include "f_uac2.c"
+#endif
 
 /*-------------------------------------------------------------------------*/
 
@@ -77,9 +80,15 @@ static struct usb_device_descriptor device_desc = {
 
 	.bcdUSB =		__constant_cpu_to_le16(0x200),
 
+#ifdef CONFIG_GADGET_UAC1
 	.bDeviceClass =		USB_CLASS_PER_INTERFACE,
 	.bDeviceSubClass =	0,
 	.bDeviceProtocol =	0,
+#else
+	.bDeviceClass =		USB_CLASS_MISC,
+	.bDeviceSubClass =	0x02,
+	.bDeviceProtocol =	0x01,
+#endif
 	/* .bMaxPacketSize0 = f(hardware) */
 
 	/* Vendor and product id defaults change according to what configs
@@ -131,6 +140,9 @@ static struct usb_configuration audio_config_driver = {
 	.bConfigurationValue	= 1,
 	/* .iConfiguration = DYNAMIC */
 	.bmAttributes		= USB_CONFIG_ATT_SELFPOWER,
+#ifndef CONFIG_GADGET_UAC1
+	.unbind			= uac2_unbind_config,
+#endif
 };
 
 /*-------------------------------------------------------------------------*/
@@ -180,7 +192,9 @@ fail:
 
 static int __exit audio_unbind(struct usb_composite_dev *cdev)
 {
+#ifdef CONFIG_GADGET_UAC1
 	gaudio_cleanup();
+#endif
 	return 0;
 }
 
diff --git a/drivers/usb/gadget/f_uac2.c b/drivers/usb/gadget/f_uac2.c
new file mode 100644
index 0000000..e7cc4de
--- /dev/null
+++ b/drivers/usb/gadget/f_uac2.c
@@ -0,0 +1,1449 @@
+/*
+ * f_uac2.c -- USB Audio Class 2.0 Function
+ *
+ * Copyright (C) 2011
+ *    Yadwinder Singh (yadi.brar01-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org)
+ *    Jaswinder Singh (jaswinder.singh-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+/* Playback(USB-IN) Default Stereo - Fl/Fr */
+static int p_chmask = 0x3;
+module_param(p_chmask, uint, S_IRUGO);
+MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
+
+/* Playback Default 48 KHz */
+static int p_srate = 48000;
+module_param(p_srate, uint, S_IRUGO);
+MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
+
+/* Playback Default 16bits/sample */
+static int p_ssize = 2;
+module_param(p_ssize, uint, S_IRUGO);
+MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
+
+/* Capture(USB-OUT) Default Stereo - Fl/Fr */
+static int c_chmask = 0x3;
+module_param(c_chmask, uint, S_IRUGO);
+MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
+
+/* Capture Default 64 KHz */
+static int c_srate = 64000;
+module_param(c_srate, uint, S_IRUGO);
+MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
+
+/* Capture Default 16bits/sample */
+static int c_ssize = 2;
+module_param(c_ssize, uint, S_IRUGO);
+MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
+
+#define DMA_ADDR_INVALID	(~(dma_addr_t)0)
+
+#define ALT_SET(x, a)	do {(x) &= ~0xff; (x) |= (a); } while (0)
+#define ALT_GET(x)	((x) & 0xff)
+#define INTF_SET(x, i)	do {(x) &= 0xff; (x) |= ((i) << 8); } while (0)
+#define INTF_GET(x)	((x >> 8) & 0xff)
+
+/* Keep everyone on toes */
+#define USB_XFERS	2
+
+/*
+ * The driver implements a simple UAC_2 topology.
+ * USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture
+ * ALSA_Playback -> IT_2 -> OT_4 -> USB-IN
+ * Capture and Playback sampling rates are independently
+ *  controlled by two clock sources :
+ *    CLK_5 := c_srate, and CLK_6 := p_srate
+ */
+#define USB_OUT_IT_ID	1
+#define IO_IN_IT_ID	2
+#define IO_OUT_OT_ID	3
+#define USB_IN_OT_ID	4
+#define USB_OUT_CLK_ID	5
+#define USB_IN_CLK_ID	6
+
+#define CONTROL_ABSENT	0
+#define CONTROL_RDONLY	1
+#define CONTROL_RDWR	3
+
+#define CLK_FREQ_CTRL	0
+#define CLK_VLD_CTRL	2
+
+#define COPY_CTRL	0
+#define CONN_CTRL	2
+#define OVRLD_CTRL	4
+#define CLSTR_CTRL	6
+#define UNFLW_CTRL	8
+#define OVFLW_CTRL	10
+
+const char *uac2_name = "snd_uac2";
+
+struct uac2_req {
+	struct uac2_rtd_params *pp; /* parent param */
+	struct usb_request *req;
+};
+
+struct uac2_rtd_params {
+	bool ep_enabled; /* if the ep is enabled */
+	/* Size of the ring buffer */
+	size_t dma_bytes;
+	unsigned char *dma_area;
+
+	struct snd_pcm_substream *ss;
+
+	/* Ring buffer */
+	ssize_t hw_ptr;
+
+	void *rbuf;
+
+	size_t period_size;
+
+	unsigned max_psize;
+	struct uac2_req ureq[USB_XFERS];
+
+	spinlock_t lock;
+};
+
+struct snd_uac2_chip {
+	struct platform_device pdev;
+	struct platform_driver pdrv;
+
+	struct uac2_rtd_params p_prm;
+	struct uac2_rtd_params c_prm;
+
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+};
+
+#define BUFF_SIZE_MAX	(PAGE_SIZE * 16)
+#define PRD_SIZE_MAX	PAGE_SIZE
+#define MIN_PERIODS	4
+
+static struct snd_pcm_hardware uac2_pcm_hardware = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER
+		 | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
+		 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
+	.rates = SNDRV_PCM_RATE_CONTINUOUS,
+	.periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX,
+	.buffer_bytes_max = BUFF_SIZE_MAX,
+	.period_bytes_max = PRD_SIZE_MAX,
+	.periods_min = MIN_PERIODS,
+};
+
+struct audio_dev {
+	/* Currently active {Interface[15:8] | AltSettings[7:0]} */
+	__u16 ac_alt, as_out_alt, as_in_alt;
+
+	struct usb_ep *in_ep, *out_ep;
+	struct usb_function func;
+
+	/* The ALSA Sound Card it represents on the USB-Client side */
+	struct snd_uac2_chip uac2;
+};
+
+static struct audio_dev *agdev_g;
+
+static inline
+struct audio_dev *func_to_agdev(struct usb_function *f)
+{
+	return container_of(f, struct audio_dev, func);
+}
+
+static inline
+struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u)
+{
+	return container_of(u, struct audio_dev, uac2);
+}
+
+static inline
+struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p)
+{
+	return container_of(p, struct snd_uac2_chip, pdev);
+}
+
+static inline
+struct snd_uac2_chip *prm_to_uac2(struct uac2_rtd_params *r)
+{
+	struct snd_uac2_chip *uac2 = container_of(r,
+					struct snd_uac2_chip, c_prm);
+
+	if (&uac2->c_prm != r)
+		uac2 = container_of(r, struct snd_uac2_chip, p_prm);
+
+	return uac2;
+}
+
+static inline
+uint num_channels(uint chanmask)
+{
+	uint num = 0;
+
+	while (chanmask) {
+		num += (chanmask & 1);
+		chanmask >>= 1;
+	}
+
+	return num;
+}
+
+static void
+agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	unsigned pending;
+	unsigned long flags;
+	bool update_alsa = false;
+	unsigned char *src, *dst;
+	int status = req->status;
+	struct uac2_req *ur = req->context;
+	struct snd_pcm_substream *substream;
+	struct uac2_rtd_params *prm = ur->pp;
+	struct snd_uac2_chip *uac2 = prm_to_uac2(prm);
+
+	/* i/f shutting down */
+	if (!prm->ep_enabled)
+		return;
+
+	/*
+	 * We can't really do much about bad xfers.
+	 * Afterall, the ISOCH xfers could fail legitimately.
+	 */
+	if (status)
+		pr_debug("%s: iso_complete status(%d) %d/%d\n",
+			__func__, status, req->actual, req->length);
+
+	substream = prm->ss;
+
+	/* Do nothing if ALSA isn't active */
+	if (!substream)
+		goto exit;
+
+	spin_lock_irqsave(&prm->lock, flags);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		src = prm->dma_area + prm->hw_ptr;
+		req->actual = req->length;
+		dst = req->buf;
+	} else {
+		dst = prm->dma_area + prm->hw_ptr;
+		src = req->buf;
+	}
+
+	pending = prm->hw_ptr % prm->period_size;
+	pending += req->actual;
+	if (pending >= prm->period_size)
+		update_alsa = true;
+
+	prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes;
+
+	spin_unlock_irqrestore(&prm->lock, flags);
+
+	/* Pack USB load in ALSA ring buffer */
+	memcpy(dst, src, req->actual);
+exit:
+	if (usb_ep_queue(ep, req, GFP_ATOMIC))
+		dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
+
+	if (update_alsa)
+		snd_pcm_period_elapsed(substream);
+
+	return;
+}
+
+static int
+uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
+	struct audio_dev *agdev = uac2_to_agdev(uac2);
+	struct uac2_rtd_params *prm;
+	unsigned long flags;
+	struct usb_ep *ep;
+	int err = 0;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		ep = agdev->in_ep;
+		prm = &uac2->p_prm;
+	} else {
+		ep = agdev->out_ep;
+		prm = &uac2->c_prm;
+	}
+
+	spin_lock_irqsave(&prm->lock, flags);
+
+	/* Reset */
+	prm->hw_ptr = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		prm->ss = substream;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		prm->ss = NULL;
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+	spin_unlock_irqrestore(&prm->lock, flags);
+
+	/* Clear buffer after Play stops */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss)
+		memset(prm->rbuf, 0, prm->max_psize * USB_XFERS);
+
+	return err;
+}
+
+static snd_pcm_uframes_t uac2_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
+	struct uac2_rtd_params *prm;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		prm = &uac2->p_prm;
+	else
+		prm = &uac2->c_prm;
+
+	return bytes_to_frames(substream->runtime, prm->hw_ptr);
+}
+
+static int uac2_pcm_hw_params(struct snd_pcm_substream *substream,
+			       struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
+	struct uac2_rtd_params *prm;
+	int err;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		prm = &uac2->p_prm;
+	else
+		prm = &uac2->c_prm;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	if (err >= 0) {
+		prm->dma_bytes = substream->runtime->dma_bytes;
+		prm->dma_area = substream->runtime->dma_area;
+		prm->period_size = params_period_bytes(hw_params);
+	}
+
+	return err;
+}
+
+static int uac2_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
+	struct uac2_rtd_params *prm;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		prm = &uac2->p_prm;
+	else
+		prm = &uac2->c_prm;
+
+	prm->dma_area = NULL;
+	prm->dma_bytes = 0;
+	prm->period_size = 0;
+
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int uac2_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = uac2_pcm_hardware;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		spin_lock_init(&uac2->p_prm.lock);
+		runtime->hw.rate_min = p_srate;
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; /* ! p_ssize ! */
+		runtime->hw.channels_min = num_channels(p_chmask);
+		runtime->hw.period_bytes_min = 2 * uac2->p_prm.max_psize
+						/ runtime->hw.periods_min;
+	} else {
+		spin_lock_init(&uac2->c_prm.lock);
+		runtime->hw.rate_min = c_srate;
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; /* ! c_ssize ! */
+		runtime->hw.channels_min = num_channels(c_chmask);
+		runtime->hw.period_bytes_min = 2 * uac2->c_prm.max_psize
+						/ runtime->hw.periods_min;
+	}
+
+	runtime->hw.rate_max = runtime->hw.rate_min;
+	runtime->hw.channels_max = runtime->hw.channels_min;
+
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+	return 0;
+}
+
+/* ALSA cries without these function pointers */
+static int uac2_pcm_null(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static struct snd_pcm_ops uac2_pcm_ops = {
+	.open = uac2_pcm_open,
+	.close = uac2_pcm_null,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = uac2_pcm_hw_params,
+	.hw_free = uac2_pcm_hw_free,
+	.trigger = uac2_pcm_trigger,
+	.pointer = uac2_pcm_pointer,
+	.prepare = uac2_pcm_null,
+};
+
+static int __devinit snd_uac2_probe(struct platform_device *pdev)
+{
+	struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev);
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	int err;
+
+	/* Choose any slot, with no id */
+	err = snd_card_create(-1, NULL, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	uac2->card = card;
+
+	/*
+	 * Create first PCM device
+	 * Create a substream only for non-zero channel streams
+	 */
+	err = snd_pcm_new(uac2->card, "UAC2 PCM", 0,
+			       p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm);
+	if (err < 0)
+		goto snd_fail;
+
+	strcpy(pcm->name, "UAC2 PCM");
+	pcm->private_data = uac2;
+
+	uac2->pcm = pcm;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops);
+
+	strcpy(card->driver, "UAC2_Gadget");
+	strcpy(card->shortname, "UAC2_Gadget");
+	sprintf(card->longname, "UAC2_Gadget %i", pdev->id);
+
+	snd_card_set_dev(card, &pdev->dev);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+		snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
+
+	err = snd_card_register(card);
+	if (!err) {
+		platform_set_drvdata(pdev, card);
+		return 0;
+	}
+
+snd_fail:
+	snd_card_free(card);
+
+	uac2->pcm = NULL;
+	uac2->card = NULL;
+
+	return err;
+}
+
+static int __devexit snd_uac2_remove(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+
+	platform_set_drvdata(pdev, NULL);
+
+	if (card)
+		return snd_card_free(card);
+
+	return 0;
+}
+
+static int alsa_uac2_init(struct audio_dev *agdev)
+{
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	int err;
+
+	uac2->pdrv.probe = snd_uac2_probe;
+	uac2->pdrv.remove = snd_uac2_remove;
+	uac2->pdrv.driver.name = uac2_name;
+
+	uac2->pdev.id = 0;
+	uac2->pdev.name = uac2_name;
+
+	/* Register snd_uac2 driver */
+	err = platform_driver_register(&uac2->pdrv);
+	if (err)
+		return err;
+
+	/* Register snd_uac2 device */
+	err = platform_device_register(&uac2->pdev);
+	if (err)
+		platform_driver_unregister(&uac2->pdrv);
+
+	return err;
+}
+
+static void alsa_uac2_exit(struct audio_dev *agdev)
+{
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+
+	platform_driver_unregister(&uac2->pdrv);
+	platform_device_unregister(&uac2->pdev);
+}
+
+
+/* --------- USB Function Interface ------------- */
+
+enum {
+	STR_ASSOC,
+	STR_IF_CTRL,
+	STR_CLKSRC_IN,
+	STR_CLKSRC_OUT,
+	STR_USB_IT,
+	STR_IO_IT,
+	STR_USB_OT,
+	STR_IO_OT,
+	STR_AS_OUT_ALT0,
+	STR_AS_OUT_ALT1,
+	STR_AS_IN_ALT0,
+	STR_AS_IN_ALT1,
+};
+
+static const char ifassoc[] = "Source/Sink";
+static const char ifctrl[] = "Topology Control";
+static char clksrc_in[8];
+static char clksrc_out[8];
+static const char usb_it[] = "USBH Out";
+static const char io_it[] = "USBD Out";
+static const char usb_ot[] = "USBH In";
+static const char io_ot[] = "USBD In";
+static const char out_alt0[] = "Playback Inactive";
+static const char out_alt1[] = "Playback Active";
+static const char in_alt0[] = "Capture Inactive";
+static const char in_alt1[] = "Capture Active";
+
+static struct usb_string strings_fn[] = {
+	[STR_ASSOC].s = ifassoc,
+	[STR_IF_CTRL].s = ifctrl,
+	[STR_CLKSRC_IN].s = clksrc_in,
+	[STR_CLKSRC_OUT].s = clksrc_out,
+	[STR_USB_IT].s = usb_it,
+	[STR_IO_IT].s = io_it,
+	[STR_USB_OT].s = usb_ot,
+	[STR_IO_OT].s = io_ot,
+	[STR_AS_OUT_ALT0].s = out_alt0,
+	[STR_AS_OUT_ALT1].s = out_alt1,
+	[STR_AS_IN_ALT0].s = in_alt0,
+	[STR_AS_IN_ALT1].s = in_alt1,
+	{ },
+};
+
+static struct usb_gadget_strings str_fn = {
+	.language = 0x0409,	/* en-us */
+	.strings = strings_fn,
+};
+
+static struct usb_gadget_strings *fn_strings[] = {
+	&str_fn,
+	NULL,
+};
+
+static struct usb_qualifier_descriptor devqual_desc = {
+	.bLength = sizeof devqual_desc,
+	.bDescriptorType = USB_DT_DEVICE_QUALIFIER,
+
+	.bcdUSB = cpu_to_le16(0x200),
+	.bDeviceClass = USB_CLASS_MISC,
+	.bDeviceSubClass = 0x02,
+	.bDeviceProtocol = 0x01,
+	.bNumConfigurations = 1,
+	.bRESERVED = 0,
+};
+
+static struct usb_interface_assoc_descriptor iad_desc = {
+	.bLength = sizeof iad_desc,
+	.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
+
+	.bFirstInterface = 0,
+	.bInterfaceCount = 3,
+	.bFunctionClass = USB_CLASS_AUDIO,
+	.bFunctionSubClass = UAC2_FUNCTION_SUBCLASS_UNDEFINED,
+	.bFunctionProtocol = UAC_VERSION_2,
+};
+
+/* Audio Control Interface */
+static struct usb_interface_descriptor std_ac_if_desc = {
+	.bLength = sizeof std_ac_if_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Clock source for IN traffic */
+struct uac_clock_source_descriptor in_clk_src_desc = {
+	.bLength = sizeof in_clk_src_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
+	.bClockID = USB_IN_CLK_ID,
+	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
+	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+	.bAssocTerminal = 0,
+};
+
+/* Clock source for OUT traffic */
+struct uac_clock_source_descriptor out_clk_src_desc = {
+	.bLength = sizeof out_clk_src_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
+	.bClockID = USB_OUT_CLK_ID,
+	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
+	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+	.bAssocTerminal = 0,
+};
+
+/* Input Terminal for USB_OUT */
+struct uac2_input_terminal_descriptor usb_out_it_desc = {
+	.bLength = sizeof usb_out_it_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_INPUT_TERMINAL,
+	.bTerminalID = USB_OUT_IT_ID,
+	.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
+	.bAssocTerminal = 0,
+	.bCSourceID = USB_OUT_CLK_ID,
+	.iChannelNames = 0,
+	.bmControls = (CONTROL_RDWR << COPY_CTRL),
+};
+
+/* Input Terminal for I/O-In */
+struct uac2_input_terminal_descriptor io_in_it_desc = {
+	.bLength = sizeof io_in_it_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_INPUT_TERMINAL,
+	.bTerminalID = IO_IN_IT_ID,
+	.wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_UNDEFINED),
+	.bAssocTerminal = 0,
+	.bCSourceID = USB_IN_CLK_ID,
+	.iChannelNames = 0,
+	.bmControls = (CONTROL_RDWR << COPY_CTRL),
+};
+
+/* Ouput Terminal for USB_IN */
+struct uac2_output_terminal_descriptor usb_in_ot_desc = {
+	.bLength = sizeof usb_in_ot_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
+	.bTerminalID = USB_IN_OT_ID,
+	.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
+	.bAssocTerminal = 0,
+	.bSourceID = IO_IN_IT_ID,
+	.bCSourceID = USB_IN_CLK_ID,
+	.bmControls = (CONTROL_RDWR << COPY_CTRL),
+};
+
+/* Ouput Terminal for I/O-Out */
+struct uac2_output_terminal_descriptor io_out_ot_desc = {
+	.bLength = sizeof io_out_ot_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
+	.bTerminalID = IO_OUT_OT_ID,
+	.wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_UNDEFINED),
+	.bAssocTerminal = 0,
+	.bSourceID = USB_OUT_IT_ID,
+	.bCSourceID = USB_OUT_CLK_ID,
+	.bmControls = (CONTROL_RDWR << COPY_CTRL),
+};
+
+struct uac2_ac_header_descriptor ac_hdr_desc = {
+	.bLength = sizeof ac_hdr_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_MS_HEADER,
+	.bcdADC = cpu_to_le16(0x200),
+	.bCategory = UAC2_FUNCTION_IO_BOX,
+	.wTotalLength = sizeof in_clk_src_desc + sizeof out_clk_src_desc
+			 + sizeof usb_out_it_desc + sizeof io_in_it_desc
+			+ sizeof usb_in_ot_desc + sizeof io_out_ot_desc,
+	.bmControls = 0,
+};
+
+/* Audio Streaming OUT Interface - Alt0 */
+static struct usb_interface_descriptor std_as_out_if0_desc = {
+	.bLength = sizeof std_as_out_if0_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Streaming OUT Interface - Alt1 */
+static struct usb_interface_descriptor std_as_out_if1_desc = {
+	.bLength = sizeof std_as_out_if1_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 1,
+	.bNumEndpoints = 1,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Stream OUT Intface Desc */
+struct uac2_as_header_descriptor as_out_hdr_desc = {
+	.bLength = sizeof as_out_hdr_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_AS_GENERAL,
+	.bTerminalLink = USB_OUT_IT_ID,
+	.bmControls = 0,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+	.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
+	.iChannelNames = 0,
+};
+
+/* Audio USB_OUT Format */
+struct uac2_format_type_i_descriptor as_out_fmt1_desc = {
+	.bLength = sizeof as_out_fmt1_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype = UAC_FORMAT_TYPE,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+};
+
+/* STD AS ISO OUT Endpoint */
+struct usb_endpoint_descriptor fs_epout_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bEndpointAddress = USB_DIR_OUT,
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	.bInterval = 1,
+};
+
+struct usb_endpoint_descriptor hs_epout_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	.bInterval = 4,
+};
+
+/* CS AS ISO OUT Endpoint */
+static struct uac2_iso_endpoint_descriptor as_iso_out_desc = {
+	.bLength = sizeof as_iso_out_desc,
+	.bDescriptorType = USB_DT_CS_ENDPOINT,
+
+	.bDescriptorSubtype = UAC_EP_GENERAL,
+	.bmAttributes = 0,
+	.bmControls = 0,
+	.bLockDelayUnits = 0,
+	.wLockDelay = 0,
+};
+
+/* Audio Streaming IN Interface - Alt0 */
+static struct usb_interface_descriptor std_as_in_if0_desc = {
+	.bLength = sizeof std_as_in_if0_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Streaming IN Interface - Alt1 */
+static struct usb_interface_descriptor std_as_in_if1_desc = {
+	.bLength = sizeof std_as_in_if1_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 1,
+	.bNumEndpoints = 1,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Stream IN Intface Desc */
+struct uac2_as_header_descriptor as_in_hdr_desc = {
+	.bLength = sizeof as_in_hdr_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_AS_GENERAL,
+	.bTerminalLink = USB_IN_OT_ID,
+	.bmControls = 0,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+	.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
+	.iChannelNames = 0,
+};
+
+/* Audio USB_IN Format */
+struct uac2_format_type_i_descriptor as_in_fmt1_desc = {
+	.bLength = sizeof as_in_fmt1_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype = UAC_FORMAT_TYPE,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+};
+
+/* STD AS ISO IN Endpoint */
+struct usb_endpoint_descriptor fs_epin_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bEndpointAddress = USB_DIR_IN,
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	.bInterval = 1,
+};
+
+struct usb_endpoint_descriptor hs_epin_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	.bInterval = 4,
+};
+
+/* CS AS ISO IN Endpoint */
+static struct uac2_iso_endpoint_descriptor as_iso_in_desc = {
+	.bLength = sizeof as_iso_in_desc,
+	.bDescriptorType = USB_DT_CS_ENDPOINT,
+
+	.bDescriptorSubtype = UAC_EP_GENERAL,
+	.bmAttributes = 0,
+	.bmControls = 0,
+	.bLockDelayUnits = 0,
+	.wLockDelay = 0,
+};
+
+static struct usb_descriptor_header *fs_audio_desc[] = {
+	(struct usb_descriptor_header *)&iad_desc,
+	(struct usb_descriptor_header *)&std_ac_if_desc,
+
+	(struct usb_descriptor_header *)&ac_hdr_desc,
+	(struct usb_descriptor_header *)&in_clk_src_desc,
+	(struct usb_descriptor_header *)&out_clk_src_desc,
+	(struct usb_descriptor_header *)&usb_out_it_desc,
+	(struct usb_descriptor_header *)&io_in_it_desc,
+	(struct usb_descriptor_header *)&usb_in_ot_desc,
+	(struct usb_descriptor_header *)&io_out_ot_desc,
+
+	(struct usb_descriptor_header *)&std_as_out_if0_desc,
+	(struct usb_descriptor_header *)&std_as_out_if1_desc,
+
+	(struct usb_descriptor_header *)&as_out_hdr_desc,
+	(struct usb_descriptor_header *)&as_out_fmt1_desc,
+	(struct usb_descriptor_header *)&fs_epout_desc,
+	(struct usb_descriptor_header *)&as_iso_out_desc,
+
+	(struct usb_descriptor_header *)&std_as_in_if0_desc,
+	(struct usb_descriptor_header *)&std_as_in_if1_desc,
+
+	(struct usb_descriptor_header *)&as_in_hdr_desc,
+	(struct usb_descriptor_header *)&as_in_fmt1_desc,
+	(struct usb_descriptor_header *)&fs_epin_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *hs_audio_desc[] = {
+	(struct usb_descriptor_header *)&iad_desc,
+	(struct usb_descriptor_header *)&std_ac_if_desc,
+
+	(struct usb_descriptor_header *)&ac_hdr_desc,
+	(struct usb_descriptor_header *)&in_clk_src_desc,
+	(struct usb_descriptor_header *)&out_clk_src_desc,
+	(struct usb_descriptor_header *)&usb_out_it_desc,
+	(struct usb_descriptor_header *)&io_in_it_desc,
+	(struct usb_descriptor_header *)&usb_in_ot_desc,
+	(struct usb_descriptor_header *)&io_out_ot_desc,
+
+	(struct usb_descriptor_header *)&std_as_out_if0_desc,
+	(struct usb_descriptor_header *)&std_as_out_if1_desc,
+
+	(struct usb_descriptor_header *)&as_out_hdr_desc,
+	(struct usb_descriptor_header *)&as_out_fmt1_desc,
+	(struct usb_descriptor_header *)&hs_epout_desc,
+	(struct usb_descriptor_header *)&as_iso_out_desc,
+
+	(struct usb_descriptor_header *)&std_as_in_if0_desc,
+	(struct usb_descriptor_header *)&std_as_in_if1_desc,
+
+	(struct usb_descriptor_header *)&as_in_hdr_desc,
+	(struct usb_descriptor_header *)&as_in_fmt1_desc,
+	(struct usb_descriptor_header *)&hs_epin_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+struct cntrl_cur_lay3 {
+	__u32	dCUR;
+};
+
+struct cntrl_range_lay3 {
+	__u16	wNumSubRanges;
+	__u32	dMIN;
+	__u32	dMAX;
+	__u32	dRES;
+} __packed;
+
+static inline void
+free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep)
+{
+	struct snd_uac2_chip *uac2 = prm_to_uac2(prm);
+	int i;
+
+	prm->ep_enabled = false;
+
+	for (i = 0; i < USB_XFERS; i++) {
+		if (prm->ureq[i].req) {
+			usb_ep_dequeue(ep, prm->ureq[i].req);
+			usb_ep_free_request(ep, prm->ureq[i].req);
+			prm->ureq[i].req = NULL;
+		}
+	}
+
+	if (usb_ep_disable(ep))
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+}
+
+static int __init
+afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
+{
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	struct usb_composite_dev *cdev = cfg->cdev;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct uac2_rtd_params *prm;
+	int ret;
+
+	ret = usb_interface_id(cfg, fn);
+	if (ret < 0) {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+	std_ac_if_desc.bInterfaceNumber = ret;
+	ALT_SET(agdev->ac_alt, 0);
+	INTF_SET(agdev->ac_alt, ret);
+
+	ret = usb_interface_id(cfg, fn);
+	if (ret < 0) {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+	std_as_out_if0_desc.bInterfaceNumber = ret;
+	std_as_out_if1_desc.bInterfaceNumber = ret;
+	ALT_SET(agdev->as_out_alt, 0);
+	INTF_SET(agdev->as_out_alt, ret);
+
+	ret = usb_interface_id(cfg, fn);
+	if (ret < 0) {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+	std_as_in_if0_desc.bInterfaceNumber = ret;
+	std_as_in_if1_desc.bInterfaceNumber = ret;
+	ALT_SET(agdev->as_in_alt, 0);
+	INTF_SET(agdev->as_in_alt, ret);
+
+	agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
+	if (!agdev->out_ep)
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+	agdev->out_ep->driver_data = agdev;
+
+	agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
+	if (!agdev->in_ep)
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+	agdev->in_ep->driver_data = agdev;
+
+	hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
+	hs_epout_desc.wMaxPacketSize = fs_epout_desc.wMaxPacketSize;
+	hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
+	hs_epin_desc.wMaxPacketSize = fs_epin_desc.wMaxPacketSize;
+
+	fn->descriptors = usb_copy_descriptors(fs_audio_desc);
+	if (gadget_is_dualspeed(gadget))
+		fn->hs_descriptors = usb_copy_descriptors(hs_audio_desc);
+
+	prm = &agdev->uac2.c_prm;
+	prm->max_psize = hs_epout_desc.wMaxPacketSize;
+	prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL);
+	if (!prm->rbuf) {
+		prm->max_psize = 0;
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+	}
+
+	prm = &agdev->uac2.p_prm;
+	prm->max_psize = hs_epin_desc.wMaxPacketSize;
+	prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL);
+	if (!prm->rbuf) {
+		prm->max_psize = 0;
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+	}
+
+	return alsa_uac2_init(agdev);
+}
+
+static void
+afunc_unbind(struct usb_configuration *cfg, struct usb_function *fn)
+{
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct usb_composite_dev *cdev = cfg->cdev;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct uac2_rtd_params *prm;
+
+	alsa_uac2_exit(agdev);
+
+	prm = &agdev->uac2.p_prm;
+	kfree(prm->rbuf);
+
+	prm = &agdev->uac2.c_prm;
+	kfree(prm->rbuf);
+
+	if (gadget_is_dualspeed(gadget))
+		usb_free_descriptors(fn->hs_descriptors);
+	usb_free_descriptors(fn->descriptors);
+
+	if (agdev->in_ep)
+		agdev->in_ep->driver_data = NULL;
+	if (agdev->out_ep)
+		agdev->out_ep->driver_data = NULL;
+}
+
+static int
+afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
+{
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct usb_request *req;
+	struct usb_ep *ep;
+	struct uac2_rtd_params *prm;
+	int i;
+
+	/* No i/f has more than 2 alt settings */
+	if (alt > 1) {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	if (intf == INTF_GET(agdev->ac_alt)) {
+		/* Control I/f has only 1 AltSetting - 0 */
+		if (alt) {
+			dev_err(&uac2->pdev.dev,
+				"%s:%d Error!\n", __func__, __LINE__);
+			return -EINVAL;
+		}
+		return 0;
+	}
+
+	if (intf == INTF_GET(agdev->as_out_alt)) {
+		ep = agdev->out_ep;
+		prm = &uac2->c_prm;
+		config_ep_by_speed(gadget, fn, ep);
+		ALT_SET(agdev->as_out_alt, alt);
+	} else if (intf == INTF_GET(agdev->as_in_alt)) {
+		ep = agdev->in_ep;
+		prm = &uac2->p_prm;
+		config_ep_by_speed(gadget, fn, ep);
+		ALT_SET(agdev->as_in_alt, alt);
+	} else {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	if (alt == 0) {
+		free_ep(prm, ep);
+		return 0;
+	}
+
+	prm->ep_enabled = true;
+	usb_ep_enable(ep);
+
+	for (i = 0; i < USB_XFERS; i++) {
+		if (prm->ureq[i].req) {
+			if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
+				dev_err(&uac2->pdev.dev, "%d Error!\n",
+					__LINE__);
+			continue;
+		}
+
+		req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+		if (req == NULL) {
+			dev_err(&uac2->pdev.dev,
+				"%s:%d Error!\n", __func__, __LINE__);
+			return -EINVAL;
+		}
+
+		prm->ureq[i].req = req;
+		prm->ureq[i].pp = prm;
+
+		req->zero = 0;
+		req->dma = DMA_ADDR_INVALID;
+		req->context = &prm->ureq[i];
+		req->length = prm->max_psize;
+		req->complete =	agdev_iso_complete;
+		req->buf = prm->rbuf + i * req->length;
+
+		if (usb_ep_queue(ep, req, GFP_ATOMIC))
+			dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
+	}
+
+	return 0;
+}
+
+static int
+afunc_get_alt(struct usb_function *fn, unsigned intf)
+{
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+
+	if (intf == INTF_GET(agdev->ac_alt))
+		return ALT_GET(agdev->ac_alt);
+	else if (intf == INTF_GET(agdev->as_out_alt))
+		return ALT_GET(agdev->as_out_alt);
+	else if (intf == INTF_GET(agdev->as_in_alt))
+		return ALT_GET(agdev->as_in_alt);
+	else
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Invalid Interface %d!\n",
+			__func__, __LINE__, intf);
+
+	return -EINVAL;
+}
+
+static void
+afunc_disable(struct usb_function *fn)
+{
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+
+	free_ep(&uac2->p_prm, agdev->in_ep);
+	ALT_SET(agdev->as_in_alt, 0);
+
+	free_ep(&uac2->c_prm, agdev->out_ep);
+	ALT_SET(agdev->as_out_alt, 0);
+}
+
+static int
+in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct usb_request *req = fn->config->cdev->req;
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	u16 w_length = le16_to_cpu(cr->wLength);
+	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;
+	int value = -EOPNOTSUPP;
+
+	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+		struct cntrl_cur_lay3 c;
+
+		if (entity_id == USB_IN_CLK_ID)
+			c.dCUR = p_srate;
+		else if (entity_id == USB_OUT_CLK_ID)
+			c.dCUR = c_srate;
+
+		value = min_t(unsigned, w_length, sizeof c);
+		memcpy(req->buf, &c, value);
+	} else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) {
+		*(u8 *)req->buf = 1;
+		value = min_t(unsigned, w_length, 1);
+	} else {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d control_selector=%d TODO!\n",
+			__func__, __LINE__, control_selector);
+	}
+
+	return value;
+}
+
+static int
+in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct usb_request *req = fn->config->cdev->req;
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	u16 w_length = le16_to_cpu(cr->wLength);
+	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;
+	struct cntrl_range_lay3 r;
+	int value = -EOPNOTSUPP;
+
+	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+		if (entity_id == USB_IN_CLK_ID)
+			r.dMIN = p_srate;
+		else if (entity_id == USB_OUT_CLK_ID)
+			r.dMIN = c_srate;
+		else
+			return -EOPNOTSUPP;
+
+		r.dMAX = r.dMIN;
+		r.dRES = 0;
+		r.wNumSubRanges = 1;
+
+		value = min_t(unsigned, w_length, sizeof r);
+		memcpy(req->buf, &r, value);
+	} else {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d control_selector=%d TODO!\n",
+			__func__, __LINE__, control_selector);
+	}
+
+	return value;
+}
+
+static int
+ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	if (cr->bRequest == UAC2_CS_CUR)
+		return in_rq_cur(fn, cr);
+	else if (cr->bRequest == UAC2_CS_RANGE)
+		return in_rq_range(fn, cr);
+	else
+		return -EOPNOTSUPP;
+}
+
+static int
+out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	u16 w_length = le16_to_cpu(cr->wLength);
+	u16 w_value = le16_to_cpu(cr->wValue);
+	u8 control_selector = w_value >> 8;
+
+	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
+		return w_length;
+
+	return -EOPNOTSUPP;
+}
+
+static int
+setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	u16 w_index = le16_to_cpu(cr->wIndex);
+	u8 intf = w_index & 0xff;
+
+	if (intf != INTF_GET(agdev->ac_alt)) {
+		dev_err(&uac2->pdev.dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return -EOPNOTSUPP;
+	}
+
+	if (cr->bRequestType & USB_DIR_IN)
+		return ac_rq_in(fn, cr);
+	else if (cr->bRequest == UAC2_CS_CUR)
+		return out_rq_cur(fn, cr);
+
+	return -EOPNOTSUPP;
+}
+
+static int
+afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct audio_dev *agdev = func_to_agdev(fn);
+	struct snd_uac2_chip *uac2 = &agdev->uac2;
+	struct usb_request *req = cdev->req;
+	u16 w_length = le16_to_cpu(cr->wLength);
+	int value = -EOPNOTSUPP;
+
+	/* Only Class specific requests are supposed to reach here */
+	if ((cr->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS)
+		return -EOPNOTSUPP;
+
+	if ((cr->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE)
+		value = setup_rq_inf(fn, cr);
+	else
+		dev_err(&uac2->pdev.dev, "%s:%d Error!\n", __func__, __LINE__);
+
+	if (value >= 0) {
+		req->length = value;
+		req->zero = value < w_length;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0) {
+			dev_err(&uac2->pdev.dev,
+				"%s:%d Error!\n", __func__, __LINE__);
+			req->status = 0;
+		}
+	}
+
+	return value;
+}
+
+static int audio_bind_config(struct usb_configuration *cfg)
+{
+	int id, res;
+
+	agdev_g = kzalloc(sizeof *agdev_g, GFP_KERNEL);
+	if (agdev_g == NULL) {
+		printk(KERN_ERR "Unable to allocate audio gadget\n");
+		return -ENOMEM;
+	}
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_ASSOC].id = id;
+	iad_desc.iFunction = id,
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_IF_CTRL].id = id;
+	std_ac_if_desc.iInterface = id,
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_CLKSRC_IN].id = id;
+	in_clk_src_desc.iClockSource = id,
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_CLKSRC_OUT].id = id;
+	out_clk_src_desc.iClockSource = id,
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_USB_IT].id = id;
+	usb_out_it_desc.iTerminal = id,
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_IO_IT].id = id;
+	io_in_it_desc.iTerminal = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_USB_OT].id = id;
+	usb_in_ot_desc.iTerminal = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_IO_OT].id = id;
+	io_out_ot_desc.iTerminal = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_AS_OUT_ALT0].id = id;
+	std_as_out_if0_desc.iInterface = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_AS_OUT_ALT1].id = id;
+	std_as_out_if1_desc.iInterface = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_AS_IN_ALT0].id = id;
+	std_as_in_if0_desc.iInterface = id;
+
+	id = usb_string_id(cfg->cdev);
+	if (id < 0)
+		return id;
+
+	strings_fn[STR_AS_IN_ALT1].id = id;
+	std_as_in_if1_desc.iInterface = id;
+
+	agdev_g->func.name = "uac2_func";
+	agdev_g->func.strings = fn_strings;
+	agdev_g->func.bind = afunc_bind;
+	agdev_g->func.unbind = afunc_unbind;
+	agdev_g->func.set_alt = afunc_set_alt;
+	agdev_g->func.get_alt = afunc_get_alt;
+	agdev_g->func.disable = afunc_disable;
+	agdev_g->func.setup = afunc_setup;
+
+	/* Initialize the configurable parameters */
+	usb_out_it_desc.bNrChannels = num_channels(c_chmask);
+	usb_out_it_desc.bmChannelConfig = cpu_to_le32(c_chmask);
+	io_in_it_desc.bNrChannels = num_channels(p_chmask);
+	io_in_it_desc.bmChannelConfig = cpu_to_le32(p_chmask);
+	as_out_hdr_desc.bNrChannels = num_channels(c_chmask);
+	as_out_hdr_desc.bmChannelConfig = cpu_to_le32(c_chmask);
+	as_in_hdr_desc.bNrChannels = num_channels(p_chmask);
+	as_in_hdr_desc.bmChannelConfig = cpu_to_le32(p_chmask);
+	as_out_fmt1_desc.bSubslotSize = c_ssize;
+	as_out_fmt1_desc.bBitResolution = c_ssize * 8;
+	as_in_fmt1_desc.bSubslotSize = p_ssize;
+	as_in_fmt1_desc.bBitResolution = p_ssize * 8;
+
+	snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", p_srate);
+	snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", c_srate);
+
+	res = usb_add_function(cfg, &agdev_g->func);
+	if (res < 0)
+		kfree(agdev_g);
+
+	return res;
+}
+
+static void
+uac2_unbind_config(struct usb_configuration *cfg)
+{
+	kfree(agdev_g);
+	agdev_g = NULL;
+}
-- 
1.7.4.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 1/4] USB: Gadget: Rename audio function to uac1
  2012-02-02 16:29                                 ` [PATCH 1/4] USB: Gadget: Rename audio function to uac1 Jassi Brar
@ 2012-02-15  8:12                                   ` Felipe Balbi
  0 siblings, 0 replies; 17+ messages in thread
From: Felipe Balbi @ 2012-02-15  8:12 UTC (permalink / raw)
  To: Jassi Brar
  Cc: alsa-devel, greg, linux-usb, jaredwiltshire, balbi, zonque, yadi.brar01


[-- Attachment #1.1: Type: text/plain, Size: 422 bytes --]

On Thu, Feb 02, 2012 at 09:59:53PM +0530, Jassi Brar wrote:
> The extant USB-Audio function driver complies to UAC_1 spec.
> So name the files accordingly, paving way for inclusion of
> a new UAC_2 specified driver.
> 
> Signed-off-by: Yadi Brar <yadi.brar01@gmail.com>
> Signed-off-by: Jassi Brar <jaswinder.singh@linaro.org>

nobody complains in 2 weeks so I'm applying this entire series, thanks

-- 
balbi

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

[-- Attachment #2: Type: text/plain, Size: 0 bytes --]



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

end of thread, other threads:[~2012-02-15  8:12 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-08-19 11:22 [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver Jassi Brar
     [not found] ` <1313752934-20081-1-git-send-email-jassisinghbrar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2011-08-22 11:30   ` Felipe Balbi
     [not found]     ` <20110822113002.GD30398-UiBtZHVXSwEVvW8u9ZQWYwjfymiNCTlR@public.gmane.org>
2011-08-22 12:37       ` Jassi Brar
     [not found]         ` <CAJe_Zhdb7d23_x=1GWV2EnEjdn69XBKEz+hCeCuBx4v9XAmykg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-08-22 12:44           ` Felipe Balbi
2011-08-22 13:00             ` Jassi Brar
     [not found]               ` <CABb+yY3QxvHf38-qfD5ig77nJK=0eVLiSPVpbTh_DCfHNGRyGQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-08-22 13:46                 ` Felipe Balbi
     [not found]                   ` <20110822134627.GP30398-UiBtZHVXSwEVvW8u9ZQWYwjfymiNCTlR@public.gmane.org>
2011-08-22 14:00                     ` Jassi Brar
     [not found]                       ` <CABb+yY2m2MfkFwf+XTdKzsCCeSFz_J2LoGxJricamhNtTcSAcg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-08-22 14:28                         ` Felipe Balbi
     [not found]                           ` <20110822142805.GT30398-UiBtZHVXSwEVvW8u9ZQWYwjfymiNCTlR@public.gmane.org>
2011-08-22 14:29                             ` Felipe Balbi
2012-02-02 16:19                             ` Jassi Brar
     [not found]                               ` <CABb+yY3X0XDk0zByZZcJOO0h2Kxu14UD4w-GS7p1HPAAWU3SVA-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2012-02-02 16:29                                 ` [PATCH 1/4] USB: Gadget: Rename audio function to uac1 Jassi Brar
2012-02-15  8:12                                   ` Felipe Balbi
2012-02-02 16:30                                 ` [PATCH 2/4] USB: UAC2: Add ACHeader and FormatType descriptor Jassi Brar
2012-02-02 16:31                                 ` [PATCH 3/4] USB: Gadget: Audio: Move string IDs to audio.c Jassi Brar
2012-02-02 16:31                                 ` [PATCH 4/4] USB: Gadget: Add Audio Class 2.0 Driver Jassi Brar
2011-08-24  2:00   ` [PATCH 2/2] " Chen Peter-B29397
     [not found]     ` <35AB98346D25394A9354ED89C4D7658717FD0C-TcFNo7jSaXOLgTCmFNXF2K4g8xLGJsHaLnY5E4hWTkheoWH0uzbU5w@public.gmane.org>
2011-08-24  4:22       ` Jassi Brar

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.