| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: Mike Lockwood <lockwood@google.com> |
| Date: Fri, 11 May 2012 09:01:08 -0700 |
| Subject: NOUPSTREAM: ANDROID: usb: gadget: f_audio_source: New gadget driver |
| for audio output |
| |
| This driver presents a standard USB audio class interface to the host |
| and an ALSA PCM device to userspace |
| |
| [CPNOTE: 19/07/21] Lee: Should be re-written to use upstream APIs - pinged Badhri |
| |
| Bug: 78114713 |
| Bug: 120441124 |
| [badhri: f_audio_source is being migrated to userspace.] |
| Change-Id: If16b14a5ff27045f9cb2daaf1ae9195c5eeab7d0 |
| Signed-off-by: Mike Lockwood <lockwood@google.com> |
| [AmitP: Folded following android-4.9 commit changes into this patch |
| Parts of e27543931009 ("ANDROID: usb: gadget: Fixes and hacks to make android usb gadget compile on 3.8") |
| i6d9285e2574a ("ANDROID: usb: gadget: f_audio_source:replace deprecated API")] |
| Signed-off-by: Amit Pundir <amit.pundir@linaro.org> |
| [adelva: Folded the following changes into this patch: |
| ddfd0c4070c1 ("ANDROID: usb: gadget: f_audio_source: Move to USB_FUNCTION API") |
| a3ab81aaa19e ("ANDROID: usb: gadget: f_audio_source: Move gadget functions code") |
| 2095c953d894 ("ANDROID: usb: gadget: f_audio_source: change max ISO packet size") |
| 8671b3e53638 ("ANDROID: usb: gadget: f_audio_source: Fixed USB Audio Class Interface Descriptor") |
| af98f36edbe2 ("ANDROID: usb: gadget: f_audio_source: disable the CPU C-states upon playback") |
| a830751a338e ("CHROMIUM: usb: gadget: f_audio_source: add .free_func callback")] |
| Signed-off-by: Alistair Delva <adelva@google.com> |
| --- |
| drivers/usb/gadget/Kconfig | 12 + |
| drivers/usb/gadget/function/Makefile | 2 + |
| drivers/usb/gadget/function/f_audio_source.c | 1071 ++++++++++++++++++ |
| 3 files changed, 1085 insertions(+) |
| create mode 100644 drivers/usb/gadget/function/f_audio_source.c |
| |
| diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig |
| --- a/drivers/usb/gadget/Kconfig |
| +++ b/drivers/usb/gadget/Kconfig |
| @@ -219,6 +219,9 @@ config USB_F_TCM |
| config USB_F_ACC |
| tristate |
| |
| +config USB_F_AUDIO_SRC |
| + tristate |
| + |
| # this first set of drivers all depend on bulk-capable hardware. |
| |
| config USB_CONFIGFS |
| @@ -390,6 +393,15 @@ config USB_CONFIGFS_F_ACC |
| help |
| USB gadget Accessory support |
| |
| +config USB_CONFIGFS_F_AUDIO_SRC |
| + bool "Audio Source gadget" |
| + depends on USB_CONFIGFS |
| + depends on SND |
| + select SND_PCM |
| + select USB_F_AUDIO_SRC |
| + help |
| + USB gadget Audio Source support |
| + |
| config USB_CONFIGFS_F_UAC1 |
| bool "Audio Class 1.0" |
| depends on USB_CONFIGFS |
| diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile |
| --- a/drivers/usb/gadget/function/Makefile |
| +++ b/drivers/usb/gadget/function/Makefile |
| @@ -52,3 +52,5 @@ usb_f_tcm-y := f_tcm.o |
| obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o |
| usb_f_accessory-y := f_accessory.o |
| obj-$(CONFIG_USB_F_ACC) += usb_f_accessory.o |
| +usb_f_audio_source-y := f_audio_source.o |
| +obj-$(CONFIG_USB_F_AUDIO_SRC) += usb_f_audio_source.o |
| diff --git a/drivers/usb/gadget/function/f_audio_source.c b/drivers/usb/gadget/function/f_audio_source.c |
| new file mode 100644 |
| --- /dev/null |
| +++ b/drivers/usb/gadget/function/f_audio_source.c |
| @@ -0,0 +1,1071 @@ |
| +/* |
| + * 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 */ |
| + cpu_latency_qos_add_request(&audio->pm_qos, 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 */ |
| + cpu_latency_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"); |