1072 lines
27 KiB
C
1072 lines
27 KiB
C
/*
|
|
* Gadget Function Driver for USB audio source device
|
|
*
|
|
* Copyright (C) 2012 Google, Inc.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/usb/audio.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/pm_qos.h>
|
|
#include <sound/core.h>
|
|
#include <sound/initval.h>
|
|
#include <sound/pcm.h>
|
|
|
|
#include <linux/usb.h>
|
|
#include <linux/usb_usual.h>
|
|
#include <linux/usb/ch9.h>
|
|
#include <linux/configfs.h>
|
|
#include <linux/usb/composite.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#define SAMPLE_RATE 44100
|
|
#define FRAMES_PER_MSEC (SAMPLE_RATE / 1000)
|
|
|
|
#define IN_EP_MAX_PACKET_SIZE 256
|
|
|
|
/* Number of requests to allocate */
|
|
#define IN_EP_REQ_COUNT 4
|
|
|
|
#define AUDIO_AC_INTERFACE 0
|
|
#define AUDIO_AS_INTERFACE 1
|
|
#define AUDIO_NUM_INTERFACES 2
|
|
#define MAX_INST_NAME_LEN 40
|
|
|
|
/* B.3.1 Standard AC Interface Descriptor */
|
|
static struct usb_interface_descriptor ac_interface_desc = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bNumEndpoints = 0,
|
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
|
.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
|
|
};
|
|
|
|
DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
|
|
|
|
#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(AUDIO_NUM_INTERFACES)
|
|
/* 1 input terminal, 1 output terminal and 1 feature unit */
|
|
#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \
|
|
+ UAC_DT_INPUT_TERMINAL_SIZE + UAC_DT_OUTPUT_TERMINAL_SIZE \
|
|
+ UAC_DT_FEATURE_UNIT_SIZE(0))
|
|
/* B.3.2 Class-Specific AC Interface Descriptor */
|
|
static struct uac1_ac_header_descriptor_2 ac_header_desc = {
|
|
.bLength = UAC_DT_AC_HEADER_LENGTH,
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubtype = UAC_HEADER,
|
|
.bcdADC = __constant_cpu_to_le16(0x0100),
|
|
.wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH),
|
|
.bInCollection = AUDIO_NUM_INTERFACES,
|
|
.baInterfaceNr = {
|
|
[0] = AUDIO_AC_INTERFACE,
|
|
[1] = AUDIO_AS_INTERFACE,
|
|
}
|
|
};
|
|
|
|
#define INPUT_TERMINAL_ID 1
|
|
static struct uac_input_terminal_descriptor input_terminal_desc = {
|
|
.bLength = UAC_DT_INPUT_TERMINAL_SIZE,
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubtype = UAC_INPUT_TERMINAL,
|
|
.bTerminalID = INPUT_TERMINAL_ID,
|
|
.wTerminalType = UAC_INPUT_TERMINAL_MICROPHONE,
|
|
.bAssocTerminal = 0,
|
|
.wChannelConfig = 0x3,
|
|
};
|
|
|
|
DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0);
|
|
|
|
#define FEATURE_UNIT_ID 2
|
|
static struct uac_feature_unit_descriptor_0 feature_unit_desc = {
|
|
.bLength = UAC_DT_FEATURE_UNIT_SIZE(0),
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubtype = UAC_FEATURE_UNIT,
|
|
.bUnitID = FEATURE_UNIT_ID,
|
|
.bSourceID = INPUT_TERMINAL_ID,
|
|
.bControlSize = 2,
|
|
};
|
|
|
|
#define OUTPUT_TERMINAL_ID 3
|
|
static struct uac1_output_terminal_descriptor output_terminal_desc = {
|
|
.bLength = UAC_DT_OUTPUT_TERMINAL_SIZE,
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
|
|
.bTerminalID = OUTPUT_TERMINAL_ID,
|
|
.wTerminalType = UAC_TERMINAL_STREAMING,
|
|
.bAssocTerminal = FEATURE_UNIT_ID,
|
|
.bSourceID = FEATURE_UNIT_ID,
|
|
};
|
|
|
|
/* B.4.1 Standard AS Interface Descriptor */
|
|
static struct usb_interface_descriptor as_interface_alt_0_desc = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bAlternateSetting = 0,
|
|
.bNumEndpoints = 0,
|
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
|
.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
|
|
};
|
|
|
|
static struct usb_interface_descriptor as_interface_alt_1_desc = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bAlternateSetting = 1,
|
|
.bNumEndpoints = 1,
|
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
|
.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
|
|
};
|
|
|
|
/* B.4.2 Class-Specific AS Interface Descriptor */
|
|
static struct uac1_as_header_descriptor as_header_desc = {
|
|
.bLength = UAC_DT_AS_HEADER_SIZE,
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubtype = UAC_AS_GENERAL,
|
|
.bTerminalLink = INPUT_TERMINAL_ID,
|
|
.bDelay = 1,
|
|
.wFormatTag = UAC_FORMAT_TYPE_I_PCM,
|
|
};
|
|
|
|
DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
|
|
|
|
static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = {
|
|
.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubtype = UAC_FORMAT_TYPE,
|
|
.bFormatType = UAC_FORMAT_TYPE_I,
|
|
.bSubframeSize = 2,
|
|
.bBitResolution = 16,
|
|
.bSamFreqType = 1,
|
|
};
|
|
|
|
/* Standard ISO IN Endpoint Descriptor for highspeed */
|
|
static struct usb_endpoint_descriptor hs_as_in_ep_desc = {
|
|
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_IN,
|
|
.bmAttributes = USB_ENDPOINT_SYNC_SYNC
|
|
| USB_ENDPOINT_XFER_ISOC,
|
|
.wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE),
|
|
.bInterval = 4, /* poll 1 per millisecond */
|
|
};
|
|
|
|
/* Standard ISO IN Endpoint Descriptor for highspeed */
|
|
static struct usb_endpoint_descriptor fs_as_in_ep_desc = {
|
|
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_IN,
|
|
.bmAttributes = USB_ENDPOINT_SYNC_SYNC
|
|
| USB_ENDPOINT_XFER_ISOC,
|
|
.wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE),
|
|
.bInterval = 1, /* poll 1 per millisecond */
|
|
};
|
|
|
|
/* Class-specific AS ISO OUT Endpoint Descriptor */
|
|
static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
|
|
.bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
|
|
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
|
.bDescriptorSubtype = UAC_EP_GENERAL,
|
|
.bmAttributes = 1,
|
|
.bLockDelayUnits = 1,
|
|
.wLockDelay = __constant_cpu_to_le16(1),
|
|
};
|
|
|
|
static struct usb_descriptor_header *hs_audio_desc[] = {
|
|
(struct usb_descriptor_header *)&ac_interface_desc,
|
|
(struct usb_descriptor_header *)&ac_header_desc,
|
|
|
|
(struct usb_descriptor_header *)&input_terminal_desc,
|
|
(struct usb_descriptor_header *)&output_terminal_desc,
|
|
(struct usb_descriptor_header *)&feature_unit_desc,
|
|
|
|
(struct usb_descriptor_header *)&as_interface_alt_0_desc,
|
|
(struct usb_descriptor_header *)&as_interface_alt_1_desc,
|
|
(struct usb_descriptor_header *)&as_header_desc,
|
|
|
|
(struct usb_descriptor_header *)&as_type_i_desc,
|
|
|
|
(struct usb_descriptor_header *)&hs_as_in_ep_desc,
|
|
(struct usb_descriptor_header *)&as_iso_in_desc,
|
|
NULL,
|
|
};
|
|
|
|
static struct usb_descriptor_header *fs_audio_desc[] = {
|
|
(struct usb_descriptor_header *)&ac_interface_desc,
|
|
(struct usb_descriptor_header *)&ac_header_desc,
|
|
|
|
(struct usb_descriptor_header *)&input_terminal_desc,
|
|
(struct usb_descriptor_header *)&output_terminal_desc,
|
|
(struct usb_descriptor_header *)&feature_unit_desc,
|
|
|
|
(struct usb_descriptor_header *)&as_interface_alt_0_desc,
|
|
(struct usb_descriptor_header *)&as_interface_alt_1_desc,
|
|
(struct usb_descriptor_header *)&as_header_desc,
|
|
|
|
(struct usb_descriptor_header *)&as_type_i_desc,
|
|
|
|
(struct usb_descriptor_header *)&fs_as_in_ep_desc,
|
|
(struct usb_descriptor_header *)&as_iso_in_desc,
|
|
NULL,
|
|
};
|
|
|
|
static struct snd_pcm_hardware audio_hw_info = {
|
|
.info = SNDRV_PCM_INFO_MMAP |
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
SNDRV_PCM_INFO_BATCH |
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_BLOCK_TRANSFER,
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.rate_min = SAMPLE_RATE,
|
|
.rate_max = SAMPLE_RATE,
|
|
|
|
.buffer_bytes_max = 1024 * 1024,
|
|
.period_bytes_min = 64,
|
|
.period_bytes_max = 512 * 1024,
|
|
.periods_min = 2,
|
|
.periods_max = 1024,
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
struct audio_source_config {
|
|
int card;
|
|
int device;
|
|
};
|
|
|
|
struct audio_dev {
|
|
struct usb_function func;
|
|
struct snd_card *card;
|
|
struct snd_pcm *pcm;
|
|
struct snd_pcm_substream *substream;
|
|
|
|
struct list_head idle_reqs;
|
|
struct usb_ep *in_ep;
|
|
|
|
spinlock_t lock;
|
|
|
|
/* beginning, end and current position in our buffer */
|
|
void *buffer_start;
|
|
void *buffer_end;
|
|
void *buffer_pos;
|
|
|
|
/* byte size of a "period" */
|
|
unsigned int period;
|
|
/* bytes sent since last call to snd_pcm_period_elapsed */
|
|
unsigned int period_offset;
|
|
/* time we started playing */
|
|
ktime_t start_time;
|
|
/* number of frames sent since start_time */
|
|
s64 frames_sent;
|
|
struct audio_source_config *config;
|
|
/* for creating and issuing QoS requests */
|
|
struct pm_qos_request pm_qos;
|
|
};
|
|
|
|
static inline struct audio_dev *func_to_audio(struct usb_function *f)
|
|
{
|
|
return container_of(f, struct audio_dev, func);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
struct audio_source_instance {
|
|
struct usb_function_instance func_inst;
|
|
const char *name;
|
|
struct audio_source_config *config;
|
|
struct device *audio_device;
|
|
};
|
|
|
|
static void audio_source_attr_release(struct config_item *item);
|
|
|
|
static struct configfs_item_operations audio_source_item_ops = {
|
|
.release = audio_source_attr_release,
|
|
};
|
|
|
|
static struct config_item_type audio_source_func_type = {
|
|
.ct_item_ops = &audio_source_item_ops,
|
|
.ct_owner = THIS_MODULE,
|
|
};
|
|
|
|
static ssize_t audio_source_pcm_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf);
|
|
|
|
static DEVICE_ATTR(pcm, S_IRUGO, audio_source_pcm_show, NULL);
|
|
|
|
static struct device_attribute *audio_source_function_attributes[] = {
|
|
&dev_attr_pcm,
|
|
NULL
|
|
};
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
static struct usb_request *audio_request_new(struct usb_ep *ep, int buffer_size)
|
|
{
|
|
struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
|
|
|
|
if (!req)
|
|
return NULL;
|
|
|
|
req->buf = kmalloc(buffer_size, GFP_KERNEL);
|
|
if (!req->buf) {
|
|
usb_ep_free_request(ep, req);
|
|
return NULL;
|
|
}
|
|
req->length = buffer_size;
|
|
return req;
|
|
}
|
|
|
|
static void audio_request_free(struct usb_request *req, struct usb_ep *ep)
|
|
{
|
|
if (req) {
|
|
kfree(req->buf);
|
|
usb_ep_free_request(ep, req);
|
|
}
|
|
}
|
|
|
|
static void audio_req_put(struct audio_dev *audio, struct usb_request *req)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&audio->lock, flags);
|
|
list_add_tail(&req->list, &audio->idle_reqs);
|
|
spin_unlock_irqrestore(&audio->lock, flags);
|
|
}
|
|
|
|
static struct usb_request *audio_req_get(struct audio_dev *audio)
|
|
{
|
|
unsigned long flags;
|
|
struct usb_request *req;
|
|
|
|
spin_lock_irqsave(&audio->lock, flags);
|
|
if (list_empty(&audio->idle_reqs)) {
|
|
req = 0;
|
|
} else {
|
|
req = list_first_entry(&audio->idle_reqs, struct usb_request,
|
|
list);
|
|
list_del(&req->list);
|
|
}
|
|
spin_unlock_irqrestore(&audio->lock, flags);
|
|
return req;
|
|
}
|
|
|
|
/* send the appropriate number of packets to match our bitrate */
|
|
static void audio_send(struct audio_dev *audio)
|
|
{
|
|
struct snd_pcm_runtime *runtime;
|
|
struct usb_request *req;
|
|
int length, length1, length2, ret;
|
|
s64 msecs;
|
|
s64 frames;
|
|
ktime_t now;
|
|
|
|
/* audio->substream will be null if we have been closed */
|
|
if (!audio->substream)
|
|
return;
|
|
/* audio->buffer_pos will be null if we have been stopped */
|
|
if (!audio->buffer_pos)
|
|
return;
|
|
|
|
runtime = audio->substream->runtime;
|
|
|
|
/* compute number of frames to send */
|
|
now = ktime_get();
|
|
msecs = div_s64((ktime_to_ns(now) - ktime_to_ns(audio->start_time)),
|
|
1000000);
|
|
frames = div_s64((msecs * SAMPLE_RATE), 1000);
|
|
|
|
/* Readjust our frames_sent if we fall too far behind.
|
|
* If we get too far behind it is better to drop some frames than
|
|
* to keep sending data too fast in an attempt to catch up.
|
|
*/
|
|
if (frames - audio->frames_sent > 10 * FRAMES_PER_MSEC)
|
|
audio->frames_sent = frames - FRAMES_PER_MSEC;
|
|
|
|
frames -= audio->frames_sent;
|
|
|
|
/* We need to send something to keep the pipeline going */
|
|
if (frames <= 0)
|
|
frames = FRAMES_PER_MSEC;
|
|
|
|
while (frames > 0) {
|
|
req = audio_req_get(audio);
|
|
if (!req)
|
|
break;
|
|
|
|
length = frames_to_bytes(runtime, frames);
|
|
if (length > IN_EP_MAX_PACKET_SIZE)
|
|
length = IN_EP_MAX_PACKET_SIZE;
|
|
|
|
if (audio->buffer_pos + length > audio->buffer_end)
|
|
length1 = audio->buffer_end - audio->buffer_pos;
|
|
else
|
|
length1 = length;
|
|
memcpy(req->buf, audio->buffer_pos, length1);
|
|
if (length1 < length) {
|
|
/* Wrap around and copy remaining length
|
|
* at beginning of buffer.
|
|
*/
|
|
length2 = length - length1;
|
|
memcpy(req->buf + length1, audio->buffer_start,
|
|
length2);
|
|
audio->buffer_pos = audio->buffer_start + length2;
|
|
} else {
|
|
audio->buffer_pos += length1;
|
|
if (audio->buffer_pos >= audio->buffer_end)
|
|
audio->buffer_pos = audio->buffer_start;
|
|
}
|
|
|
|
req->length = length;
|
|
ret = usb_ep_queue(audio->in_ep, req, GFP_ATOMIC);
|
|
if (ret < 0) {
|
|
pr_err("usb_ep_queue failed ret: %d\n", ret);
|
|
audio_req_put(audio, req);
|
|
break;
|
|
}
|
|
|
|
frames -= bytes_to_frames(runtime, length);
|
|
audio->frames_sent += bytes_to_frames(runtime, length);
|
|
}
|
|
}
|
|
|
|
static void audio_control_complete(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
/* nothing to do here */
|
|
}
|
|
|
|
static void audio_data_complete(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
struct audio_dev *audio = req->context;
|
|
|
|
pr_debug("audio_data_complete req->status %d req->actual %d\n",
|
|
req->status, req->actual);
|
|
|
|
audio_req_put(audio, req);
|
|
|
|
if (!audio->buffer_start || req->status)
|
|
return;
|
|
|
|
audio->period_offset += req->actual;
|
|
if (audio->period_offset >= audio->period) {
|
|
snd_pcm_period_elapsed(audio->substream);
|
|
audio->period_offset = 0;
|
|
}
|
|
audio_send(audio);
|
|
}
|
|
|
|
static int audio_set_endpoint_req(struct usb_function *f,
|
|
const struct usb_ctrlrequest *ctrl)
|
|
{
|
|
int value = -EOPNOTSUPP;
|
|
u16 ep = le16_to_cpu(ctrl->wIndex);
|
|
u16 len = le16_to_cpu(ctrl->wLength);
|
|
u16 w_value = le16_to_cpu(ctrl->wValue);
|
|
|
|
pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
|
|
ctrl->bRequest, w_value, len, ep);
|
|
|
|
switch (ctrl->bRequest) {
|
|
case UAC_SET_CUR:
|
|
case UAC_SET_MIN:
|
|
case UAC_SET_MAX:
|
|
case UAC_SET_RES:
|
|
value = len;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static int audio_get_endpoint_req(struct usb_function *f,
|
|
const struct usb_ctrlrequest *ctrl)
|
|
{
|
|
struct usb_composite_dev *cdev = f->config->cdev;
|
|
int value = -EOPNOTSUPP;
|
|
u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
|
|
u16 len = le16_to_cpu(ctrl->wLength);
|
|
u16 w_value = le16_to_cpu(ctrl->wValue);
|
|
u8 *buf = cdev->req->buf;
|
|
|
|
pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
|
|
ctrl->bRequest, w_value, len, ep);
|
|
|
|
if (w_value == UAC_EP_CS_ATTR_SAMPLE_RATE << 8) {
|
|
switch (ctrl->bRequest) {
|
|
case UAC_GET_CUR:
|
|
case UAC_GET_MIN:
|
|
case UAC_GET_MAX:
|
|
case UAC_GET_RES:
|
|
/* return our sample rate */
|
|
buf[0] = (u8)SAMPLE_RATE;
|
|
buf[1] = (u8)(SAMPLE_RATE >> 8);
|
|
buf[2] = (u8)(SAMPLE_RATE >> 16);
|
|
value = 3;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static int
|
|
audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
|
{
|
|
struct usb_composite_dev *cdev = f->config->cdev;
|
|
struct usb_request *req = cdev->req;
|
|
int value = -EOPNOTSUPP;
|
|
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
|
u16 w_value = le16_to_cpu(ctrl->wValue);
|
|
u16 w_length = le16_to_cpu(ctrl->wLength);
|
|
|
|
/* composite driver infrastructure handles everything; interface
|
|
* activation uses set_alt().
|
|
*/
|
|
switch (ctrl->bRequestType) {
|
|
case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
|
|
value = audio_set_endpoint_req(f, ctrl);
|
|
break;
|
|
|
|
case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
|
|
value = audio_get_endpoint_req(f, ctrl);
|
|
break;
|
|
}
|
|
|
|
/* respond with data transfer or status phase? */
|
|
if (value >= 0) {
|
|
pr_debug("audio req%02x.%02x v%04x i%04x l%d\n",
|
|
ctrl->bRequestType, ctrl->bRequest,
|
|
w_value, w_index, w_length);
|
|
req->zero = 0;
|
|
req->length = value;
|
|
req->complete = audio_control_complete;
|
|
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
|
if (value < 0)
|
|
pr_err("audio response on err %d\n", value);
|
|
}
|
|
|
|
/* device either stalls (value < 0) or reports success */
|
|
return value;
|
|
}
|
|
|
|
static int audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
|
{
|
|
struct audio_dev *audio = func_to_audio(f);
|
|
struct usb_composite_dev *cdev = f->config->cdev;
|
|
int ret;
|
|
|
|
pr_debug("audio_set_alt intf %d, alt %d\n", intf, alt);
|
|
|
|
ret = config_ep_by_speed(cdev->gadget, f, audio->in_ep);
|
|
if (ret)
|
|
return ret;
|
|
|
|
usb_ep_enable(audio->in_ep);
|
|
return 0;
|
|
}
|
|
|
|
static void audio_disable(struct usb_function *f)
|
|
{
|
|
struct audio_dev *audio = func_to_audio(f);
|
|
|
|
pr_debug("audio_disable\n");
|
|
usb_ep_disable(audio->in_ep);
|
|
}
|
|
|
|
static void audio_free_func(struct usb_function *f)
|
|
{
|
|
/* no-op */
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void audio_build_desc(struct audio_dev *audio)
|
|
{
|
|
u8 *sam_freq;
|
|
int rate;
|
|
|
|
/* Set channel numbers */
|
|
input_terminal_desc.bNrChannels = 2;
|
|
as_type_i_desc.bNrChannels = 2;
|
|
|
|
/* Set sample rates */
|
|
rate = SAMPLE_RATE;
|
|
sam_freq = as_type_i_desc.tSamFreq[0];
|
|
memcpy(sam_freq, &rate, 3);
|
|
}
|
|
|
|
|
|
static int snd_card_setup(struct usb_configuration *c,
|
|
struct audio_source_config *config);
|
|
static struct audio_source_instance *to_fi_audio_source(
|
|
const struct usb_function_instance *fi);
|
|
|
|
|
|
/* audio function driver setup/binding */
|
|
static int
|
|
audio_bind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
struct usb_composite_dev *cdev = c->cdev;
|
|
struct audio_dev *audio = func_to_audio(f);
|
|
int status;
|
|
struct usb_ep *ep;
|
|
struct usb_request *req;
|
|
int i;
|
|
int err;
|
|
|
|
if (IS_ENABLED(CONFIG_USB_CONFIGFS)) {
|
|
struct audio_source_instance *fi_audio =
|
|
to_fi_audio_source(f->fi);
|
|
struct audio_source_config *config =
|
|
fi_audio->config;
|
|
|
|
err = snd_card_setup(c, config);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
audio_build_desc(audio);
|
|
|
|
/* allocate instance-specific interface IDs, and patch descriptors */
|
|
status = usb_interface_id(c, f);
|
|
if (status < 0)
|
|
goto fail;
|
|
ac_interface_desc.bInterfaceNumber = status;
|
|
|
|
/* AUDIO_AC_INTERFACE */
|
|
ac_header_desc.baInterfaceNr[0] = status;
|
|
|
|
status = usb_interface_id(c, f);
|
|
if (status < 0)
|
|
goto fail;
|
|
as_interface_alt_0_desc.bInterfaceNumber = status;
|
|
as_interface_alt_1_desc.bInterfaceNumber = status;
|
|
|
|
/* AUDIO_AS_INTERFACE */
|
|
ac_header_desc.baInterfaceNr[1] = status;
|
|
|
|
status = -ENODEV;
|
|
|
|
/* allocate our endpoint */
|
|
ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_ep_desc);
|
|
if (!ep)
|
|
goto fail;
|
|
audio->in_ep = ep;
|
|
ep->driver_data = audio; /* claim */
|
|
|
|
if (gadget_is_dualspeed(c->cdev->gadget))
|
|
hs_as_in_ep_desc.bEndpointAddress =
|
|
fs_as_in_ep_desc.bEndpointAddress;
|
|
|
|
f->fs_descriptors = fs_audio_desc;
|
|
f->hs_descriptors = hs_audio_desc;
|
|
|
|
for (i = 0, status = 0; i < IN_EP_REQ_COUNT && status == 0; i++) {
|
|
req = audio_request_new(ep, IN_EP_MAX_PACKET_SIZE);
|
|
if (req) {
|
|
req->context = audio;
|
|
req->complete = audio_data_complete;
|
|
audio_req_put(audio, req);
|
|
} else
|
|
status = -ENOMEM;
|
|
}
|
|
|
|
fail:
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
audio_unbind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
struct audio_dev *audio = func_to_audio(f);
|
|
struct usb_request *req;
|
|
|
|
while ((req = audio_req_get(audio)))
|
|
audio_request_free(req, audio->in_ep);
|
|
|
|
snd_card_free_when_closed(audio->card);
|
|
audio->card = NULL;
|
|
audio->pcm = NULL;
|
|
audio->substream = NULL;
|
|
audio->in_ep = NULL;
|
|
|
|
if (IS_ENABLED(CONFIG_USB_CONFIGFS)) {
|
|
struct audio_source_instance *fi_audio =
|
|
to_fi_audio_source(f->fi);
|
|
struct audio_source_config *config =
|
|
fi_audio->config;
|
|
|
|
config->card = -1;
|
|
config->device = -1;
|
|
}
|
|
}
|
|
|
|
static void audio_pcm_playback_start(struct audio_dev *audio)
|
|
{
|
|
audio->start_time = ktime_get();
|
|
audio->frames_sent = 0;
|
|
audio_send(audio);
|
|
}
|
|
|
|
static void audio_pcm_playback_stop(struct audio_dev *audio)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&audio->lock, flags);
|
|
audio->buffer_start = 0;
|
|
audio->buffer_end = 0;
|
|
audio->buffer_pos = 0;
|
|
spin_unlock_irqrestore(&audio->lock, flags);
|
|
}
|
|
|
|
static int audio_pcm_open(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct audio_dev *audio = substream->private_data;
|
|
|
|
runtime->private_data = audio;
|
|
runtime->hw = audio_hw_info;
|
|
snd_pcm_limit_hw_rates(runtime);
|
|
runtime->hw.channels_max = 2;
|
|
|
|
audio->substream = substream;
|
|
|
|
/* Add the QoS request and set the latency to 0 */
|
|
pm_qos_add_request(&audio->pm_qos, PM_QOS_CPU_DMA_LATENCY, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int audio_pcm_close(struct snd_pcm_substream *substream)
|
|
{
|
|
struct audio_dev *audio = substream->private_data;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&audio->lock, flags);
|
|
|
|
/* Remove the QoS request */
|
|
pm_qos_remove_request(&audio->pm_qos);
|
|
|
|
audio->substream = NULL;
|
|
spin_unlock_irqrestore(&audio->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int audio_pcm_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
unsigned int channels = params_channels(params);
|
|
unsigned int rate = params_rate(params);
|
|
|
|
if (rate != SAMPLE_RATE)
|
|
return -EINVAL;
|
|
if (channels != 2)
|
|
return -EINVAL;
|
|
|
|
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
|
params_buffer_bytes(params));
|
|
}
|
|
|
|
static int audio_pcm_hw_free(struct snd_pcm_substream *substream)
|
|
{
|
|
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
|
}
|
|
|
|
static int audio_pcm_prepare(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct audio_dev *audio = runtime->private_data;
|
|
|
|
audio->period = snd_pcm_lib_period_bytes(substream);
|
|
audio->period_offset = 0;
|
|
audio->buffer_start = runtime->dma_area;
|
|
audio->buffer_end = audio->buffer_start
|
|
+ snd_pcm_lib_buffer_bytes(substream);
|
|
audio->buffer_pos = audio->buffer_start;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_uframes_t audio_pcm_pointer(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct audio_dev *audio = runtime->private_data;
|
|
ssize_t bytes = audio->buffer_pos - audio->buffer_start;
|
|
|
|
/* return offset of next frame to fill in our buffer */
|
|
return bytes_to_frames(runtime, bytes);
|
|
}
|
|
|
|
static int audio_pcm_playback_trigger(struct snd_pcm_substream *substream,
|
|
int cmd)
|
|
{
|
|
struct audio_dev *audio = substream->runtime->private_data;
|
|
int ret = 0;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
audio_pcm_playback_start(audio);
|
|
break;
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
audio_pcm_playback_stop(audio);
|
|
break;
|
|
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct audio_dev _audio_dev = {
|
|
.func = {
|
|
.name = "audio_source",
|
|
.bind = audio_bind,
|
|
.unbind = audio_unbind,
|
|
.set_alt = audio_set_alt,
|
|
.setup = audio_setup,
|
|
.disable = audio_disable,
|
|
.free_func = audio_free_func,
|
|
},
|
|
.lock = __SPIN_LOCK_UNLOCKED(_audio_dev.lock),
|
|
.idle_reqs = LIST_HEAD_INIT(_audio_dev.idle_reqs),
|
|
};
|
|
|
|
static struct snd_pcm_ops audio_playback_ops = {
|
|
.open = audio_pcm_open,
|
|
.close = audio_pcm_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = audio_pcm_hw_params,
|
|
.hw_free = audio_pcm_hw_free,
|
|
.prepare = audio_pcm_prepare,
|
|
.trigger = audio_pcm_playback_trigger,
|
|
.pointer = audio_pcm_pointer,
|
|
};
|
|
|
|
int audio_source_bind_config(struct usb_configuration *c,
|
|
struct audio_source_config *config)
|
|
{
|
|
struct audio_dev *audio;
|
|
int err;
|
|
|
|
config->card = -1;
|
|
config->device = -1;
|
|
|
|
audio = &_audio_dev;
|
|
|
|
err = snd_card_setup(c, config);
|
|
if (err)
|
|
return err;
|
|
|
|
err = usb_add_function(c, &audio->func);
|
|
if (err)
|
|
goto add_fail;
|
|
|
|
return 0;
|
|
|
|
add_fail:
|
|
snd_card_free(audio->card);
|
|
return err;
|
|
}
|
|
|
|
static int snd_card_setup(struct usb_configuration *c,
|
|
struct audio_source_config *config)
|
|
{
|
|
struct audio_dev *audio;
|
|
struct snd_card *card;
|
|
struct snd_pcm *pcm;
|
|
int err;
|
|
|
|
audio = &_audio_dev;
|
|
|
|
err = snd_card_new(&c->cdev->gadget->dev,
|
|
SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
|
|
THIS_MODULE, 0, &card);
|
|
if (err)
|
|
return err;
|
|
|
|
err = snd_pcm_new(card, "USB audio source", 0, 1, 0, &pcm);
|
|
if (err)
|
|
goto pcm_fail;
|
|
|
|
pcm->private_data = audio;
|
|
pcm->info_flags = 0;
|
|
audio->pcm = pcm;
|
|
|
|
strlcpy(pcm->name, "USB gadget audio", sizeof(pcm->name));
|
|
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &audio_playback_ops);
|
|
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
|
NULL, 0, 64 * 1024);
|
|
|
|
strlcpy(card->driver, "audio_source", sizeof(card->driver));
|
|
strlcpy(card->shortname, card->driver, sizeof(card->shortname));
|
|
strlcpy(card->longname, "USB accessory audio source",
|
|
sizeof(card->longname));
|
|
|
|
err = snd_card_register(card);
|
|
if (err)
|
|
goto register_fail;
|
|
|
|
config->card = pcm->card->number;
|
|
config->device = pcm->device;
|
|
audio->card = card;
|
|
return 0;
|
|
|
|
register_fail:
|
|
pcm_fail:
|
|
snd_card_free(audio->card);
|
|
return err;
|
|
}
|
|
|
|
static struct audio_source_instance *to_audio_source_instance(
|
|
struct config_item *item)
|
|
{
|
|
return container_of(to_config_group(item), struct audio_source_instance,
|
|
func_inst.group);
|
|
}
|
|
|
|
static struct audio_source_instance *to_fi_audio_source(
|
|
const struct usb_function_instance *fi)
|
|
{
|
|
return container_of(fi, struct audio_source_instance, func_inst);
|
|
}
|
|
|
|
static void audio_source_attr_release(struct config_item *item)
|
|
{
|
|
struct audio_source_instance *fi_audio = to_audio_source_instance(item);
|
|
|
|
usb_put_function_instance(&fi_audio->func_inst);
|
|
}
|
|
|
|
static int audio_source_set_inst_name(struct usb_function_instance *fi,
|
|
const char *name)
|
|
{
|
|
struct audio_source_instance *fi_audio;
|
|
char *ptr;
|
|
int name_len;
|
|
|
|
name_len = strlen(name) + 1;
|
|
if (name_len > MAX_INST_NAME_LEN)
|
|
return -ENAMETOOLONG;
|
|
|
|
ptr = kstrndup(name, name_len, GFP_KERNEL);
|
|
if (!ptr)
|
|
return -ENOMEM;
|
|
|
|
fi_audio = to_fi_audio_source(fi);
|
|
fi_audio->name = ptr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void audio_source_free_inst(struct usb_function_instance *fi)
|
|
{
|
|
struct audio_source_instance *fi_audio;
|
|
|
|
fi_audio = to_fi_audio_source(fi);
|
|
device_destroy(fi_audio->audio_device->class,
|
|
fi_audio->audio_device->devt);
|
|
kfree(fi_audio->name);
|
|
kfree(fi_audio->config);
|
|
}
|
|
|
|
static ssize_t audio_source_pcm_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct audio_source_instance *fi_audio = dev_get_drvdata(dev);
|
|
struct audio_source_config *config = fi_audio->config;
|
|
|
|
/* print PCM card and device numbers */
|
|
return sprintf(buf, "%d %d\n", config->card, config->device);
|
|
}
|
|
|
|
struct device *create_function_device(char *name);
|
|
|
|
static struct usb_function_instance *audio_source_alloc_inst(void)
|
|
{
|
|
struct audio_source_instance *fi_audio;
|
|
struct device_attribute **attrs;
|
|
struct device_attribute *attr;
|
|
struct device *dev;
|
|
void *err_ptr;
|
|
int err = 0;
|
|
|
|
fi_audio = kzalloc(sizeof(*fi_audio), GFP_KERNEL);
|
|
if (!fi_audio)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
fi_audio->func_inst.set_inst_name = audio_source_set_inst_name;
|
|
fi_audio->func_inst.free_func_inst = audio_source_free_inst;
|
|
|
|
fi_audio->config = kzalloc(sizeof(struct audio_source_config),
|
|
GFP_KERNEL);
|
|
if (!fi_audio->config) {
|
|
err_ptr = ERR_PTR(-ENOMEM);
|
|
goto fail_audio;
|
|
}
|
|
|
|
config_group_init_type_name(&fi_audio->func_inst.group, "",
|
|
&audio_source_func_type);
|
|
dev = create_function_device("f_audio_source");
|
|
|
|
if (IS_ERR(dev)) {
|
|
err_ptr = dev;
|
|
goto fail_audio_config;
|
|
}
|
|
|
|
fi_audio->config->card = -1;
|
|
fi_audio->config->device = -1;
|
|
fi_audio->audio_device = dev;
|
|
|
|
attrs = audio_source_function_attributes;
|
|
if (attrs) {
|
|
while ((attr = *attrs++) && !err)
|
|
err = device_create_file(dev, attr);
|
|
if (err) {
|
|
err_ptr = ERR_PTR(-EINVAL);
|
|
goto fail_device;
|
|
}
|
|
}
|
|
|
|
dev_set_drvdata(dev, fi_audio);
|
|
_audio_dev.config = fi_audio->config;
|
|
|
|
return &fi_audio->func_inst;
|
|
|
|
fail_device:
|
|
device_destroy(dev->class, dev->devt);
|
|
fail_audio_config:
|
|
kfree(fi_audio->config);
|
|
fail_audio:
|
|
kfree(fi_audio);
|
|
return err_ptr;
|
|
|
|
}
|
|
|
|
static struct usb_function *audio_source_alloc(struct usb_function_instance *fi)
|
|
{
|
|
return &_audio_dev.func;
|
|
}
|
|
|
|
DECLARE_USB_FUNCTION_INIT(audio_source, audio_source_alloc_inst,
|
|
audio_source_alloc);
|
|
MODULE_LICENSE("GPL");
|