/* * multi.c -- Multifunction Composite driver * * Copyright (C) 2008 David Brownell * Copyright (C) 2008 Nokia Corporation * Copyright (C) 2009 Samsung Electronics * Author: Michal Nazarewicz (mina86@mina86.com) * * 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. */ #include #include #include #include "u_serial.h" #define DRIVER_DESC "Multifunction Composite Gadget" /***************************** Device Descriptor ****************************/ #define MULTI_VENDOR_NUM 0x1d6b /* Linux Foundation */ #define MULTI_PRODUCT_NUM 0x0104 /* Multifunction Composite Gadget */ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, .bcdUSB = cpu_to_le16(0x0200), .bDeviceClass = USB_CLASS_MISC /* 0xEF */, .bDeviceSubClass = 2, .bDeviceProtocol = 1, }; #define STRING_DESCRIPTION_IDX USB_GADGET_FIRST_AVAIL_IDX static struct usb_string strings_dev[] = { [USB_GADGET_MANUFACTURER_IDX].s = "", [USB_GADGET_PRODUCT_IDX].s = "", [USB_GADGET_SERIAL_IDX].s = "", [STRING_DESCRIPTION_IDX].s = "Multifunction Composite Gadget", { } /* end of list */ }; static struct usb_gadget_strings *dev_strings[] = { &(struct usb_gadget_strings){ .language = 0x0409, /* en-us */ .strings = strings_dev, }, NULL, }; static struct usb_function_instance *fi_acm; static struct usb_function *f_acm; static struct usb_function_instance *fi_dfu; static struct usb_function *f_dfu; static struct usb_function_instance *fi_fastboot; static struct usb_function *f_fastboot; static struct usb_function_instance *fi_ums; static struct usb_function *f_ums; static struct usb_configuration config = { .bConfigurationValue = 1, .bmAttributes = USB_CONFIG_ATT_SELFPOWER, }; struct f_multi_opts *gadget_multi_opts; static int multi_bind_acm(struct usb_composite_dev *cdev) { int ret; fi_acm = usb_get_function_instance("acm"); if (IS_ERR(fi_acm)) { ret = PTR_ERR(fi_acm); fi_acm = NULL; return ret; } f_acm = usb_get_function(fi_acm); if (IS_ERR(f_acm)) { ret = PTR_ERR(f_acm); f_acm = NULL; return ret; } return usb_add_function(&config, f_acm); } static int multi_bind_dfu(struct usb_composite_dev *cdev) { int ret; struct f_dfu_opts *opts; fi_dfu = usb_get_function_instance("dfu"); if (IS_ERR(fi_dfu)) { ret = PTR_ERR(fi_dfu); fi_dfu = NULL; return ret; } opts = container_of(fi_dfu, struct f_dfu_opts, func_inst); opts->files = gadget_multi_opts->dfu_opts.files; f_dfu = usb_get_function(fi_dfu); if (IS_ERR(f_dfu)) { ret = PTR_ERR(f_dfu); f_dfu = NULL; return ret; } return usb_add_function(&config, f_dfu); } static int multi_bind_fastboot(struct usb_composite_dev *cdev) { int ret; struct f_fastboot_opts *opts; fi_fastboot = usb_get_function_instance("fastboot"); if (IS_ERR(fi_fastboot)) { ret = PTR_ERR(fi_fastboot); fi_fastboot = NULL; return ret; } opts = container_of(fi_fastboot, struct f_fastboot_opts, func_inst); opts->common = gadget_multi_opts->fastboot_opts; f_fastboot = usb_get_function(fi_fastboot); if (IS_ERR(f_fastboot)) { ret = PTR_ERR(f_fastboot); f_fastboot = NULL; return ret; } return usb_add_function(&config, f_fastboot); } static int multi_bind_ums(struct usb_composite_dev *cdev) { int ret; struct f_ums_opts *opts; fi_ums = usb_get_function_instance("ums"); if (IS_ERR(fi_ums)) { ret = PTR_ERR(fi_ums); fi_ums = NULL; return ret; } opts = container_of(fi_ums, struct f_ums_opts, func_inst); opts->files = gadget_multi_opts->ums_opts.files; f_ums = usb_get_function(fi_ums); if (IS_ERR(f_ums)) { ret = PTR_ERR(f_ums); f_ums = NULL; return ret; } return usb_add_function(&config, f_ums); } static int multi_unbind(struct usb_composite_dev *cdev) { if (gadget_multi_opts->create_acm) { usb_put_function(f_acm); usb_put_function_instance(fi_acm); } if (gadget_multi_opts->dfu_opts.files) { usb_put_function(f_dfu); usb_put_function_instance(fi_dfu); } if (gadget_multi_opts->fastboot_opts.files) { usb_put_function(f_fastboot); usb_put_function_instance(fi_fastboot); } return 0; } static int multi_bind(struct usb_composite_dev *cdev) { struct usb_gadget *gadget = cdev->gadget; int ret; /* allocate string IDs */ ret = usb_string_ids_tab(cdev, strings_dev); if (ret < 0) return ret; if (gadget->vendor_id && gadget->product_id) { device_desc.idVendor = cpu_to_le16(gadget->vendor_id); device_desc.idProduct = cpu_to_le16(gadget->product_id); } else { device_desc.idVendor = cpu_to_le16(MULTI_VENDOR_NUM); device_desc.idProduct = cpu_to_le16(MULTI_PRODUCT_NUM); } strings_dev[USB_GADGET_MANUFACTURER_IDX].s = gadget->manufacturer; strings_dev[USB_GADGET_PRODUCT_IDX].s = gadget->productname; strings_dev[USB_GADGET_SERIAL_IDX].s = gadget->serialnumber; device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; device_desc.iSerialNumber = strings_dev[USB_GADGET_SERIAL_IDX].id; config.label = strings_dev[STRING_DESCRIPTION_IDX].s; config.iConfiguration = strings_dev[STRING_DESCRIPTION_IDX].id; ret = usb_add_config_only(cdev, &config); if (ret) return ret; if (gadget_multi_opts->fastboot_opts.files) { printf("%s: creating Fastboot function\n", __func__); ret = multi_bind_fastboot(cdev); if (ret) goto out; } if (gadget_multi_opts->dfu_opts.files) { printf("%s: creating DFU function\n", __func__); ret = multi_bind_dfu(cdev); if (ret) goto out; } if (gadget_multi_opts->ums_opts.files) { printf("%s: creating USB Mass Storage function\n", __func__); ret = multi_bind_ums(cdev); if (ret) goto out; } if (gadget_multi_opts->create_acm) { printf("%s: creating ACM function\n", __func__); ret = multi_bind_acm(cdev); if (ret) goto out; } usb_ep_autoconfig_reset(cdev->gadget); dev_info(&gadget->dev, DRIVER_DESC "\n"); return 0; out: multi_unbind(cdev); return ret; } static struct usb_composite_driver multi_driver = { .name = "g_multi", .dev = &device_desc, .strings = dev_strings, .max_speed = USB_SPEED_HIGH, .bind = multi_bind, .unbind = multi_unbind, .needs_serial = 1, }; int usb_multi_register(struct f_multi_opts *opts) { int ret; if (gadget_multi_opts) { pr_err("USB multi gadget already registered\n"); return -EBUSY; } gadget_multi_opts = opts; ret = usb_composite_probe(&multi_driver); if (ret) { usb_composite_unregister(&multi_driver); gadget_multi_opts = NULL; } return ret; } void usb_multi_unregister(void) { if (!gadget_multi_opts) return; usb_composite_unregister(&multi_driver); if (gadget_multi_opts->release) gadget_multi_opts->release(gadget_multi_opts); gadget_multi_opts = NULL; } unsigned usb_multi_count_functions(struct f_multi_opts *opts) { unsigned count = 0; count += !file_list_empty(opts->fastboot_opts.files) || opts->fastboot_opts.export_bbu; count += !file_list_empty(opts->dfu_opts.files); count += !file_list_empty(opts->ums_opts.files); count += opts->create_acm; return count; } void usb_multi_opts_release(struct f_multi_opts *opts) { file_list_free(opts->fastboot_opts.files); file_list_free(opts->dfu_opts.files); file_list_free(opts->ums_opts.files); free(opts); }