Skip to content

Commit

Permalink
USB: gadget: add USB Audio Gadget driver
Browse files Browse the repository at this point in the history
Funtions added:
 - setup all the USB audio class device descriptors
 - handle class specific setup request
 - receive data from USB host by ISO transfer
 - play audio data by ALSA sound card
 - open and setup playback PCM interface
 - set default playback PCM parameters
 - provide playback functions for USB audio driver
 - provide PCM parameters set/get functions

Test on:
 - Host: Ubuntu 8.10, kernel 2.6.27
 - Gadget: EZKIT-BF548 with ASoC AD1980 codec

Todo:
 - add real Mute control code
 - add real Volume control code
 - maybe find another way to replace dynamic buffer handling
   with static buffer allocation
 - test on Windows system
 - provide control interface to handle mute/volume control
 - provide capture interface in the future
 - test on BF527, other USB device controler and other audio codec

Signed-off-by: Bryan Wu <cooloney@kernel.org>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
  • Loading branch information
Bryan Wu authored and Greg Kroah-Hartman committed Jun 16, 2009
1 parent c47d7b0 commit c6994e6
Show file tree
Hide file tree
Showing 6 changed files with 1,400 additions and 0 deletions.
14 changes: 14 additions & 0 deletions drivers/usb/gadget/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,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
Expand Down
2 changes: 2 additions & 0 deletions drivers/usb/gadget/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.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
Expand All @@ -40,6 +41,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
Expand Down
302 changes: 302 additions & 0 deletions drivers/usb/gadget/audio.c
Original file line number Diff line number Diff line change
@@ -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");

Loading

0 comments on commit c6994e6

Please sign in to comment.