> -----Original Message----- > From: linux-usb-owner@xxxxxxxxxxxxxxx [mailto:linux-usb- > owner@xxxxxxxxxxxxxxx] On Behalf Of Jassi Brar > Sent: Friday, August 19, 2011 7:22 PM > To: linux-usb@xxxxxxxxxxxxxxx; balbi@xxxxxx; gregkh@xxxxxxx > Cc: alsa-devel@xxxxxxxxxxxxxxxx; zonque@xxxxxxxxx; yadi.brar01@xxxxxxxxx; > 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@xxxxxxxxx> > Signed-off-by: Jassi Brar <jassisinghbrar@xxxxxxxxx> > --- > > =========== > 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@xxxxxxxxx) > + * Jaswinder Singh (jassisinghbrar@xxxxxxxxx) > + * > + * 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@xxxxxxxxx) > + * Jaswinder Singh (jassisinghbrar@xxxxxxxxx) > + * > + * 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@xxxxxxxxxxxxxxx > 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@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html