new file mode 100644
@@ -0,0 +1,654 @@
+/*
+ * f_uac.c -- USB Audio class function driver
+ *
+ * Copyright (C) 2009
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ * Based on f_audio.c
+ * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ * Copyright (C) 2008 Analog Devices, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <asm/atomic.h>
+
+#include "uac.h"
+
+/* I'd like 68 bytes packets, but for some reason the MUSB controller refuses
+ * to transfer 68 bytes isochronous packets. 64 bytes and 70 bytes work though.
+ * Go figure.
+ */
+#define IN_EP_MAX_PACKET_SIZE 70
+
+static int req_buf_size = IN_EP_MAX_PACKET_SIZE;
+module_param(req_buf_size, int, S_IRUGO);
+MODULE_PARM_DESC(req_buf_size, "ISO IN endpoint request buffer size");
+
+static int req_count = 256;
+module_param(req_count, int, S_IRUGO);
+MODULE_PARM_DESC(req_count, "ISO IN endpoint request count");
+
+static int audio_buf_size = 48000;
+module_param(audio_buf_size, int, S_IRUGO);
+MODULE_PARM_DESC(audio_buf_size, "Audio buffer size");
+
+static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value);
+static int generic_get_cmd(struct usb_audio_control *con, u8 cmd);
+
+/* --------------------------------------------------------------------------
+ * Function descriptors
+ */
+
+#define INPUT_TERMINAL_ID 1
+#define FEATURE_UNIT_ID 2
+#define OUTPUT_TERMINAL_ID 3
+
+static struct usb_interface_assoc_descriptor uac_iad __initdata = {
+ .bLength = USB_DT_INTERFACE_ASSOCIATION_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
+ .bFirstInterface = 0,
+ .bInterfaceCount = 2,
+ .bFunctionClass = USB_CLASS_AUDIO,
+ .bFunctionSubClass = USB_SUBCLASS_AUDIOSTREAMING, /* FIXME Where is this documented ? */
+ .bFunctionProtocol = 0x00,
+ .iFunction = 0,
+};
+
+/* B.3.1 Standard AC Interface Descriptor */
+static struct usb_interface_descriptor ac_interface_desc __initdata = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0, /* dynamic */
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+};
+
+DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
+
+/* B.3.2 Class-Specific AC Interface Descriptor */
+static struct uac_ac_header_descriptor_2 ac_header_desc = {
+ .bLength = UAC_DT_AC_HEADER_SIZE(1),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubtype = UAC_HEADER,
+ .bcdADC = cpu_to_le16(0x0100),
+ .wTotalLength = cpu_to_le16(UAC_DT_AC_HEADER_SIZE(1) +
+ UAC_DT_INPUT_TERMINAL_SIZE +
+ UAC_DT_FEATURE_UNIT_SIZE(0)),
+ .bInCollection = 1,
+ .baInterfaceNr[0] = 0, /* dynamic */
+};
+
+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,
+ .bNrChannels = 1, /* TODO make this dynamic */
+ .wChannelConfig = 0, /* dynamic */
+ .iChannelNames = 0,
+ .iTerminal = 0,
+};
+
+DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0);
+
+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,
+ .bmaControls[0] = (UAC_FU_MUTE | UAC_FU_VOLUME),
+ .iFeature = 0,
+};
+
+static struct uac_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 = 0,
+ .bSourceID = FEATURE_UNIT_ID,
+ .iTerminal = 0,
+};
+
+/* 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,
+ .bInterfaceNumber = 0, /* dynamic */
+ .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,
+ .bInterfaceNumber = 0, /* dynamic */
+ .bAlternateSetting = 1,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+/* B.4.2 Class-Specific AS Interface Descriptor */
+static struct uac_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,
+ .bNrChannels = 1, /* TODO make this dynamic */
+ .bSubframeSize = 2,
+ .bBitResolution = 16,
+ .bSamFreqType = 1,
+ .tSamFreq[0] = { 0x00, 0x00, 0x00 }, /* dynamic */
+};
+
+/* Standard ISO IN Endpoint Descriptor */
+static struct usb_endpoint_descriptor 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 = cpu_to_le16(IN_EP_MAX_PACKET_SIZE),
+ .bInterval = 4,
+};
+
+/* Class-specific AS ISO IN Endpoint Descriptor */
+static struct uac_iso_endpoint_descriptor as_iso_in_desc __initdata = {
+ .bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
+ .bDescriptorType = USB_DT_CS_ENDPOINT,
+ .bDescriptorSubtype = UAC_EP_GENERAL,
+ .bmAttributes = 1,
+ .bLockDelayUnits = 1,
+ .wLockDelay = cpu_to_le16(1),
+};
+
+static struct usb_descriptor_header *f_audio_desc[] __initdata = {
+ (struct usb_descriptor_header *)&uac_iad,
+
+ (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 *)&as_in_ep_desc,
+ (struct usb_descriptor_header *)&as_iso_in_desc,
+ NULL,
+};
+
+/* --------------------------------------------------------------------------
+ * Control requests
+ */
+
+static inline struct uac_device *func_to_audio(struct usb_function *f)
+{
+ return container_of(f, struct uac_device, func);
+}
+
+static struct usb_audio_control mute_control = {
+ .list = LIST_HEAD_INIT(mute_control.list),
+ .name = "Mute Control",
+ .type = UAC_MUTE_CONTROL,
+ /* Todo: add real Mute control code */
+ .set = generic_set_cmd,
+ .get = generic_get_cmd,
+};
+
+static struct usb_audio_control volume_control = {
+ .list = LIST_HEAD_INIT(volume_control.list),
+ .name = "Volume Control",
+ .type = UAC_VOLUME_CONTROL,
+ /* Todo: add real Volume control code */
+ .set = generic_set_cmd,
+ .get = generic_get_cmd,
+};
+
+static struct usb_audio_unit feature_unit = {
+ .list = LIST_HEAD_INIT(feature_unit.list),
+ .id = FEATURE_UNIT_ID,
+ .name = "Mute & Volume Control",
+ .type = UAC_FEATURE_UNIT,
+ .desc = (struct usb_descriptor_header *)&feature_unit_desc,
+};
+
+static void
+uac_function_ep0_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct uac_device *uac = req->context;
+ struct usb_composite_dev *cdev = uac->func.config->cdev;
+ int value = le16_to_cpup((__le16 *)req->buf);
+
+ if (req->status != 0 || uac->set_con == NULL)
+ return;
+
+ DBG(cdev, "setting control %s to %d\n", uac->set_con->name, value);
+
+ uac->set_con->set(uac->set_con, uac->set_cmd, value);
+ uac->set_con = NULL;
+}
+
+static int
+uac_function_set_endpoint_req(struct uac_device *uac,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct usb_composite_dev *cdev = uac->func.config->cdev;
+ u8 ep = le16_to_cpu(ctrl->wIndex) & 0xff;
+ u8 cs = le16_to_cpu(ctrl->wValue) >> 8;
+ int value = -EOPNOTSUPP;
+
+ if (cs != UAC_EP_CS_ATTR_SAMPLE_RATE ||
+ ep != as_in_ep_desc.bEndpointAddress)
+ return -EOPNOTSUPP;
+
+ switch (ctrl->bRequest) {
+ case UAC_SET_CUR:
+ DBG(cdev, "setting sampling frequency\n");
+ value = 3;
+ break;
+
+ case UAC_SET_MIN:
+ case UAC_SET_MAX:
+ case UAC_SET_RES:
+ case UAC_SET_MEM:
+ default:
+ break;
+ }
+
+ return value;
+}
+
+static int
+uac_function_get_endpoint_req(struct uac_device *uac,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct usb_composite_dev *cdev = uac->func.config->cdev;
+ u8 ep = le16_to_cpu(ctrl->wIndex) & 0xff;
+ u8 cs = le16_to_cpu(ctrl->wValue) >> 8;
+ int value = -EOPNOTSUPP;
+
+ if (cs != UAC_EP_CS_ATTR_SAMPLE_RATE ||
+ ep != as_in_ep_desc.bEndpointAddress)
+ return -EOPNOTSUPP;
+
+ switch (ctrl->bRequest) {
+ case UAC_GET_CUR:
+ case UAC_GET_MIN:
+ case UAC_GET_MAX:
+ case UAC_GET_RES:
+ DBG(cdev, "getting sampling frequency\n");
+ memcpy(uac->control_buf, as_type_i_desc.tSamFreq[0], 3);
+ value = 3;
+ break;
+
+ case UAC_GET_MEM:
+ default:
+ break;
+ }
+
+ return value;
+}
+
+static struct usb_audio_control *
+uac_function_find_control(struct uac_device *uac, int unit, int cs)
+{
+ struct usb_audio_unit *entity;
+ struct usb_audio_control *ctrl;
+
+ list_for_each_entry(entity, &uac->units, list) {
+ if (entity->id != unit)
+ continue;
+
+ list_for_each_entry(ctrl, &entity->control, list) {
+ if (ctrl->type == cs)
+ return ctrl;
+ }
+ }
+
+ return NULL;
+}
+
+static int
+uac_function_set_intf_req(struct uac_device *uac,
+ const struct usb_ctrlrequest *creq)
+{
+ struct usb_composite_dev *cdev = uac->func.config->cdev;
+ u16 len = le16_to_cpu(creq->wLength);
+ u8 unit = le16_to_cpu(creq->wIndex) >> 8;
+ u8 cs = le16_to_cpu(creq->wValue) >> 8;
+ struct usb_audio_control *ctrl;
+
+ ctrl = uac_function_find_control(uac, unit, cs);
+ if (ctrl == NULL)
+ return -EOPNOTSUPP;
+
+ uac->set_con = ctrl;
+ uac->set_cmd = creq->bRequest & 0x0f;
+
+ DBG(cdev, "setting control %s value\n", ctrl->name);
+ return le16_to_cpu(len);
+}
+
+static int
+uac_function_get_intf_req(struct uac_device *uac,
+ const struct usb_ctrlrequest *creq)
+{
+ struct usb_composite_dev *cdev = uac->func.config->cdev;
+ u16 len = le16_to_cpu(creq->wLength);
+ u8 unit = le16_to_cpu(creq->wIndex) >> 8;
+ u8 cs = le16_to_cpu(creq->wValue) >> 8;
+ struct usb_audio_control *ctrl;
+ int value;
+
+ ctrl = uac_function_find_control(uac, unit, cs);
+ if (ctrl == NULL)
+ return -EOPNOTSUPP;
+
+ value = ctrl->get(ctrl, creq->bRequest & 0x0f);
+ memcpy(uac->control_buf, &value, len);
+
+ DBG(cdev, "getting control %s value (%d)\n", ctrl->name, value);
+ return len;
+}
+
+static int
+uac_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct uac_device *uac = func_to_audio(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = uac->control_req;
+ int value = -EOPNOTSUPP;
+ u16 wIndex = le16_to_cpu(ctrl->wIndex);
+ u16 wValue = le16_to_cpu(ctrl->wValue);
+ u16 wLength = 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_INTERFACE:
+ value = uac_function_set_intf_req(uac, ctrl);
+ break;
+
+ case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
+ value = uac_function_get_intf_req(uac, ctrl);
+ break;
+
+ case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+ value = uac_function_set_endpoint_req(uac, ctrl);
+ break;
+
+ case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+ value = uac_function_get_endpoint_req(uac, ctrl);
+ break;
+
+ default:
+ ERROR(cdev, "invalid setup request %02x %02x value %04x index "
+ "%04x %04x\n", ctrl->bRequestType, ctrl->bRequest,
+ wValue, wIndex, wLength);
+ break;
+ }
+
+ if (value >= 0) {
+ req->zero = 0;
+ req->length = value;
+ value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+ }
+
+ return value;
+}
+
+static int
+uac_function_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+ struct uac_device *uac = func_to_audio(f);
+
+ DBG(f->config->cdev, "intf %d, alt %d\n", intf, alt);
+
+ if (intf == uac->control_intf) {
+ if (alt)
+ return -EINVAL;
+
+ /* TODO Notify userspace that the device has been connected. */
+ return 0;
+ }
+
+ if (intf != uac->streaming_intf)
+ return -EINVAL;
+
+ switch (alt) {
+ case 0:
+ uac_audio_enable(uac, 0);
+ usb_ep_disable(uac->audio_ep);
+
+ /* TODO Notify userspace that the audio stream should be stopped. */
+ break;
+
+ case 1:
+ usb_ep_enable(uac->audio_ep, &as_in_ep_desc);
+ uac_audio_enable(uac, 1);
+
+ /* TODO Notify userspace that the audio stream should be started. */
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void
+uac_function_disable(struct usb_function *f)
+{
+ // struct uac_device *uac = func_to_audio(f);
+
+ DBG(f->config->cdev, "uac_function_disable\n");
+
+ /* TODO Notify userspace that the device has been disconnected. */
+ return;
+}
+
+/* --------------------------------------------------------------------------
+ * USB probe and disconnect
+ */
+
+static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value)
+{
+ con->data[cmd] = value;
+
+ return 0;
+}
+
+static int generic_get_cmd(struct usb_audio_control *con, u8 cmd)
+{
+ return con->data[cmd];
+}
+
+/* Todo: add more control selectors dynamically */
+static int __init
+control_selector_init(struct uac_device *uac)
+{
+ INIT_LIST_HEAD(&uac->units);
+ list_add(&feature_unit.list, &uac->units);
+
+ INIT_LIST_HEAD(&feature_unit.control);
+ list_add(&mute_control.list, &feature_unit.control);
+ list_add(&volume_control.list, &feature_unit.control);
+
+ volume_control.data[UAC_SET_CUR] = 0xffc0;
+ volume_control.data[UAC_SET_MIN] = 0xe3a0;
+ volume_control.data[UAC_SET_MAX] = 0xfff0;
+ volume_control.data[UAC_SET_RES] = 0x0030;
+
+ return 0;
+}
+
+static void
+uac_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct uac_device *uac = func_to_audio(f);
+
+ uac_audio_cleanup(uac);
+
+ if (uac->audio_ep)
+ uac->audio_ep->driver_data = NULL;
+
+ if (uac->control_req) {
+ usb_ep_free_request(cdev->gadget->ep0, uac->control_req);
+ kfree(uac->control_buf);
+ }
+
+ usb_free_descriptors(f->descriptors);
+ usb_free_descriptors(f->hs_descriptors);
+
+ kfree(uac);
+}
+
+/* audio function driver setup/binding */
+static int __init
+uac_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct uac_device *uac = func_to_audio(f);
+ int ret;
+ struct usb_ep *ep;
+
+ /* Allocate endpoints. */
+ ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
+ if (!ep) {
+ ret = -ENODEV;
+ goto error;
+ }
+
+ uac->audio_ep = ep;
+ ep->driver_data = uac;
+
+ /* Allocate interface IDs. */
+ if ((ret = usb_interface_id(c, f)) < 0)
+ goto error;
+ ac_interface_desc.bInterfaceNumber = ret;
+ uac->control_intf = ret;
+ uac_iad.bFirstInterface = ret;
+
+ if ((ret = usb_interface_id(c, f)) < 0)
+ goto error;
+ as_interface_alt_0_desc.bInterfaceNumber = ret;
+ as_interface_alt_1_desc.bInterfaceNumber = ret;
+ ac_header_desc.baInterfaceNr[0] = ret;
+ uac->streaming_intf = ret;
+
+ /* Copy descriptors. Support all relevant hardware speeds. We expect
+ * that when hardware is dual speed, all isochronous-capable endpoints
+ * work at both speeds.
+ */
+ as_type_i_desc.tSamFreq[0][0] = (uac->rate >> 0) & 0xff;
+ as_type_i_desc.tSamFreq[0][1] = (uac->rate >> 8) & 0xff;
+ as_type_i_desc.tSamFreq[0][2] = (uac->rate >> 16) & 0xff;
+
+ f->descriptors = usb_copy_descriptors(f_audio_desc);
+ if (gadget_is_dualspeed(c->cdev->gadget))
+ f->hs_descriptors = usb_copy_descriptors(f_audio_desc);
+
+ /* Preallocate control endpoint request. */
+ uac->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
+ uac->control_buf = kmalloc(UAC_MAX_REQUEST_SIZE, GFP_KERNEL);
+ if (uac->control_req == NULL || uac->control_buf == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ uac->control_req->buf = uac->control_buf;
+ uac->control_req->complete = uac_function_ep0_complete;
+ uac->control_req->context = uac;
+
+ /* Set up ALSA audio devices. */
+ ret = uac_audio_init(uac);
+ if (ret < 0)
+ return ret;
+
+ control_selector_init(uac);
+
+ return 0;
+
+error:
+ uac_function_unbind(c, f);
+ return ret;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/**
+ * audio_bind_config - add USB audio fucntion to a configuration
+ * @c: the configuration to supcard the USB audio function
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ */
+int __init
+audio_bind_config(struct usb_configuration *c, unsigned int rate)
+{
+ struct uac_device *uac;
+ int ret;
+
+ /* Allocate and initialize one new instance. */
+ uac = kzalloc(sizeof *uac, GFP_KERNEL);
+ if (uac == NULL)
+ return -ENOMEM;
+
+ uac->rate = rate;
+ uac->func.name = "g_audio";
+
+ /* Register the function. */
+ uac->func.strings = NULL;
+ uac->func.bind = uac_function_bind;
+ uac->func.unbind = uac_function_unbind;
+ uac->func.set_alt = uac_function_set_alt;
+ uac->func.setup = uac_function_setup;
+ uac->func.disable = uac_function_disable;
+
+ ret = usb_add_function(c, &uac->func);
+ if (ret) {
+ kfree(uac);
+ return ret;
+ }
+
+ INFO(c->cdev, "audio_buf_size %d, req_buf_size %d, req_count %d\n",
+ audio_buf_size, req_buf_size, req_count);
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,99 @@
+/*
+ * uac.h -- USB audio class function driver
+ *
+ * Copyright (C) 2009
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ * 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.
+ */
+
+#ifndef __UAC_H
+#define __UAC_H
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/composite.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "gadget_chips.h"
+
+#define UAC_NUM_REQUESTS 16
+#define UAC_MAX_REQUEST_SIZE 64
+
+struct usb_audio_unit {
+ struct list_head list;
+ struct list_head control;
+ u8 id;
+ const char *name;
+ u8 type;
+ struct usb_descriptor_header *desc;
+};
+
+/*
+ * This represents the USB side of an audio card device, managed by a USB
+ * function which provides control and stream interfaces.
+ */
+
+struct uac_device;
+
+struct snd_uac_substream {
+ struct uac_device *uac;
+ struct snd_pcm_substream *substream;
+ spinlock_t lock;
+ unsigned int streaming;
+
+ unsigned int frame_bytes;
+ unsigned int buffer_bytes;
+ unsigned int buffer_pos;
+ unsigned int period_bytes;
+ unsigned int period_pos;
+};
+
+struct snd_uac_device {
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ struct snd_uac_substream substreams[2];
+};
+
+struct uac_device {
+ struct usb_function func;
+
+ struct snd_uac_device sound;
+ unsigned int rate;
+
+ /* endpoints handle full and/or high speeds */
+ struct usb_ep *audio_ep;
+
+ /* Control Set command */
+ struct list_head units;
+ u8 set_cmd;
+ struct usb_audio_control *set_con;
+
+ unsigned int control_intf;
+ struct usb_request *control_req;
+ void *control_buf;
+
+ unsigned int streaming_intf;
+
+ /* Audio streaming requests */
+ unsigned int req_size;
+ struct usb_request *req[UAC_NUM_REQUESTS];
+ __u8 *req_buffer[UAC_NUM_REQUESTS];
+ struct list_head req_free;
+ spinlock_t req_lock;
+};
+
+extern int audio_bind_config(struct usb_configuration *c, unsigned int rate);
+
+extern int uac_audio_enable(struct uac_device *audio, int enable);
+extern int uac_audio_init(struct uac_device *audio);
+extern void uac_audio_cleanup(struct uac_device *audio);
+
+#endif /* __UAC_H */
new file mode 100644
@@ -0,0 +1,348 @@
+/*
+ * uac_alsa.c -- USB audio class function driver, ALSA interface
+ *
+ * Copyright (C) 2009
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+#include "uac.h"
+
+/*
+ * This component encapsulates the ALSA devices for USB audio gadget
+ */
+
+static int uac_capture = 1;
+static int uac_playback = 1;
+
+module_param(uac_capture, int, 0444);
+MODULE_PARM_DESC(uac_capture, "Support audio capture.");
+module_param(uac_playback, int, 0444);
+MODULE_PARM_DESC(uac_playback, "Support audio playback.");
+
+/* -------------------------------------------------------------------------
+ * Utility functions
+ */
+
+static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
+ unsigned long offset)
+{
+ void *pageptr = subs->runtime->dma_area + offset;
+ return vmalloc_to_page(pageptr);
+}
+
+static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, size_t size)
+{
+ struct snd_pcm_runtime *runtime = subs->runtime;
+ if (runtime->dma_area) {
+ if (runtime->dma_bytes >= size)
+ return 0; /* already large enough */
+ vfree(runtime->dma_area);
+ }
+ runtime->dma_area = vmalloc(size);
+ if (!runtime->dma_area)
+ return -ENOMEM;
+ runtime->dma_bytes = size;
+ return 0;
+}
+
+static int snd_pcm_free_vmalloc_buffer(struct snd_pcm_substream *subs)
+{
+ struct snd_pcm_runtime *runtime = subs->runtime;
+
+ vfree(runtime->dma_area);
+ runtime->dma_area = NULL;
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct snd_pcm_hardware snd_uac_capture_hw = {
+ .info = SNDRV_PCM_INFO_MMAP
+ | SNDRV_PCM_INFO_INTERLEAVED
+ | SNDRV_PCM_INFO_BLOCK_TRANSFER
+ | SNDRV_PCM_INFO_MMAP_VALID,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = 32 * 1024,
+ .period_bytes_min = 4 * 1024,
+ .period_bytes_max = 32 * 1024,
+ .periods_min = 1,
+ .periods_max = 1024,
+};
+
+static struct snd_pcm_hardware snd_uac_playback_hw = {
+ .info = SNDRV_PCM_INFO_MMAP
+ | SNDRV_PCM_INFO_INTERLEAVED
+ | SNDRV_PCM_INFO_BLOCK_TRANSFER
+ | SNDRV_PCM_INFO_MMAP_VALID,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = 32 * 1024,
+ .period_bytes_min = 4 * 1024,
+ .period_bytes_max = 32 * 1024,
+ .periods_min = 1,
+ .periods_max = 1024,
+};
+
+static int
+snd_uac_pcm_open(struct snd_pcm_substream *substream, int stream)
+{
+ struct uac_device *uac = snd_pcm_substream_chip(substream);
+ struct snd_uac_substream *subs = &uac->sound.substreams[stream];
+
+ INFO(uac->func.config->cdev, "snd_uac_pcm_open\n");
+
+ substream->runtime->hw = stream == SNDRV_PCM_STREAM_PLAYBACK
+ ? snd_uac_playback_hw
+ : snd_uac_capture_hw;
+ substream->runtime->hw.rate_min = uac->rate;
+ substream->runtime->hw.rate_max = uac->rate;
+
+ substream->runtime->private_data = subs;
+ subs->substream = substream;
+
+ return 0;
+}
+
+static int
+snd_uac_capture_open(struct snd_pcm_substream *substream)
+{
+ return snd_uac_pcm_open(substream, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int
+snd_uac_playback_open(struct snd_pcm_substream *substream)
+{
+ return snd_uac_pcm_open(substream, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static int
+snd_uac_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct uac_device *uac = snd_pcm_substream_chip(substream);
+ struct snd_uac_substream *subs = substream->runtime->private_data;
+
+ INFO(uac->func.config->cdev, "snd_uac_pcm_close\n");
+
+ subs->substream = NULL;
+ return 0;
+}
+
+static int
+snd_uac_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct uac_device *uac = snd_pcm_substream_chip(substream);
+ size_t size;
+
+ INFO(uac->func.config->cdev, "snd_uac_pcm_hw_params\n");
+
+ size = params_buffer_bytes(hw_params);
+ return snd_pcm_alloc_vmalloc_buffer(substream, size);
+}
+
+static int
+snd_uac_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct uac_device *uac = snd_pcm_substream_chip(substream);
+
+ INFO(uac->func.config->cdev, "snd_uac_pcm_hw_free\n");
+
+ return snd_pcm_free_vmalloc_buffer(substream);
+}
+
+static int
+snd_uac_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct uac_device *uac = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_uac_substream *subs = runtime->private_data;
+
+ INFO(uac->func.config->cdev, "snd_uac_pcm_prepare\n");
+
+ subs->frame_bytes = frames_to_bytes(runtime, uac->rate) / 1000;
+ subs->buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size);
+ subs->period_bytes = frames_to_bytes(runtime, runtime->period_size);
+ subs->buffer_pos = 0;
+ subs->period_pos = 0;
+
+ INFO(uac->func.config->cdev, "buffer: %u bytes, period: %u bytes, "
+ "usb frame: %u bytes\n", subs->buffer_bytes, subs->period_bytes,
+ subs->frame_bytes);
+ return 0;
+}
+
+static int
+snd_uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct uac_device *uac = snd_pcm_substream_chip(substream);
+ struct snd_uac_substream *subs = substream->runtime->private_data;
+ unsigned long flags;
+
+ INFO(uac->func.config->cdev, "snd_uac_pcm_trigger(%d)\n", cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ spin_lock_irqsave(&subs->lock, flags);
+ subs->streaming = 1;
+ spin_unlock_irqrestore(&subs->lock, flags);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ spin_lock_irqsave(&subs->lock, flags);
+ subs->streaming = 0;
+ spin_unlock_irqrestore(&subs->lock, flags);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+snd_uac_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct uac_device *uac = snd_pcm_substream_chip(substream);
+ struct snd_uac_substream *subs = substream->runtime->private_data;
+ snd_pcm_uframes_t pointer;
+
+ pointer = bytes_to_frames(substream->runtime, subs->buffer_pos);
+// INFO(uac->func.config->cdev, "snd_uac_pcm_pointer -> %lu\n", pointer);
+
+ return pointer;
+}
+
+static struct snd_pcm_ops snd_uac_capture_ops = {
+ .open = snd_uac_capture_open,
+ .close = snd_uac_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_uac_pcm_hw_params,
+ .hw_free = snd_uac_pcm_hw_free,
+ .prepare = snd_uac_pcm_prepare,
+ .trigger = snd_uac_pcm_trigger,
+ .pointer = snd_uac_pcm_pointer,
+ .page = snd_pcm_get_vmalloc_page,
+};
+
+static struct snd_pcm_ops snd_uac_playback_ops = {
+ .open = snd_uac_playback_open,
+ .close = snd_uac_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_uac_pcm_hw_params,
+ .hw_free = snd_uac_pcm_hw_free,
+ .prepare = snd_uac_pcm_prepare,
+ .trigger = snd_uac_pcm_trigger,
+ .pointer = snd_uac_pcm_pointer,
+ .page = snd_pcm_get_vmalloc_page,
+};
+
+static void
+uac_init_substream(struct uac_device *uac, int stream)
+{
+ struct snd_uac_substream *subs = &uac->sound.substreams[stream];
+
+ snd_pcm_set_ops(uac->sound.pcm, stream,
+ stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ &snd_uac_playback_ops : &snd_uac_capture_ops);
+
+ subs->uac = uac;
+ subs->streaming = 0;
+ spin_lock_init(&subs->lock);
+}
+
+/**
+ * uac_audio_init - setup ALSA interface and preparing for USB transfer
+ *
+ * This sets up PCM, mixer or MIDI ALSA devices for USB gadget using.
+ *
+ * Returns negative errno, or zero on success
+ */
+int __init uac_audio_init(struct uac_device *uac)
+{
+ static int dev = 0;
+
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ int ret;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+
+ INIT_LIST_HEAD(&uac->req_free);
+ spin_lock_init(&uac->req_lock);
+
+ /* Create a card instance. */
+ ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ THIS_MODULE, 0, &card);
+ if (ret < 0)
+ return ret;
+
+ strlcpy(card->driver, "UAC gadget", sizeof(card->driver));
+ strlcpy(card->shortname, "UAC gadget", sizeof(card->shortname));
+ strlcpy(card->longname, "UAC gadget", sizeof(card->longname));
+
+ snd_card_set_dev(card, &uac->func.config->cdev->gadget->dev);
+
+ /* Create a PCM device. */
+ ret = snd_pcm_new(card, "UAC gadget", 0, uac_capture ? 1 : 0,
+ uac_playback ? 1 : 0, &pcm);
+ if (ret < 0)
+ goto error;
+
+ uac->sound.pcm = pcm;
+ pcm->private_data = uac;
+
+ if (uac_capture)
+ uac_init_substream(uac, SNDRV_PCM_STREAM_PLAYBACK);
+ if (uac_playback)
+ uac_init_substream(uac, SNDRV_PCM_STREAM_CAPTURE);
+
+ /* Register the sound card. */
+ if ((ret = snd_card_register(card)) < 0)
+ goto error;
+
+ uac->sound.card = card;
+ dev++;
+
+ return 0;
+
+error:
+ snd_card_free(card);
+ return ret;
+}
+
+/**
+ * uac_audio_cleanup - remove ALSA device interface
+ *
+ * This is called to free all resources allocated by @uac_audio_setup().
+ */
+void uac_audio_cleanup(struct uac_device *uac)
+{
+ snd_card_free(uac->sound.card);
+ uac->sound.card = NULL;
+}
+
new file mode 100644
@@ -0,0 +1,238 @@
+/*
+ * uac_audio.c -- USB audio class function driver, audio data handling
+ *
+ * Copyright (C) 2009
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/usb/gadget.h>
+
+#include "uac.h"
+
+/* --------------------------------------------------------------------------
+ * Video codecs
+ */
+
+static void
+uac_audio_encode(struct snd_uac_substream *subs, struct usb_request *req)
+{
+ unsigned char *dma_area;
+ unsigned long flags;
+ unsigned int size;
+
+ spin_lock_irqsave(&subs->lock, flags);
+ if (!subs->streaming) {
+ spin_unlock_irqrestore(&subs->lock, flags);
+ req->length = 0;
+ return;
+ }
+
+ /* TODO Handle buffer underruns. */
+ size = subs->frame_bytes;
+ dma_area = subs->substream->runtime->dma_area;
+
+ if (subs->buffer_pos + size > subs->buffer_bytes) {
+ unsigned int left = subs->buffer_bytes - subs->buffer_pos;
+ memcpy(req->buf, dma_area + subs->buffer_pos, left);
+ memcpy(req->buf + left, dma_area, size - left);
+ } else
+ memcpy(req->buf, dma_area + subs->buffer_pos, size);
+
+ spin_unlock_irqrestore(&subs->lock, flags);
+
+ req->length = size;
+
+ /* Update the buffer and period positions, and update the pcm status
+ * for the next period if we reached the end of the current one.
+ */
+ subs->buffer_pos += size;
+ if (subs->buffer_pos >= subs->buffer_bytes)
+ subs->buffer_pos -= subs->buffer_bytes;
+
+ subs->period_pos += size;
+ if (subs->period_pos >= subs->period_bytes) {
+ subs->period_pos %= subs->period_bytes;
+ snd_pcm_period_elapsed(subs->substream);
+ }
+}
+
+/* --------------------------------------------------------------------------
+ * Request handling
+ */
+
+static void
+uac_audio_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct snd_uac_substream *subs = req->context;
+ struct uac_device *uac = subs->uac;
+ unsigned long flags;
+ int ret;
+
+ switch (req->status) {
+ case 0:
+ break;
+
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ goto requeue;
+
+ default:
+ INFO(uac->func.config->cdev, "AS request completed with "
+ "status %d.\n", req->status);
+ goto requeue;
+ }
+
+ uac_audio_encode(subs, req);
+
+ if ((ret = usb_ep_queue(ep, req, GFP_ATOMIC)) < 0) {
+ INFO(uac->func.config->cdev, "Failed to queue request (%d).\n",
+ ret);
+ usb_ep_set_halt(ep);
+ goto requeue;
+ }
+ return;
+
+requeue:
+ spin_lock_irqsave(&uac->req_lock, flags);
+ list_add_tail(&req->list, &uac->req_free);
+ spin_unlock_irqrestore(&uac->req_lock, flags);
+}
+
+static int
+uac_audio_free_requests(struct uac_device *uac)
+{
+ unsigned int i;
+
+ for (i = 0; i < UAC_NUM_REQUESTS; ++i) {
+ if (uac->req[i]) {
+ usb_ep_free_request(uac->audio_ep, uac->req[i]);
+ uac->req[i] = NULL;
+ }
+
+ kfree(uac->req_buffer[i]);
+ uac->req_buffer[i] = NULL;
+ }
+
+ INIT_LIST_HEAD(&uac->req_free);
+ uac->req_size = 0;
+ return 0;
+}
+
+static int
+uac_audio_alloc_requests(struct uac_device *uac)
+{
+ struct snd_uac_substream *subs = &uac->sound.substreams[SNDRV_PCM_STREAM_PLAYBACK];
+ unsigned int i;
+
+ BUG_ON(uac->req_size);
+
+ for (i = 0; i < UAC_NUM_REQUESTS; ++i) {
+ uac->req_buffer[i] = kmalloc(uac->audio_ep->maxpacket,
+ GFP_KERNEL);
+ if (uac->req_buffer[i] == NULL)
+ goto error;
+
+ uac->req[i] = usb_ep_alloc_request(uac->audio_ep, GFP_KERNEL);
+ if (uac->req[i] == NULL)
+ goto error;
+
+ uac->req[i]->buf = uac->req_buffer[i];
+ uac->req[i]->length = 0;
+ uac->req[i]->dma = DMA_ADDR_INVALID;
+ uac->req[i]->complete = uac_audio_complete;
+ uac->req[i]->context = subs;
+
+ list_add_tail(&uac->req[i]->list, &uac->req_free);
+ }
+
+ uac->req_size = uac->audio_ep->maxpacket;
+ return 0;
+
+error:
+ uac_audio_free_requests(uac);
+ return -ENOMEM;
+}
+
+/* --------------------------------------------------------------------------
+ * Audio streaming
+ */
+
+/*
+ * uac_audio_pump - Pump audio data into the USB requests
+ *
+ * This function fills the available USB requests (listed in req_free) with
+ * audio data from the ALSA buffer.
+ */
+int
+uac_audio_pump(struct uac_device *uac)
+{
+ struct snd_uac_substream *subs = &uac->sound.substreams[SNDRV_PCM_STREAM_PLAYBACK];
+ struct usb_request *req;
+ unsigned long flags;
+ int ret;
+
+ /* FIXME TODO Race between uac_audio_pump and requests completion
+ * handler ???
+ */
+
+ while (1) {
+ /* Retrieve the first available USB request, protected by the
+ * request lock.
+ */
+ spin_lock_irqsave(&uac->req_lock, flags);
+ if (list_empty(&uac->req_free)) {
+ spin_unlock_irqrestore(&uac->req_lock, flags);
+ return 0;
+ }
+ req = list_first_entry(&uac->req_free, struct usb_request,
+ list);
+ list_del(&req->list);
+ spin_unlock_irqrestore(&uac->req_lock, flags);
+
+ uac_audio_encode(subs, req);
+
+ if ((ret = usb_ep_queue(uac->audio_ep, req, GFP_KERNEL)) < 0) {
+ printk(KERN_INFO "Failed to queue request (%d)\n", ret);
+ usb_ep_set_halt(uac->audio_ep);
+ break;
+ }
+ }
+
+ spin_lock_irqsave(&uac->req_lock, flags);
+ list_add_tail(&req->list, &uac->req_free);
+ spin_unlock_irqrestore(&uac->req_lock, flags);
+ return 0;
+}
+
+/*
+ * Enable or disable the audio stream.
+ */
+
+int
+uac_audio_enable(struct uac_device *uac, int enable)
+{
+ unsigned int i;
+ int ret;
+
+ if (!enable) {
+ for (i = 0; i < UAC_NUM_REQUESTS; ++i)
+ usb_ep_dequeue(uac->audio_ep, uac->req[i]);
+
+ uac_audio_free_requests(uac);
+ return 0;
+ }
+
+ if ((ret = uac_audio_alloc_requests(uac)) < 0)
+ return ret;
+
+ return uac_audio_pump(uac);
+}
+
This USB audio class function driver exposes an ALSA interface to userspace to stream audio data from an application over USB. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> --- drivers/usb/gadget/f_uac.c | 654 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/uac.h | 99 ++++++ drivers/usb/gadget/uac_alsa.c | 348 +++++++++++++++++++++ drivers/usb/gadget/uac_audio.c | 238 +++++++++++++++ 4 files changed, 1339 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/gadget/f_uac.c create mode 100644 drivers/usb/gadget/uac.h create mode 100644 drivers/usb/gadget/uac_alsa.c create mode 100644 drivers/usb/gadget/uac_audio.c