mirror of
https://github.com/torvalds/linux.git
synced 2024-12-26 21:02:19 +00:00
132fcb4608
This is a flexible USB Audio Class 2.0 compliant gadget driver that implements a simple topology with a virtual sound card exposed at the function side. The driver doesn't expect any real audio codec to be present on the function - the audio streams are simply sinked to and sourced from a virtual ALSA sound card created. The user-space application may choose to do whatever it wants with the data received from the USB Host and choose to provide whatever it wants as audio data to the USB Host. Capture(USB-Out) and Playback(USB-In) can be run at independent configurations specified via module parameters while loading the driver. Make this new version as the default selection by a new Kconfig choice. Signed-off-by: Yadi Brar <yadi.brar01@gmail.com> Signed-off-by: Jassi Brar <jaswinder.singh@linaro.org> Signed-off-by: Felipe Balbi <balbi@ti.com>
1450 lines
36 KiB
C
1450 lines
36 KiB
C
/*
|
|
* 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;
|
|
}
|