diff options
Diffstat (limited to 'drivers/usb/gadget/android.c')
-rw-r--r-- | drivers/usb/gadget/android.c | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c new file mode 100644 index 000000000000..ed4f5739cb9c --- /dev/null +++ b/drivers/usb/gadget/android.c @@ -0,0 +1,417 @@ +/* + * 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/android_composite.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" + +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 platform data */ +#define VENDOR_ID 0x18D1 +#define PRODUCT_ID 0x0001 + +struct android_dev { + struct usb_composite_dev *cdev; + struct usb_configuration *config; + int num_products; + struct android_usb_product *products; + int num_functions; + char **functions; + + int product_id; + int version; +}; + +static struct android_dev *_android_dev; + +/* string IDs are assigned dynamically */ + +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_SERIAL_IDX 2 + +/* String Table */ +static struct usb_string strings_dev[] = { + /* These dummy values should be overridden by platform data */ + [STRING_MANUFACTURER_IDX].s = "Android", + [STRING_PRODUCT_IDX].s = "Android", + [STRING_SERIAL_IDX].s = "0123456789ABCDEF", + { } /* 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 list_head _functions = LIST_HEAD_INIT(_functions); +static int _registered_function_count = 0; + +static struct android_usb_function *get_function(const char *name) +{ + struct android_usb_function *f; + list_for_each_entry(f, &_functions, list) { + if (!strcmp(name, f->name)) + return f; + } + return 0; +} + +static void bind_functions(struct android_dev *dev) +{ + struct android_usb_function *f; + char **functions = dev->functions; + int i; + + for (i = 0; i < dev->num_functions; i++) { + char *name = *functions++; + f = get_function(name); + if (f) + f->bind_config(dev->config); + else + printk(KERN_ERR "function %s not found in bind_functions\n", name); + } +} + +static int android_bind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + + printk(KERN_DEBUG "android_bind_config\n"); + dev->config = c; + + /* bind our functions if they have all registered */ + if (_registered_function_count == dev->num_functions) + bind_functions(dev); + + return 0; +} + +static int android_setup_config(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl); + +static struct usb_configuration android_config_driver = { + .label = "android", + .bind = android_bind_config, + .setup = android_setup_config, + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 0xFA, /* 500ma */ +}; + +static int android_setup_config(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl) +{ + int i; + int ret = -EOPNOTSUPP; + + for (i = 0; i < android_config_driver.next_interface_id; i++) { + if (android_config_driver.interface[i]->setup) { + ret = android_config_driver.interface[i]->setup( + android_config_driver.interface[i], ctrl); + if (ret >= 0) + return ret; + } + } + return ret; +} + +static int product_has_function(struct android_usb_product *p, + struct usb_function *f) +{ + char **functions = p->functions; + int count = p->num_functions; + const char *name = f->name; + int i; + + for (i = 0; i < count; i++) { + if (!strcmp(name, *functions++)) + return 1; + } + return 0; +} + +static int product_matches_functions(struct android_usb_product *p) +{ + struct usb_function *f; + list_for_each_entry(f, &android_config_driver.functions, list) { + if (product_has_function(p, f) == !!f->disabled) + return 0; + } + return 1; +} + +static int get_product_id(struct android_dev *dev) +{ + struct android_usb_product *p = dev->products; + int count = dev->num_products; + int i; + + if (p) { + for (i = 0; i < count; i++, p++) { + if (product_matches_functions(p)) + return p->product_id; + } + } + /* use default product ID */ + return dev->product_id; +} + +static int android_bind(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + struct usb_gadget *gadget = cdev->gadget; + int gcnum, id, product_id, ret; + + printk(KERN_INFO "android_bind\n"); + + /* 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; + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_SERIAL_IDX].id = id; + device_desc.iSerialNumber = id; + + /* register our configuration */ + ret = usb_add_config(cdev, &android_config_driver); + if (ret) { + printk(KERN_ERR "usb_add_config failed\n"); + return ret; + } + + 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); + } + + usb_gadget_set_selfpowered(gadget); + dev->cdev = cdev; + product_id = get_product_id(dev); + device_desc.idProduct = __constant_cpu_to_le16(product_id); + cdev->desc.idProduct = device_desc.idProduct; + + return 0; +} + +static struct usb_composite_driver android_usb_driver = { + .name = "android_usb", + .dev = &device_desc, + .strings = dev_strings, + .bind = android_bind, + .enable_function = android_enable_function, +}; + +void android_register_function(struct android_usb_function *f) +{ + struct android_dev *dev = _android_dev; + + printk(KERN_INFO "android_register_function %s\n", f->name); + list_add_tail(&f->list, &_functions); + _registered_function_count++; + + /* bind our functions if they have all registered + * and the main driver has bound. + */ + if (dev && dev->config && _registered_function_count == dev->num_functions) + bind_functions(dev); +} + +void android_enable_function(struct usb_function *f, int enable) +{ + struct android_dev *dev = _android_dev; + int disable = !enable; + int product_id; + + if (!!f->disabled != disable) { + usb_function_set_enabled(f, !disable); + +#ifdef CONFIG_USB_ANDROID_RNDIS + if (!strcmp(f->name, "rndis")) { + struct usb_function *func; + + /* We need to specify the COMM class in the device descriptor + * if we are using RNDIS. + */ + if (enable) +#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS + dev->cdev->desc.bDeviceClass = USB_CLASS_WIRELESS_CONTROLLER; +#else + dev->cdev->desc.bDeviceClass = USB_CLASS_COMM; +#endif + else + dev->cdev->desc.bDeviceClass = USB_CLASS_PER_INTERFACE; + + /* Windows does not support other interfaces when RNDIS is enabled, + * so we disable UMS and MTP when RNDIS is on. + */ + list_for_each_entry(func, &android_config_driver.functions, list) { + if (!strcmp(func->name, "usb_mass_storage") + || !strcmp(func->name, "mtp")) { + usb_function_set_enabled(func, !enable); + } + } + } +#endif + + product_id = get_product_id(dev); + device_desc.idProduct = __constant_cpu_to_le16(product_id); + if (dev->cdev) + dev->cdev->desc.idProduct = device_desc.idProduct; + usb_composite_force_reset(dev->cdev); + } +} + +static int android_probe(struct platform_device *pdev) +{ + struct android_usb_platform_data *pdata = pdev->dev.platform_data; + struct android_dev *dev = _android_dev; + + printk(KERN_INFO "android_probe pdata: %p\n", pdata); + + if (pdata) { + dev->products = pdata->products; + dev->num_products = pdata->num_products; + dev->functions = pdata->functions; + dev->num_functions = pdata->num_functions; + if (pdata->vendor_id) + device_desc.idVendor = + __constant_cpu_to_le16(pdata->vendor_id); + if (pdata->product_id) { + dev->product_id = pdata->product_id; + device_desc.idProduct = + __constant_cpu_to_le16(pdata->product_id); + } + if (pdata->version) + dev->version = pdata->version; + + if (pdata->product_name) + strings_dev[STRING_PRODUCT_IDX].s = pdata->product_name; + if (pdata->manufacturer_name) + strings_dev[STRING_MANUFACTURER_IDX].s = + pdata->manufacturer_name; + if (pdata->serial_number) + strings_dev[STRING_SERIAL_IDX].s = pdata->serial_number; + } + + return usb_composite_register(&android_usb_driver); +} + +static struct platform_driver android_platform_driver = { + .driver = { .name = "android_usb", }, + .probe = android_probe, +}; + +static int __init init(void) +{ + struct android_dev *dev; + + printk(KERN_INFO "android init\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* set default values, which should be overridden by platform data */ + dev->product_id = PRODUCT_ID; + _android_dev = dev; + + return platform_driver_register(&android_platform_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&android_usb_driver); + platform_driver_unregister(&android_platform_driver); + kfree(_android_dev); + _android_dev = NULL; +} +module_exit(cleanup); |