diff options
Diffstat (limited to 'drivers/usb/gadget')
-rw-r--r-- | drivers/usb/gadget/Kconfig | 66 | ||||
-rwxr-xr-x[-rw-r--r--] | drivers/usb/gadget/Makefile | 3 | ||||
-rw-r--r-- | drivers/usb/gadget/android.c | 1355 | ||||
-rw-r--r-- | drivers/usb/gadget/composite.c | 62 | ||||
-rw-r--r-- | drivers/usb/gadget/epautoconf.c | 54 | ||||
-rw-r--r-- | drivers/usb/gadget/f_accessory.c | 1176 | ||||
-rw-r--r-- | drivers/usb/gadget/f_acm.c | 21 | ||||
-rw-r--r-- | drivers/usb/gadget/f_adb.c | 614 | ||||
-rw-r--r-- | drivers/usb/gadget/f_audio_source.c | 821 | ||||
-rw-r--r-- | drivers/usb/gadget/f_mass_storage.c | 28 | ||||
-rw-r--r-- | drivers/usb/gadget/f_mtp.c | 1267 | ||||
-rw-r--r-- | drivers/usb/gadget/f_rndis.c | 46 | ||||
-rw-r--r-- | drivers/usb/gadget/gadget_chips.h | 8 | ||||
-rw-r--r-- | drivers/usb/gadget/rndis.c | 11 | ||||
-rw-r--r-- | drivers/usb/gadget/s3c_udc.h | 146 | ||||
-rw-r--r-- | drivers/usb/gadget/s3c_udc_otg.c | 1351 | ||||
-rw-r--r-- | drivers/usb/gadget/s3c_udc_otg_xfer_dma.c | 1387 | ||||
-rw-r--r-- | drivers/usb/gadget/storage_common.c | 52 | ||||
-rw-r--r-- | drivers/usb/gadget/u_ether.c | 60 | ||||
-rw-r--r-- | drivers/usb/gadget/u_ether.h | 9 | ||||
-rw-r--r-- | drivers/usb/gadget/u_serial.c | 6 |
21 files changed, 8469 insertions, 74 deletions
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 46f81ae..60e51d4 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -112,6 +112,7 @@ config USB_GADGET_SELECTED choice prompt "USB Peripheral Controller" depends on USB_GADGET + default USB_GADGET_S3C_OTGD if (ARCH_S5PV210) help A USB device uses a controller to talk to its host. Systems should have only one such upstream link. @@ -373,6 +374,20 @@ config USB_S3C_HSUDC default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_S3C_OTGD + boolean "S3C HS USB OTG Device" + depends on (ARCH_S5PV210) && !(USB_S3C_OTG_HOST) + help + Samsung's S3C64XX processors include high speed USB OTG2.0 + controller. It has 15 configurable endpoints, as well as + endpoint zero (for control transfers). + + This driver has been tested on the S3C6410, S5P6440, S5PC100 processor. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "s3c-udc-otg" and force all + gadget drivers to also be dynamically linked. + config USB_GADGET_PXA_U2O boolean "PXA9xx Processor USB2.0 controller" select USB_GADGET_DUALSPEED @@ -633,6 +648,43 @@ config USB_DUMMY_HCD endchoice +comment "NOTE: S3C OTG device role enables the controller driver below" + depends on USB_GADGET_S3C_OTGD + +config USB_S3C_OTGD + tristate "S3C high speed(2.0, dual-speed) USB OTG device" + depends on USB_GADGET && USB_GADGET_S3C_OTGD && !(USB_S3C_OTG_HOST) + default y + default USB_GADGET + select USB_GADGET_SELECTED + select USB_GADGET_DUALSPEED + help + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "s3c-udc-otg-hs" and force all + gadget drivers to also be dynamically linked. + +choice + prompt "S3C OTGD transfer mode" + depends on USB_S3C_OTGD + default y + help + S3C USB OTG conteroller supports DMA mode and Slave mode + for the dat transfer. You must slect one for the core + operation mode. + +config USB_GADGET_S3C_OTGD_DMA_MODE + bool "enabled DMA MODE" + depends on USB_GADGET_S3C_OTGD + help + S3C USB OTG core operates in DMA mode. + +config USB_GADGET_S3C_OTGD_SLAVE_MODE + bool "enabled Slave MODE" + depends on USB_GADGET_S3C_OTGD + help + S3C USB OTG core operates in Slave mode. +endchoice + # Selected by UDC drivers that support high-speed operation. config USB_GADGET_DUALSPEED bool @@ -936,6 +988,20 @@ config USB_G_PRINTER For more information, see Documentation/usb/gadget_printer.txt which includes sample code for accessing the device file. +config USB_G_ANDROID + boolean "Android Gadget" + depends on SWITCH + help + The Android gadget driver supports multiple USB functions. + The functions can be configured via a board file and may be + enabled and disabled dynamically. + +config USB_ANDROID_RNDIS_DWORD_ALIGNED + boolean "Use double word aligned" + depends on USB_G_ANDROID + help + Provides dword aligned for DMA controller. + config USB_CDC_COMPOSITE tristate "CDC Composite Device (Ethernet and ACM)" depends on NET diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 4fe92b1..0cb5b62 100644..100755 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_USB_IMX) += imx_udc.o obj-$(CONFIG_USB_GOKU) += goku_udc.o obj-$(CONFIG_USB_OMAP) += omap_udc.o obj-$(CONFIG_USB_S3C2410) += s3c2410_udc.o +obj-$(CONFIG_USB_S3C_OTGD) += s3c_udc_otg.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 @@ -49,6 +50,7 @@ g_dbgp-y := dbgp.o g_nokia-y := nokia.o g_webcam-y := webcam.o g_ncm-y := ncm.o +g_android-y := android.o obj-$(CONFIG_USB_ZERO) += g_zero.o obj-$(CONFIG_USB_AUDIO) += g_audio.o @@ -67,3 +69,4 @@ obj-$(CONFIG_USB_G_MULTI) += g_multi.o obj-$(CONFIG_USB_G_NOKIA) += g_nokia.o obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o obj-$(CONFIG_USB_G_NCM) += g_ncm.o +obj-$(CONFIG_USB_G_ANDROID) += g_android.o diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c new file mode 100644 index 0000000..7d0c095 --- /dev/null +++ b/drivers/usb/gadget/android.c @@ -0,0 +1,1355 @@ +/* + * Gadget Driver for Android + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * 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. + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/fs.h> + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/utsname.h> +#include <linux/platform_device.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/composite.h> +#include <linux/usb/gadget.h> + +#include "gadget_chips.h" + +/* + * 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 "usbstring.c" +#include "config.c" +#include "epautoconf.c" +#include "composite.c" + +#include "f_audio_source.c" +#include "f_mass_storage.c" +#include "u_serial.c" +#include "f_acm.c" +#include "f_adb.c" +#include "f_mtp.c" +#include "f_accessory.c" +#define USB_ETH_RNDIS y +#include "f_rndis.c" +#include "rndis.c" +#include "u_ether.c" + +MODULE_AUTHOR("Mike Lockwood"); +MODULE_DESCRIPTION("Android Composite USB Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +static const char longname[] = "Gadget Android"; + +/* Default vendor and product IDs, overridden by userspace */ +#define VENDOR_ID 0x18D1 +#define PRODUCT_ID 0x0001 + +struct android_usb_function { + char *name; + void *config; + + struct device *dev; + char *dev_name; + struct device_attribute **attributes; + + /* for android_dev.enabled_functions */ + struct list_head enabled_list; + + /* Optional: initialization during gadget bind */ + int (*init)(struct android_usb_function *, struct usb_composite_dev *); + /* Optional: cleanup during gadget unbind */ + void (*cleanup)(struct android_usb_function *); + /* Optional: called when the function is added the list of + * enabled functions */ + void (*enable)(struct android_usb_function *); + /* Optional: called when it is removed */ + void (*disable)(struct android_usb_function *); + + int (*bind_config)(struct android_usb_function *, struct usb_configuration *); + + /* Optional: called when the configuration is removed */ + void (*unbind_config)(struct android_usb_function *, struct usb_configuration *); + /* Optional: handle ctrl requests before the device is configured */ + int (*ctrlrequest)(struct android_usb_function *, + struct usb_composite_dev *, + const struct usb_ctrlrequest *); +}; + +struct android_dev { + struct android_usb_function **functions; + struct list_head enabled_functions; + struct usb_composite_dev *cdev; + struct device *dev; + + bool enabled; + int disable_depth; + struct mutex mutex; + bool connected; + bool sw_connected; + struct work_struct work; +}; + +static struct class *android_class; +static struct android_dev *_android_dev; +static int android_bind_config(struct usb_configuration *c); +static void android_unbind_config(struct usb_configuration *c); + +/* string IDs are assigned dynamically */ +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_SERIAL_IDX 2 + +static char manufacturer_string[256]; +static char product_string[256]; +static char serial_string[256]; + +/* String Table */ +static struct usb_string strings_dev[] = { + [STRING_MANUFACTURER_IDX].s = manufacturer_string, + [STRING_PRODUCT_IDX].s = product_string, + [STRING_SERIAL_IDX].s = serial_string, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof(device_desc), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = __constant_cpu_to_le16(VENDOR_ID), + .idProduct = __constant_cpu_to_le16(PRODUCT_ID), + .bcdDevice = __constant_cpu_to_le16(0xffff), + .bNumConfigurations = 1, +}; + +static struct usb_configuration android_config_driver = { + .label = "android", + .unbind = android_unbind_config, + .bConfigurationValue = 1, +}; + +static void android_work(struct work_struct *data) +{ + struct android_dev *dev = container_of(data, struct android_dev, work); + struct usb_composite_dev *cdev = dev->cdev; + char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL }; + char *connected[2] = { "USB_STATE=CONNECTED", NULL }; + char *configured[2] = { "USB_STATE=CONFIGURED", NULL }; + char **uevent_envp = NULL; + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + uevent_envp = configured; + else if (dev->connected != dev->sw_connected) + uevent_envp = dev->connected ? connected : disconnected; + dev->sw_connected = dev->connected; + spin_unlock_irqrestore(&cdev->lock, flags); + + if (uevent_envp) { + kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp); + pr_info("%s: sent uevent %s\n", __func__, uevent_envp[0]); + } else { + pr_info("%s: did not send uevent (%d %d %p)\n", __func__, + dev->connected, dev->sw_connected, cdev->config); + } +} + +static void android_enable(struct android_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + + BUG_ON(!mutex_is_locked(&dev->mutex)); + BUG_ON(!dev->disable_depth); + + if (--dev->disable_depth == 0) { + usb_add_config(cdev, &android_config_driver, + android_bind_config); + usb_gadget_connect(cdev->gadget); + } +} + +static void android_disable(struct android_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + + BUG_ON(!mutex_is_locked(&dev->mutex)); + + if (dev->disable_depth++ == 0) { + usb_gadget_disconnect(cdev->gadget); + /* Cancel pending control requests */ + usb_ep_dequeue(cdev->gadget->ep0, cdev->req); + usb_remove_config(cdev, &android_config_driver); + } +} + +/*-------------------------------------------------------------------------*/ +/* Supported functions initialization */ + +struct adb_data { + bool opened; + bool enabled; +}; + +static int adb_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + f->config = kzalloc(sizeof(struct adb_data), GFP_KERNEL); + if (!f->config) + return -ENOMEM; + + return adb_setup(); +} + +static void adb_function_cleanup(struct android_usb_function *f) +{ + adb_cleanup(); + kfree(f->config); +} + +static int adb_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) +{ + return adb_bind_config(c); +} + +static void adb_android_function_enable(struct android_usb_function *f) +{ + struct android_dev *dev = _android_dev; + struct adb_data *data = f->config; + + data->enabled = true; + + /* Disable the gadget until adbd is ready */ + if (!data->opened) + android_disable(dev); +} + +static void adb_android_function_disable(struct android_usb_function *f) +{ + struct android_dev *dev = _android_dev; + struct adb_data *data = f->config; + + data->enabled = false; + + /* Balance the disable that was called in closed_callback */ + if (!data->opened) + android_enable(dev); +} + +static struct android_usb_function adb_function = { + .name = "adb", + .enable = adb_android_function_enable, + .disable = adb_android_function_disable, + .init = adb_function_init, + .cleanup = adb_function_cleanup, + .bind_config = adb_function_bind_config, +}; + +static void adb_ready_callback(void) +{ + struct android_dev *dev = _android_dev; + struct adb_data *data = adb_function.config; + + mutex_lock(&dev->mutex); + + data->opened = true; + + if (data->enabled) + android_enable(dev); + + mutex_unlock(&dev->mutex); +} + +static void adb_closed_callback(void) +{ + struct android_dev *dev = _android_dev; + struct adb_data *data = adb_function.config; + + mutex_lock(&dev->mutex); + + data->opened = false; + + if (data->enabled) + android_disable(dev); + + mutex_unlock(&dev->mutex); +} + + +#define MAX_ACM_INSTANCES 4 +struct acm_function_config { + int instances; +}; + +static int acm_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + f->config = kzalloc(sizeof(struct acm_function_config), GFP_KERNEL); + if (!f->config) + return -ENOMEM; + + return gserial_setup(cdev->gadget, MAX_ACM_INSTANCES); +} + +static void acm_function_cleanup(struct android_usb_function *f) +{ + gserial_cleanup(); + kfree(f->config); + f->config = NULL; +} + +static int acm_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) +{ + int i; + int ret = 0; + struct acm_function_config *config = f->config; + + for (i = 0; i < config->instances; i++) { + ret = acm_bind_config(c, i); + if (ret) { + pr_err("Could not bind acm%u config\n", i); + break; + } + } + + return ret; +} + +static ssize_t acm_instances_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct acm_function_config *config = f->config; + return sprintf(buf, "%d\n", config->instances); +} + +static ssize_t acm_instances_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct acm_function_config *config = f->config; + int value; + + sscanf(buf, "%d", &value); + if (value > MAX_ACM_INSTANCES) + value = MAX_ACM_INSTANCES; + config->instances = value; + return size; +} + +static DEVICE_ATTR(instances, S_IRUGO | S_IWUSR, acm_instances_show, acm_instances_store); +static struct device_attribute *acm_function_attributes[] = { &dev_attr_instances, NULL }; + +static struct android_usb_function acm_function = { + .name = "acm", + .init = acm_function_init, + .cleanup = acm_function_cleanup, + .bind_config = acm_function_bind_config, + .attributes = acm_function_attributes, +}; + + +static int mtp_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + return mtp_setup(); +} + +static void mtp_function_cleanup(struct android_usb_function *f) +{ + mtp_cleanup(); +} + +static int mtp_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) +{ + return mtp_bind_config(c, false); +} + +static int ptp_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + /* nothing to do - initialization is handled by mtp_function_init */ + return 0; +} + +static void ptp_function_cleanup(struct android_usb_function *f) +{ + /* nothing to do - cleanup is handled by mtp_function_cleanup */ +} + +static int ptp_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) +{ + return mtp_bind_config(c, true); +} + +static int mtp_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return mtp_ctrlrequest(cdev, c); +} + +static struct android_usb_function mtp_function = { + .name = "mtp", + .init = mtp_function_init, + .cleanup = mtp_function_cleanup, + .bind_config = mtp_function_bind_config, + .ctrlrequest = mtp_function_ctrlrequest, +}; + +/* PTP function is same as MTP with slightly different interface descriptor */ +static struct android_usb_function ptp_function = { + .name = "ptp", + .init = ptp_function_init, + .cleanup = ptp_function_cleanup, + .bind_config = ptp_function_bind_config, +}; + + +struct rndis_function_config { + u8 ethaddr[ETH_ALEN]; + u32 dummy; // rndis_ethaddr_store will corrupt this variable due to casting (char *) as (int *) + u32 vendorID; + char manufacturer[256]; + bool wceis; +}; + +static int rndis_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + f->config = kzalloc(sizeof(struct rndis_function_config), GFP_KERNEL); + if (!f->config) + return -ENOMEM; + return 0; +} + +static void rndis_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); + f->config = NULL; +} + +static int rndis_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int ret; + struct rndis_function_config *rndis = f->config; + + if (!rndis) { + pr_err("%s: rndis_pdata\n", __func__); + return -1; + } + + pr_info("%s MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", __func__, + rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2], + rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]); + + ret = gether_setup_name(c->cdev->gadget, rndis->ethaddr, "rndis"); + if (ret) { + pr_err("%s: gether_setup failed\n", __func__); + return ret; + } + + if (rndis->wceis) { + /* "Wireless" RNDIS; auto-detected by Windows */ + rndis_iad_descriptor.bFunctionClass = + USB_CLASS_WIRELESS_CONTROLLER; + rndis_iad_descriptor.bFunctionSubClass = 0x01; + rndis_iad_descriptor.bFunctionProtocol = 0x03; + rndis_control_intf.bInterfaceClass = + USB_CLASS_WIRELESS_CONTROLLER; + rndis_control_intf.bInterfaceSubClass = 0x01; + rndis_control_intf.bInterfaceProtocol = 0x03; + } + + return rndis_bind_config(c, rndis->ethaddr, rndis->vendorID, + rndis->manufacturer); +} + +static void rndis_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + gether_cleanup(); +} + +static ssize_t rndis_manufacturer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%s\n", config->manufacturer); +} + +static ssize_t rndis_manufacturer_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + + if (size >= sizeof(config->manufacturer)) + return -EINVAL; + if (sscanf(buf, "%s", config->manufacturer) == 1) + return size; + return -1; +} + +static DEVICE_ATTR(manufacturer, S_IRUGO | S_IWUSR, rndis_manufacturer_show, + rndis_manufacturer_store); + +static ssize_t rndis_wceis_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%d\n", config->wceis); +} + +static ssize_t rndis_wceis_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + int value; + + if (sscanf(buf, "%d", &value) == 1) { + config->wceis = value; + return size; + } + return -EINVAL; +} + +static DEVICE_ATTR(wceis, S_IRUGO | S_IWUSR, rndis_wceis_show, + rndis_wceis_store); + +static ssize_t rndis_ethaddr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *rndis = f->config; + return sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2], + rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]); +} + +static ssize_t rndis_ethaddr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *rndis = f->config; + + if (sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + (int *)&rndis->ethaddr[0], (int *)&rndis->ethaddr[1], + (int *)&rndis->ethaddr[2], (int *)&rndis->ethaddr[3], + (int *)&rndis->ethaddr[4], (int *)&rndis->ethaddr[5]) == 6) + return size; + return -EINVAL; +} + +static DEVICE_ATTR(ethaddr, S_IRUGO | S_IWUSR, rndis_ethaddr_show, + rndis_ethaddr_store); + +static ssize_t rndis_vendorID_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%04x\n", config->vendorID); +} + +static ssize_t rndis_vendorID_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + int value; + + if (sscanf(buf, "%04x", &value) == 1) { + config->vendorID = value; + return size; + } + return -EINVAL; +} + +static DEVICE_ATTR(vendorID, S_IRUGO | S_IWUSR, rndis_vendorID_show, + rndis_vendorID_store); + +static struct device_attribute *rndis_function_attributes[] = { + &dev_attr_manufacturer, + &dev_attr_wceis, + &dev_attr_ethaddr, + &dev_attr_vendorID, + NULL +}; + +static struct android_usb_function rndis_function = { + .name = "rndis", + .init = rndis_function_init, + .cleanup = rndis_function_cleanup, + .bind_config = rndis_function_bind_config, + .unbind_config = rndis_function_unbind_config, + .attributes = rndis_function_attributes, +}; + + +struct mass_storage_function_config { + struct fsg_config fsg; + struct fsg_common *common; +}; + +static int mass_storage_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + struct mass_storage_function_config *config; + struct fsg_common *common; + int err; + + config = kzalloc(sizeof(struct mass_storage_function_config), + GFP_KERNEL); + if (!config) + return -ENOMEM; + +#ifdef CONFIG_SAMSUNG_FASCINATE + config->fsg.nluns = 1; +#else + config->fsg.nluns = 2; +#endif + config->fsg.luns[0].removable = 1; + config->fsg.luns[1].removable = 1; + + common = fsg_common_init(NULL, cdev, &config->fsg); + if (IS_ERR(common)) { + kfree(config); + return PTR_ERR(common); + } + + err = sysfs_create_link(&f->dev->kobj, + &common->luns[0].dev.kobj, + "lun"); + if (err) { + kfree(config); + return err; + } + + config->common = common; + f->config = config; + return 0; +} + +static void mass_storage_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); + f->config = NULL; +} + +static int mass_storage_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct mass_storage_function_config *config = f->config; + return fsg_bind_config(c->cdev, c, config->common); +} + +static ssize_t mass_storage_inquiry_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + return sprintf(buf, "%s\n", config->common->inquiry_string); +} + +static ssize_t mass_storage_inquiry_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + if (size >= sizeof(config->common->inquiry_string)) + return -EINVAL; + if (sscanf(buf, "%s", config->common->inquiry_string) != 1) + return -EINVAL; + return size; +} + +static DEVICE_ATTR(inquiry_string, S_IRUGO | S_IWUSR, + mass_storage_inquiry_show, + mass_storage_inquiry_store); + +static struct device_attribute *mass_storage_function_attributes[] = { + &dev_attr_inquiry_string, + NULL +}; + +static struct android_usb_function mass_storage_function = { + .name = "mass_storage", + .init = mass_storage_function_init, + .cleanup = mass_storage_function_cleanup, + .bind_config = mass_storage_function_bind_config, + .attributes = mass_storage_function_attributes, +}; + + +static int accessory_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return acc_setup(); +} + +static void accessory_function_cleanup(struct android_usb_function *f) +{ + acc_cleanup(); +} + +static int accessory_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return acc_bind_config(c); +} + +static int accessory_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return acc_ctrlrequest(cdev, c); +} + +static struct android_usb_function accessory_function = { + .name = "accessory", + .init = accessory_function_init, + .cleanup = accessory_function_cleanup, + .bind_config = accessory_function_bind_config, + .ctrlrequest = accessory_function_ctrlrequest, +}; + +static int audio_source_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + struct audio_source_config *config; + + config = kzalloc(sizeof(struct audio_source_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + config->card = -1; + config->device = -1; + f->config = config; + return 0; +} + +static void audio_source_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); +} + +static int audio_source_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct audio_source_config *config = f->config; + + return audio_source_bind_config(c, config); +} + +static void audio_source_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct audio_source_config *config = f->config; + + config->card = -1; + config->device = -1; +} + +static ssize_t audio_source_pcm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct audio_source_config *config = f->config; + + /* print PCM card and device numbers */ + return sprintf(buf, "%d %d\n", config->card, config->device); +} + +static DEVICE_ATTR(pcm, S_IRUGO | S_IWUSR, audio_source_pcm_show, NULL); + +static struct device_attribute *audio_source_function_attributes[] = { + &dev_attr_pcm, + NULL +}; + +static struct android_usb_function audio_source_function = { + .name = "audio_source", + .init = audio_source_function_init, + .cleanup = audio_source_function_cleanup, + .bind_config = audio_source_function_bind_config, + .unbind_config = audio_source_function_unbind_config, + .attributes = audio_source_function_attributes, +}; + +static struct android_usb_function *supported_functions[] = { + &adb_function, + &acm_function, + &mtp_function, + &ptp_function, + &rndis_function, + &mass_storage_function, + &accessory_function, + &audio_source_function, + NULL +}; + + +static int android_init_functions(struct android_usb_function **functions, + struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + struct android_usb_function *f; + struct device_attribute **attrs; + struct device_attribute *attr; + int err; + int index = 0; + + for (; (f = *functions++); index++) { + f->dev_name = kasprintf(GFP_KERNEL, "f_%s", f->name); + f->dev = device_create(android_class, dev->dev, + MKDEV(0, index), f, f->dev_name); + if (IS_ERR(f->dev)) { + pr_err("%s: Failed to create dev %s", __func__, + f->dev_name); + err = PTR_ERR(f->dev); + goto err_create; + } + + if (f->init) { + err = f->init(f, cdev); + if (err) { + pr_err("%s: Failed to init %s", __func__, + f->name); + goto err_out; + } + } + + attrs = f->attributes; + if (attrs) { + while ((attr = *attrs++) && !err) + err = device_create_file(f->dev, attr); + } + if (err) { + pr_err("%s: Failed to create function %s attributes", + __func__, f->name); + goto err_out; + } + } + return 0; + +err_out: + device_destroy(android_class, f->dev->devt); +err_create: + kfree(f->dev_name); + return err; +} + +static void android_cleanup_functions(struct android_usb_function **functions) +{ + struct android_usb_function *f; + + while (*functions) { + f = *functions++; + + if (f->dev) { + device_destroy(android_class, f->dev->devt); + kfree(f->dev_name); + } + + if (f->cleanup) + f->cleanup(f); + } +} + +static int +android_bind_enabled_functions(struct android_dev *dev, + struct usb_configuration *c) +{ + struct android_usb_function *f; + int ret; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + ret = f->bind_config(f, c); + if (ret) { + pr_err("%s: %s failed", __func__, f->name); + return ret; + } + } + return 0; +} + +static void +android_unbind_enabled_functions(struct android_dev *dev, + struct usb_configuration *c) +{ + struct android_usb_function *f; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->unbind_config) + f->unbind_config(f, c); + } +} + +static int android_enable_function(struct android_dev *dev, char *name) +{ + struct android_usb_function **functions = dev->functions; + struct android_usb_function *f; + while ((f = *functions++)) { + if (!strcmp(name, f->name)) { + list_add_tail(&f->enabled_list, &dev->enabled_functions); + return 0; + } + } + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ +/* /sys/class/android_usb/android%d/ interface */ + +static ssize_t +functions_show(struct device *pdev, struct device_attribute *attr, char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct android_usb_function *f; + char *buff = buf; + + mutex_lock(&dev->mutex); + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) + buff += sprintf(buff, "%s,", f->name); + + mutex_unlock(&dev->mutex); + + if (buff != buf) + *(buff-1) = '\n'; + return buff - buf; +} + +static ssize_t +functions_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + char *name; + char buf[256], *b; + int err; + + mutex_lock(&dev->mutex); + + if (dev->enabled) { + mutex_unlock(&dev->mutex); + return -EBUSY; + } + + INIT_LIST_HEAD(&dev->enabled_functions); + + strncpy(buf, buff, sizeof(buf)); + b = strim(buf); + + while (b) { + name = strsep(&b, ","); + if (name) { + err = android_enable_function(dev, name); + if (err) + pr_err("android_usb: Cannot enable '%s'", name); + } + } + + mutex_unlock(&dev->mutex); + + return size; +} + +static ssize_t enable_show(struct device *pdev, struct device_attribute *attr, + char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + return sprintf(buf, "%d\n", dev->enabled); +} + +static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct usb_composite_dev *cdev = dev->cdev; + struct android_usb_function *f; + int enabled = 0; + + mutex_lock(&dev->mutex); + + sscanf(buff, "%d", &enabled); + if (enabled && !dev->enabled) { + /* update values in composite driver's copy of device descriptor */ + cdev->desc.idVendor = device_desc.idVendor; + cdev->desc.idProduct = device_desc.idProduct; + cdev->desc.bcdDevice = device_desc.bcdDevice; + cdev->desc.bDeviceClass = device_desc.bDeviceClass; + cdev->desc.bDeviceSubClass = device_desc.bDeviceSubClass; + cdev->desc.bDeviceProtocol = device_desc.bDeviceProtocol; + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->enable) + f->enable(f); + } + android_enable(dev); + dev->enabled = true; + } else if (!enabled && dev->enabled) { + android_disable(dev); + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->disable) + f->disable(f); + } + dev->enabled = false; + } else { + pr_err("android_usb: already %s\n", + dev->enabled ? "enabled" : "disabled"); + } + + mutex_unlock(&dev->mutex); + return size; +} + +static ssize_t state_show(struct device *pdev, struct device_attribute *attr, + char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct usb_composite_dev *cdev = dev->cdev; + char *state = "DISCONNECTED"; + unsigned long flags; + + if (!cdev) + goto out; + + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + state = "CONFIGURED"; + else if (dev->connected) + state = "CONNECTED"; + spin_unlock_irqrestore(&cdev->lock, flags); +out: + return sprintf(buf, "%s\n", state); +} + +#define DESCRIPTOR_ATTR(field, format_string) \ +static ssize_t \ +field ## _show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, format_string, device_desc.field); \ +} \ +static ssize_t \ +field ## _store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + int value; \ + if (sscanf(buf, format_string, &value) == 1) { \ + device_desc.field = value; \ + return size; \ + } \ + return -1; \ +} \ +static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); + +#define DESCRIPTOR_STRING_ATTR(field, buffer) \ +static ssize_t \ +field ## _show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, "%s", buffer); \ +} \ +static ssize_t \ +field ## _store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + if (size >= sizeof(buffer)) return -EINVAL; \ + return strlcpy(buffer, buf, sizeof(buffer)); \ +} \ +static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); + + +DESCRIPTOR_ATTR(idVendor, "%04x\n") +DESCRIPTOR_ATTR(idProduct, "%04x\n") +DESCRIPTOR_ATTR(bcdDevice, "%04x\n") +DESCRIPTOR_ATTR(bDeviceClass, "%d\n") +DESCRIPTOR_ATTR(bDeviceSubClass, "%d\n") +DESCRIPTOR_ATTR(bDeviceProtocol, "%d\n") +DESCRIPTOR_STRING_ATTR(iManufacturer, manufacturer_string) +DESCRIPTOR_STRING_ATTR(iProduct, product_string) +DESCRIPTOR_STRING_ATTR(iSerial, serial_string) + +static DEVICE_ATTR(functions, S_IRUGO | S_IWUSR, functions_show, functions_store); +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store); +static DEVICE_ATTR(state, S_IRUGO, state_show, NULL); + +static struct device_attribute *android_usb_attributes[] = { + &dev_attr_idVendor, + &dev_attr_idProduct, + &dev_attr_bcdDevice, + &dev_attr_bDeviceClass, + &dev_attr_bDeviceSubClass, + &dev_attr_bDeviceProtocol, + &dev_attr_iManufacturer, + &dev_attr_iProduct, + &dev_attr_iSerial, + &dev_attr_functions, + &dev_attr_enable, + &dev_attr_state, + NULL +}; + +/*-------------------------------------------------------------------------*/ +/* Composite driver */ + +static int android_bind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + int ret = 0; + + ret = android_bind_enabled_functions(dev, c); + if (ret) + return ret; + + return 0; +} + +static void android_unbind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + + android_unbind_enabled_functions(dev, c); +} + +static int android_bind(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + struct usb_gadget *gadget = cdev->gadget; + int gcnum, id, ret; + + usb_gadget_disconnect(gadget); + + ret = android_init_functions(dev->functions, cdev); + if (ret) + return ret; + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_MANUFACTURER_IDX].id = id; + device_desc.iManufacturer = id; + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_PRODUCT_IDX].id = id; + device_desc.iProduct = id; + + /* Default strings - should be updated by userspace */ + strncpy(manufacturer_string, "Android", sizeof(manufacturer_string) - 1); + strncpy(product_string, "Android", sizeof(product_string) - 1); + strncpy(serial_string, "0123456789ABCDEF", sizeof(serial_string) - 1); + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_SERIAL_IDX].id = id; + device_desc.iSerialNumber = id; + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum); + else { + /* gadget zero is so simple (for now, no altsettings) that + * it SHOULD NOT have problems with bulk-capable hardware. + * so just warn about unrcognized controllers -- don't panic. + * + * things like configuration and altsetting numbering + * can need hardware-specific attention though. + */ + pr_warning("%s: controller '%s' not recognized\n", + longname, gadget->name); + device_desc.bcdDevice = __constant_cpu_to_le16(0x9999); + } + + dev->cdev = cdev; + + return 0; +} + +static int android_usb_unbind(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + + cancel_work_sync(&dev->work); + android_cleanup_functions(dev->functions); + return 0; +} + +static struct usb_composite_driver android_usb_driver = { + .name = "android_usb", + .dev = &device_desc, + .strings = dev_strings, + .unbind = android_usb_unbind, +}; + +static int +android_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *c) +{ + struct android_dev *dev = _android_dev; + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_request *req = cdev->req; + struct android_usb_function *f; + int value = -EOPNOTSUPP; + unsigned long flags; + + req->zero = 0; + req->complete = composite_setup_complete; + req->length = 0; + gadget->ep0->driver_data = cdev; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->ctrlrequest) { + value = f->ctrlrequest(f, cdev, c); + if (value >= 0) + break; + } + } + + /* Special case the accessory function. + * It needs to handle control requests before it is enabled. + */ + if (value < 0) + value = acc_ctrlrequest(cdev, c); + + if (value < 0) + value = composite_setup(gadget, c); + + spin_lock_irqsave(&cdev->lock, flags); + if (!dev->connected) { + dev->connected = 1; + schedule_work(&dev->work); + } + else if (c->bRequest == USB_REQ_SET_CONFIGURATION && cdev->config) { + schedule_work(&dev->work); + } + spin_unlock_irqrestore(&cdev->lock, flags); + + return value; +} + +static void android_disconnect(struct usb_gadget *gadget) +{ + struct android_dev *dev = _android_dev; + struct usb_composite_dev *cdev = get_gadget_data(gadget); + unsigned long flags; + + composite_disconnect(gadget); + /* accessory HID support can be active while the + accessory function is not actually enabled, + so we need to inform it when we are disconnected. + */ + acc_disconnect(); + + spin_lock_irqsave(&cdev->lock, flags); + dev->connected = 0; + schedule_work(&dev->work); + spin_unlock_irqrestore(&cdev->lock, flags); +} + +static int android_create_device(struct android_dev *dev) +{ + struct device_attribute **attrs = android_usb_attributes; + struct device_attribute *attr; + int err; + + dev->dev = device_create(android_class, NULL, + MKDEV(0, 0), NULL, "android0"); + if (IS_ERR(dev->dev)) + return PTR_ERR(dev->dev); + + dev_set_drvdata(dev->dev, dev); + + while ((attr = *attrs++)) { + err = device_create_file(dev->dev, attr); + if (err) { + device_destroy(android_class, dev->dev->devt); + return err; + } + } + return 0; +} + + +static int __init init(void) +{ + struct android_dev *dev; + int err; + + android_class = class_create(THIS_MODULE, "android_usb"); + if (IS_ERR(android_class)) + return PTR_ERR(android_class); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->disable_depth = 1; + dev->functions = supported_functions; + INIT_LIST_HEAD(&dev->enabled_functions); + INIT_WORK(&dev->work, android_work); + mutex_init(&dev->mutex); + + err = android_create_device(dev); + if (err) { + class_destroy(android_class); + kfree(dev); + return err; + } + + _android_dev = dev; + + /* Override composite driver functions */ + composite_driver.setup = android_setup; + composite_driver.disconnect = android_disconnect; + + return usb_composite_probe(&android_usb_driver, android_bind); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&android_usb_driver); + class_destroy(android_class); + kfree(_android_dev); + _android_dev = NULL; +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 5cbb1a4..1d88a80 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -476,6 +476,7 @@ static int set_config(struct usb_composite_dev *cdev, power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW; done: usb_gadget_vbus_draw(gadget, power); + if (result >= 0 && cdev->delayed_status) result = USB_GADGET_DELAYED_STATUS; return result; @@ -523,6 +524,7 @@ int usb_add_config(struct usb_composite_dev *cdev, INIT_LIST_HEAD(&config->functions); config->next_interface_id = 0; + memset(config->interface, '\0', sizeof(config->interface)); status = bind(config); if (status < 0) { @@ -562,6 +564,46 @@ done: return status; } +static int unbind_config(struct usb_composite_dev *cdev, + struct usb_configuration *config) +{ + while (!list_empty(&config->functions)) { + struct usb_function *f; + + f = list_first_entry(&config->functions, + struct usb_function, list); + list_del(&f->list); + if (f->unbind) { + DBG(cdev, "unbind function '%s'/%p\n", f->name, f); + f->unbind(config, f); + /* may free memory for "f" */ + } + } + if (config->unbind) { + DBG(cdev, "unbind config '%s'/%p\n", config->label, config); + config->unbind(config); + /* may free memory for "c" */ + } + return 0; +} + +int usb_remove_config(struct usb_composite_dev *cdev, + struct usb_configuration *config) +{ + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + + if (cdev->config == config) + reset_config(cdev); + + list_del(&config->list); + + spin_unlock_irqrestore(&cdev->lock, flags); + + return unbind_config(cdev, config); +} + /*-------------------------------------------------------------------------*/ /* We support strings in multiple languages ... string descriptor zero @@ -1041,28 +1083,10 @@ composite_unbind(struct usb_gadget *gadget) while (!list_empty(&cdev->configs)) { struct usb_configuration *c; - c = list_first_entry(&cdev->configs, struct usb_configuration, list); - while (!list_empty(&c->functions)) { - struct usb_function *f; - - f = list_first_entry(&c->functions, - struct usb_function, list); - list_del(&f->list); - if (f->unbind) { - DBG(cdev, "unbind function '%s'/%p\n", - f->name, f); - f->unbind(c, f); - /* may free memory for "f" */ - } - } list_del(&c->list); - if (c->unbind) { - DBG(cdev, "unbind config '%s'/%p\n", c->label, c); - c->unbind(c); - /* may free memory for "c" */ - } + unbind_config(cdev, c); } if (composite->unbind) composite->unbind(cdev); diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c index 9b7360f..431cc35 100644 --- a/drivers/usb/gadget/epautoconf.c +++ b/drivers/usb/gadget/epautoconf.c @@ -290,6 +290,60 @@ struct usb_ep *usb_ep_autoconfig ( if (ep && ep_matches (gadget, ep, desc)) return ep; #endif + } else if (gadget_is_s3c(gadget)) { + if (USB_ENDPOINT_XFER_INT == type) { + /* single buffering is enough */ + ep = find_ep (gadget, "ep3-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep6-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep9-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep12-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_BULK == type + && (USB_DIR_IN & desc->bEndpointAddress)) { + ep = find_ep (gadget, "ep2-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep5-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep8-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep11-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep14-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_BULK == type + && !(USB_DIR_IN & desc->bEndpointAddress)) { + ep = find_ep (gadget, "ep1-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep4-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep7-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep10-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep13-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_ISOC == type) { + ep = find_ep(gadget, "ep15-iso"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + } } /* Second, look at endpoints until an unclaimed one looks usable */ diff --git a/drivers/usb/gadget/f_accessory.c b/drivers/usb/gadget/f_accessory.c new file mode 100644 index 0000000..1df88a6 --- /dev/null +++ b/drivers/usb/gadget/f_accessory.c @@ -0,0 +1,1176 @@ +/* + * Gadget Function Driver for Android USB accessories + * + * Copyright (C) 2011 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * 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. + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kthread.h> +#include <linux/freezer.h> + +#include <linux/types.h> +#include <linux/file.h> +#include <linux/device.h> +#include <linux/miscdevice.h> + +#include <linux/hid.h> +#include <linux/hiddev.h> +#include <linux/usb.h> +#include <linux/usb/ch9.h> +#include <linux/usb/f_accessory.h> + +#define BULK_BUFFER_SIZE 16384 +#define ACC_STRING_SIZE 256 + +#define PROTOCOL_VERSION 2 + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 + +struct acc_hid_dev { + struct list_head list; + struct hid_device *hid; + struct acc_dev *dev; + /* accessory defined ID */ + int id; + /* HID report descriptor */ + u8 *report_desc; + /* length of HID report descriptor */ + int report_desc_len; + /* number of bytes of report_desc we have received so far */ + int report_desc_offset; +}; + +struct acc_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + /* set to 1 when we connect */ + int online:1; + /* Set to 1 when we disconnect. + * Not cleared until our file is closed. + */ + int disconnected:1; + + /* strings sent by the host */ + char manufacturer[ACC_STRING_SIZE]; + char model[ACC_STRING_SIZE]; + char description[ACC_STRING_SIZE]; + char version[ACC_STRING_SIZE]; + char uri[ACC_STRING_SIZE]; + char serial[ACC_STRING_SIZE]; + + /* for acc_complete_set_string */ + int string_index; + + /* set to 1 if we have a pending start request */ + int start_requested; + + int audio_mode; + + /* synchronize access to our device file */ + atomic_t open_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + int rx_done; + + /* delayed work for handling ACCESSORY_START */ + struct delayed_work start_work; + + /* worker for registering and unregistering hid devices */ + struct work_struct hid_work; + + /* list of active HID devices */ + struct list_head hid_list; + + /* list of new HID devices to register */ + struct list_head new_hid_list; + + /* list of dead HID devices to unregister */ + struct list_head dead_hid_list; +}; + +static struct usb_interface_descriptor acc_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_endpoint_descriptor acc_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acc_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acc_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor acc_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_fullspeed_in_desc, + (struct usb_descriptor_header *) &acc_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_highspeed_in_desc, + (struct usb_descriptor_header *) &acc_highspeed_out_desc, + NULL, +}; + +static struct usb_string acc_string_defs[] = { + [INTERFACE_STRING_INDEX].s = "Android Accessory Interface", + { }, /* end of list */ +}; + +static struct usb_gadget_strings acc_string_table = { + .language = 0x0409, /* en-US */ + .strings = acc_string_defs, +}; + +static struct usb_gadget_strings *acc_strings[] = { + &acc_string_table, + NULL, +}; + +/* temporary variable used between acc_open() and acc_gadget_bind() */ +static struct acc_dev *_acc_dev; + +static inline struct acc_dev *func_to_dev(struct usb_function *f) +{ + return container_of(f, struct acc_dev, function); +} + +static struct usb_request *acc_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void acc_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +/* add a request to the tail of a list */ +static void req_put(struct acc_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request *req_get(struct acc_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void acc_set_disconnected(struct acc_dev *dev) +{ + dev->online = 0; + dev->disconnected = 1; +} + +static void acc_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = _acc_dev; + + if (req->status != 0) + acc_set_disconnected(dev); + + req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void acc_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = _acc_dev; + + dev->rx_done = 1; + if (req->status != 0) + acc_set_disconnected(dev); + + wake_up(&dev->read_wq); +} + +static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = ep->driver_data; + char *string_dest = NULL; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_set_string, err %d\n", req->status); + return; + } + + switch (dev->string_index) { + case ACCESSORY_STRING_MANUFACTURER: + string_dest = dev->manufacturer; + break; + case ACCESSORY_STRING_MODEL: + string_dest = dev->model; + break; + case ACCESSORY_STRING_DESCRIPTION: + string_dest = dev->description; + break; + case ACCESSORY_STRING_VERSION: + string_dest = dev->version; + break; + case ACCESSORY_STRING_URI: + string_dest = dev->uri; + break; + case ACCESSORY_STRING_SERIAL: + string_dest = dev->serial; + break; + } + if (string_dest) { + unsigned long flags; + + if (length >= ACC_STRING_SIZE) + length = ACC_STRING_SIZE - 1; + + spin_lock_irqsave(&dev->lock, flags); + memcpy(string_dest, req->buf, length); + /* ensure zero termination */ + string_dest[length] = 0; + spin_unlock_irqrestore(&dev->lock, flags); + } else { + pr_err("unknown accessory string index %d\n", + dev->string_index); + } +} + +static void acc_complete_set_hid_report_desc(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + struct acc_dev *dev = hid->dev; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_set_hid_report_desc, err %d\n", + req->status); + return; + } + + memcpy(hid->report_desc + hid->report_desc_offset, req->buf, length); + hid->report_desc_offset += length; + if (hid->report_desc_offset == hid->report_desc_len) { + /* After we have received the entire report descriptor + * we schedule work to initialize the HID device + */ + schedule_work(&dev->hid_work); + } +} + +static void acc_complete_send_hid_event(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_send_hid_event, err %d\n", req->status); + return; + } + + hid_report_raw_event(hid->hid, HID_INPUT_REPORT, req->buf, length, 1); +} + +static int acc_hid_parse(struct hid_device *hid) +{ + struct acc_hid_dev *hdev = hid->driver_data; + + hid_parse_report(hid, hdev->report_desc, hdev->report_desc_len); + return 0; +} + +static int acc_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void acc_hid_stop(struct hid_device *hid) +{ +} + +static int acc_hid_open(struct hid_device *hid) +{ + return 0; +} + +static void acc_hid_close(struct hid_device *hid) +{ +} + +static struct hid_ll_driver acc_hid_ll_driver = { + .parse = acc_hid_parse, + .start = acc_hid_start, + .stop = acc_hid_stop, + .open = acc_hid_open, + .close = acc_hid_close, +}; + +static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev, + int id, int desc_len) +{ + struct acc_hid_dev *hdev; + + hdev = kzalloc(sizeof(*hdev), GFP_ATOMIC); + if (!hdev) + return NULL; + hdev->report_desc = kzalloc(desc_len, GFP_ATOMIC); + if (!hdev->report_desc) { + kfree(hdev); + return NULL; + } + hdev->dev = dev; + hdev->id = id; + hdev->report_desc_len = desc_len; + + return hdev; +} + +static struct acc_hid_dev *acc_hid_get(struct list_head *list, int id) +{ + struct acc_hid_dev *hid; + + list_for_each_entry(hid, list, list) { + if (hid->id == id) + return hid; + } + return NULL; +} + +static int acc_register_hid(struct acc_dev *dev, int id, int desc_length) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + /* report descriptor length must be > 0 */ + if (desc_length <= 0) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + /* replace HID if one already exists with this ID */ + hid = acc_hid_get(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get(&dev->new_hid_list, id); + if (hid) + list_move(&hid->list, &dev->dead_hid_list); + + hid = acc_hid_new(dev, id, desc_length); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -ENOMEM; + } + + list_add(&hid->list, &dev->new_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + /* schedule work to register the HID device */ + schedule_work(&dev->hid_work); + return 0; +} + +static int acc_unregister_hid(struct acc_dev *dev, int id) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get(&dev->new_hid_list, id); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -EINVAL; + } + + list_move(&hid->list, &dev->dead_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); + return 0; +} + +static int __init create_bulk_endpoints(struct acc_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = acc_request_new(dev->ep_in, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = acc_complete_in; + req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = acc_request_new(dev->ep_out, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = acc_complete_out; + dev->rx_req[i] = req; + } + + return 0; + +fail: + pr_err("acc_bind() could not allocate requests\n"); + while ((req = req_get(dev, &dev->tx_idle))) + acc_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + acc_request_free(dev->rx_req[i], dev->ep_out); + return -1; +} + +static ssize_t acc_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct acc_dev *dev = fp->private_data; + struct usb_request *req; + int r = count, xfer; + int ret = 0; + + pr_debug("acc_read(%d)\n", count); + + if (dev->disconnected) + return -ENODEV; + + if (count > BULK_BUFFER_SIZE) + count = BULK_BUFFER_SIZE; + + /* we will block until we're online */ + pr_debug("acc_read: waiting for online\n"); + ret = wait_event_interruptible(dev->read_wq, dev->online); + if (ret < 0) { + r = ret; + goto done; + } + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + pr_debug("rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (dev->online) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + pr_debug("rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + pr_debug("acc_read returning %d\n", r); + return r; +} + +static ssize_t acc_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct acc_dev *dev = fp->private_data; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + pr_debug("acc_write(%d)\n", count); + + if (!dev->online || dev->disconnected) + return -ENODEV; + + while (count > 0) { + if (!dev->online) { + pr_debug("acc_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = req_get(dev, &dev->tx_idle)) || !dev->online)); + if (!req) { + r = ret; + break; + } + + if (count > BULK_BUFFER_SIZE) + xfer = BULK_BUFFER_SIZE; + else + xfer = count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + pr_debug("acc_write: xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + req_put(dev, &dev->tx_idle, req); + + pr_debug("acc_write returning %d\n", r); + return r; +} + +static long acc_ioctl(struct file *fp, unsigned code, unsigned long value) +{ + struct acc_dev *dev = fp->private_data; + char *src = NULL; + int ret; + + switch (code) { + case ACCESSORY_GET_STRING_MANUFACTURER: + src = dev->manufacturer; + break; + case ACCESSORY_GET_STRING_MODEL: + src = dev->model; + break; + case ACCESSORY_GET_STRING_DESCRIPTION: + src = dev->description; + break; + case ACCESSORY_GET_STRING_VERSION: + src = dev->version; + break; + case ACCESSORY_GET_STRING_URI: + src = dev->uri; + break; + case ACCESSORY_GET_STRING_SERIAL: + src = dev->serial; + break; + case ACCESSORY_IS_START_REQUESTED: + return dev->start_requested; + case ACCESSORY_GET_AUDIO_MODE: + return dev->audio_mode; + } + if (!src) + return -EINVAL; + + ret = strlen(src) + 1; + if (copy_to_user((void __user *)value, src, ret)) + ret = -EFAULT; + return ret; +} + +static int acc_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "acc_open\n"); + if (atomic_xchg(&_acc_dev->open_excl, 1)) + return -EBUSY; + + _acc_dev->disconnected = 0; + fp->private_data = _acc_dev; + return 0; +} + +static int acc_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "acc_release\n"); + + WARN_ON(!atomic_xchg(&_acc_dev->open_excl, 0)); + _acc_dev->disconnected = 0; + return 0; +} + +/* file operations for /dev/usb_accessory */ +static const struct file_operations acc_fops = { + .owner = THIS_MODULE, + .read = acc_read, + .write = acc_write, + .unlocked_ioctl = acc_ioctl, + .open = acc_open, + .release = acc_release, +}; + +static int acc_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) + return ret; + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + +static struct miscdevice acc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "usb_accessory", + .fops = &acc_fops, +}; + +static const struct hid_device_id acc_hid_table[] = { + { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) }, + { } +}; + +static struct hid_driver acc_hid_driver = { + .name = "USB accessory", + .id_table = acc_hid_table, + .probe = acc_hid_probe, +}; + +static int acc_ctrlrequest(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct acc_dev *dev = _acc_dev; + int value = -EOPNOTSUPP; + struct acc_hid_dev *hid; + int offset; + u8 b_requestType = ctrl->bRequestType; + u8 b_request = ctrl->bRequest; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + unsigned long flags; + +/* + printk(KERN_INFO "acc_ctrlrequest " + "%02x.%02x v%04x i%04x l%u\n", + b_requestType, b_request, + w_value, w_index, w_length); +*/ + + if (b_requestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) { + if (b_request == ACCESSORY_START) { + dev->start_requested = 1; + schedule_delayed_work( + &dev->start_work, msecs_to_jiffies(10)); + value = 0; + } else if (b_request == ACCESSORY_SEND_STRING) { + dev->string_index = w_index; + cdev->gadget->ep0->driver_data = dev; + cdev->req->complete = acc_complete_set_string; + value = w_length; + } else if (b_request == ACCESSORY_SET_AUDIO_MODE && + w_index == 0 && w_length == 0) { + dev->audio_mode = w_value; + value = 0; + } else if (b_request == ACCESSORY_REGISTER_HID) { + value = acc_register_hid(dev, w_value, w_index); + } else if (b_request == ACCESSORY_UNREGISTER_HID) { + value = acc_unregister_hid(dev, w_value); + } else if (b_request == ACCESSORY_SET_HID_REPORT_DESC) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->new_hid_list, w_value); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + offset = w_index; + if (offset != hid->report_desc_offset + || offset + w_length > hid->report_desc_len) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_set_hid_report_desc; + value = w_length; + } else if (b_request == ACCESSORY_SEND_HID_EVENT) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->hid_list, w_value); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_send_hid_event; + value = w_length; + } + } else if (b_requestType == (USB_DIR_IN | USB_TYPE_VENDOR)) { + if (b_request == ACCESSORY_GET_PROTOCOL) { + *((u16 *)cdev->req->buf) = PROTOCOL_VERSION; + value = sizeof(u16); + + /* clear strings left over from a previous session */ + memset(dev->manufacturer, 0, sizeof(dev->manufacturer)); + memset(dev->model, 0, sizeof(dev->model)); + memset(dev->description, 0, sizeof(dev->description)); + memset(dev->version, 0, sizeof(dev->version)); + memset(dev->uri, 0, sizeof(dev->uri)); + memset(dev->serial, 0, sizeof(dev->serial)); + dev->start_requested = 0; + dev->audio_mode = 0; + } + } + + if (value >= 0) { + cdev->req->zero = 0; + cdev->req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "%s setup response queue error\n", + __func__); + } + +err: + if (value == -EOPNOTSUPP) + VDBG(cdev, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + return value; +} + +static int +acc_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct acc_dev *dev = func_to_dev(f); + int id; + int ret; + + DBG(cdev, "acc_function_bind dev: %p\n", dev); + + ret = hid_register_driver(&acc_hid_driver); + if (ret) + return ret; + + dev->start_requested = 0; + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + acc_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = create_bulk_endpoints(dev, &acc_fullspeed_in_desc, + &acc_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + acc_highspeed_in_desc.bEndpointAddress = + acc_fullspeed_in_desc.bEndpointAddress; + acc_highspeed_out_desc.bEndpointAddress = + acc_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +kill_all_hid_devices(struct acc_dev *dev) +{ + struct acc_hid_dev *hid; + struct list_head *entry, *temp; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_for_each_safe(entry, temp, &dev->hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); +} + +static void +acc_hid_unbind(struct acc_dev *dev) +{ + hid_unregister_driver(&acc_hid_driver); + kill_all_hid_devices(dev); +} + +static void +acc_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_request *req; + int i; + + while ((req = req_get(dev, &dev->tx_idle))) + acc_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + acc_request_free(dev->rx_req[i], dev->ep_out); + + acc_hid_unbind(dev); +} + +static void acc_start_work(struct work_struct *data) +{ + char *envp[2] = { "ACCESSORY=START", NULL }; + kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp); +} + +static int acc_hid_init(struct acc_hid_dev *hdev) +{ + struct hid_device *hid; + int ret; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + hid->ll_driver = &acc_hid_ll_driver; + hid->dev.parent = acc_device.this_device; + + hid->bus = BUS_USB; + hid->vendor = HID_ANY_ID; + hid->product = HID_ANY_ID; + hid->driver_data = hdev; + ret = hid_add_device(hid); + if (ret) { + pr_err("can't add hid device: %d\n", ret); + hid_destroy_device(hid); + return ret; + } + + hdev->hid = hid; + return 0; +} + +static void acc_hid_delete(struct acc_hid_dev *hid) +{ + kfree(hid->report_desc); + kfree(hid); +} + +static void acc_hid_work(struct work_struct *data) +{ + struct acc_dev *dev = _acc_dev; + struct list_head *entry, *temp; + struct acc_hid_dev *hid; + struct list_head new_list, dead_list; + unsigned long flags; + + INIT_LIST_HEAD(&new_list); + + spin_lock_irqsave(&dev->lock, flags); + + /* copy hids that are ready for initialization to new_list */ + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (hid->report_desc_offset == hid->report_desc_len) + list_move(&hid->list, &new_list); + } + + if (list_empty(&dev->dead_hid_list)) { + INIT_LIST_HEAD(&dead_list); + } else { + /* move all of dev->dead_hid_list to dead_list */ + dead_list.prev = dev->dead_hid_list.prev; + dead_list.next = dev->dead_hid_list.next; + dead_list.next->prev = &dead_list; + dead_list.prev->next = &dead_list; + INIT_LIST_HEAD(&dev->dead_hid_list); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + /* register new HID devices */ + list_for_each_safe(entry, temp, &new_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (acc_hid_init(hid)) { + pr_err("can't add HID device %p\n", hid); + acc_hid_delete(hid); + } else { + spin_lock_irqsave(&dev->lock, flags); + list_move(&hid->list, &dev->hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + } + } + + /* remove dead HID devices */ + list_for_each_safe(entry, temp, &dead_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + if (hid->hid) + hid_destroy_device(hid->hid); + acc_hid_delete(hid); + } +} + +static int acc_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "acc_function_set_alt intf: %d alt: %d\n", intf, alt); + ret = usb_ep_enable(dev->ep_in, + ep_choose(cdev->gadget, + &acc_highspeed_in_desc, + &acc_fullspeed_in_desc)); + if (ret) + return ret; + ret = usb_ep_enable(dev->ep_out, + ep_choose(cdev->gadget, + &acc_highspeed_out_desc, + &acc_fullspeed_out_desc)); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void acc_function_disable(struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "acc_function_disable\n"); + acc_set_disconnected(dev); + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int acc_bind_config(struct usb_configuration *c) +{ + struct acc_dev *dev = _acc_dev; + int ret; + + printk(KERN_INFO "acc_bind_config\n"); + + /* allocate a string ID for our interface */ + if (acc_string_defs[INTERFACE_STRING_INDEX].id == 0) { + ret = usb_string_id(c->cdev); + if (ret < 0) + return ret; + acc_string_defs[INTERFACE_STRING_INDEX].id = ret; + acc_interface_desc.iInterface = ret; + } + + dev->cdev = c->cdev; + dev->function.name = "accessory"; + dev->function.strings = acc_strings, + dev->function.descriptors = fs_acc_descs; + dev->function.hs_descriptors = hs_acc_descs; + dev->function.bind = acc_function_bind; + dev->function.unbind = acc_function_unbind; + dev->function.set_alt = acc_function_set_alt; + dev->function.disable = acc_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int acc_setup(void) +{ + struct acc_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + atomic_set(&dev->open_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + INIT_LIST_HEAD(&dev->hid_list); + INIT_LIST_HEAD(&dev->new_hid_list); + INIT_LIST_HEAD(&dev->dead_hid_list); + INIT_DELAYED_WORK(&dev->start_work, acc_start_work); + INIT_WORK(&dev->hid_work, acc_hid_work); + + /* _acc_dev must be set before calling usb_gadget_register_driver */ + _acc_dev = dev; + + ret = misc_register(&acc_device); + if (ret) + goto err; + + return 0; + +err: + kfree(dev); + pr_err("USB accessory gadget driver failed to initialize\n"); + return ret; +} + +static void acc_disconnect(void) +{ + /* unregister all HID devices if USB is disconnected */ + kill_all_hid_devices(_acc_dev); +} + +static void acc_cleanup(void) +{ + misc_deregister(&acc_device); + kfree(_acc_dev); + _acc_dev = NULL; +} diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c index bd6226c..68b1a8e 100644 --- a/drivers/usb/gadget/f_acm.c +++ b/drivers/usb/gadget/f_acm.c @@ -405,10 +405,10 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) usb_ep_disable(acm->notify); } else { VDBG(cdev, "init acm ctrl interface %d\n", intf); - acm->notify_desc = ep_choose(cdev->gadget, - acm->hs.notify, - acm->fs.notify); } + acm->notify_desc = ep_choose(cdev->gadget, + acm->hs.notify, + acm->fs.notify); usb_ep_enable(acm->notify, acm->notify_desc); acm->notify->driver_data = acm; @@ -418,11 +418,11 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) gserial_disconnect(&acm->port); } else { DBG(cdev, "activate acm ttyGS%d\n", acm->port_num); - acm->port.in_desc = ep_choose(cdev->gadget, - acm->hs.in, acm->fs.in); - acm->port.out_desc = ep_choose(cdev->gadget, - acm->hs.out, acm->fs.out); } + acm->port.in_desc = ep_choose(cdev->gadget, + acm->hs.in, acm->fs.in); + acm->port.out_desc = ep_choose(cdev->gadget, + acm->hs.out, acm->fs.out); gserial_connect(&acm->port, acm->port_num); } else @@ -697,6 +697,7 @@ acm_unbind(struct usb_configuration *c, struct usb_function *f) usb_free_descriptors(f->hs_descriptors); usb_free_descriptors(f->descriptors); gs_free_req(acm->notify, acm->notify_req); + kfree(acm->port.func.name); kfree(acm); } @@ -768,7 +769,11 @@ int acm_bind_config(struct usb_configuration *c, u8 port_num) acm->port.disconnect = acm_disconnect; acm->port.send_break = acm_send_break; - acm->port.func.name = "acm"; + acm->port.func.name = kasprintf(GFP_KERNEL, "acm%u", port_num); + if (!acm->port.func.name) { + kfree(acm); + return -ENOMEM; + } acm->port.func.strings = acm_strings; /* descriptors are per-instance copies */ acm->port.func.bind = acm_bind; diff --git a/drivers/usb/gadget/f_adb.c b/drivers/usb/gadget/f_adb.c new file mode 100644 index 0000000..1c0166c --- /dev/null +++ b/drivers/usb/gadget/f_adb.c @@ -0,0 +1,614 @@ +/* + * Gadget Driver for Android ADB + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * 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/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/miscdevice.h> + +#define ADB_BULK_BUFFER_SIZE 4096 + +/* number of tx requests to allocate */ +#define TX_REQ_MAX 4 + +static const char adb_shortname[] = "android_adb"; + +struct adb_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + int online; + int error; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req; + int rx_done; +}; + +static struct usb_interface_descriptor adb_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0x42, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor adb_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor adb_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor adb_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor adb_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_fullspeed_in_desc, + (struct usb_descriptor_header *) &adb_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_highspeed_in_desc, + (struct usb_descriptor_header *) &adb_highspeed_out_desc, + NULL, +}; + +static void adb_ready_callback(void); +static void adb_closed_callback(void); + +/* temporary variable used between adb_open() and adb_gadget_bind() */ +static struct adb_dev *_adb_dev; + +static inline struct adb_dev *func_to_adb(struct usb_function *f) +{ + return container_of(f, struct adb_dev, function); +} + + +static struct usb_request *adb_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void adb_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int adb_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void adb_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +void adb_req_put(struct adb_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +struct usb_request *adb_req_get(struct adb_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void adb_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct adb_dev *dev = _adb_dev; + + if (req->status != 0) + dev->error = 1; + + adb_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void adb_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct adb_dev *dev = _adb_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->error = 1; + + wake_up(&dev->read_wq); +} + +static int adb_create_bulk_endpoints(struct adb_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for adb ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + req = adb_request_new(dev->ep_out, ADB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_out; + dev->rx_req = req; + + for (i = 0; i < TX_REQ_MAX; i++) { + req = adb_request_new(dev->ep_in, ADB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_in; + adb_req_put(dev, &dev->tx_idle, req); + } + + return 0; + +fail: + printk(KERN_ERR "adb_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t adb_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_dev *dev = fp->private_data; + struct usb_request *req; + int r = count, xfer; + int ret; + + pr_debug("adb_read(%d)\n", count); + if (!_adb_dev) + return -ENODEV; + + if (count > ADB_BULK_BUFFER_SIZE) + return -EINVAL; + + if (adb_lock(&dev->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(dev->online || dev->error)) { + pr_debug("adb_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + (dev->online || dev->error)); + if (ret < 0) { + adb_unlock(&dev->read_excl); + return ret; + } + } + if (dev->error) { + r = -EIO; + goto done; + } + +requeue_req: + /* queue a request */ + req = dev->rx_req; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC); + if (ret < 0) { + pr_debug("adb_read: failed to queue req %p (%d)\n", req, ret); + r = -EIO; + dev->error = 1; + goto done; + } else { + pr_debug("rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + dev->error = 1; + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (!dev->error) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + pr_debug("rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + + } else + r = -EIO; + +done: + adb_unlock(&dev->read_excl); + pr_debug("adb_read returning %d\n", r); + return r; +} + +static ssize_t adb_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_dev *dev = fp->private_data; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + if (!_adb_dev) + return -ENODEV; + pr_debug("adb_write(%d)\n", count); + + if (adb_lock(&dev->write_excl)) + return -EBUSY; + + while (count > 0) { + if (dev->error) { + pr_debug("adb_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = adb_req_get(dev, &dev->tx_idle)) || dev->error); + + if (ret < 0) { + r = ret; + break; + } + + if (req != 0) { + if (count > ADB_BULK_BUFFER_SIZE) + xfer = ADB_BULK_BUFFER_SIZE; + else + xfer = count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); + if (ret < 0) { + pr_debug("adb_write: xfer error %d\n", ret); + dev->error = 1; + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) + adb_req_put(dev, &dev->tx_idle, req); + + adb_unlock(&dev->write_excl); + pr_debug("adb_write returning %d\n", r); + return r; +} + +static int adb_open(struct inode *ip, struct file *fp) +{ + pr_info("adb_open\n"); + if (!_adb_dev) + return -ENODEV; + + if (adb_lock(&_adb_dev->open_excl)) + return -EBUSY; + + fp->private_data = _adb_dev; + + /* clear the error latch */ + _adb_dev->error = 0; + + adb_ready_callback(); + + return 0; +} + +static int adb_release(struct inode *ip, struct file *fp) +{ + pr_info("adb_release\n"); + + adb_closed_callback(); + + adb_unlock(&_adb_dev->open_excl); + return 0; +} + +/* file operations for ADB device /dev/android_adb */ +static struct file_operations adb_fops = { + .owner = THIS_MODULE, + .read = adb_read, + .write = adb_write, + .open = adb_open, + .release = adb_release, +}; + +static struct miscdevice adb_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = adb_shortname, + .fops = &adb_fops, +}; + + + + +static int +adb_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct adb_dev *dev = func_to_adb(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "adb_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + adb_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = adb_create_bulk_endpoints(dev, &adb_fullspeed_in_desc, + &adb_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + adb_highspeed_in_desc.bEndpointAddress = + adb_fullspeed_in_desc.bEndpointAddress; + adb_highspeed_out_desc.bEndpointAddress = + adb_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +adb_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_request *req; + + + dev->online = 0; + dev->error = 1; + + wake_up(&dev->read_wq); + + adb_request_free(dev->rx_req, dev->ep_out); + while ((req = adb_req_get(dev, &dev->tx_idle))) + adb_request_free(req, dev->ep_in); +} + +static int adb_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "adb_function_set_alt intf: %d alt: %d\n", intf, alt); + ret = usb_ep_enable(dev->ep_in, + ep_choose(cdev->gadget, + &adb_highspeed_in_desc, + &adb_fullspeed_in_desc)); + if (ret) + return ret; + ret = usb_ep_enable(dev->ep_out, + ep_choose(cdev->gadget, + &adb_highspeed_out_desc, + &adb_fullspeed_out_desc)); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void adb_function_disable(struct usb_function *f) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "adb_function_disable cdev %p\n", cdev); + dev->online = 0; + dev->error = 1; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int adb_bind_config(struct usb_configuration *c) +{ + struct adb_dev *dev = _adb_dev; + + printk(KERN_INFO "adb_bind_config\n"); + + dev->cdev = c->cdev; + dev->function.name = "adb"; + dev->function.descriptors = fs_adb_descs; + dev->function.hs_descriptors = hs_adb_descs; + dev->function.bind = adb_function_bind; + dev->function.unbind = adb_function_unbind; + dev->function.set_alt = adb_function_set_alt; + dev->function.disable = adb_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int adb_setup(void) +{ + struct adb_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev->write_excl, 0); + + INIT_LIST_HEAD(&dev->tx_idle); + + _adb_dev = dev; + + ret = misc_register(&adb_device); + if (ret) + goto err; + + return 0; + +err: + kfree(dev); + printk(KERN_ERR "adb gadget driver failed to initialize\n"); + return ret; +} + +static void adb_cleanup(void) +{ + misc_deregister(&adb_device); + + kfree(_adb_dev); + _adb_dev = NULL; +} diff --git a/drivers/usb/gadget/f_audio_source.c b/drivers/usb/gadget/f_audio_source.c new file mode 100644 index 0000000..c5dbf71 --- /dev/null +++ b/drivers/usb/gadget/f_audio_source.c @@ -0,0 +1,821 @@ +/* + * 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 <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> + +#define SAMPLE_RATE 44100 +#define FRAMES_PER_MSEC (SAMPLE_RATE / 1000) + +#define IN_EP_MAX_PACKET_SIZE 384 + +/* 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 + +/* 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; +}; + +static inline struct audio_dev *func_to_audio(struct usb_function *f) +{ + return container_of(f, struct audio_dev, func); +} + +/*-------------------------------------------------------------------------*/ + +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 = ktime_to_ns(now) - ktime_to_ns(audio->start_time); + do_div(msecs, 1000000); + frames = msecs * SAMPLE_RATE; + do_div(frames, 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); + + pr_debug("audio_set_alt intf %d, alt %d\n", intf, alt); + usb_ep_enable(audio->in_ep, &fs_as_in_ep_desc); + 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_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); +} + +/* 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; + + 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; + + 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; + + 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->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; +} + +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; + 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); + 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, + }, + .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; + struct snd_card *card; + struct snd_pcm *pcm; + int err; + + config->card = -1; + config->device = -1; + + audio = &_audio_dev; + + err = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (err) + return err; + + snd_card_set_dev(card, &c->cdev->gadget->dev); + + 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; + + err = usb_add_function(c, &audio->func); + if (err) + goto add_fail; + + config->card = pcm->card->number; + config->device = pcm->device; + audio->card = card; + return 0; + +add_fail: +register_fail: +pcm_fail: + snd_card_free(audio->card); + return err; +} diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 3bbdc9a..a6b21cb 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -771,12 +771,17 @@ static int do_read(struct fsg_common *common) curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; return -EINVAL; } - file_offset = ((loff_t) lba) << 9; + if (curlun->cdrom) + file_offset = ((loff_t) lba) << 11; + else + file_offset = ((loff_t) lba) << 9; /* Carry out the file reads */ amount_left = common->data_size_from_cmnd; if (unlikely(amount_left == 0)) return -EIO; /* No default reply */ + if (curlun->cdrom) + amount_left <<= 2; for (;;) { /* @@ -908,11 +913,13 @@ static int do_write(struct fsg_common *common) curlun->sense_data = SS_INVALID_FIELD_IN_CDB; return -EINVAL; } +#ifndef CONFIG_USB_ANDROID_MASS_STORAGE if (!curlun->nofua && (common->cmnd[1] & 0x08)) { /* FUA */ spin_lock(&curlun->filp->f_lock); curlun->filp->f_flags |= O_SYNC; spin_unlock(&curlun->filp->f_lock); } +#endif } if (lba >= curlun->num_sectors) { curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; @@ -1289,7 +1296,7 @@ static int do_read_capacity(struct fsg_common *common, struct fsg_buffhd *bh) put_unaligned_be32(curlun->num_sectors - 1, &buf[0]); /* Max logical block */ - put_unaligned_be32(512, &buf[4]); /* Block length */ + put_unaligned_be32(curlun->cdrom ? 2048 : 512, &buf[4]); /* Block length */ return 8; } @@ -1396,7 +1403,7 @@ static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh) memset(buf+2, 0, 10); /* None of the fields are changeable */ if (!changeable_values) { - buf[2] = 0x04; /* Write cache enable, */ + buf[2] = 0x00; /* Write cache disable, */ /* Read cache not disabled */ /* No cache retention priorities */ put_unaligned_be16(0xffff, &buf[4]); @@ -1527,7 +1534,7 @@ static int do_read_format_capacities(struct fsg_common *common, put_unaligned_be32(curlun->num_sectors, &buf[0]); /* Number of blocks */ - put_unaligned_be32(512, &buf[4]); /* Block length */ + put_unaligned_be32(curlun->cdrom ? 2048 : 512, &buf[4]); /* Block length */ buf[4] = 0x02; /* Current capacity */ return 12; } @@ -1920,7 +1927,9 @@ static int check_command(struct fsg_common *common, int cmnd_size, return -EINVAL; } - /* Check that only command bytes listed in the mask are non-zero */ + /* Check that only command bytes listed in the mask are non-zero + * Some BIOSes put some non-zero values in READ_TOC requests in + * the last two bytes */ common->cmnd[1] &= 0x1f; /* Mask away the LUN */ for (i = 1; i < cmnd_size; ++i) { if (common->cmnd[i] && !(mask & (1 << i))) { @@ -2077,7 +2086,7 @@ static int do_scsi_command(struct fsg_common *common) common->data_size_from_cmnd = get_unaligned_be16(&common->cmnd[7]); reply = check_command(common, 10, DATA_DIR_TO_HOST, - (7<<6) | (1<<1), 1, + (0xf<<6) | (1<<1), 1, "READ TOC"); if (reply == 0) reply = do_read_toc(common, bh); @@ -2694,6 +2703,7 @@ static int fsg_main_thread(void *common_) static DEVICE_ATTR(ro, 0644, fsg_show_ro, fsg_store_ro); static DEVICE_ATTR(nofua, 0644, fsg_show_nofua, fsg_store_nofua); static DEVICE_ATTR(file, 0644, fsg_show_file, fsg_store_file); +static DEVICE_ATTR(cdrom, 0644, fsg_show_cdrom, fsg_store_cdrom); /****************************** FSG COMMON ******************************/ @@ -2806,6 +2816,9 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common, rc = device_create_file(&curlun->dev, &dev_attr_nofua); if (rc) goto error_luns; + rc = device_create_file(&curlun->dev, &dev_attr_cdrom); + if (rc) + goto error_luns; if (lcfg->filename) { rc = fsg_lun_open(curlun, lcfg->filename); @@ -2938,6 +2951,7 @@ static void fsg_common_release(struct kref *ref) device_remove_file(&lun->dev, &dev_attr_nofua); device_remove_file(&lun->dev, &dev_attr_ro); device_remove_file(&lun->dev, &dev_attr_file); + device_remove_file(&lun->dev, &dev_attr_cdrom); fsg_lun_close(lun); device_unregister(&lun->dev); } @@ -3052,7 +3066,7 @@ static int fsg_bind_config(struct usb_composite_dev *cdev, if (unlikely(!fsg)) return -ENOMEM; - fsg->function.name = FSG_DRIVER_DESC; + fsg->function.name = "mass_storage"; fsg->function.strings = fsg_strings_array; fsg->function.bind = fsg_bind; fsg->function.unbind = fsg_unbind; diff --git a/drivers/usb/gadget/f_mtp.c b/drivers/usb/gadget/f_mtp.c new file mode 100644 index 0000000..2829231 --- /dev/null +++ b/drivers/usb/gadget/f_mtp.c @@ -0,0 +1,1267 @@ +/* + * Gadget Function Driver for MTP + * + * Copyright (C) 2010 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * 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. + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/interrupt.h> + +#include <linux/types.h> +#include <linux/file.h> +#include <linux/device.h> +#include <linux/miscdevice.h> + +#include <linux/usb.h> +#include <linux/usb_usual.h> +#include <linux/usb/ch9.h> +#include <linux/usb/f_mtp.h> + +#define MTP_BULK_BUFFER_SIZE 16384 +#define INTR_BUFFER_SIZE 28 + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* values for mtp_dev.state */ +#define STATE_OFFLINE 0 /* initial state, disconnected */ +#define STATE_READY 1 /* ready for userspace calls */ +#define STATE_BUSY 2 /* processing userspace calls */ +#define STATE_CANCELED 3 /* transaction canceled by host */ +#define STATE_ERROR 4 /* error from completion routine */ + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 +#define INTR_REQ_MAX 5 + +/* ID for Microsoft MTP OS String */ +#define MTP_OS_STRING_ID 0xEE + +/* MTP class reqeusts */ +#define MTP_REQ_CANCEL 0x64 +#define MTP_REQ_GET_EXT_EVENT_DATA 0x65 +#define MTP_REQ_RESET 0x66 +#define MTP_REQ_GET_DEVICE_STATUS 0x67 + +/* constants for device status */ +#define MTP_RESPONSE_OK 0x2001 +#define MTP_RESPONSE_DEVICE_BUSY 0x2019 + +static const char mtp_shortname[] = "mtp_usb"; + +struct mtp_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + struct usb_ep *ep_intr; + + int state; + + /* synchronize access to our device file */ + atomic_t open_excl; + /* to enforce only one ioctl at a time */ + atomic_t ioctl_excl; + + struct list_head tx_idle; + struct list_head intr_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + wait_queue_head_t intr_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + int rx_done; + + /* for processing MTP_SEND_FILE, MTP_RECEIVE_FILE and + * MTP_SEND_FILE_WITH_HEADER ioctls on a work queue + */ + struct workqueue_struct *wq; + struct work_struct send_file_work; + struct work_struct receive_file_work; + struct file *xfer_file; + loff_t xfer_file_offset; + int64_t xfer_file_length; + unsigned xfer_send_header; + uint16_t xfer_command; + uint32_t xfer_transaction_id; + int xfer_result; +}; + +static struct usb_interface_descriptor mtp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_interface_descriptor ptp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor mtp_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mtp_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_intr_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(INTR_BUFFER_SIZE), + .bInterval = 6, +}; + +static struct usb_descriptor_header *fs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *fs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_string mtp_string_defs[] = { + /* Naming interface "MTP" so libmtp will recognize us */ + [INTERFACE_STRING_INDEX].s = "MTP", + { }, /* end of list */ +}; + +static struct usb_gadget_strings mtp_string_table = { + .language = 0x0409, /* en-US */ + .strings = mtp_string_defs, +}; + +static struct usb_gadget_strings *mtp_strings[] = { + &mtp_string_table, + NULL, +}; + +/* Microsoft MTP OS String */ +static u8 mtp_os_string[] = { + 18, /* sizeof(mtp_os_string) */ + USB_DT_STRING, + /* Signature field: "MSFT100" */ + 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, + /* vendor code */ + 1, + /* padding */ + 0 +}; + +/* Microsoft Extended Configuration Descriptor Header Section */ +struct mtp_ext_config_desc_header { + __le32 dwLength; + __u16 bcdVersion; + __le16 wIndex; + __u8 bCount; + __u8 reserved[7]; +}; + +/* Microsoft Extended Configuration Descriptor Function Section */ +struct mtp_ext_config_desc_function { + __u8 bFirstInterfaceNumber; + __u8 bInterfaceCount; + __u8 compatibleID[8]; + __u8 subCompatibleID[8]; + __u8 reserved[6]; +}; + +/* MTP Extended Configuration Descriptor */ +struct { + struct mtp_ext_config_desc_header header; + struct mtp_ext_config_desc_function function; +} mtp_ext_config_desc = { + .header = { + .dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)), + .bcdVersion = __constant_cpu_to_le16(0x0100), + .wIndex = __constant_cpu_to_le16(4), + .bCount = __constant_cpu_to_le16(1), + }, + .function = { + .bFirstInterfaceNumber = 0, + .bInterfaceCount = 1, + .compatibleID = { 'M', 'T', 'P' }, + }, +}; + +struct mtp_device_status { + __le16 wLength; + __le16 wCode; +}; + +/* temporary variable used between mtp_open() and mtp_gadget_bind() */ +static struct mtp_dev *_mtp_dev; + +static inline struct mtp_dev *func_to_mtp(struct usb_function *f) +{ + return container_of(f, struct mtp_dev, function); +} + +static struct usb_request *mtp_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void mtp_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int mtp_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void mtp_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void mtp_req_put(struct mtp_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request +*mtp_req_get(struct mtp_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void mtp_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + mtp_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void mtp_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->state = STATE_ERROR; + + wake_up(&dev->read_wq); +} + +static void mtp_complete_intr(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + mtp_req_put(dev, &dev->intr_idle, req); + + wake_up(&dev->intr_wq); +} + +static int mtp_create_bulk_endpoints(struct mtp_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc, + struct usb_endpoint_descriptor *intr_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, intr_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_intr got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_intr = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_in, MTP_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_in; + mtp_req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_out, MTP_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_out; + dev->rx_req[i] = req; + } + for (i = 0; i < INTR_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_intr; + mtp_req_put(dev, &dev->intr_idle, req); + } + + return 0; + +fail: + printk(KERN_ERR "mtp_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t mtp_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int r = count, xfer; + int ret = 0; + + DBG(cdev, "mtp_read(%d)\n", count); + + if (count > MTP_BULK_BUFFER_SIZE) + return -EINVAL; + + /* we will block until we're online */ + DBG(cdev, "mtp_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + dev->state != STATE_OFFLINE); + if (ret < 0) { + r = ret; + goto done; + } + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + DBG(cdev, "rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (dev->state == STATE_BUSY) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + DBG(cdev, "rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_read returning %d\n", r); + return r; +} + +static ssize_t mtp_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + int r = count, xfer; + int sendZLP = 0; + int ret; + + DBG(cdev, "mtp_write(%d)\n", count); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + return -ENODEV; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) { + sendZLP = 1; + } + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + if (dev->state != STATE_BUSY) { + DBG(cdev, "mtp_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = mtp_req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY)); + if (!req) { + r = ret; + break; + } + + if (count > MTP_BULK_BUFFER_SIZE) + xfer = MTP_BULK_BUFFER_SIZE; + else + xfer = count; + if (xfer && copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "mtp_write: xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + mtp_req_put(dev, &dev->tx_idle, req); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_write returning %d\n", r); + return r; +} + +/* read from a local file and write to USB */ +static void send_file_work(struct work_struct *data) { + struct mtp_dev *dev = container_of(data, struct mtp_dev, send_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + struct mtp_data_header *header; + struct file *filp; + loff_t offset; + int64_t count; + int xfer, ret, hdr_size; + int r = 0; + int sendZLP = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "send_file_work(%lld %lld)\n", offset, count); + + if (dev->xfer_send_header) { + hdr_size = sizeof(struct mtp_data_header); + count += hdr_size; + } else { + hdr_size = 0; + } + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) { + sendZLP = 1; + } + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = mtp_req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + break; + } + if (!req) { + r = ret; + break; + } + + if (count > MTP_BULK_BUFFER_SIZE) + xfer = MTP_BULK_BUFFER_SIZE; + else + xfer = count; + + if (hdr_size) { + /* prepend MTP data header */ + header = (struct mtp_data_header *)req->buf; + header->length = __cpu_to_le32(count); + header->type = __cpu_to_le16(2); /* data packet */ + header->command = __cpu_to_le16(dev->xfer_command); + header->transaction_id = __cpu_to_le32(dev->xfer_transaction_id); + } + + ret = vfs_read(filp, req->buf + hdr_size, xfer - hdr_size, &offset); + if (ret < 0) { + r = ret; + break; + } + xfer = ret + hdr_size; + hdr_size = 0; + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "send_file_work: xfer error %d\n", ret); + dev->state = STATE_ERROR; + r = -EIO; + break; + } + + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + mtp_req_put(dev, &dev->tx_idle, req); + + DBG(cdev, "send_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +/* read from USB and write to a local file */ +static void receive_file_work(struct work_struct *data) +{ + struct mtp_dev *dev = container_of(data, struct mtp_dev, receive_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *read_req = NULL, *write_req = NULL; + struct file *filp; + loff_t offset; + int64_t count; + int ret, cur_buf = 0; + int r = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "receive_file_work(%lld)\n", count); + + while (count > 0 || write_req) { + if (count > 0) { + /* queue a request */ + read_req = dev->rx_req[cur_buf]; + cur_buf = (cur_buf + 1) % RX_REQ_MAX; + + read_req->length = (count > MTP_BULK_BUFFER_SIZE + ? MTP_BULK_BUFFER_SIZE : count); + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + } + + if (write_req) { + DBG(cdev, "rx %p %d\n", write_req, write_req->actual); + ret = vfs_write(filp, write_req->buf, write_req->actual, + &offset); + DBG(cdev, "vfs_write %d\n", ret); + if (ret != write_req->actual) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + write_req = NULL; + } + + if (read_req) { + /* wait for our last read to complete */ + ret = wait_event_interruptible(dev->read_wq, + dev->rx_done || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + if (!dev->rx_done) + usb_ep_dequeue(dev->ep_out, read_req); + break; + } + /* if xfer_file_length is 0xFFFFFFFF, then we read until + * we get a zero length packet + */ + if (count != 0xFFFFFFFF) + count -= read_req->actual; + if (read_req->actual < read_req->length) { + /* short packet is used to signal EOF for sizes > 4 gig */ + DBG(cdev, "got short packet\n"); + count = 0; + } + + write_req = read_req; + read_req = NULL; + } + } + + DBG(cdev, "receive_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event) +{ + struct usb_request *req= NULL; + int ret; + int length = event->length; + + DBG(dev->cdev, "mtp_send_event(%d)\n", event->length); + + if (length < 0 || length > INTR_BUFFER_SIZE) + return -EINVAL; + if (dev->state == STATE_OFFLINE) + return -ENODEV; + + ret = wait_event_interruptible_timeout(dev->intr_wq, + (req = mtp_req_get(dev, &dev->intr_idle)), msecs_to_jiffies(1000)); + if (!req) + return -ETIME; + + if (copy_from_user(req->buf, (void __user *)event->data, length)) { + mtp_req_put(dev, &dev->intr_idle, req); + return -EFAULT; + } + req->length = length; + ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL); + if (ret) + mtp_req_put(dev, &dev->intr_idle, req); + + return ret; +} + +static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value) +{ + struct mtp_dev *dev = fp->private_data; + struct file *filp = NULL; + int ret = -EINVAL; + + if (mtp_lock(&dev->ioctl_excl)) + return -EBUSY; + + switch (code) { + case MTP_SEND_FILE: + case MTP_RECEIVE_FILE: + case MTP_SEND_FILE_WITH_HEADER: + { + struct mtp_file_range mfr; + struct work_struct *work; + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + ret = -ECANCELED; + goto out; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + ret = -ENODEV; + goto out; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) { + ret = -EFAULT; + goto fail; + } + /* hold a reference to the file while we are working with it */ + filp = fget(mfr.fd); + if (!filp) { + ret = -EBADF; + goto fail; + } + + /* write the parameters */ + dev->xfer_file = filp; + dev->xfer_file_offset = mfr.offset; + dev->xfer_file_length = mfr.length; + smp_wmb(); + + if (code == MTP_SEND_FILE_WITH_HEADER) { + work = &dev->send_file_work; + dev->xfer_send_header = 1; + dev->xfer_command = mfr.command; + dev->xfer_transaction_id = mfr.transaction_id; + } else if (code == MTP_SEND_FILE) { + work = &dev->send_file_work; + dev->xfer_send_header = 0; + } else { + work = &dev->receive_file_work; + } + + /* We do the file transfer on a work queue so it will run + * in kernel context, which is necessary for vfs_read and + * vfs_write to use our buffers in the kernel address space. + */ + queue_work(dev->wq, work); + /* wait for operation to complete */ + flush_workqueue(dev->wq); + fput(filp); + + /* read the result */ + smp_rmb(); + ret = dev->xfer_result; + break; + } + case MTP_SEND_EVENT: + { + struct mtp_event event; + /* return here so we don't change dev->state below, + * which would interfere with bulk transfer state. + */ + if (copy_from_user(&event, (void __user *)value, sizeof(event))) + ret = -EFAULT; + else + ret = mtp_send_event(dev, &event); + goto out; + } + } + +fail: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + ret = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); +out: + mtp_unlock(&dev->ioctl_excl); + DBG(dev->cdev, "ioctl returning %d\n", ret); + return ret; +} + +static int mtp_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_open\n"); + if (mtp_lock(&_mtp_dev->open_excl)) + return -EBUSY; + + /* clear any error condition */ + if (_mtp_dev->state != STATE_OFFLINE) + _mtp_dev->state = STATE_READY; + + fp->private_data = _mtp_dev; + return 0; +} + +static int mtp_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_release\n"); + + mtp_unlock(&_mtp_dev->open_excl); + return 0; +} + +/* file operations for /dev/mtp_usb */ +static const struct file_operations mtp_fops = { + .owner = THIS_MODULE, + .read = mtp_read, + .write = mtp_write, + .unlocked_ioctl = mtp_ioctl, + .open = mtp_open, + .release = mtp_release, +}; + +static struct miscdevice mtp_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = mtp_shortname, + .fops = &mtp_fops, +}; + +static int mtp_ctrlrequest(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct mtp_dev *dev = _mtp_dev; + 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); + unsigned long flags; + + VDBG(cdev, "mtp_ctrlrequest " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + /* Handle MTP OS string */ + if (ctrl->bRequestType == + (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) + && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR + && (w_value >> 8) == USB_DT_STRING + && (w_value & 0xFF) == MTP_OS_STRING_ID) { + value = (w_length < sizeof(mtp_os_string) + ? w_length : sizeof(mtp_os_string)); + memcpy(cdev->req->buf, mtp_os_string, value); + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + /* Handle MTP OS descriptor */ + DBG(cdev, "vendor request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == 1 + && (ctrl->bRequestType & USB_DIR_IN) + && (w_index == 4 || w_index == 5)) { + value = (w_length < sizeof(mtp_ext_config_desc) ? + w_length : sizeof(mtp_ext_config_desc)); + memcpy(cdev->req->buf, &mtp_ext_config_desc, value); + } + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + DBG(cdev, "class request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0 + && w_value == 0) { + DBG(cdev, "MTP_REQ_CANCEL\n"); + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state == STATE_BUSY) { + dev->state = STATE_CANCELED; + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); + } + spin_unlock_irqrestore(&dev->lock, flags); + + /* We need to queue a request to read the remaining + * bytes, but we don't actually need to look at + * the contents. + */ + value = w_length; + } else if (ctrl->bRequest == MTP_REQ_GET_DEVICE_STATUS + && w_index == 0 && w_value == 0) { + struct mtp_device_status *status = cdev->req->buf; + status->wLength = + __constant_cpu_to_le16(sizeof(*status)); + + DBG(cdev, "MTP_REQ_GET_DEVICE_STATUS\n"); + spin_lock_irqsave(&dev->lock, flags); + /* device status is "busy" until we report + * the cancelation to userspace + */ + if (dev->state == STATE_CANCELED) + status->wCode = + __cpu_to_le16(MTP_RESPONSE_DEVICE_BUSY); + else + status->wCode = + __cpu_to_le16(MTP_RESPONSE_OK); + spin_unlock_irqrestore(&dev->lock, flags); + value = sizeof(*status); + } + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + int rc; + cdev->req->zero = value < w_length; + cdev->req->length = value; + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + ERROR(cdev, "%s setup response queue error\n", __func__); + } + return value; +} + +static int +mtp_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct mtp_dev *dev = func_to_mtp(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "mtp_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + mtp_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = mtp_create_bulk_endpoints(dev, &mtp_fullspeed_in_desc, + &mtp_fullspeed_out_desc, &mtp_intr_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + mtp_highspeed_in_desc.bEndpointAddress = + mtp_fullspeed_in_desc.bEndpointAddress; + mtp_highspeed_out_desc.bEndpointAddress = + mtp_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +mtp_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_request *req; + int i; + + while ((req = mtp_req_get(dev, &dev->tx_idle))) + mtp_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + mtp_request_free(dev->rx_req[i], dev->ep_out); + while ((req = mtp_req_get(dev, &dev->intr_idle))) + mtp_request_free(req, dev->ep_intr); + dev->state = STATE_OFFLINE; +} + +static int mtp_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt); + ret = usb_ep_enable(dev->ep_in, + ep_choose(cdev->gadget, + &mtp_highspeed_in_desc, + &mtp_fullspeed_in_desc)); + if (ret) + return ret; + ret = usb_ep_enable(dev->ep_out, + ep_choose(cdev->gadget, + &mtp_highspeed_out_desc, + &mtp_fullspeed_out_desc)); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + ret = usb_ep_enable(dev->ep_intr, &mtp_intr_desc); + if (ret) { + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_in); + return ret; + } + dev->state = STATE_READY; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void mtp_function_disable(struct usb_function *f) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "mtp_function_disable\n"); + dev->state = STATE_OFFLINE; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_intr); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int mtp_bind_config(struct usb_configuration *c, bool ptp_config) +{ + struct mtp_dev *dev = _mtp_dev; + int ret = 0; + + printk(KERN_INFO "mtp_bind_config\n"); + + /* allocate a string ID for our interface */ + if (mtp_string_defs[INTERFACE_STRING_INDEX].id == 0) { + ret = usb_string_id(c->cdev); + if (ret < 0) + return ret; + mtp_string_defs[INTERFACE_STRING_INDEX].id = ret; + mtp_interface_desc.iInterface = ret; + } + + dev->cdev = c->cdev; + dev->function.name = "mtp"; + dev->function.strings = mtp_strings; + if (ptp_config) { + dev->function.descriptors = fs_ptp_descs; + dev->function.hs_descriptors = hs_ptp_descs; + } else { + dev->function.descriptors = fs_mtp_descs; + dev->function.hs_descriptors = hs_mtp_descs; + } + dev->function.bind = mtp_function_bind; + dev->function.unbind = mtp_function_unbind; + dev->function.set_alt = mtp_function_set_alt; + dev->function.disable = mtp_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int mtp_setup(void) +{ + struct mtp_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + init_waitqueue_head(&dev->intr_wq); + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->ioctl_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + INIT_LIST_HEAD(&dev->intr_idle); + + dev->wq = create_singlethread_workqueue("f_mtp"); + if (!dev->wq) { + ret = -ENOMEM; + goto err1; + } + INIT_WORK(&dev->send_file_work, send_file_work); + INIT_WORK(&dev->receive_file_work, receive_file_work); + + _mtp_dev = dev; + + ret = misc_register(&mtp_device); + if (ret) + goto err2; + + return 0; + +err2: + destroy_workqueue(dev->wq); +err1: + _mtp_dev = NULL; + kfree(dev); + printk(KERN_ERR "mtp gadget driver failed to initialize\n"); + return ret; +} + +static void mtp_cleanup(void) +{ + struct mtp_dev *dev = _mtp_dev; + + if (!dev) + return; + + misc_deregister(&mtp_device); + destroy_workqueue(dev->wq); + _mtp_dev = NULL; + kfree(dev); +} diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index fa12ec8..96adf45 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -26,7 +26,7 @@ #include <linux/slab.h> #include <linux/kernel.h> -#include <linux/device.h> +#include <linux/platform_device.h> #include <linux/etherdevice.h> #include <asm/atomic.h> @@ -86,8 +86,11 @@ struct f_rndis { struct gether port; u8 ctrl_id, data_id; u8 ethaddr[ETH_ALEN]; + u32 vendorID; + const char *manufacturer; int config; + struct rndis_ep_descs fs; struct rndis_ep_descs hs; @@ -187,12 +190,11 @@ static struct usb_interface_assoc_descriptor rndis_iad_descriptor = { .bLength = sizeof rndis_iad_descriptor, .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, - .bFirstInterface = 0, /* XXX, hardcoded */ .bInterfaceCount = 2, // control + data .bFunctionClass = USB_CLASS_COMM, .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET, - .bFunctionProtocol = USB_CDC_PROTO_NONE, + .bFunctionProtocol = USB_CDC_ACM_PROTO_VENDOR, /* .iFunction = DYNAMIC */ }; @@ -486,10 +488,10 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt) usb_ep_disable(rndis->notify); } else { VDBG(cdev, "init rndis ctrl %d\n", intf); - rndis->notify_desc = ep_choose(cdev->gadget, - rndis->hs.notify, - rndis->fs.notify); } + rndis->notify_desc = ep_choose(cdev->gadget, + rndis->hs.notify, + rndis->fs.notify); usb_ep_enable(rndis->notify, rndis->notify_desc); rndis->notify->driver_data = rndis; @@ -503,11 +505,11 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (!rndis->port.in) { DBG(cdev, "init rndis\n"); - rndis->port.in = ep_choose(cdev->gadget, - rndis->hs.in, rndis->fs.in); - rndis->port.out = ep_choose(cdev->gadget, - rndis->hs.out, rndis->fs.out); } + rndis->port.in = ep_choose(cdev->gadget, + rndis->hs.in, rndis->fs.in); + rndis->port.out = ep_choose(cdev->gadget, + rndis->hs.out, rndis->fs.out); /* Avoid ZLPs; they can be troublesome. */ rndis->port.is_zlp_ok = false; @@ -706,12 +708,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0); rndis_set_host_mac(rndis->config, rndis->ethaddr); -#if 0 -// FIXME - if (rndis_set_param_vendor(rndis->config, vendorID, - manufacturer)) - goto fail0; -#endif + if (rndis_set_param_vendor(rndis->config, rndis->vendorID, + rndis->manufacturer)) + goto fail; /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code @@ -786,7 +785,8 @@ static inline bool can_support_rndis(struct usb_configuration *c) * for calling @gether_cleanup() before module unload. */ int -rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer) { struct f_rndis *rndis; int status; @@ -794,14 +794,14 @@ rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) if (!can_support_rndis(c) || !ethaddr) return -EINVAL; + /* setup RNDIS itself */ + status = rndis_init(); + if (status < 0) + return status; + /* maybe allocate device-global string IDs */ if (rndis_string_defs[0].id == 0) { - /* ... and setup RNDIS itself */ - status = rndis_init(); - if (status < 0) - return status; - /* control interface label */ status = usb_string_id(c->cdev); if (status < 0) @@ -831,6 +831,8 @@ rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) goto fail; memcpy(rndis->ethaddr, ethaddr, ETH_ALEN); + rndis->vendorID = vendorID; + rndis->manufacturer = manufacturer; /* RNDIS activates when the host changes this filter */ rndis->port.cdc_filter = 0; diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index bcdac7c..839c9a3 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -70,6 +70,12 @@ #define gadget_is_s3c2410(g) 0 #endif +#if CONFIG_USB_GADGET_S3C_OTGD +#define gadget_is_s3c(g) !strcmp("s3c-udc", (g)->name) +#else +#define gadget_is_s3c(g) 0 +#endif + #ifdef CONFIG_USB_GADGET_AT91 #define gadget_is_at91(g) !strcmp("at91_udc", (g)->name) #else @@ -223,6 +229,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x29; else if (gadget_is_s3c_hsudc(gadget)) return 0x30; + else if (gadget_is_s3c(gadget)) + return 0x31; return -ENOENT; } diff --git a/drivers/usb/gadget/rndis.c b/drivers/usb/gadget/rndis.c index d3cdffe..bbfbde7 100644 --- a/drivers/usb/gadget/rndis.c +++ b/drivers/usb/gadget/rndis.c @@ -1147,11 +1147,15 @@ static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS]; #endif /* CONFIG_USB_GADGET_DEBUG_FILES */ +static bool rndis_initialized; int rndis_init(void) { u8 i; + if (rndis_initialized) + return 0; + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { #ifdef CONFIG_USB_GADGET_DEBUG_FILES char name [20]; @@ -1178,6 +1182,7 @@ int rndis_init(void) INIT_LIST_HEAD(&(rndis_per_dev_params[i].resp_queue)); } + rndis_initialized = true; return 0; } @@ -1186,7 +1191,13 @@ void rndis_exit(void) #ifdef CONFIG_USB_GADGET_DEBUG_FILES u8 i; char name[20]; +#endif + if (!rndis_initialized) + return; + rndis_initialized = false; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { sprintf(name, NAME_TEMPLATE, i); remove_proc_entry(name, NULL); diff --git a/drivers/usb/gadget/s3c_udc.h b/drivers/usb/gadget/s3c_udc.h new file mode 100644 index 0000000..7f63a86 --- /dev/null +++ b/drivers/usb/gadget/s3c_udc.h @@ -0,0 +1,146 @@ +/* + * drivers/usb/gadget/s3c_udc.h + * Samsung S3C on-chip full/high speed USB device controllers + * Copyright (C) 2005 for Samsung Electronics + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __S3C_USB_GADGET +#define __S3C_USB_GADGET + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/types.h> +#include <linux/version.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/mm.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> + +#include <asm/byteorder.h> +#include <asm/dma.h> +#include <linux/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> +/* #include <asm/hardware.h> */ + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +/* Max packet size */ +#if defined(CONFIG_USB_GADGET_S3C_FS) +#define EP0_FIFO_SIZE 8 +#define EP_FIFO_SIZE 64 +#define S3C_MAX_ENDPOINTS 5 +#elif defined(CONFIG_USB_GADGET_S3C_HS) || defined(CONFIG_ARCH_S5PV210) +#define EP0_FIFO_SIZE 64 +#define EP_FIFO_SIZE 512 +#define EP_FIFO_SIZE2 1024 +#define S3C_MAX_ENDPOINTS 16 +#define DED_TX_FIFO 1 /* Dedicated NPTx fifo for s5p6440 */ +#else +#define EP0_FIFO_SIZE 64 +#define EP_FIFO_SIZE 512 +#define EP_FIFO_SIZE2 1024 +#define S3C_MAX_ENDPOINTS 16 +#endif + +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 +#define RegReadErr 5 +#define FAIL_TO_SETUP 6 + +#define TEST_J_SEL 0x1 +#define TEST_K_SEL 0x2 +#define TEST_SE0_NAK_SEL 0x3 +#define TEST_PACKET_SEL 0x4 +#define TEST_FORCE_ENABLE_SEL 0x5 + + +typedef enum ep_type { + ep_control, ep_bulk_in, ep_bulk_out, ep_interrupt, ep_isochronous +} ep_type_t; + +struct s3c_ep { + struct usb_ep ep; + struct s3c_udc *dev; + + const struct usb_endpoint_descriptor *desc; + struct list_head queue; + unsigned long pio_irqs; + + u8 stopped; + u8 bEndpointAddress; + u8 bmAttributes; + + ep_type_t ep_type; + u32 fifo; +#ifdef CONFIG_USB_GADGET_S3C_FS + u32 csr1; + u32 csr2; +#endif +}; + +struct s3c_request { + struct usb_request req; + struct list_head queue; + unsigned char mapped; +}; + +struct s3c_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + /* struct device *dev; */ + struct platform_device *dev; + spinlock_t lock; + u16 status; + int ep0state; + struct s3c_ep ep[S3C_MAX_ENDPOINTS]; + + unsigned char usb_address; + + unsigned req_pending:1, req_std:1, req_config:1; + + struct regulator *udc_vcc_d, *udc_vcc_a; + int udc_enabled; + int soft_disconnected; +}; + +extern struct s3c_udc *the_controller; +extern void otg_phy_init(void); +extern void otg_phy_off(void); +extern struct usb_ctrlrequest usb_ctrl; +extern struct i2c_driver fsa9480_i2c_driver; + +#define ep_is_in(EP) (((EP)->bEndpointAddress&USB_DIR_IN) == USB_DIR_IN) +#define ep_index(EP) ((EP)->bEndpointAddress&0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) + +#endif diff --git a/drivers/usb/gadget/s3c_udc_otg.c b/drivers/usb/gadget/s3c_udc_otg.c new file mode 100644 index 0000000..b15edde --- /dev/null +++ b/drivers/usb/gadget/s3c_udc_otg.c @@ -0,0 +1,1351 @@ +/* + * drivers/usb/gadget/s3c_udc_otg.c + * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers + * + * Copyright (C) 2008 for Samsung Electronics + * + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "s3c_udc.h" +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <mach/map.h> +#include <plat/regs-otg.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#if defined(CONFIG_USB_GADGET_S3C_OTGD_DMA_MODE) /* DMA mode */ +#define OTG_DMA_MODE 1 + +#elif defined(CONFIG_USB_GADGET_S3C_OTGD_SLAVE_MODE) /* Slave mode */ +#define OTG_DMA_MODE 0 +#error " Slave Mode is not implemented to do later" +#else +#error " Unknown S3C OTG operation mode, Select a correct operation mode" +#endif + +#define DEBUG_S3C_UDC_SETUP +#undef DEBUG_S3C_UDC_EP0 +#define DEBUG_S3C_UDC_ISR +#undef DEBUG_S3C_UDC_OUT_EP +#undef DEBUG_S3C_UDC_IN_EP +#undef DEBUG_S3C_UDC + +#define EP0_CON 0 +#define EP1_OUT 1 +#define EP2_IN 2 +#define EP3_IN 3 +#define EP_MASK 0xF + +#if defined(DEBUG_S3C_UDC_SETUP) || defined(DEBUG_S3C_UDC_ISR)\ + || defined(DEBUG_S3C_UDC_OUT_EP) + +static char *state_names[] = { + "WAIT_FOR_SETUP", + "DATA_STATE_XMIT", + "DATA_STATE_NEED_ZLP", + "WAIT_FOR_OUT_STATUS", + "DATA_STATE_RECV", + }; +#endif + +#ifdef DEBUG_S3C_UDC_SETUP +#define DEBUG_SETUP(fmt, args...) pr_debug(fmt, ##args) +#else +#define DEBUG_SETUP(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_EP0 +#define DEBUG_EP0(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_EP0(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC +#define DEBUG(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_ISR +#define DEBUG_ISR(fmt, args...) pr_debug(fmt, ##args) +#else +#define DEBUG_ISR(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_OUT_EP +#define DEBUG_OUT_EP(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_OUT_EP(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_IN_EP +#define DEBUG_IN_EP(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_IN_EP(fmt, args...) do {} while (0) +#endif + + +#define DRIVER_DESC "S3C HS USB Device Controller Driver, (c) 2008-2009 Samsung Electronics" +#define DRIVER_VERSION "15 March 2009" + +struct s3c_udc *the_controller; + +static struct clk *otg_clock; +static const char driver_name[] = "s3c-udc"; +static const char driver_desc[] = DRIVER_DESC; +static const char ep0name[] = "ep0-control"; + +/* Max packet size*/ +static unsigned int ep0_fifo_size = 64; +static unsigned int ep_fifo_size = 512; +static unsigned int ep_fifo_size2 = 1024; +static int reset_available = 1; + +/* + Local declarations. +*/ +static int s3c_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *); +static int s3c_ep_disable(struct usb_ep *ep); +static struct usb_request *s3c_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags); +static void s3c_free_request(struct usb_ep *ep, struct usb_request *); + +static int s3c_queue(struct usb_ep *ep, + struct usb_request *, gfp_t gfp_flags); +static int s3c_dequeue(struct usb_ep *ep, struct usb_request *); +static int s3c_fifo_status(struct usb_ep *ep); +static void s3c_fifo_flush(struct usb_ep *ep); +static void s3c_ep0_read(struct s3c_udc *dev); +static void s3c_ep0_kick(struct s3c_udc *dev, struct s3c_ep *ep); +static void s3c_handle_ep0(struct s3c_udc *dev); +static int s3c_ep0_write(struct s3c_udc *dev); +static int write_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req); +static void done(struct s3c_ep *ep, struct s3c_request *req, int status); +static void stop_activity(struct s3c_udc *dev, + struct usb_gadget_driver *driver); +static int udc_enable(struct s3c_udc *dev); +static void udc_set_address(struct s3c_udc *dev, unsigned char address); +static void reconfig_usbd(void); +static void set_max_pktsize(struct s3c_udc *dev, enum usb_device_speed speed); +static void nuke(struct s3c_ep *ep, int status); +static int s3c_udc_set_halt(struct usb_ep *_ep, int value); + +static struct usb_ep_ops s3c_ep_ops = { + .enable = s3c_ep_enable, + .disable = s3c_ep_disable, + + .alloc_request = s3c_alloc_request, + .free_request = s3c_free_request, + + .queue = s3c_queue, + .dequeue = s3c_dequeue, + + .set_halt = s3c_udc_set_halt, + .fifo_status = s3c_fifo_status, + .fifo_flush = s3c_fifo_flush, +}; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static const char proc_node_name[] = "driver/udc"; + +static int +udc_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + char *buf = page; + struct s3c_udc *dev = _dev; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t; + + if (off != 0) + return 0; + + local_irq_save(flags); + + /* basic device status */ + t = scnprintf(next, size, + DRIVER_DESC "\n" + "%s version: %s\n" + "Gadget driver: %s\n" + "\n", + driver_name, DRIVER_VERSION, + dev->driver ? dev->driver->driver.name : "(none)"); + size -= t; + next += t; + + local_irq_restore(flags); + *eof = 1; + return count - size; +} + +#define create_proc_files() \ + create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev) +#define remove_proc_files() \ + remove_proc_entry(proc_node_name, NULL) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_proc_files() do {} while (0) +#define remove_proc_files() do {} while (0) + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +#if OTG_DMA_MODE /* DMA Mode */ +#include "s3c_udc_otg_xfer_dma.c" + +#else /* Slave Mode */ +#include "s3c_udc_otg_xfer_slave.c" +#endif + +/* + * udc_disable - disable USB device controller + */ +static void udc_disable(struct s3c_udc *dev) +{ + DEBUG_SETUP("%s: %p\n", __func__, dev); + + udc_set_address(dev, 0); + + dev->ep0state = WAIT_FOR_SETUP; + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->usb_address = 0; + + otg_phy_off(); +} + +/* + * udc_reinit - initialize software state + */ +static void udc_reinit(struct s3c_udc *dev) +{ + unsigned int i; + + DEBUG_SETUP("%s: %p\n", __func__, dev); + + /* device/ep0 records init */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + dev->ep0state = WAIT_FOR_SETUP; + + /* basic endpoint records init */ + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + struct s3c_ep *ep = &dev->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->desc = 0; + ep->stopped = 0; + INIT_LIST_HEAD(&ep->queue); + ep->pio_irqs = 0; + } + + /* the rest was statically initialized, and is read-only */ +} + +#define BYTES2MAXP(x) (x / 8) +#define MAXP2BYTES(x) (x * 8) + +/* until it's enabled, this UDC should be completely invisible + * to any USB host. + */ +static int udc_enable(struct s3c_udc *dev) +{ + unsigned long flags; + + DEBUG_SETUP("%s: %p\n", __func__, dev); + + otg_phy_init(); + + spin_lock_irqsave(&dev->lock, flags); + reconfig_usbd(); + spin_unlock_irqrestore(&dev->lock, flags); + + DEBUG_SETUP("S3C USB 2.0 OTG Controller Core Initialized : 0x%x\n", + readl(S3C_UDC_OTG_GINTMSK)); + + dev->gadget.speed = USB_SPEED_UNKNOWN; + + return 0; +} + +/* + Register entry point for the peripheral controller driver. +*/ +int usb_gadget_probe_driver(struct usb_gadget_driver *driver, + int (*bind)(struct usb_gadget *)) +{ + struct s3c_udc *dev = the_controller; + int retval; + + DEBUG_SETUP("%s: %s\n", __func__, driver->driver.name); + +/* + * adb composite fail to !driver->unbind in composite.c as below + * static struct usb_gadget_driver composite_driver = { + * .speed = USB_SPEED_HIGH, + * .bind = composite_bind, + * .unbind = __exit_p(composite_unbind), + */ + if (!driver + || (driver->speed != USB_SPEED_FULL && driver->speed != USB_SPEED_HIGH) + || !bind + || !driver->disconnect || !driver->setup) + return -EINVAL; + if (!dev) + return -ENODEV; + if (dev->driver) + return -EBUSY; + + /* first hook up the driver ... */ + dev->driver = driver; + /* initialize device status as self-powered.*/ + dev->status = 1 << USB_DEVICE_SELF_POWERED; + dev->gadget.dev.driver = &driver->driver; + retval = device_add(&dev->gadget.dev); + + if (retval) { + printk(KERN_ERR "target device_add failed, error %d\n", retval); + return retval; + } + + retval = bind(&dev->gadget); + if (retval) { + printk(KERN_ERR "%s: bind to driver %s --> error %d\n", + dev->gadget.name, driver->driver.name, retval); + device_del(&dev->gadget.dev); + + dev->driver = 0; + dev->gadget.dev.driver = 0; + return retval; + } + + enable_irq(IRQ_OTG); + + retval = usb_gadget_vbus_connect(&dev->gadget); + if (retval < 0) + dev_dbg(&dev->gadget.dev, "%s vbus connect %d\n", + __func__, retval); + + if (!(readl(S3C_UDC_OTG_GOTGCTL) & B_SESSION_VALID)) { + retval = usb_gadget_vbus_disconnect(&dev->gadget); + if (retval < 0) { + dev_dbg(&dev->gadget.dev, "%s vbus disconnect %d\n", + __func__, retval); + } + } + + printk(KERN_DEBUG "Registered gadget driver '%s'\n", + driver->driver.name); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_probe_driver); + +/* + Unregister entry point for the peripheral controller driver. +*/ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct s3c_udc *dev = the_controller; + unsigned long flags; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + dev->driver = 0; + stop_activity(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + driver->unbind(&dev->gadget); + device_del(&dev->gadget.dev); + + disable_irq(IRQ_OTG); + + printk(KERN_DEBUG "Unregistered gadget driver '%s'\n", + driver->driver.name); + + udc_disable(dev); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +static int s3c_udc_power(struct s3c_udc *dev, char en) +{ + pr_debug("%s : %s\n", __func__, en ? "ON" : "OFF"); + + if (en) { + regulator_enable(dev->udc_vcc_d); + regulator_enable(dev->udc_vcc_a); + } else { + regulator_disable(dev->udc_vcc_d); + regulator_disable(dev->udc_vcc_a); + } + + return 0; +} + +int s3c_vbus_enable(struct usb_gadget *gadget, int enable) +{ + unsigned long flags; + struct s3c_udc *dev = the_controller; + + if (dev->udc_enabled != enable) { + dev->udc_enabled = enable; + if (!enable) { + spin_lock_irqsave(&dev->lock, flags); + stop_activity(dev, dev->driver); + spin_unlock_irqrestore(&dev->lock, flags); + udc_disable(dev); + clk_disable(otg_clock); + s3c_udc_power(dev, 0); + } else { + s3c_udc_power(dev, 1); + clk_enable(otg_clock); + udc_reinit(dev); + udc_enable(dev); + } + } else + dev_dbg(&dev->gadget.dev, "%s, udc : %d, en : %d\n", + __func__, dev->udc_enabled, enable); + + return 0; +} + +/* + * done - retire a request; caller blocked irqs + */ +static void done(struct s3c_ep *ep, struct s3c_request *req, int status) +{ + unsigned int stopped = ep->stopped; + struct device *dev = &the_controller->dev->dev; + DEBUG("%s: %s %p, req = %p, stopped = %d\n", + __func__, ep->ep.name, ep, &req->req, stopped); + + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (req->mapped) { + dma_unmap_single(dev, req->req.dma, req->req.length, + (ep->bEndpointAddress & USB_DIR_IN) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } + if (status && status != -ESHUTDOWN) { + DEBUG("complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + } + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + + spin_unlock(&ep->dev->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->dev->lock); + + ep->stopped = stopped; +} + +/* + * nuke - dequeue ALL requests + */ +static void nuke(struct s3c_ep *ep, int status) +{ + struct s3c_request *req; + + DEBUG("%s: %s %p\n", __func__, ep->ep.name, ep); + + /* called with irqs blocked */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + done(ep, req, status); + } +} + +static void stop_activity(struct s3c_udc *dev, + struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect drivers more than once */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = 0; + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + struct s3c_ep *ep = &dev->ep[i]; + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + /* re-init driver-visible data structures */ + udc_reinit(dev); +} + +static void reconfig_usbd(void) +{ + struct s3c_udc *dev = the_controller; + /* 2. Soft-reset OTG Core and then unreset again. */ +#ifdef DED_TX_FIFO + int i; +#endif + unsigned int uTemp; + writel(CORE_SOFT_RESET, S3C_UDC_OTG_GRSTCTL); + + writel(0<<15 /* PHY Low Power Clock sel*/ + |1<<14 /* Non-Periodic TxFIFO Rewind Enable*/ + |0x5<<10 /* Turnaround time*/ + |0<<9|0<<8 /* [0:HNP disable, 1:HNP enable][ 0:SRP disable, 1:SRP enable] H1= 1,1*/ + |0<<7 /* Ulpi DDR sel*/ + |0<<6 /* 0: high speed utmi+, 1: full speed serial*/ + |0<<4 /* 0: utmi+, 1:ulpi*/ + |1<<3 /* phy i/f 0:8bit, 1:16bit*/ + |0x7<<0, /* HS/FS Timeout**/ + S3C_UDC_OTG_GUSBCFG); + + /* 3. Put the OTG device core in the disconnected state.*/ + uTemp = readl(S3C_UDC_OTG_DCTL); + uTemp |= SOFT_DISCONNECT; + writel(uTemp, S3C_UDC_OTG_DCTL); + + udelay(20); + + /* 4. Make the OTG device core exit from the disconnected state.*/ + if (!dev->soft_disconnected) { + uTemp = readl(S3C_UDC_OTG_DCTL); + uTemp = uTemp & ~SOFT_DISCONNECT; + writel(uTemp, S3C_UDC_OTG_DCTL); + } + + /* 5. Configure OTG Core to initial settings of device mode.*/ + /* [][1: full speed(30Mhz) 0:high speed]*/ + writel(1<<18|0x0<<0, S3C_UDC_OTG_DCFG); + + mdelay(1); + + /* 6. Unmask the core interrupts*/ + writel(GINTMSK_INIT, S3C_UDC_OTG_GINTMSK); + + /* 7. Set NAK bit of EP0, EP1, EP2*/ + writel(DEPCTL_EPDIS|DEPCTL_SNAK|(0<<0), S3C_UDC_OTG_DOEPCTL(EP0_CON)); + writel(DEPCTL_EPDIS|DEPCTL_SNAK|(0<<0), S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + /* 8. Unmask EPO interrupts*/ + writel(((1<<EP0_CON)<<DAINT_OUT_BIT)|(1<<EP0_CON), + S3C_UDC_OTG_DAINTMSK); + + /* 9. Unmask device OUT EP common interrupts*/ + writel(DOEPMSK_INIT, S3C_UDC_OTG_DOEPMSK); + + /* 10. Unmask device IN EP common interrupts*/ + writel(DIEPMSK_INIT, S3C_UDC_OTG_DIEPMSK); + + /* 11. Set Rx FIFO Size (in 32-bit words) */ + writel(RX_FIFO_SIZE, S3C_UDC_OTG_GRXFSIZ); + + /* 12. Set Non Periodic Tx FIFO Size*/ + writel((NPTX_FIFO_SIZE) << 16 | (NPTX_FIFO_START_ADDR) << 0, + S3C_UDC_OTG_GNPTXFSIZ); + +#ifdef DED_TX_FIFO + for (i = 1; i < S3C_MAX_ENDPOINTS; i++) + writel((PTX_FIFO_SIZE) << 16 | + (NPTX_FIFO_START_ADDR + NPTX_FIFO_SIZE + PTX_FIFO_SIZE*(i-1)) << 0, + S3C_UDC_OTG_DIEPTXF(i)); +#endif + + /* Flush the RX FIFO */ + writel(0x10, S3C_UDC_OTG_GRSTCTL); + while (readl(S3C_UDC_OTG_GRSTCTL) & 0x10) + ; + + /* Flush all the Tx FIFO's */ + writel(0x10<<6, S3C_UDC_OTG_GRSTCTL); + writel((0x10<<6)|0x20, S3C_UDC_OTG_GRSTCTL); + while (readl(S3C_UDC_OTG_GRSTCTL) & 0x20) + ; + + /* 13. Initialize OTG Link Core.*/ + writel(GAHBCFG_INIT, S3C_UDC_OTG_GAHBCFG); +} + +static void set_max_pktsize(struct s3c_udc *dev, enum usb_device_speed speed) +{ + unsigned int ep_ctrl; + int i; + if (speed == USB_SPEED_HIGH) { + ep0_fifo_size = 64; + ep_fifo_size = 512; + ep_fifo_size2 = 1024; + dev->gadget.speed = USB_SPEED_HIGH; + } else { + ep0_fifo_size = 64; + ep_fifo_size = 64; + ep_fifo_size2 = 64; + dev->gadget.speed = USB_SPEED_FULL; + } + + dev->ep[0].ep.maxpacket = ep0_fifo_size; + for (i = 1; i < S3C_MAX_ENDPOINTS; i++) { + /* fullspeed limitations don't apply to isochronous endpoints */ + if (dev->ep[i].bmAttributes != USB_ENDPOINT_XFER_ISOC) + dev->ep[i].ep.maxpacket = ep_fifo_size; + } + + /* EP0 - Control IN (64 bytes)*/ + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + writel(ep_ctrl|(0<<0), S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + /* EP0 - Control OUT (64 bytes)*/ + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(EP0_CON)); + writel(ep_ctrl|(0<<0), S3C_UDC_OTG_DOEPCTL(EP0_CON)); +} + +static int s3c_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + + DEBUG("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->bEndpointAddress != desc->bEndpointAddress + || ep_maxpacket(ep) < le16_to_cpu(desc->wMaxPacketSize)) { + + DEBUG("%s: bad ep or descriptor\n", __func__); + return -EINVAL; + } + + /* xfer types must match, except that interrupt ~= bulk */ + if (ep->bmAttributes != + (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + && ep->bmAttributes != USB_ENDPOINT_XFER_BULK + && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { + + DEBUG("%s: %s type mismatch\n", __func__, _ep->name); + return -EINVAL; + } + + /* hardware _could_ do smaller, but driver doesn't */ + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && le16_to_cpu(desc->wMaxPacketSize) != ep_maxpacket(ep)) + || !desc->wMaxPacketSize) { + + DEBUG("%s: bad %s maxpacket\n", __func__, _ep->name); + return -ERANGE; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + + DEBUG("%s: bogus device state\n", __func__); + return -ESHUTDOWN; + } + + ep->stopped = 0; + ep->desc = desc; + ep->pio_irqs = 0; + ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize); + + /* Reset halt state */ + s3c_udc_set_halt(_ep, 0); + + spin_lock_irqsave(&ep->dev->lock, flags); + s3c_udc_ep_activate(ep); + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DEBUG("%s: enabled %s, stopped = %d, maxpacket = %d\n", + __func__, _ep->name, ep->stopped, ep->ep.maxpacket); + return 0; +} + +/** Disable EP + */ +static int s3c_ep_disable(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + unsigned long flags; + + DEBUG("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || !ep->desc) { + DEBUG("%s: %s not enabled\n", __func__, + _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* Nuke all pending requests */ + nuke(ep, -ESHUTDOWN); + + ep->desc = 0; + ep->stopped = 1; + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DEBUG("%s: disabled %s\n", __func__, _ep->name); + return 0; +} + +static struct usb_request *s3c_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct s3c_request *req; + + DEBUG("%s: %s %p\n", __func__, ep->name, ep); + + req = kzalloc(sizeof(*req), gfp_flags); + if (req) + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void s3c_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct s3c_request *req = container_of(_req, struct s3c_request, req); + DEBUG("%s: %p\n", __func__, ep); + kfree(req); +} + +/* dequeue JUST ONE request */ +static int s3c_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct s3c_ep *ep; + struct s3c_request *req; + unsigned long flags; + + DEBUG("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || ep->ep.name == ep0name) + return -EINVAL; + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->dev->lock, flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + + spin_unlock_irqrestore(&ep->dev->lock, flags); + return 0; +} + +/** Return bytes in EP FIFO + */ +static int s3c_fifo_status(struct usb_ep *_ep) +{ + int count = 0; + struct s3c_ep *ep; + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep) { + DEBUG("%s: bad ep\n", __func__); + return -ENODEV; + } + + DEBUG("%s: %d\n", __func__, ep_index(ep)); + + /* LPD can't report unclaimed bytes from IN fifos */ + if (ep_is_in(ep)) + return -EOPNOTSUPP; + + return count; +} + +/** Flush EP FIFO + */ +static void s3c_fifo_flush(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + + ep = container_of(_ep, struct s3c_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + DEBUG("%s: bad ep\n", __func__); + return; + } + + DEBUG("%s: %d\n", __func__, ep_index(ep)); +} + +/* --------------------------------------------------------------------------- + * device-scoped parts of the api to the usb controller hardware + * --------------------------------------------------------------------------- + */ + +static int s3c_udc_get_frame(struct usb_gadget *_gadget) +{ + unsigned int frame; + DEBUG("%s: %p\n", __func__, _gadget); + /*fram count number [21:8]*/ + frame = readl(S3C_UDC_OTG_DSTS); + frame &= SOFFN_MASK; + frame >>= SOFFN_SHIFT; + return frame; +} + +static int s3c_udc_wakeup(struct usb_gadget *_gadget) +{ + unsigned int dev_gctl, dev_dctl, dev_status; + unsigned long flags; + int retVal = -EINVAL; + struct s3c_udc *dev = the_controller; + + DEBUG("%s: %p\n", __func__, _gadget); + + if (!_gadget) + return -ENODEV; + + spin_lock_irqsave(&dev->lock, flags); + + if (!(dev->status & (1 << USB_DEVICE_REMOTE_WAKEUP))) { + DEBUG_SETUP("%s::Remote Wakeup is not set\n", __func__); + goto wakeup_exit; + } + /* check for session */ + dev_gctl = readl(S3C_UDC_OTG_GOTGCTL); + if (dev_gctl & B_SESSION_VALID) { + /* check for suspend state */ + dev_status = readl(S3C_UDC_OTG_DSTS); + if (dev_status & USB_SUSPEND) { + DEBUG_SETUP("%s:: Set Remote wakeup\n", __func__); + dev_dctl = readl(S3C_UDC_OTG_DCTL); + dev_dctl |= REMOTE_WAKEUP; + writel(dev_dctl, S3C_UDC_OTG_DCTL); + mdelay(1); + DEBUG_SETUP("%s:: Clear Remote Wakeup\n", __func__); + dev_dctl = readl(S3C_UDC_OTG_DCTL); + dev_dctl &= (~REMOTE_WAKEUP); + writel(dev_dctl, S3C_UDC_OTG_DCTL); + } else { + DEBUG_SETUP("%s:: already woke up\n", __func__); + } + + } else if (dev_gctl & SESSION_REQ) { + DEBUG_SETUP("%s:: Session request already active\n", __func__); + goto wakeup_exit; + } + + retVal = 0; +wakeup_exit: + spin_unlock_irqrestore(&dev->lock, flags); + return retVal; +} + +void s3c_udc_soft_connect(void) +{ + struct s3c_udc *dev = the_controller; + unsigned long flags; + u32 uTemp; + + DEBUG("[%s]\n", __func__); + + spin_lock_irqsave(&dev->lock, flags); + + uTemp = readl(S3C_UDC_OTG_DCTL); + uTemp = uTemp & ~SOFT_DISCONNECT; + writel(uTemp, S3C_UDC_OTG_DCTL); + udelay(20); + + reset_available = 1; + + /* Unmask the core interrupt */ + writel(readl(S3C_UDC_OTG_GINTSTS), S3C_UDC_OTG_GINTSTS); + writel(GINTMSK_INIT, S3C_UDC_OTG_GINTMSK); + + dev->soft_disconnected = 0; + spin_unlock_irqrestore(&dev->lock, flags); +} + +void s3c_udc_soft_disconnect(void) +{ + u32 uTemp; + struct s3c_udc *dev = the_controller; + unsigned long flags; + + DEBUG("[%s]\n", __func__); + + spin_lock_irqsave(&dev->lock, flags); + + /* Mask the core interrupt */ + writel(0, S3C_UDC_OTG_GINTMSK); + + uTemp = readl(S3C_UDC_OTG_DCTL); + uTemp |= SOFT_DISCONNECT; + writel(uTemp, S3C_UDC_OTG_DCTL); + + stop_activity(dev, dev->driver); + + dev->soft_disconnected = 1; + spin_unlock_irqrestore(&dev->lock, flags); +} + +static int s3c_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + if (is_on) + s3c_udc_soft_connect(); + else + s3c_udc_soft_disconnect(); + return 0; +} + + +static const struct usb_gadget_ops s3c_udc_ops = { + .get_frame = s3c_udc_get_frame, + .wakeup = s3c_udc_wakeup, + /* current versions must always be self-powered */ + .pullup = s3c_udc_pullup, + .vbus_session = s3c_vbus_enable, +}; + +static void nop_release(struct device *dev) +{ + DEBUG("%s\n", __func__); +} + +static struct s3c_udc memory = { + .usb_address = 0, + + .gadget = { + .ops = &s3c_udc_ops, + .ep0 = &memory.ep[0].ep, + .name = driver_name, + .dev = { + .release = nop_release, + }, + }, + + /* control endpoint */ + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &s3c_ep_ops, + .maxpacket = EP0_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = 0, + .bmAttributes = 0, + + .ep_type = ep_control, + .fifo = (unsigned int) S3C_UDC_OTG_EP0_FIFO, + }, + + /* first group of endpoints */ + .ep[1] = { + .ep = { + .name = "ep1-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP1_FIFO, + }, + + .ep[2] = { + .ep = { + .name = "ep2-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP2_FIFO, + }, + + .ep[3] = { + .ep = { + .name = "ep3-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 3, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP3_FIFO, + }, + .ep[4] = { + .ep = { + .name = "ep4-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 4, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP4_FIFO, + }, + .ep[5] = { + .ep = { + .name = "ep5-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 5, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP5_FIFO, + }, + .ep[6] = { + .ep = { + .name = "ep6-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 6, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP6_FIFO, + }, + .ep[7] = { + .ep = { + .name = "ep7-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 7, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP7_FIFO, + }, + .ep[8] = { + .ep = { + .name = "ep8-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 8, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP8_FIFO, + }, + .ep[9] = { + .ep = { + .name = "ep9-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 9, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP9_FIFO, + }, + .ep[10] = { + .ep = { + .name = "ep10-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 0xa, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP10_FIFO, + }, + .ep[11] = { + .ep = { + .name = "ep11-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 0xb, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP11_FIFO, + }, + .ep[12] = { + .ep = { + .name = "ep12-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 0xc, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP12_FIFO, + }, + .ep[13] = { + .ep = { + .name = "ep13-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 0xd, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP13_FIFO, + }, + .ep[14] = { + .ep = { + .name = "ep14-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 0xe, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP14_FIFO, + }, + .ep[15] = { + .ep = { + .name = "ep15-iso", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 0xf, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + + .ep_type = ep_isochronous, + .fifo = (unsigned int) S3C_UDC_OTG_EP15_FIFO, + }, +}; + +/* + * probe - binds to the platform device + */ +static int s3c_udc_probe(struct platform_device *pdev) +{ + struct s3c_udc *dev = &memory; + int retval; + DEBUG("%s: %p\n", __func__, pdev); + + spin_lock_init(&dev->lock); + dev->dev = pdev; + + device_initialize(&dev->gadget.dev); + dev->gadget.dev.parent = &pdev->dev; + + dev->gadget.is_dualspeed = 1; /* Hack only*/ + dev->gadget.is_otg = 0; + dev->gadget.is_a_peripheral = 0; + dev->gadget.b_hnp_enable = 0; + dev->gadget.a_hnp_support = 0; + dev->gadget.a_alt_hnp_support = 0; + + dev->udc_vcc_d = regulator_get(&pdev->dev, "pd_io"); + dev->udc_vcc_a = regulator_get(&pdev->dev, "pd_core"); + if (IS_ERR(dev->udc_vcc_d) || IS_ERR(dev->udc_vcc_a)) { + printk(KERN_ERR "failed to find udc vcc source\n"); + return -ENOENT; + } + + the_controller = dev; + platform_set_drvdata(pdev, dev); + + dev_set_name(&dev->gadget.dev, "gadget"); + otg_clock = clk_get(&pdev->dev, "otg"); + if (otg_clock == NULL) { + printk(KERN_ERR "failed to find otg clock source\n"); + return -ENOENT; + } + + udc_reinit(dev); + + /* irq setup after old hardware state is cleaned up */ + retval = + request_irq(IRQ_OTG, s3c_udc_irq, 0, driver_name, dev); + + if (retval != 0) { + DEBUG(KERN_ERR "%s: can't get irq %i, err %d\n", driver_name, + IRQ_OTG, retval); + return -EBUSY; + } + + disable_irq(IRQ_OTG); + create_proc_files(); + return retval; +} + +static int s3c_udc_remove(struct platform_device *pdev) +{ + struct s3c_udc *dev = platform_get_drvdata(pdev); + + DEBUG("%s: %p\n", __func__, pdev); + + if (otg_clock != NULL) { + clk_disable(otg_clock); + clk_put(otg_clock); + otg_clock = NULL; + } + + remove_proc_files(); + usb_gadget_unregister_driver(dev->driver); + + free_irq(IRQ_OTG, dev); + + platform_set_drvdata(pdev, 0); + + the_controller = 0; + + return 0; +} + +#ifdef CONFIG_PM +static int s3c_udc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct s3c_udc *dev = the_controller; + + disable_irq(IRQ_OTG); + + if (dev->driver && dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + + if (dev->udc_enabled) + usb_gadget_vbus_disconnect(&dev->gadget); + + return 0; +} + +static int s3c_udc_resume(struct platform_device *pdev) +{ + struct s3c_udc *dev = the_controller; + + if (dev->driver && dev->driver->resume) + dev->driver->resume(&dev->gadget); + + enable_irq(IRQ_OTG); + + return 0; +} +#else +#define s3c_udc_suspend NULL +#define s3c_udc_resume NULL +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ +static struct platform_driver s3c_udc_driver = { + .probe = s3c_udc_probe, + .remove = s3c_udc_remove, + .suspend = s3c_udc_suspend, + .resume = s3c_udc_resume, + .driver = { + .owner = THIS_MODULE, + .name = "s3c-usbgadget", + }, +}; + +static int __init udc_init(void) +{ + int ret; + + ret = platform_driver_register(&s3c_udc_driver); + if (!ret) + printk(KERN_INFO "%s : %s\n" + "%s : version %s %s\n", + driver_name, DRIVER_DESC, + driver_name, DRIVER_VERSION, OTG_DMA_MODE ? + "(DMA Mode)" : "(Slave Mode)"); + + return ret; +} + +static void __exit udc_exit(void) +{ + platform_driver_unregister(&s3c_udc_driver); + printk(KERN_INFO "Unloaded %s version %s\n", + driver_name, DRIVER_VERSION); +} + +module_init(udc_init); +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Samsung"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c new file mode 100644 index 0000000..70cf602 --- /dev/null +++ b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c @@ -0,0 +1,1387 @@ +/* + * drivers/usb/gadget/s3c_udc_otg_xfer_dma.c + * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers + * + * Copyright (C) 2009 for Samsung Electronics + * + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define GINTMSK_INIT (INT_OUT_EP|INT_IN_EP|INT_RESUME|INT_ENUMDONE|INT_RESET|INT_SUSPEND) +#define DOEPMSK_INIT (CTRL_OUT_EP_SETUP_PHASE_DONE|AHB_ERROR|TRANSFER_DONE) +#define DIEPMSK_INIT (NON_ISO_IN_EP_TIMEOUT|AHB_ERROR|TRANSFER_DONE) +#define GAHBCFG_INIT (PTXFE_HALF|NPTXFE_HALF|MODE_DMA|BURST_INCR4|GBL_INT_UNMASK) + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +static u8 clear_feature_num; +static int clear_feature_flag; +static int set_conf_done; + +/* Bulk-Only Mass Storage Reset (class-specific request) */ +#define GET_MAX_LUN_REQUEST 0xFE +#define BOT_RESET_REQUEST 0xFF + +/* TEST MODE in set_feature request */ +#define TEST_SELECTOR_MASK 0xFF +#define TEST_PKT_SIZE 53 + +static u8 test_pkt[TEST_PKT_SIZE] __attribute__((aligned(8))) = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* JKJKJKJK x 9 */ + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, /* JJKKJJKK x 8 */ + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, /* JJJJKKKK x 8 */ + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* JJJJJJJKKKKKKK x 8 - '1' */ + 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, /* '1' + JJJJJJJK x 8 */ + 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x7E /* {JKKKKKKK x 10}, JK */ +}; + +void s3c_udc_ep_set_stall(struct s3c_ep *ep); + +#if defined(CONFIG_MACH_SMDKC110) || defined(CONFIG_MACH_SMDKV210) +extern void s3c_cable_check_status(int flag); + +void s3c_udc_cable_connect(struct s3c_udc *dev) +{ + s3c_cable_check_status(1); +} + +void s3c_udc_cable_disconnect(struct s3c_udc *dev) +{ + s3c_cable_check_status(0); +} +#endif + +static inline void s3c_udc_ep0_zlp(void) +{ + u32 ep_ctrl; + + writel(virt_to_phys(&usb_ctrl), S3C_UDC_OTG_DIEPDMA(EP0_CON)); + writel((1<<19 | 0<<0), S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + DEBUG_EP0("%s:EP0 ZLP DIEPCTL0 = 0x%x\n", + __func__, readl(S3C_UDC_OTG_DIEPCTL(EP0_CON))); +} + +static inline void s3c_udc_pre_setup(void) +{ + u32 ep_ctrl; + + DEBUG_IN_EP("%s : Prepare Setup packets.\n", __func__); + + writel((1 << 19)|sizeof(struct usb_ctrlrequest), S3C_UDC_OTG_DOEPTSIZ(EP0_CON)); + writel(virt_to_phys(&usb_ctrl), S3C_UDC_OTG_DOEPDMA(EP0_CON)); + + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, S3C_UDC_OTG_DOEPCTL(EP0_CON)); +} + +static int setdma_rx(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 *buf, ctrl; + u32 length, pktcnt; + u32 ep_num = ep_index(ep); + + struct device *dev = &the_controller->dev->dev; + buf = req->req.buf + req->req.actual; + prefetchw(buf); + + length = req->req.length - req->req.actual; + req->req.dma = dma_map_single(dev, buf, + length, DMA_FROM_DEVICE); + req->mapped = 1; + + if (length == 0) + pktcnt = 1; + else + pktcnt = (length - 1)/(ep->ep.maxpacket) + 1; + + ctrl = readl(S3C_UDC_OTG_DOEPCTL(ep_num)); + + writel(virt_to_phys(buf), S3C_UDC_OTG_DOEPDMA(ep_num)); + writel((pktcnt<<19)|(length<<0), S3C_UDC_OTG_DOEPTSIZ(ep_num)); + writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, S3C_UDC_OTG_DOEPCTL(ep_num)); + + DEBUG_OUT_EP("%s: EP%d RX DMA start : DOEPDMA = 0x%x, DOEPTSIZ = 0x%x, DOEPCTL = 0x%x\n" + "\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n", + __func__, ep_num, + readl(S3C_UDC_OTG_DOEPDMA(ep_num)), + readl(S3C_UDC_OTG_DOEPTSIZ(ep_num)), + readl(S3C_UDC_OTG_DOEPCTL(ep_num)), + buf, pktcnt, length); + return 0; + +} + +static int setdma_tx(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 *buf, ctrl = 0; + u32 length, pktcnt; + u32 ep_num = ep_index(ep); + struct device *dev = &the_controller->dev->dev; + + buf = req->req.buf + req->req.actual; + prefetch(buf); + length = req->req.length - req->req.actual; + + if (ep_num == EP0_CON) + length = min(length, (u32)ep_maxpacket(ep)); + + req->req.actual += length; + req->req.dma = dma_map_single(dev, buf, + length, DMA_TO_DEVICE); + req->mapped = 1; + + if (length == 0) + pktcnt = 1; + else + pktcnt = (length - 1)/(ep->ep.maxpacket) + 1; + +#ifdef DED_TX_FIFO + /* Write the FIFO number to be used for this endpoint */ + ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num)); + ctrl &= ~DEPCTL_TXFNUM_MASK;; + ctrl |= (ep_num << DEPCTL_TXFNUM_BIT); + writel(ctrl , S3C_UDC_OTG_DIEPCTL(ep_num)); +#endif + + writel(virt_to_phys(buf), S3C_UDC_OTG_DIEPDMA(ep_num)); + writel((pktcnt<<19)|(length<<0), S3C_UDC_OTG_DIEPTSIZ(ep_num)); + ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num)); + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) + ctrl |= DEPCTL_SET_ODD_FRM; + writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, S3C_UDC_OTG_DIEPCTL(ep_num)); + + DEBUG_IN_EP("%s:EP%d TX DMA start : DIEPDMA0 = 0x%x, DIEPTSIZ0 = 0x%x, DIEPCTL0 = 0x%x\n" + "\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n", + __func__, ep_num, + readl(S3C_UDC_OTG_DIEPDMA(ep_num)), + readl(S3C_UDC_OTG_DIEPTSIZ(ep_num)), + readl(S3C_UDC_OTG_DIEPCTL(ep_num)), + buf, pktcnt, length); + + return length; +} + +static void complete_rx(struct s3c_udc *dev, u8 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req = NULL; + u32 ep_tsr = 0, xfer_size = 0, xfer_length, is_short = 0; + + if (list_empty(&ep->queue)) { + DEBUG_OUT_EP("%s: RX DMA done : NULL REQ on OUT EP-%d\n", + __func__, ep_num); + return; + + } + + req = list_entry(ep->queue.next, struct s3c_request, queue); + + ep_tsr = readl(S3C_UDC_OTG_DOEPTSIZ(ep_num)); + + if (ep_num == EP0_CON) + xfer_size = (ep_tsr & 0x7f); + + else + xfer_size = (ep_tsr & 0x7fff); + + __dma_single_cpu_to_dev(req->req.buf, req->req.length, DMA_FROM_DEVICE); + xfer_length = req->req.length - xfer_size; + req->req.actual += min(xfer_length, req->req.length - req->req.actual); + is_short = (xfer_length < ep->ep.maxpacket); + + DEBUG_OUT_EP("%s: RX DMA done : ep = %d, rx bytes = %d/%d, " + "is_short = %d, DOEPTSIZ = 0x%x, remained bytes = %d\n", + __func__, ep_num, req->req.actual, req->req.length, + is_short, ep_tsr, xfer_size); + + if (is_short || req->req.actual == xfer_length) { + if (ep_num == EP0_CON && dev->ep0state == DATA_STATE_RECV) { + DEBUG_OUT_EP(" => Send ZLP\n"); + dev->ep0state = WAIT_FOR_SETUP; + s3c_udc_ep0_zlp(); + + } else { + done(ep, req, 0); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + DEBUG_OUT_EP("%s: Next Rx request start...\n", __func__); + setdma_rx(ep, req); + } + } + } +} + +static void complete_tx(struct s3c_udc *dev, u8 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req; + u32 ep_tsr = 0, xfer_size = 0, xfer_length, is_short = 0; + u32 last; + + if (list_empty(&ep->queue)) { + DEBUG_IN_EP("%s: TX DMA done : NULL REQ on IN EP-%d\n", + __func__, ep_num); + return; + + } + + req = list_entry(ep->queue.next, struct s3c_request, queue); + + if (dev->ep0state == DATA_STATE_XMIT) { + DEBUG_IN_EP("%s: ep_num = %d, ep0stat == DATA_STATE_XMIT\n", + __func__, ep_num); + + last = write_fifo_ep0(ep, req); + + if (last) + dev->ep0state = WAIT_FOR_SETUP; + + return; + } + + ep_tsr = readl(S3C_UDC_OTG_DIEPTSIZ(ep_num)); + + if (ep_num == EP0_CON) + xfer_size = (ep_tsr & 0x7f); + else + xfer_size = (ep_tsr & 0x7fff); + + req->req.actual = req->req.length - xfer_size; + xfer_length = req->req.length - xfer_size; + req->req.actual += min(xfer_length, req->req.length - req->req.actual); + is_short = (xfer_length < ep->ep.maxpacket); + + DEBUG_IN_EP("%s: TX DMA done : ep = %d, tx bytes = %d/%d, " + "is_short = %d, DIEPTSIZ = 0x%x, remained bytes = %d\n", + __func__, ep_num, req->req.actual, req->req.length, + is_short, ep_tsr, xfer_size); + + if (req->req.actual == req->req.length) { + done(ep, req, 0); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + DEBUG_IN_EP("%s: Next Tx request start...\n", __func__); + setdma_tx(ep, req); + } + } +} +static inline void s3c_udc_check_tx_queue(struct s3c_udc *dev, u8 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req; + + DEBUG_IN_EP("%s: Check queue, ep_num = %d\n", __func__, ep_num); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + DEBUG_IN_EP("%s: Next Tx request(0x%p) start...\n", __func__, req); + + if (ep_is_in(ep)) + setdma_tx(ep, req); + else + setdma_rx(ep, req); + } else { + DEBUG_IN_EP("%s: NULL REQ on IN EP-%d\n", __func__, ep_num); + + return; + } + +} + +static void process_ep_in_intr(struct s3c_udc *dev) +{ + u32 ep_intr, ep_intr_status; + u8 ep_num = 0; + + ep_intr = readl(S3C_UDC_OTG_DAINT); + DEBUG_IN_EP("*** %s: EP In interrupt : DAINT = 0x%x\n", + __func__, ep_intr); + + ep_intr &= DAINT_MASK; + + while (ep_intr) { + if (ep_intr & 0x1) { + ep_intr_status = readl(S3C_UDC_OTG_DIEPINT(ep_num)); + DEBUG_IN_EP("\tEP%d-IN : DIEPINT = 0x%x\n", + ep_num, ep_intr_status); + + /* Interrupt Clear */ + writel(ep_intr_status, S3C_UDC_OTG_DIEPINT(ep_num)); + + if (ep_intr_status & TRANSFER_DONE) { + complete_tx(dev, ep_num); + + if (ep_num == 0) { + if (dev->ep0state == WAIT_FOR_SETUP) + s3c_udc_pre_setup(); + + /* continue transfer after set_clear_halt for DMA mode */ + if (clear_feature_flag == 1) { + s3c_udc_check_tx_queue(dev, clear_feature_num); + clear_feature_flag = 0; + } + } + } + } + ep_num++; + ep_intr >>= 1; + } + +} + +static void process_ep_out_intr(struct s3c_udc *dev) +{ + u32 ep_intr, ep_intr_status, ep_ctrl; + u8 ep_num = 0; + + ep_intr = readl(S3C_UDC_OTG_DAINT); + DEBUG_OUT_EP("*** %s: EP OUT interrupt : DAINT = 0x%x\n", + __func__, ep_intr); + + ep_intr = (ep_intr >> DAINT_OUT_BIT) & DAINT_MASK; + + while (ep_intr) { + if (ep_intr & 0x1) { + ep_intr_status = readl(S3C_UDC_OTG_DOEPINT(ep_num)); + DEBUG_OUT_EP("\tEP%d-OUT : DOEPINT = 0x%x\n", + ep_num, ep_intr_status); + + /* Interrupt Clear */ + writel(ep_intr_status, S3C_UDC_OTG_DOEPINT(ep_num)); + + if (ep_num == 0) { + if (ep_intr_status & CTRL_OUT_EP_SETUP_PHASE_DONE) { + DEBUG_OUT_EP("\tSETUP packet(transaction) arrived\n"); + s3c_handle_ep0(dev); + } + + if (ep_intr_status & TRANSFER_DONE) { + complete_rx(dev, ep_num); + + writel((3<<29)|(1<<19)|sizeof(struct usb_ctrlrequest), + S3C_UDC_OTG_DOEPTSIZ(EP0_CON)); + writel(virt_to_phys(&usb_ctrl), + S3C_UDC_OTG_DOEPDMA(EP0_CON)); + + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_SNAK, + S3C_UDC_OTG_DOEPCTL(EP0_CON)); + } + + } else { + if (ep_intr_status & TRANSFER_DONE) + complete_rx(dev, ep_num); + } + } + ep_num++; + ep_intr >>= 1; + } +} + +/* + * usb client interrupt handler. + */ +static irqreturn_t s3c_udc_irq(int irq, void *_dev) +{ + struct s3c_udc *dev = _dev; + u32 intr_status; + u32 usb_status, gintmsk; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + intr_status = readl(S3C_UDC_OTG_GINTSTS); + gintmsk = readl(S3C_UDC_OTG_GINTMSK); + + DEBUG_ISR("\n*** %s : GINTSTS=0x%x(on state %s), GINTMSK : 0x%x, DAINT : 0x%x, DAINTMSK : 0x%x\n", + __func__, intr_status, state_names[dev->ep0state], gintmsk, + readl(S3C_UDC_OTG_DAINT), readl(S3C_UDC_OTG_DAINTMSK)); + + if (!intr_status) { + spin_unlock_irqrestore(&dev->lock, flags); + return IRQ_HANDLED; + } + + if (intr_status & INT_ENUMDONE) { + DEBUG_ISR("\tSpeed Detection interrupt\n"); + + writel(INT_ENUMDONE, S3C_UDC_OTG_GINTSTS); + usb_status = (readl(S3C_UDC_OTG_DSTS) & 0x6); + + if (usb_status & (USB_FULL_30_60MHZ | USB_FULL_48MHZ)) { + DEBUG_ISR("\t\tFull Speed Detection\n"); + set_max_pktsize(dev, USB_SPEED_FULL); + + } else { + DEBUG_ISR("\t\tHigh Speed Detection : 0x%x\n", usb_status); + set_max_pktsize(dev, USB_SPEED_HIGH); + } + } + + if (intr_status & INT_EARLY_SUSPEND) { + DEBUG_ISR("\tEarly suspend interrupt\n"); + writel(INT_EARLY_SUSPEND, S3C_UDC_OTG_GINTSTS); + } + + if (intr_status & INT_SUSPEND) { + usb_status = readl(S3C_UDC_OTG_DSTS); + DEBUG_ISR("\tSuspend interrupt :(DSTS):0x%x\n", usb_status); + writel(INT_SUSPEND, S3C_UDC_OTG_GINTSTS); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->suspend) { + spin_unlock(&dev->lock); + dev->driver->suspend(&dev->gadget); + spin_lock(&dev->lock); + } + if (dev->status & (1 << USB_DEVICE_REMOTE_WAKEUP)) { + DEBUG_ISR("device is under remote wakeup\n"); + spin_unlock_irqrestore(&dev->lock, flags); + return IRQ_HANDLED; + } + if (dev->driver) { + spin_unlock(&dev->lock); + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } +#if defined(CONFIG_MACH_SMDKC110) || defined(CONFIG_MACH_SMDKV210) + s3c_udc_cable_disconnect(dev); +#endif + } + + if (intr_status & INT_RESUME) { + DEBUG_ISR("\tResume interrupt\n"); + writel(INT_RESUME, S3C_UDC_OTG_GINTSTS); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) { + + dev->driver->resume(&dev->gadget); + } + } + + if (intr_status & INT_RESET) { + usb_status = readl(S3C_UDC_OTG_GOTGCTL); + DEBUG_ISR("\tReset interrupt - (GOTGCTL):0x%x\n", usb_status); + writel(INT_RESET, S3C_UDC_OTG_GINTSTS); + + set_conf_done = 0; + + if ((usb_status & 0xc0000) == (0x3 << 18)) { + if (reset_available) { + DEBUG_ISR("\t\tOTG core got reset (%d)!!\n", reset_available); + stop_activity(dev, dev->driver); + reconfig_usbd(); + dev->ep0state = WAIT_FOR_SETUP; + reset_available = 0; + s3c_udc_pre_setup(); + } + } else if (!(usb_status & B_SESSION_VALID)) { + reset_available = 1; + if (dev->udc_enabled) { + DEBUG_ISR("Reset without B_SESSION\n"); + if (dev->driver) { + spin_unlock(&dev->lock); + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + } + } else { + reset_available = 1; + DEBUG_ISR("\t\tRESET handling skipped\n"); + } + } + + if (intr_status & INT_IN_EP) + process_ep_in_intr(dev); + + if (intr_status & INT_OUT_EP) + process_ep_out_intr(dev); + + spin_unlock_irqrestore(&dev->lock, flags); + + return IRQ_HANDLED; +} + +/** Queue one request + * Kickstart transfer if needed + */ +static int s3c_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct s3c_request *req; + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + u32 ep_num, gintsts; + + req = container_of(_req, struct s3c_request, req); + if (unlikely(!_req || !_req->complete || !_req->buf || !list_empty(&req->queue))) { + + DEBUG("%s: bad params\n", __func__); + return -EINVAL; + } + + ep = container_of(_ep, struct s3c_ep, ep); + + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + + DEBUG("%s: bad ep\n", __func__); + return -EINVAL; + } + + ep_num = ep_index(ep); + dev = ep->dev; + if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + + DEBUG("%s: bogus device state %p\n", __func__, dev->driver); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + DEBUG("\n*** %s: %s-%s req = %p, len = %d, buf = %p" + "Q empty = %d, stopped = %d\n", + __func__, _ep->name, ep_is_in(ep) ? "in" : "out", + _req, _req->length, _req->buf, + list_empty(&ep->queue), ep->stopped); + + if (list_empty(&ep->queue) && !ep->stopped) { + + if (ep_num == 0) { + /* EP0 */ + list_add_tail(&req->queue, &ep->queue); + s3c_ep0_kick(dev, ep); + req = 0; + + } else if (ep_is_in(ep)) { + gintsts = readl(S3C_UDC_OTG_GINTSTS); + DEBUG_IN_EP("%s: ep_is_in, S3C_UDC_OTG_GINTSTS=0x%x\n", + __func__, gintsts); + + if (set_conf_done == 1) { + setdma_tx(ep, req); + } else { + done(ep, req, 0); + DEBUG("%s: Not yet Set_configureation, ep_num = %d, req = %p\n", + __func__, ep_num, req); + req = 0; + } + + } else { + gintsts = readl(S3C_UDC_OTG_GINTSTS); + DEBUG_OUT_EP("%s: ep_is_out, S3C_UDC_OTG_GINTSTS=0x%x\n", + __func__, gintsts); + + setdma_rx(ep, req); + } + } + + /* pio or dma irq handler advances the queue. */ + if (likely(req != 0)) + list_add_tail(&req->queue, &ep->queue); + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/****************************************************************/ +/* End Point 0 related functions */ +/****************************************************************/ + +/* return: 0 = still running, 1 = completed, negative = errno */ +static int write_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 max; + unsigned count; + int is_last; + + max = ep_maxpacket(ep); + + DEBUG_EP0("%s: max = %d\n", __func__, max); + + count = setdma_tx(ep, req); + + /* last packet is usually short (or a zlp) */ + if (likely(count != max)) + is_last = 1; + else { + if (likely(req->req.length != req->req.actual) || req->req.zero) + is_last = 0; + else + is_last = 1; + } + + DEBUG_EP0("%s: wrote %s %d bytes%s %d left %p\n", __func__, + ep->ep.name, count, + is_last ? "/L" : "", req->req.length - req->req.actual, req); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + ep->dev->ep0state = WAIT_FOR_SETUP; + return 1; + } + + return 0; +} + +static inline int s3c_fifo_read(struct s3c_ep *ep, u32 *cp, int max) +{ + u32 bytes; + + bytes = sizeof(struct usb_ctrlrequest); + __dma_single_cpu_to_dev(&usb_ctrl, bytes, DMA_FROM_DEVICE); + DEBUG_EP0("%s: bytes=%d, ep_index=%d\n", __func__, bytes, ep_index(ep)); + + return bytes; +} + +/** + * udc_set_address - set the USB address for this device + * @address: + * + * Called from control endpoint function + * after it decodes a set address setup packet. + */ +static void udc_set_address(struct s3c_udc *dev, unsigned char address) +{ + u32 ctrl = readl(S3C_UDC_OTG_DCFG); + writel(address << 4 | ctrl, S3C_UDC_OTG_DCFG); + + s3c_udc_ep0_zlp(); + + DEBUG_EP0("%s: USB OTG 2.0 Device address=%d, DCFG=0x%x\n", + __func__, address, readl(S3C_UDC_OTG_DCFG)); + + dev->usb_address = address; +} + +static inline void s3c_udc_ep0_set_stall(struct s3c_ep *ep) +{ + struct s3c_udc *dev; + u32 ep_ctrl = 0; + + dev = ep->dev; + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + /* set the disable and stall bits */ + if (ep_ctrl & DEPCTL_EPENA) + ep_ctrl |= DEPCTL_EPDIS; + + ep_ctrl |= DEPCTL_STALL; + + writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + DEBUG_EP0("%s: set ep%d stall, DIEPCTL0 = 0x%x\n", + __func__, ep_index(ep), readl(S3C_UDC_OTG_DIEPCTL(EP0_CON))); + /* + * The application can only set this bit, and the core clears it, + * when a SETUP token is received for this endpoint + */ + dev->ep0state = WAIT_FOR_SETUP; + + s3c_udc_pre_setup(); +} + +static void s3c_ep0_read(struct s3c_udc *dev) +{ + struct s3c_request *req; + struct s3c_ep *ep = &dev->ep[0]; + int ret; + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + + } else { + DEBUG("%s: ---> BUG\n", __func__); + BUG(); + return; + } + + DEBUG_EP0("%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n", + __func__, req, req->req.length, req->req.actual); + + if (req->req.length == 0) { + /* zlp for Set_configuration, Set_interface, + * or Bulk-Only mass storge reset */ + + dev->ep0state = WAIT_FOR_SETUP; + set_conf_done = 1; + s3c_udc_ep0_zlp(); + done(ep, req, 0); + DEBUG_EP0("%s: req.length = 0, bRequest = %d\n", __func__, usb_ctrl.bRequest); + return; + } + + ret = setdma_rx(ep, req); +} + +/* + * DATA_STATE_XMIT + */ +static int s3c_ep0_write(struct s3c_udc *dev) +{ + struct s3c_request *req; + struct s3c_ep *ep = &dev->ep[0]; + int ret, need_zlp = 0; + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct s3c_request, queue); + + if (!req) { + DEBUG_EP0("%s: NULL REQ\n", __func__); + return 0; + } + + DEBUG_EP0("%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n", + __func__, req, req->req.length, req->req.actual); + + if (req->req.length - req->req.actual == ep0_fifo_size) { + /* Next write will end with the packet size, */ + /* so we need Zero-length-packet */ + need_zlp = 1; + } + + ret = write_fifo_ep0(ep, req); + + if ((ret == 1) && !need_zlp) { + /* Last packet */ + dev->ep0state = WAIT_FOR_SETUP; + DEBUG_EP0("%s: finished, waiting for status\n", __func__); + + } else { + dev->ep0state = DATA_STATE_XMIT; + DEBUG_EP0("%s: not finished\n", __func__); + } + + if (need_zlp) { + dev->ep0state = DATA_STATE_NEED_ZLP; + DEBUG_EP0("%s: Need ZLP!\n", __func__); + } + + return 1; +} + +u16 g_status __attribute__((aligned(8))); + +static int s3c_udc_get_status(struct s3c_udc *dev, + struct usb_ctrlrequest *crq) +{ + u8 ep_num = crq->wIndex & 0x7F; + u32 ep_ctrl; + + DEBUG_SETUP("%s: *** USB_REQ_GET_STATUS\n", __func__); + + switch (crq->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + g_status = 0; + DEBUG_SETUP("\tGET_STATUS: USB_RECIP_INTERFACE, g_stauts = %d\n", g_status); + break; + + case USB_RECIP_DEVICE: + /* update device status */ + g_status = dev->status; + DEBUG_SETUP("\tGET_STATUS: USB_RECIP_DEVICE, g_stauts = %d\n", g_status); + break; + + case USB_RECIP_ENDPOINT: + if (crq->wLength > 2) { + DEBUG_SETUP("\tGET_STATUS: Not support EP or wLength\n"); + return 1; + } + + g_status = dev->ep[ep_num].stopped; + DEBUG_SETUP("\tGET_STATUS: USB_RECIP_ENDPOINT, g_stauts = %d\n", g_status); + + break; + + default: + return 1; + } + __dma_single_cpu_to_dev(&g_status, 2, DMA_TO_DEVICE); + + writel(virt_to_phys(&g_status), S3C_UDC_OTG_DIEPDMA(EP0_CON)); + writel((1<<19)|(2<<0), S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, S3C_UDC_OTG_DIEPCTL(EP0_CON)); + dev->ep0state = WAIT_FOR_SETUP; + + return 0; +} + +void s3c_udc_ep_set_stall(struct s3c_ep *ep) +{ + u8 ep_num; + u32 ep_ctrl = 0; + + ep_num = ep_index(ep); + DEBUG("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type); + + if (ep_is_in(ep)) { + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num)); + + /* set the disable and stall bits */ + if (ep_ctrl & DEPCTL_EPENA) + ep_ctrl |= DEPCTL_EPDIS; + + ep_ctrl |= DEPCTL_STALL; + + writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(ep_num)); + DEBUG("%s: set stall, DIEPCTL%d = 0x%x\n", + __func__, ep_num, readl(S3C_UDC_OTG_DIEPCTL(ep_num))); + + } else { + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(ep_num)); + + /* set the stall bit */ + ep_ctrl |= DEPCTL_STALL; + + writel(ep_ctrl, S3C_UDC_OTG_DOEPCTL(ep_num)); + DEBUG("%s: set stall, DOEPCTL%d = 0x%x\n", + __func__, ep_num, readl(S3C_UDC_OTG_DOEPCTL(ep_num))); + } + + return; +} + +void s3c_udc_ep_clear_stall(struct s3c_ep *ep) +{ + u8 ep_num; + u32 ep_ctrl = 0; + + ep_num = ep_index(ep); + DEBUG("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type); + + if (ep_is_in(ep)) { + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num)); + + /* clear stall bit */ + ep_ctrl &= ~DEPCTL_STALL; + + /* + * USB Spec 9.4.5: For endpoints using data toggle, regardless + * of whether an endpoint has the Halt feature set, a + * ClearFeature(ENDPOINT_HALT) request always results in the + * data toggle being reinitialized to DATA0. + */ + if (ep->bmAttributes == USB_ENDPOINT_XFER_INT + || ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { + ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */ + } + + writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(ep_num)); + DEBUG("%s: cleared stall, DIEPCTL%d = 0x%x\n", + __func__, ep_num, readl(S3C_UDC_OTG_DIEPCTL(ep_num))); + + } else { + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(ep_num)); + + /* clear stall bit */ + ep_ctrl &= ~DEPCTL_STALL; + + if (ep->bmAttributes == USB_ENDPOINT_XFER_INT + || ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { + ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */ + } + + writel(ep_ctrl, S3C_UDC_OTG_DOEPCTL(ep_num)); + DEBUG("%s: cleared stall, DOEPCTL%d = 0x%x\n", + __func__, ep_num, readl(S3C_UDC_OTG_DOEPCTL(ep_num))); + } + + return; +} + +static int s3c_udc_set_halt(struct usb_ep *_ep, int value) +{ + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + u8 ep_num; + + ep = container_of(_ep, struct s3c_ep, ep); + ep_num = ep_index(ep); + + if (unlikely(!_ep || !ep->desc || ep_num == EP0_CON || + ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC)) { + DEBUG("%s: %s bad ep or descriptor\n", __func__, ep->ep.name); + return -EINVAL; + } + + /* Attempt to halt IN ep will fail if any transfer requests + * are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + DEBUG("%s: %s queue not empty, req = %p\n", + __func__, ep->ep.name, + list_entry(ep->queue.next, struct s3c_request, queue)); + + return -EAGAIN; + } + + dev = ep->dev; + DEBUG("%s: ep_num = %d, value = %d\n", __func__, ep_num, value); + + spin_lock_irqsave(&dev->lock, flags); + + if (value == 0) { + ep->stopped = 0; + s3c_udc_ep_clear_stall(ep); + } else { + ep->stopped = 1; + s3c_udc_ep_set_stall(ep); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +void s3c_udc_ep_activate(struct s3c_ep *ep) +{ + u8 ep_num; + u32 ep_ctrl = 0, daintmsk = 0; + + ep_num = ep_index(ep); + + /* Read DEPCTLn register */ + if (ep_is_in(ep)) { + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num)); + daintmsk = 1 << ep_num; + } else { + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(ep_num)); + daintmsk = (1 << ep_num) << DAINT_OUT_BIT; + } + + DEBUG("%s: EPCTRL%d = 0x%x, ep_is_in = %d\n", + __func__, ep_num, ep_ctrl, ep_is_in(ep)); + + /* If the EP is already active don't change the EP Control + * register. */ + if (!(ep_ctrl & DEPCTL_USBACTEP)) { + ep_ctrl = (ep_ctrl & ~DEPCTL_TYPE_MASK) | (ep->bmAttributes << DEPCTL_TYPE_BIT); + ep_ctrl = (ep_ctrl & ~DEPCTL_MPS_MASK) | (ep->ep.maxpacket << DEPCTL_MPS_BIT); + ep_ctrl |= (DEPCTL_SETD0PID | DEPCTL_USBACTEP); + + if (ep_is_in(ep)) { + writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(ep_num)); + DEBUG("%s: USB Ative EP%d, DIEPCTRL%d = 0x%x\n", + __func__, ep_num, ep_num, readl(S3C_UDC_OTG_DIEPCTL(ep_num))); + } else { + writel(ep_ctrl, S3C_UDC_OTG_DOEPCTL(ep_num)); + DEBUG("%s: USB Ative EP%d, DOEPCTRL%d = 0x%x\n", + __func__, ep_num, ep_num, readl(S3C_UDC_OTG_DOEPCTL(ep_num))); + } + } + + /* Unmask EP Interrtupt */ + writel(readl(S3C_UDC_OTG_DAINTMSK)|daintmsk, S3C_UDC_OTG_DAINTMSK); + DEBUG("%s: DAINTMSK = 0x%x\n", __func__, readl(S3C_UDC_OTG_DAINTMSK)); + +} + +static int s3c_udc_clear_feature(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + u8 ep_num; + struct s3c_udc *dev = the_controller; + ep = container_of(_ep, struct s3c_ep, ep); + ep_num = ep_index(ep); + + DEBUG_SETUP("%s: ep_num = %d, is_in = %d, clear_feature_flag = %d\n", + __func__, ep_num, ep_is_in(ep), clear_feature_flag); + + if (usb_ctrl.wLength != 0) { + DEBUG_SETUP("\tCLEAR_FEATURE: wLength is not zero.....\n"); + return 1; + } + + switch (usb_ctrl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (usb_ctrl.wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + DEBUG_SETUP("\tCLEAR_FEATURE: USB_DEVICE_REMOTE_WAKEUP\n"); + printk(KERN_INFO "%s:: USB_DEVICE_REMOTE_WAKEUP\n", __func__); + dev->status &= ~(1 << USB_DEVICE_REMOTE_WAKEUP); + break; + + case USB_DEVICE_TEST_MODE: + DEBUG_SETUP("\tCLEAR_FEATURE: USB_DEVICE_TEST_MODE\n"); + /** @todo Add CLEAR_FEATURE for TEST modes. */ + break; + } + + s3c_udc_ep0_zlp(); + break; + + case USB_RECIP_ENDPOINT: + DEBUG_SETUP("\tCLEAR_FEATURE: USB_RECIP_ENDPOINT, wValue = %d\n", + usb_ctrl.wValue); + + if (usb_ctrl.wValue == USB_ENDPOINT_HALT) { + if (ep_num == 0) { + s3c_udc_ep0_set_stall(ep); + return 0; + } + + s3c_udc_ep0_zlp(); + + s3c_udc_ep_clear_stall(ep); + s3c_udc_ep_activate(ep); + ep->stopped = 0; + + clear_feature_num = ep_num; + clear_feature_flag = 1; + } + break; + } + + return 0; +} + +/* Set into the test mode for Test Mode set_feature request */ +static inline void set_test_mode(void) +{ + u32 ep_ctrl, dctl; + u8 test_selector = (usb_ctrl.wIndex>>8) & TEST_SELECTOR_MASK; + + if (test_selector > 0 && test_selector < 6) { + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + writel(1<<19 | 0<<0, S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK|EP0_CON<<DEPCTL_NEXT_EP_BIT , S3C_UDC_OTG_DIEPCTL(EP0_CON)); + } + + switch (test_selector) { + case TEST_J_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is TEST J\n"); + + dctl = readl(S3C_UDC_OTG_DCTL); + writel((dctl&~(TEST_CONTROL_MASK))|TEST_J_MODE, S3C_UDC_OTG_DCTL); + break; + case TEST_K_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is TEST K\n"); + + dctl = readl(S3C_UDC_OTG_DCTL); + writel((dctl&~(TEST_CONTROL_MASK))|TEST_K_MODE, S3C_UDC_OTG_DCTL); + break; + case TEST_SE0_NAK_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is TEST SE0 NAK\n"); + + dctl = readl(S3C_UDC_OTG_DCTL); + writel((dctl&~(TEST_CONTROL_MASK))|TEST_SE0_NAK_MODE, S3C_UDC_OTG_DCTL); + break; + case TEST_PACKET_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is TEST PACKET\n"); + __dma_single_cpu_to_dev(test_pkt, TEST_PKT_SIZE, DMA_TO_DEVICE); + writel(virt_to_phys(test_pkt), S3C_UDC_OTG_DIEPDMA(EP0_CON)); + + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + writel(1<<19 | TEST_PKT_SIZE<<0, S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK|EP0_CON<<DEPCTL_NEXT_EP_BIT, S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + dctl = readl(S3C_UDC_OTG_DCTL); + writel((dctl&~(TEST_CONTROL_MASK))|TEST_PACKET_MODE, S3C_UDC_OTG_DCTL); + break; + case TEST_FORCE_ENABLE_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is TEST FORCE ENABLE\n"); + + dctl = readl(S3C_UDC_OTG_DCTL); + writel((dctl&~(TEST_CONTROL_MASK))|TEST_FORCE_ENABLE_MODE, S3C_UDC_OTG_DCTL); + break; + } +} + +static int s3c_udc_set_feature(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + u8 ep_num; + struct s3c_udc *dev = the_controller; + ep = container_of(_ep, struct s3c_ep, ep); + ep_num = ep_index(ep); + + DEBUG_SETUP("%s: *** USB_REQ_SET_FEATURE , ep_num = %d\n", __func__, ep_num); + + if (usb_ctrl.wLength != 0) { + DEBUG_SETUP("\tSET_FEATURE: wLength is not zero.....\n"); + return 1; + } + + switch (usb_ctrl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (usb_ctrl.wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_REMOTE_WAKEUP\n"); + printk(KERN_INFO "%s:: USB_DEVICE_REMOTE_WAKEUP\n", __func__); + dev->status |= (1 << USB_DEVICE_REMOTE_WAKEUP); + break; + + case USB_DEVICE_TEST_MODE: + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_TEST_MODE\n"); + set_test_mode(); + break; + + case USB_DEVICE_B_HNP_ENABLE: + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n"); + break; + + case USB_DEVICE_A_HNP_SUPPORT: + /* RH port supports HNP */ + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_A_HNP_SUPPORT\n"); + break; + + case USB_DEVICE_A_ALT_HNP_SUPPORT: + /* other RH port does */ + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_A_ALT_HNP_SUPPORT\n"); + break; + } + + s3c_udc_ep0_zlp(); + return 0; + + case USB_RECIP_INTERFACE: + DEBUG_SETUP("\tSET_FEATURE: USB_RECIP_INTERFACE\n"); + break; + + case USB_RECIP_ENDPOINT: + DEBUG_SETUP("\tSET_FEATURE: USB_RECIP_ENDPOINT\n"); + if (usb_ctrl.wValue == USB_ENDPOINT_HALT) { + if (ep_num == 0) { + s3c_udc_ep0_set_stall(ep); + return 0; + } + ep->stopped = 1; + s3c_udc_ep_set_stall(ep); + } + + s3c_udc_ep0_zlp(); + return 0; + } + + return 1; +} + +/* + * WAIT_FOR_SETUP (OUT_PKT_RDY) + */ +static void s3c_ep0_setup(struct s3c_udc *dev) +{ + struct s3c_ep *ep = &dev->ep[0]; + int i, bytes, is_in; + u8 ep_num; + + /* Nuke all previous transfers */ + nuke(ep, -EPROTO); + + /* read control req from fifo (8 bytes) */ + bytes = s3c_fifo_read(ep, (u32 *)&usb_ctrl, 8); + + DEBUG_SETUP("%s: bRequestType = 0x%x(%s), bRequest = 0x%x" + "\twLength = 0x%x, wValue = 0x%x, wIndex= 0x%x\n", + __func__, usb_ctrl.bRequestType, + (usb_ctrl.bRequestType & USB_DIR_IN) ? "IN" : "OUT", usb_ctrl.bRequest, + usb_ctrl.wLength, usb_ctrl.wValue, usb_ctrl.wIndex); + + if (usb_ctrl.bRequest == GET_MAX_LUN_REQUEST && usb_ctrl.wLength != 1) { + DEBUG_SETUP("\t%s:GET_MAX_LUN_REQUEST:invalid wLength = %d, setup returned\n", + __func__, usb_ctrl.wLength); + + s3c_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + return; + } else if (usb_ctrl.bRequest == BOT_RESET_REQUEST && usb_ctrl.wLength != 0) { + /* Bulk-Only *mass storge reset of class-specific request */ + DEBUG_SETUP("\t%s:BOT Rest:invalid wLength = %d, setup returned\n", + __func__, usb_ctrl.wLength); + + s3c_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + return; + } + + /* Set direction of EP0 */ + if (likely(usb_ctrl.bRequestType & USB_DIR_IN)) { + ep->bEndpointAddress |= USB_DIR_IN; + is_in = 1; + + } else { + ep->bEndpointAddress &= ~USB_DIR_IN; + is_in = 0; + } + /* cope with automagic for some standard requests. */ + dev->req_std = (usb_ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD; + dev->req_config = 0; + dev->req_pending = 1; + + /* Handle some SETUP packets ourselves */ + switch (usb_ctrl.bRequest) { + case USB_REQ_SET_ADDRESS: + DEBUG_SETUP("%s: *** USB_REQ_SET_ADDRESS (%d)\n", + __func__, usb_ctrl.wValue); + + if (usb_ctrl.bRequestType + != (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + + udc_set_address(dev, usb_ctrl.wValue); + return; + + case USB_REQ_SET_CONFIGURATION: + DEBUG_SETUP("============================================\n"); + DEBUG_SETUP("%s: USB_REQ_SET_CONFIGURATION (%d)\n", + __func__, usb_ctrl.wValue); + + if (usb_ctrl.bRequestType == USB_RECIP_DEVICE) { + reset_available = 1; + dev->req_config = 1; + } + +#if defined(CONFIG_MACH_SMDKC110) || defined(CONFIG_MACH_SMDKV210) + s3c_udc_cable_connect(dev); +#endif + break; + + case USB_REQ_GET_DESCRIPTOR: + DEBUG_SETUP("%s: *** USB_REQ_GET_DESCRIPTOR\n", __func__); + break; + + case USB_REQ_SET_INTERFACE: + DEBUG_SETUP("%s: *** USB_REQ_SET_INTERFACE (%d)\n", + __func__, usb_ctrl.wValue); + + if (usb_ctrl.bRequestType == USB_RECIP_INTERFACE) { + reset_available = 1; + dev->req_config = 1; + } + break; + + case USB_REQ_GET_CONFIGURATION: + DEBUG_SETUP("%s: *** USB_REQ_GET_CONFIGURATION\n", __func__); + break; + + case USB_REQ_GET_STATUS: + if (dev->req_std) { + if (!s3c_udc_get_status(dev, &usb_ctrl)) + return; + } + break; + + case USB_REQ_CLEAR_FEATURE: + ep_num = usb_ctrl.wIndex & 0x7f; + + if (!s3c_udc_clear_feature(&dev->ep[ep_num].ep)) + return; + break; + + case USB_REQ_SET_FEATURE: + ep_num = usb_ctrl.wIndex & 0x7f; + + if (!s3c_udc_set_feature(&dev->ep[ep_num].ep)) + return; + break; + + default: + DEBUG_SETUP("%s: *** Default of usb_ctrl.bRequest=0x%x happened.\n", + __func__, usb_ctrl.bRequest); + break; + } + + if (likely(dev->driver)) { + /* device-2-host (IN) or no data setup command, + * process immediately */ + DEBUG_SETUP("%s: usb_ctrlrequest will be passed to fsg_setup()\n", __func__); + + spin_unlock(&dev->lock); + i = dev->driver->setup(&dev->gadget, &usb_ctrl); + spin_lock(&dev->lock); + + if (i < 0) { + if (dev->req_config) { + DEBUG_SETUP("\tconfig change 0x%02x fail %d?\n", + (u32)&usb_ctrl.bRequest, i); + return; + } + + /* setup processing failed, force stall */ + s3c_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + DEBUG_SETUP("\tdev->driver->setup failed (%d), bRequest = %d\n", + i, usb_ctrl.bRequest); + + + } else if (dev->req_pending) { + dev->req_pending = 0; + DEBUG_SETUP("\tdev->req_pending...\n"); + } + + DEBUG_SETUP("\tep0state = %s\n", state_names[dev->ep0state]); + + } +} + +/* + * handle ep0 interrupt + */ +static void s3c_handle_ep0(struct s3c_udc *dev) +{ + if (dev->ep0state == WAIT_FOR_SETUP) { + DEBUG_OUT_EP("%s: WAIT_FOR_SETUP\n", __func__); + s3c_ep0_setup(dev); + + } else { + DEBUG_OUT_EP("%s: strange state!!(state = %s)\n", + __func__, state_names[dev->ep0state]); + } +} + +static void s3c_ep0_kick(struct s3c_udc *dev, struct s3c_ep *ep) +{ + DEBUG_EP0("%s: ep_is_in = %d\n", __func__, ep_is_in(ep)); + if (ep_is_in(ep)) { + dev->ep0state = DATA_STATE_XMIT; + s3c_ep0_write(dev); + + } else { + dev->ep0state = DATA_STATE_RECV; + s3c_ep0_read(dev); + } +} + diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c index 1fa4f70..4dfede4 100644 --- a/drivers/usb/gadget/storage_common.c +++ b/drivers/usb/gadget/storage_common.c @@ -583,10 +583,10 @@ static int fsg_lun_open(struct fsg_lun *curlun, const char *filename) num_sectors = size >> 9; /* File size in 512-byte blocks */ min_sectors = 1; if (curlun->cdrom) { - num_sectors &= ~3; /* Reduce to a multiple of 2048 */ - min_sectors = 300*4; /* Smallest track is 300 frames */ - if (num_sectors >= 256*60*75*4) { - num_sectors = (256*60*75 - 1) * 4; + num_sectors >>= 2; /* Reduce to a multiple of 2048 */ + min_sectors = 300; /* Smallest track is 300 frames */ + if (num_sectors >= 256*60*75) { + num_sectors = (256*60*75 - 1); LINFO(curlun, "file too big: %s\n", filename); LINFO(curlun, "using only first %d blocks\n", (int) num_sectors); @@ -641,7 +641,6 @@ static void store_cdrom_address(u8 *dest, int msf, u32 addr) { if (msf) { /* Convert to Minutes-Seconds-Frames */ - addr >>= 2; /* Convert to 2048-byte frames */ addr += 2*75; /* Lead-in occupies 2 seconds */ dest[3] = addr % 75; /* Frames */ addr /= 75; @@ -763,10 +762,16 @@ static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr, struct rw_semaphore *filesem = dev_get_drvdata(dev); int rc = 0; + +#ifndef CONFIG_USB_ANDROID_MASS_STORAGE + /* disabled in android because we need to allow closing the backing file + * if the media was removed + */ if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) { LDBG(curlun, "eject attempt prevented\n"); return -EBUSY; /* "Door is locked" */ } +#endif /* Remove a trailing newline */ if (count > 0 && buf[count-1] == '\n') @@ -789,3 +794,40 @@ static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr, up_write(filesem); return (rc < 0 ? rc : count); } + +static ssize_t fsg_show_cdrom (struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + + return sprintf(buf, "%d\n", curlun->cdrom); +} + +static ssize_t fsg_store_cdrom(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + ssize_t rc; + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + struct rw_semaphore *filesem = dev_get_drvdata(dev); + unsigned cdrom; + + rc = kstrtouint(buf, 2, &cdrom); + if (rc) + return rc; + + /* + * Allow the cdrom status to change only while the + * backing file is closed. + */ + down_read(filesem); + if (fsg_lun_is_open(curlun)) { + LDBG(curlun, "cdrom status change prevented\n"); + rc = -EBUSY; + } else { + curlun->cdrom = cdrom; + LDBG(curlun, "cdrom status set to %d\n", curlun->cdrom); + rc = count; + } + up_read(filesem); + return rc; +}
\ No newline at end of file diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c index a52404a..294536c 100644 --- a/drivers/usb/gadget/u_ether.c +++ b/drivers/usb/gadget/u_ether.c @@ -249,11 +249,13 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) goto enomem; } +#ifndef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED /* Some platforms perform better when IP packets are aligned, * but on at least one, checksumming fails otherwise. Note: * RNDIS headers involve variable numbers of LE32 values. */ skb_reserve(skb, NET_IP_ALIGN); +#endif req->buf = skb->data; req->length = size; @@ -483,7 +485,10 @@ static void tx_complete(struct usb_ep *ep, struct usb_request *req) list_add(&req->list, &dev->tx_reqs); spin_unlock(&dev->req_lock); dev_kfree_skb_any(skb); - +#ifdef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED + if (req->buf != skb->data) + kfree(req->buf); +#endif atomic_dec(&dev->tx_qlen); if (netif_carrier_ok(dev->net)) netif_wake_queue(dev->net); @@ -577,7 +582,21 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, length = skb->len; } + +#ifdef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED + if ((int)skb->data & 3) { + req->buf = kmalloc(skb->len, GFP_ATOMIC); + if (!req->buf) + goto drop; + memcpy((void *)req->buf, (void *)skb->data, skb->len); + } + else { + req->buf = skb->data; + } +#else req->buf = skb->data; +#endif + req->context = skb; req->complete = tx_complete; @@ -618,6 +637,10 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, dev_kfree_skb_any(skb); drop: dev->net->stats.tx_dropped++; +#ifdef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED + if (req->buf != skb->data) + kfree(req->buf); +#endif spin_lock_irqsave(&dev->req_lock, flags); if (list_empty(&dev->tx_reqs)) netif_start_queue(net); @@ -765,6 +788,26 @@ static struct device_type gadget_type = { */ int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]) { + return gether_setup_name(g, ethaddr, "usb"); +} + +/** + * gether_setup_name - initialize one ethernet-over-usb link + * @g: gadget to associated with these links + * @ethaddr: NULL, or a buffer in which the ethernet address of the + * host side of the link is recorded + * @netname: name for network device (for example, "usb") + * Context: may sleep + * + * This sets up the single network link that may be exported by a + * gadget driver using this framework. The link layer addresses are + * set up using module parameters. + * + * Returns negative errno, or zero on success + */ +int gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], + const char *netname) +{ struct eth_dev *dev; struct net_device *net; int status; @@ -787,17 +830,19 @@ int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]) /* network device setup */ dev->net = net; - strcpy(net->name, "usb%d"); + snprintf(net->name, sizeof(net->name), "%s%%d", netname); if (get_ether_addr(dev_addr, net->dev_addr)) dev_warn(&g->dev, "using random %s ethernet address\n", "self"); - if (get_ether_addr(host_addr, dev->host_mac)) - dev_warn(&g->dev, - "using random %s ethernet address\n", "host"); - if (ethaddr) - memcpy(ethaddr, dev->host_mac, ETH_ALEN); + if (ethaddr && is_valid_ether_addr(ethaddr)) { + memcpy(dev->host_mac, ethaddr, ETH_ALEN); + } else { + if (get_ether_addr(host_addr, dev->host_mac)) + dev_warn(&g->dev, + "using random %s ethernet address\n", "host"); + } net->netdev_ops = ð_netdev_ops; @@ -943,7 +988,6 @@ void gether_disconnect(struct gether *link) struct eth_dev *dev = link->ioport; struct usb_request *req; - WARN_ON(!dev); if (!dev) return; diff --git a/drivers/usb/gadget/u_ether.h b/drivers/usb/gadget/u_ether.h index b56e1e7..64b65f9 100644 --- a/drivers/usb/gadget/u_ether.h +++ b/drivers/usb/gadget/u_ether.h @@ -86,6 +86,9 @@ struct gether { /* netdev setup/teardown as directed by the gadget driver */ int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]); void gether_cleanup(void); +/* variant of gether_setup that allows customizing network device name */ +int gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], + const char *netname); /* connect/disconnect is handled by individual functions */ struct net_device *gether_connect(struct gether *); @@ -112,12 +115,14 @@ int eem_bind_config(struct usb_configuration *c); #ifdef USB_ETH_RNDIS -int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]); +int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer); #else static inline int -rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer) { return 0; } diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c index 40f7716..3fdcc9a 100644 --- a/drivers/usb/gadget/u_serial.c +++ b/drivers/usb/gadget/u_serial.c @@ -122,7 +122,7 @@ struct gs_port { }; /* increase N_PORTS if you need more */ -#define N_PORTS 4 +#define N_PORTS 8 static struct portmaster { struct mutex lock; /* protect open/close */ struct gs_port *port; @@ -1028,7 +1028,7 @@ static const struct tty_operations gs_tty_ops = { static struct tty_driver *gs_tty_driver; -static int __init +static int gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) { struct gs_port *port; @@ -1074,7 +1074,7 @@ gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) * * Returns negative errno or zero. */ -int __init gserial_setup(struct usb_gadget *g, unsigned count) +int gserial_setup(struct usb_gadget *g, unsigned count) { unsigned i; struct usb_cdc_line_coding coding; |