diff options
Diffstat (limited to 'drivers/usb/gadget')
24 files changed, 8617 insertions, 123 deletions
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 080bb1e4b84..5d1ddf485d1 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -156,7 +156,7 @@ config USB_ATMEL_USBA config USB_GADGET_FSL_USB2 boolean "Freescale Highspeed USB DR Peripheral Controller" - depends on FSL_SOC + depends on FSL_SOC || ARCH_MXC select USB_GADGET_DUALSPEED help Some of Freescale PowerPC processors have a High Speed @@ -253,7 +253,7 @@ config USB_PXA25X_SMALL config USB_GADGET_PXA27X boolean "PXA 27x" - depends on ARCH_PXA && PXA27x + depends on ARCH_PXA && (PXA27x || PXA3xx) select USB_OTG_UTILS help Intel's PXA 27x series XScale ARM v5TE processors include @@ -272,6 +272,20 @@ config USB_PXA27X default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_S3C_HSOTG + boolean "S3C HS/OtG USB Device controller" + depends on S3C_DEV_USB_HSOTG + select USB_GADGET_S3C_HSOTG_PIO + help + The Samsung S3C64XX USB2.0 high-speed gadget controller + integrated into the S3C64XX series SoC. + +config USB_S3C_HSOTG + tristate + depends on USB_GADGET_S3C_HSOTG + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_S3C2410 boolean "S3C2410 USB Device Controller" depends on ARCH_S3C2410 @@ -460,6 +474,27 @@ config USB_GOKU default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_LANGWELL + boolean "Intel Langwell USB Device Controller" + depends on PCI + select USB_GADGET_DUALSPEED + help + Intel Langwell USB Device Controller is a High-Speed USB + On-The-Go device controller. + + The number of programmable endpoints is different through + controller revision. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "langwell_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_LANGWELL + tristate + depends on USB_GADGET_LANGWELL + default USB_GADGET + select USB_GADGET_SELECTED + # # LAST -- dummy/emulated controller @@ -566,6 +601,20 @@ config USB_ZERO_HNPTEST the "B-Peripheral" role, that device will use HNP to let this one serve as the USB host instead (in the "B-Host" role). +config USB_AUDIO + tristate "Audio Gadget (EXPERIMENTAL)" + depends on SND + help + Gadget Audio is compatible with USB Audio Class specification 1.0. + It will include at least one AudioControl interface, zero or more + AudioStream interface and zero or more MIDIStream interface. + + Gadget Audio will use on-board ALSA (CONFIG_SND) audio card to + playback or capture audio stream. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_audio". + config USB_ETH tristate "Ethernet Gadget (with CDC Ethernet support)" depends on NET diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 39a51d746cb..e6017e6bf6d 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -18,14 +18,21 @@ obj-$(CONFIG_USB_S3C2410) += s3c2410_udc.o obj-$(CONFIG_USB_AT91) += at91_udc.o obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o +fsl_usb2_udc-objs := fsl_udc_core.o +ifeq ($(CONFIG_ARCH_MXC),y) +fsl_usb2_udc-objs += fsl_mx3_udc.o +endif obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o +obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o +obj-$(CONFIG_USB_LANGWELL) += langwell_udc.o # # USB gadget drivers # g_zero-objs := zero.o +g_audio-objs := audio.o g_ether-objs := ether.o g_serial-objs := serial.o g_midi-objs := gmidi.o @@ -35,6 +42,7 @@ g_printer-objs := printer.o g_cdc-objs := cdc2.o obj-$(CONFIG_USB_ZERO) += g_zero.o +obj-$(CONFIG_USB_AUDIO) += g_audio.o obj-$(CONFIG_USB_ETH) += g_ether.o obj-$(CONFIG_USB_GADGETFS) += gadgetfs.o obj-$(CONFIG_USB_FILE_STORAGE) += g_file_storage.o diff --git a/drivers/usb/gadget/at91_udc.c b/drivers/usb/gadget/at91_udc.c index 53bcdd2f828..72bae8f39d8 100644 --- a/drivers/usb/gadget/at91_udc.c +++ b/drivers/usb/gadget/at91_udc.c @@ -485,7 +485,7 @@ static int at91_ep_enable(struct usb_ep *_ep, return -ESHUTDOWN; } - tmp = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + tmp = usb_endpoint_type(desc); switch (tmp) { case USB_ENDPOINT_XFER_CONTROL: DBG("only one control endpoint\n"); @@ -517,7 +517,7 @@ ok: local_irq_save(flags); /* initialize endpoint to match this descriptor */ - ep->is_in = (desc->bEndpointAddress & USB_DIR_IN) != 0; + ep->is_in = usb_endpoint_dir_in(desc); ep->is_iso = (tmp == USB_ENDPOINT_XFER_ISOC); ep->stopped = 0; if (ep->is_in) diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c index 05c913cc365..4e970cf0e29 100644 --- a/drivers/usb/gadget/atmel_usba_udc.c +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -326,13 +326,7 @@ static int vbus_is_present(struct usba_udc *udc) return 1; } -#if defined(CONFIG_AVR32) - -static void toggle_bias(int is_on) -{ -} - -#elif defined(CONFIG_ARCH_AT91) +#if defined(CONFIG_ARCH_AT91SAM9RL) #include <mach/at91_pmc.h> @@ -346,7 +340,13 @@ static void toggle_bias(int is_on) at91_sys_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); } -#endif /* CONFIG_ARCH_AT91 */ +#else + +static void toggle_bias(int is_on) +{ +} + +#endif /* CONFIG_ARCH_AT91SAM9RL */ static void next_fifo_transaction(struct usba_ep *ep, struct usba_request *req) { @@ -550,12 +550,12 @@ usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) DBG(DBG_HW, "%s: EPT_SIZE = %lu (maxpacket = %lu)\n", ep->ep.name, ept_cfg, maxpacket); - if ((desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + if (usb_endpoint_dir_in(desc)) { ep->is_in = 1; ept_cfg |= USBA_EPT_DIR_IN; } - switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + switch (usb_endpoint_type(desc)) { case USB_ENDPOINT_XFER_CONTROL: ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL); ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE); diff --git a/drivers/usb/gadget/audio.c b/drivers/usb/gadget/audio.c new file mode 100644 index 00000000000..94de7e86461 --- /dev/null +++ b/drivers/usb/gadget/audio.c @@ -0,0 +1,302 @@ +/* + * audio.c -- Audio gadget driver + * + * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +/* #define VERBOSE_DEBUG */ + +#include <linux/kernel.h> +#include <linux/utsname.h> + +#include "u_audio.h" + +#define DRIVER_DESC "Linux USB Audio Gadget" +#define DRIVER_VERSION "Dec 18, 2008" + +/*-------------------------------------------------------------------------*/ + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "composite.c" +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + +#include "u_audio.c" +#include "f_audio.c" + +/*-------------------------------------------------------------------------*/ + +/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* Thanks to NetChip Technologies for donating this product ID. */ +#define AUDIO_VENDOR_NUM 0x0525 /* NetChip */ +#define AUDIO_PRODUCT_NUM 0xa4a1 /* Linux-USB Audio Gadget */ + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = __constant_cpu_to_le16(0x200), + + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id defaults change according to what configs + * we support. (As does bNumConfigurations.) These values can + * also be overridden by module parameters. + */ + .idVendor = __constant_cpu_to_le16(AUDIO_VENDOR_NUM), + .idProduct = __constant_cpu_to_le16(AUDIO_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +/** + * Handle USB audio endpoint set/get command in setup class request + */ + +static int audio_set_endpoint_req(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = c->cdev; + 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); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case SET_CUR: + value = 0; + break; + + case SET_MIN: + break; + + case SET_MAX: + break; + + case SET_RES: + break; + + case SET_MEM: + break; + + default: + break; + } + + return value; +} + +static int audio_get_endpoint_req(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = c->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); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case GET_CUR: + case GET_MIN: + case GET_MAX: + case GET_RES: + value = 3; + break; + case GET_MEM: + break; + default: + break; + } + + return value; +} + +static int +audio_setup(struct usb_configuration *c, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = c->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 except + * Audio class messages; interface activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_AUDIO_SET_ENDPOINT: + value = audio_set_endpoint_req(c, ctrl); + break; + + case USB_AUDIO_GET_ENDPOINT: + value = audio_get_endpoint_req(c, ctrl); + break; + + default: + ERROR(cdev, "Invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "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; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "Audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +/*-------------------------------------------------------------------------*/ + +static int __init audio_do_config(struct usb_configuration *c) +{ + /* FIXME alloc iConfiguration string, set it in c->strings */ + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + audio_bind_config(c); + + return 0; +} + +static struct usb_configuration audio_config_driver = { + .label = DRIVER_DESC, + .bind = audio_do_config, + .setup = audio_setup, + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init audio_bind(struct usb_composite_dev *cdev) +{ + int gcnum; + int status; + + gcnum = usb_gadget_controller_number(cdev->gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0300 | gcnum); + else { + ERROR(cdev, "controller '%s' not recognized; trying %s\n", + cdev->gadget->name, + audio_config_driver.label); + device_desc.bcdDevice = + __constant_cpu_to_le16(0x0300 | 0x0099); + } + + /* device descriptor strings: manufacturer, product */ + snprintf(manufacturer, sizeof manufacturer, "%s %s with %s", + init_utsname()->sysname, init_utsname()->release, + cdev->gadget->name); + status = usb_string_id(cdev); + if (status < 0) + goto fail; + strings_dev[STRING_MANUFACTURER_IDX].id = status; + device_desc.iManufacturer = status; + + status = usb_string_id(cdev); + if (status < 0) + goto fail; + strings_dev[STRING_PRODUCT_IDX].id = status; + device_desc.iProduct = status; + + status = usb_add_config(cdev, &audio_config_driver); + if (status < 0) + goto fail; + + INFO(cdev, "%s, version: %s\n", DRIVER_DESC, DRIVER_VERSION); + return 0; + +fail: + return status; +} + +static int __exit audio_unbind(struct usb_composite_dev *cdev) +{ + return 0; +} + +static struct usb_composite_driver audio_driver = { + .name = "g_audio", + .dev = &device_desc, + .strings = audio_strings, + .bind = audio_bind, + .unbind = __exit_p(audio_unbind), +}; + +static int __init init(void) +{ + return usb_composite_register(&audio_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&audio_driver); +} +module_exit(cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Bryan Wu <cooloney@kernel.org>"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/gadget/ci13xxx_udc.c b/drivers/usb/gadget/ci13xxx_udc.c index 38e531ecae4..c7cb87a6fee 100644 --- a/drivers/usb/gadget/ci13xxx_udc.c +++ b/drivers/usb/gadget/ci13xxx_udc.c @@ -1977,9 +1977,9 @@ static int ep_enable(struct usb_ep *ep, if (!list_empty(&mEp->qh[mEp->dir].queue)) warn("enabling a non-empty endpoint!"); - mEp->dir = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? TX : RX; - mEp->num = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - mEp->type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + mEp->dir = usb_endpoint_dir_in(desc) ? TX : RX; + mEp->num = usb_endpoint_num(desc); + mEp->type = usb_endpoint_type(desc); mEp->ep.maxpacket = __constant_le16_to_cpu(desc->wMaxPacketSize); diff --git a/drivers/usb/gadget/f_audio.c b/drivers/usb/gadget/f_audio.c new file mode 100644 index 00000000000..66527ba2d2e --- /dev/null +++ b/drivers/usb/gadget/f_audio.c @@ -0,0 +1,707 @@ +/* + * f_audio.c -- USB Audio class function driver + * + * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <asm/atomic.h> + +#include "u_audio.h" + +#define OUT_EP_MAX_PACKET_SIZE 200 +static int req_buf_size = OUT_EP_MAX_PACKET_SIZE; +module_param(req_buf_size, int, S_IRUGO); +MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size"); + +static int req_count = 256; +module_param(req_count, int, S_IRUGO); +MODULE_PARM_DESC(req_count, "ISO OUT 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"); + +/* + * DESCRIPTORS ... most are static, but strings and full + * configuration descriptors are built on demand. + */ + +/* + * We have two interfaces- AudioControl and AudioStreaming + * TODO: only supcard playback currently + */ +#define F_AUDIO_AC_INTERFACE 0 +#define F_AUDIO_AS_INTERFACE 1 +#define F_AUDIO_NUM_INTERFACES 2 + +/* 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, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +DECLARE_USB_AC_HEADER_DESCRIPTOR(2); + +#define USB_DT_AC_HEADER_LENGH USB_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct usb_ac_header_descriptor_2 ac_header_desc = { + .bLength = USB_DT_AC_HEADER_LENGH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = HEADER, + .bcdADC = __constant_cpu_to_le16(0x0100), + .wTotalLength = __constant_cpu_to_le16(USB_DT_AC_HEADER_LENGH), + .bInCollection = F_AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + [0] = F_AUDIO_AC_INTERFACE, + [1] = F_AUDIO_AS_INTERFACE, + } +}; + +#define INPUT_TERMINAL_ID 1 +static struct usb_input_terminal_descriptor input_terminal_desc = { + .bLength = USB_DT_AC_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = INPUT_TERMINAL, + .bTerminalID = INPUT_TERMINAL_ID, + .wTerminalType = USB_AC_TERMINAL_STREAMING, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +DECLARE_USB_AC_FEATURE_UNIT_DESCRIPTOR(0); + +#define FEATURE_UNIT_ID 2 +static struct usb_ac_feature_unit_descriptor_0 feature_unit_desc = { + .bLength = USB_DT_AC_FEATURE_UNIT_SIZE(0), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = FEATURE_UNIT, + .bUnitID = FEATURE_UNIT_ID, + .bSourceID = INPUT_TERMINAL_ID, + .bControlSize = 2, + .bmaControls[0] = (FU_MUTE | FU_VOLUME), +}; + +static struct usb_audio_control mute_control = { + .list = LIST_HEAD_INIT(mute_control.list), + .name = "Mute Control", + .type = 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 = VOLUME_CONTROL, + /* Todo: add real Volume control code */ + .set = generic_set_cmd, + .get = generic_get_cmd, +}; + +static struct usb_audio_control_selector feature_unit = { + .list = LIST_HEAD_INIT(feature_unit.list), + .id = FEATURE_UNIT_ID, + .name = "Mute & Volume Control", + .type = FEATURE_UNIT, + .desc = (struct usb_descriptor_header *)&feature_unit_desc, +}; + +#define OUTPUT_TERMINAL_ID 3 +static struct usb_output_terminal_descriptor output_terminal_desc = { + .bLength = USB_DT_AC_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = OUTPUT_TERMINAL, + .bTerminalID = OUTPUT_TERMINAL_ID, + .wTerminalType = USB_AC_OUTPUT_TERMINAL_SPEAKER, + .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 usb_as_header_descriptor as_header_desc = { + .bLength = USB_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = AS_GENERAL, + .bTerminalLink = INPUT_TERMINAL_ID, + .bDelay = 1, + .wFormatTag = USB_AS_AUDIO_FORMAT_TYPE_I_PCM, +}; + +DECLARE_USB_AS_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct usb_as_formate_type_i_discrete_descriptor_1 as_type_i_desc = { + .bLength = USB_AS_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = FORMAT_TYPE, + .bFormatType = USB_AS_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor as_out_ep_desc __initdata = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_AS_ENDPOINT_ADAPTIVE + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct usb_as_iso_endpoint_descriptor as_iso_out_desc __initdata = { + .bLength = USB_AS_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = __constant_cpu_to_le16(1), +}; + +static struct usb_descriptor_header *f_audio_desc[] __initdata = { + (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_out_ep_desc, + (struct usb_descriptor_header *)&as_iso_out_desc, + NULL, +}; + +/* string IDs are assigned dynamically */ + +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 + +static char manufacturer[50]; + +static struct usb_string strings_dev[] = { + [STRING_MANUFACTURER_IDX].s = manufacturer, + [STRING_PRODUCT_IDX].s = DRIVER_DESC, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *audio_strings[] = { + &stringtab_dev, + NULL, +}; + +/* + * This function is an ALSA sound card following USB Audio Class Spec 1.0. + */ + +/*-------------------------------------------------------------------------*/ +struct f_audio_buf { + u8 *buf; + int actual; + struct list_head list; +}; + +static struct f_audio_buf *f_audio_buffer_alloc(int buf_size) +{ + struct f_audio_buf *copy_buf; + + copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC); + if (!copy_buf) + return (struct f_audio_buf *)-ENOMEM; + + copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC); + if (!copy_buf->buf) { + kfree(copy_buf); + return (struct f_audio_buf *)-ENOMEM; + } + + return copy_buf; +} + +static void f_audio_buffer_free(struct f_audio_buf *audio_buf) +{ + kfree(audio_buf->buf); + kfree(audio_buf); +} +/*-------------------------------------------------------------------------*/ + +struct f_audio { + struct gaudio card; + + /* endpoints handle full and/or high speeds */ + struct usb_ep *out_ep; + struct usb_endpoint_descriptor *out_desc; + + spinlock_t lock; + struct f_audio_buf *copy_buf; + struct work_struct playback_work; + struct list_head play_queue; + + /* Control Set command */ + struct list_head cs; + u8 set_cmd; + struct usb_audio_control *set_con; +}; + +static inline struct f_audio *func_to_audio(struct usb_function *f) +{ + return container_of(f, struct f_audio, card.func); +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_playback_work(struct work_struct *data) +{ + struct f_audio *audio = container_of(data, struct f_audio, + playback_work); + struct f_audio_buf *play_buf; + + spin_lock_irq(&audio->lock); + if (list_empty(&audio->play_queue)) { + spin_unlock_irq(&audio->lock); + return; + } + play_buf = list_first_entry(&audio->play_queue, + struct f_audio_buf, list); + list_del(&play_buf->list); + spin_unlock_irq(&audio->lock); + + u_audio_playback(&audio->card, play_buf->buf, play_buf->actual); + f_audio_buffer_free(play_buf); + + return; +} + +static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_audio *audio = req->context; + struct usb_composite_dev *cdev = audio->card.func.config->cdev; + struct f_audio_buf *copy_buf = audio->copy_buf; + int err; + + if (!copy_buf) + return -EINVAL; + + /* Copy buffer is full, add it to the play_queue */ + if (audio_buf_size - copy_buf->actual < req->actual) { + list_add_tail(©_buf->list, &audio->play_queue); + schedule_work(&audio->playback_work); + copy_buf = f_audio_buffer_alloc(audio_buf_size); + if (copy_buf < 0) + return -ENOMEM; + } + + memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual); + copy_buf->actual += req->actual; + audio->copy_buf = copy_buf; + + err = usb_ep_queue(ep, req, GFP_ATOMIC); + if (err) + ERROR(cdev, "%s queue req: %d\n", ep->name, err); + + return 0; + +} + +static void f_audio_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_audio *audio = req->context; + int status = req->status; + u32 data = 0; + struct usb_ep *out_ep = audio->out_ep; + + switch (status) { + + case 0: /* normal completion? */ + if (ep == out_ep) + f_audio_out_ep_complete(ep, req); + else if (audio->set_con) { + memcpy(&data, req->buf, req->length); + audio->set_con->set(audio->set_con, audio->set_cmd, + le16_to_cpu(data)); + audio->set_con = NULL; + } + break; + default: + break; + } +} + +static int audio_set_intf_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 con_sel = (w_value >> 8) & 0xFF; + u8 cmd = (ctrl->bRequest & 0x0F); + struct usb_audio_control_selector *cs; + struct usb_audio_control *con; + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", + ctrl->bRequest, w_value, len, id); + + list_for_each_entry(cs, &audio->cs, list) { + if (cs->id == id) { + list_for_each_entry(con, &cs->control, list) { + if (con->type == con_sel) { + audio->set_con = con; + break; + } + } + break; + } + } + + audio->set_cmd = cmd; + req->context = audio; + req->complete = f_audio_complete; + + return len; +} + +static int audio_get_intf_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 con_sel = (w_value >> 8) & 0xFF; + u8 cmd = (ctrl->bRequest & 0x0F); + struct usb_audio_control_selector *cs; + struct usb_audio_control *con; + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", + ctrl->bRequest, w_value, len, id); + + list_for_each_entry(cs, &audio->cs, list) { + if (cs->id == id) { + list_for_each_entry(con, &cs->control, list) { + if (con->type == con_sel && con->get) { + value = con->get(con, cmd); + break; |