Commit 132fcb46 authored by Jassi Brar's avatar Jassi Brar Committed by Felipe Balbi

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.

Make this new version as the default selection by a new Kconfig choice.
Signed-off-by: default avatarYadi Brar <yadi.brar01@gmail.com>
Signed-off-by: default avatarJassi Brar <jaswinder.singh@linaro.org>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent d747a916
...@@ -599,16 +599,29 @@ config USB_AUDIO ...@@ -599,16 +599,29 @@ config USB_AUDIO
depends on SND depends on SND
select SND_PCM select SND_PCM
help help
Gadget Audio is compatible with USB Audio Class specification 1.0. This Gadget Audio driver is compatible with USB Audio Class
It will include at least one AudioControl interface, zero or more specification 2.0. It implements 1 AudioControl interface,
AudioStream interface and zero or more MIDIStream interface. 1 AudioStreaming Interface each for USB-OUT and USB-IN.
Number of channels, sample rate and sample size can be
Gadget Audio will use on-board ALSA (CONFIG_SND) audio card to specified as module parameters.
playback or capture audio stream. 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 Say "y" to link the driver statically, or "m" to build a
dynamically linked module called "g_audio". 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 config USB_ETH
tristate "Ethernet Gadget (with CDC Ethernet support)" tristate "Ethernet Gadget (with CDC Ethernet support)"
depends on NET depends on NET
......
...@@ -14,10 +14,8 @@ ...@@ -14,10 +14,8 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/utsname.h> #include <linux/utsname.h>
#include "u_uac1.h"
#define DRIVER_DESC "Linux USB Audio Gadget" #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[] = { ...@@ -56,8 +54,13 @@ static struct usb_gadget_strings *audio_strings[] = {
NULL, NULL,
}; };
#ifdef CONFIG_GADGET_UAC1
#include "u_uac1.h"
#include "u_uac1.c" #include "u_uac1.c"
#include "f_uac1.c" #include "f_uac1.c"
#else
#include "f_uac2.c"
#endif
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
...@@ -77,9 +80,15 @@ static struct usb_device_descriptor device_desc = { ...@@ -77,9 +80,15 @@ static struct usb_device_descriptor device_desc = {
.bcdUSB = __constant_cpu_to_le16(0x200), .bcdUSB = __constant_cpu_to_le16(0x200),
#ifdef CONFIG_GADGET_UAC1
.bDeviceClass = USB_CLASS_PER_INTERFACE, .bDeviceClass = USB_CLASS_PER_INTERFACE,
.bDeviceSubClass = 0, .bDeviceSubClass = 0,
.bDeviceProtocol = 0, .bDeviceProtocol = 0,
#else
.bDeviceClass = USB_CLASS_MISC,
.bDeviceSubClass = 0x02,
.bDeviceProtocol = 0x01,
#endif
/* .bMaxPacketSize0 = f(hardware) */ /* .bMaxPacketSize0 = f(hardware) */
/* Vendor and product id defaults change according to what configs /* Vendor and product id defaults change according to what configs
...@@ -131,6 +140,9 @@ static struct usb_configuration audio_config_driver = { ...@@ -131,6 +140,9 @@ static struct usb_configuration audio_config_driver = {
.bConfigurationValue = 1, .bConfigurationValue = 1,
/* .iConfiguration = DYNAMIC */ /* .iConfiguration = DYNAMIC */
.bmAttributes = USB_CONFIG_ATT_SELFPOWER, .bmAttributes = USB_CONFIG_ATT_SELFPOWER,
#ifndef CONFIG_GADGET_UAC1
.unbind = uac2_unbind_config,
#endif
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
...@@ -180,7 +192,9 @@ static int __init audio_bind(struct usb_composite_dev *cdev) ...@@ -180,7 +192,9 @@ static int __init audio_bind(struct usb_composite_dev *cdev)
static int __exit audio_unbind(struct usb_composite_dev *cdev) static int __exit audio_unbind(struct usb_composite_dev *cdev)
{ {
#ifdef CONFIG_GADGET_UAC1
gaudio_cleanup(); gaudio_cleanup();
#endif
return 0; return 0;
} }
......
/*
* f_uac2.c -- USB Audio Class 2.0 Function
*
* Copyright (C) 2011
* Yadwinder Singh (yadi.brar01@gmail.com)
* Jaswinder Singh (jaswinder.singh@linaro.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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment