diff options
Diffstat (limited to 'drivers/usb')
36 files changed, 5730 insertions, 269 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index a6bd53ace03..b66fc04caa5 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1208,6 +1208,21 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) * and flush any outstanding URBs. */ } else { +#ifdef CONFIG_USB_OTG +#ifdef CONFIG_USB_OTG_20 + /* According to OTG supplement Rev 2.0 section 6.3 + * Unless an A-device enables b_hnp_enable before entering + * suspend it shall also continue polling while the bus is + * suspended. + * + * We don't have to perform HNP polling, as we are going to + * enable b_hnp_enable before suspending. + */ + if (udev->bus->hnp_support && + udev->portnum == udev->bus->otg_port) + cancel_delayed_work(&udev->bus->hnp_polling); +#endif +#endif udev->can_submit = 0; for (i = 0; i < 16; ++i) { usb_hcd_flush_endpoint(udev, udev->ep_out[i]); @@ -1270,6 +1285,51 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg) return status; } +#ifdef CONFIG_USB_OTG +#ifdef CONFIG_USB_OTG_20 +void usb_hnp_polling_work(struct work_struct *work) +{ + int ret; + struct usb_bus *bus = + container_of(work, struct usb_bus, hnp_polling.work); + struct usb_device *udev = + bus->root_hub->children[bus->otg_port - 1]; + u8 *status = kmalloc(sizeof(*status), GFP_KERNEL); + + if (!status) { + schedule_delayed_work(&bus->hnp_polling, + msecs_to_jiffies(THOST_REQ_POLL)); + return; + } + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RECIP_DEVICE, + 0, OTG_STATUS_SELECTOR, status, sizeof(*status), + USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + /* Peripheral may not be supporting HNP polling */ + dev_vdbg(&udev->dev, "HNP polling failed." + "status %d\n", ret); + goto out; + } + + /* Spec says host must suspend the bus with in 2 sec. */ + if (*status & (1 << HOST_REQUEST_FLAG)) { + do_unbind_rebind(udev, DO_UNBIND); + udev->do_remote_wakeup = 0; + ret = usb_suspend_both(udev, PMSG_USER_SUSPEND); + if (ret) + dev_info(&udev->dev, "suspend failed\n"); + } else { + schedule_delayed_work(&bus->hnp_polling, + msecs_to_jiffies(THOST_REQ_POLL)); + } +out: + kfree(status); +} +#endif +#endif + static void choose_wakeup(struct usb_device *udev, pm_message_t msg) { int w; @@ -1659,7 +1719,7 @@ EXPORT_SYMBOL_GPL(usb_autopm_get_interface_no_resume); /* Internal routine to check whether we may autosuspend a device. */ static int autosuspend_check(struct usb_device *udev) { - int w, i; + int w, i, audio_class; struct usb_interface *intf; unsigned long suspend_time, j; @@ -1667,6 +1727,7 @@ static int autosuspend_check(struct usb_device *udev) * any interface drivers require remote wakeup but it isn't available. */ w = 0; + audio_class = 0; if (udev->actconfig) { for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { intf = udev->actconfig->interface[i]; @@ -1694,13 +1755,32 @@ static int autosuspend_check(struct usb_device *udev) intf->needs_remote_wakeup) return -EOPNOTSUPP; } + /* Check for audio interface class */ + if (intf->cur_altsetting->desc.bInterfaceClass + == USB_CLASS_AUDIO) { + dev_dbg(&udev->dev, + "audio interface class present\n"); + audio_class = 1; + } } } - if (w && !device_can_wakeup(&udev->dev)) { - dev_dbg(&udev->dev, "remote wakeup needed for autosuspend\n"); - return -EOPNOTSUPP; + + /* To work with usb audio device having HID interface + * without remote wakeup support. + */ + if (audio_class) { + /* Audio class is enabled, disable remote wakeup */ + dev_dbg(&udev->dev, + "disabling remote wakeup for audio class\n"); + udev->do_remote_wakeup = 0; + } else { + if (w && !device_can_wakeup(&udev->dev)) { + dev_dbg(&udev->dev, + "remote wakeup needed for autosuspend\n"); + return -EOPNOTSUPP; + } + udev->do_remote_wakeup = w; } - udev->do_remote_wakeup = w; /* If everything is okay but the device hasn't been idle for long * enough, queue a delayed autosuspend request. diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 12742f152f4..834684afbea 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -870,6 +870,11 @@ static void usb_bus_init (struct usb_bus *bus) bus->bandwidth_isoc_reqs = 0; INIT_LIST_HEAD (&bus->bus_list); +#ifdef CONFIG_USB_OTG +#ifdef CONFIG_USB_OTG_20 + INIT_DELAYED_WORK(&bus->hnp_polling, usb_hnp_polling_work); +#endif +#endif } /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index ffc80e3241e..b0b470e4a24 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -31,6 +31,12 @@ #include "usb.h" +#ifdef CONFIG_ARCH_U8500 +#define MAX_TOPO_LEVEL_U8500 2 +#define MAX_USB_DEVICE_U8500 8 +int usb_device_count; +#endif + /* if we are in debug mode, always announce new devices */ #ifdef DEBUG #ifndef CONFIG_USB_ANNOUNCE_NEW_DEVICES @@ -128,12 +134,19 @@ MODULE_PARM_DESC(initial_descriptor_timeout, * otherwise the new scheme is used. If that fails and "use_both_schemes" * is set, then the driver will make another attempt, using the other scheme. */ +#ifndef CONFIG_USB_OTG_13 static int old_scheme_first = 0; +#else +static int old_scheme_first = 1; +#endif module_param(old_scheme_first, bool, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(old_scheme_first, "start with the old device initialization scheme"); - +#ifndef CONFIG_USB_OTG_13 static int use_both_schemes = 1; +#else +static int use_both_schemes; +#endif module_param(use_both_schemes, bool, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(use_both_schemes, "try the other device initialization scheme if the " @@ -593,7 +606,7 @@ static int hub_hub_status(struct usb_hub *hub, "%s failed (err = %d)\n", __func__, ret); else { *status = le16_to_cpu(hub->status->hub.wHubStatus); - *change = le16_to_cpu(hub->status->hub.wHubChange); + *change = le16_to_cpu(hub->status->hub.wHubChange); ret = 0; } mutex_unlock(&hub->status_mutex); @@ -1233,11 +1246,20 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) /* Hubs have proper suspend/resume support */ usb_enable_autosuspend(hdev); +#ifdef CONFIG_ARCH_U8500 + if (hdev->level > MAX_TOPO_LEVEL_U8500) { + dev_err(&intf->dev, + "Unsupported bus topology: > %d " + " hub nesting\n", MAX_TOPO_LEVEL_U8500); + return -E2BIG; + } +#else if (hdev->level == MAX_TOPO_LEVEL) { dev_err(&intf->dev, "Unsupported bus topology: hub nested too deep\n"); return -E2BIG; } +#endif #ifdef CONFIG_USB_OTG_BLACKLIST_HUB if (hdev->parent) { @@ -1510,12 +1532,14 @@ static void choose_address(struct usb_device *udev) * bus->devnum_next. */ devnum = find_next_zero_bit(bus->devmap.devicemap, 128, bus->devnum_next); - if (devnum >= 128) + /* Due to Hardware bugs we need to reserve a device address + * for flushing of endpoints. */ + if (devnum >= 127) devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1); - bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); + bus->devnum_next = ( devnum >= 126 ? 1 : devnum + 1); } - if (devnum < 128) { + if (devnum < 127) { set_bit(devnum, bus->devmap.devicemap); udev->devnum = devnum; } @@ -1578,6 +1602,14 @@ void usb_disconnect(struct usb_device **pdev) usb_set_device_state(udev, USB_STATE_NOTATTACHED); dev_info (&udev->dev, "USB disconnect, address %d\n", udev->devnum); +#ifdef CONFIG_USB_OTG +#ifdef CONFIG_USB_OTG_20 + if (udev->bus->hnp_support && udev->portnum == udev->bus->otg_port) { + cancel_delayed_work_sync(&udev->bus->hnp_polling); + udev->bus->hnp_support = 0; + } +#endif +#endif usb_lock_device(udev); /* Free up all the children before we remove this device */ @@ -1681,16 +1713,42 @@ static int usb_enumerate_device_otg(struct usb_device *udev) "Dual-Role OTG device on %sHNP port\n", (port1 == bus->otg_port) ? "" : "non-"); - +#ifndef CONFIG_USB_OTG_20 /* enable HNP before suspend, it's simpler */ if (port1 == bus->otg_port) bus->b_hnp_enable = 1; +#else + /* a_alt_hnp_support is obsoleted */ + if (port1 != bus->otg_port) + goto fail; + + bus->hnp_support = 1; + + /* a_hnp_support is not required for devices + * compliant to revision 2.0 or subsequent + * versions. + */ + if (desc->bLength == sizeof(*desc) && + le16_to_cpu(desc->bcdOTG) >= 0x0200) + goto out; + + /* Legacy B-device i.e compliant to spec + * revision 1.3 expect A-device to set + * a_hnp_support or b_hnp_enable before + * selecting configuration. b_hnp_enable + * is set before suspending the port. + */ +#endif err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), USB_REQ_SET_FEATURE, 0, +#ifndef CONFIG_USB_OTG_20 bus->b_hnp_enable ? USB_DEVICE_B_HNP_ENABLE : USB_DEVICE_A_ALT_HNP_SUPPORT, +#else + USB_DEVICE_A_HNP_SUPPORT, +#endif 0, NULL, 0, USB_CTRL_SET_TIMEOUT); if (err < 0) { /* OTG MESSAGE: report errors here, @@ -1699,24 +1757,133 @@ static int usb_enumerate_device_otg(struct usb_device *udev) dev_info(&udev->dev, "can't set HNP mode: %d\n", err); +#ifndef CONFIG_USB_OTG_20 bus->b_hnp_enable = 0; +#else + bus->hnp_support = 0; +#endif } } } } - if (!is_targeted(udev)) { +#ifdef CONFIG_USB_OTG_20 +out: +#endif +#ifdef CONFIG_USB_OTG + { + u16 idVendor = le16_to_cpu(udev->descriptor.idVendor); + if (idVendor == USB_OTG_TEST_MODE_VID) { + u16 wValue, typeReq, wIndex; + u32 set_feature = 0; + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + u16 idProduct = le16_to_cpu( + udev->descriptor.idProduct); + /* Convert the Test Mode Request to control request*/ + wValue = USB_PORT_FEAT_TEST; + typeReq = SetPortFeature; + wIndex = 1; + + switch (idProduct) { + case USB_OTG_TEST_SE0_NAK_PID: + wIndex |= USB_OTG_TEST_SE0_NAK << 8; + set_feature = 1; + break; + case USB_OTG_TEST_J_PID: + wIndex |= USB_OTG_TEST_J << 8; + set_feature = 1; + break; + case USB_OTG_TEST_K_PID: + wIndex |= USB_OTG_TEST_K << 8; + set_feature = 1; + break; + case USB_OTG_TEST_PACKET_PID: + wIndex |= USB_OTG_TEST_PACKET << 8; + set_feature = 1; + break; + case USB_OTG_TEST_HS_HOST_PORT_SUSPEND_RESUME_PID: + /* Sleep for 15 sec. Suspend for 15 Sec, Then Resume */ + ssleep(15); + + err = usb_port_suspend(udev, PMSG_SUSPEND); + if (err < 0) + dev_err(&udev->dev, "OTG TEST_MODE:" + "Suspend Fail, %d\n", err); + ssleep(15); + err = usb_autoresume_device(udev); + if (err < 0) { + dev_err(&udev->dev, + "can't autoresume for" + "OTG TEST_MODE: %d\n", err); + } + break; + case USB_OTG_TEST_SINGLE_STEP_GET_DEV_DESC_PID: + /* Sleep for 15 Sec. Issue the GetDeviceDescriptor */ + ssleep(15); + err = usb_get_device_descriptor(udev, + sizeof(udev->descriptor)); + if (err < 0) { + dev_err(&udev->dev, "can't re-read" + "device descriptor for " + "OTG TEST MODE: %d\n", err); + } + break; + case USB_OTG_TEST_SINGLE_STEP_GET_DEV_DESC_DATA_PID: + /* Issue GetDeviceDescriptor, Sleep for 15 Sec. */ + err = usb_get_device_descriptor(udev, + sizeof(udev->descriptor)); + if (err < 0) { + dev_err(&udev->dev, "can't re-read" + "device descriptor for " + "OTG TEST MODE: %d\n", err); + } + ssleep(15); + break; + default: + /* is_targeted() will take care for wrong PID */ + dev_dbg(&udev->dev, "OTG TEST_MODE:Wrong PID %d\n", + idProduct); + break; + } + + if (set_feature) { + err = hcd->driver->hub_control(hcd, + typeReq, wValue, wIndex, + NULL, 0); + } + } + } +#endif + + if (!is_targeted(udev)) { /* Maybe it can talk to us, though we can't talk to it. - * (Includes HNP test device.) + *(Includes HNP test device.) */ if (udev->bus->b_hnp_enable || udev->bus->is_b_host) { - err = usb_port_suspend(udev, PMSG_SUSPEND); - if (err < 0) - dev_dbg(&udev->dev, "HNP fail, %d\n", err); +#ifdef CONFIG_USB_OTG_20 + if (udev->bus->hnp_support) { +#endif + err = usb_port_suspend(udev, PMSG_SUSPEND); + if (err < 0) + dev_dbg(&udev->dev, "HNP fail, %d\n" + , err); } err = -ENOTSUPP; +#ifndef CONFIG_USB_OTG_20 goto fail; +#else + } else if (udev->bus->hnp_support && + udev->portnum == udev->bus->otg_port) { + /* HNP polling is introduced in OTG supplement Rev 2.0 + * and older devices does not support. Work is not + * re-armed if device returns STALL. B-Host also + * performs HNP polling. + */ + schedule_delayed_work(&udev->bus->hnp_polling, + msecs_to_jiffies(THOST_REQ_POLL)); + } +#endif } fail: #endif @@ -2197,6 +2364,22 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) return status; } } +#ifdef CONFIG_USB_OTG +#ifdef CONFIG_USB_OTG_20 + if (!udev->bus->is_b_host && udev->bus->hnp_support && + udev->portnum == udev->bus->otg_port) { + status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, 0, + USB_DEVICE_B_HNP_ENABLE, + 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + if (status < 0) + dev_dbg(&udev->dev, "can't enable HNP on port %d, " + "status %d\n", port1, status); + else + udev->bus->b_hnp_enable = 1; + } +#endif +#endif /* see 7.1.7.6 */ status = set_port_feature(hub->hdev, port1, USB_PORT_FEAT_SUSPEND); @@ -2340,6 +2523,13 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) int status; u16 portchange, portstatus; +#ifdef CONFIG_USB_OTG +#ifdef CONFIG_USB_OTG_20 + if (!udev->bus->is_b_host && udev->bus->hnp_support && + udev->portnum == udev->bus->otg_port) + udev->bus->b_hnp_enable = 0; +#endif +#endif /* Skip the initial Clear-Suspend step for a remote wakeup */ status = hub_port_status(hub, port1, &portstatus, &portchange); if (status == 0 && !(portstatus & USB_PORT_STAT_SUSPEND)) @@ -2516,7 +2706,7 @@ EXPORT_SYMBOL_GPL(usb_root_hub_lost_power); * Between connect detection and reset signaling there must be a delay * of 100ms at least for debounce and power-settling. The corresponding * timer shall restart whenever the downstream port detects a disconnect. - * + * * Apparently there are some bluetooth and irda-dongles and a number of * low-speed devices for which this debounce period may last over a second. * Not covered by the spec - but easy to deal with. @@ -2694,7 +2884,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, default: goto fail; } - + type = ""; switch (udev->speed) { case USB_SPEED_LOW: speed = "low"; break; @@ -2724,7 +2914,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, udev->tt = &hub->tt; udev->ttport = port1; } - + /* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way? * Because device hardware and firmware is sometimes buggy in * this area, and this is how Linux has done it for ages. @@ -2869,7 +3059,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i); usb_ep0_reinit(udev); } - + retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); if (retval < (signed)sizeof(udev->descriptor)) { dev_err(&udev->dev, "device descriptor read/all, error %d\n", @@ -3104,6 +3294,20 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, status = -ENOTCONN; /* Don't retry */ goto loop; } +#ifdef CONFIG_ARCH_U8500 + if (hdev->parent == NULL) + usb_device_count = 1; + + if (usb_device_count > MAX_USB_DEVICE_U8500) { + + dev_err(&udev->dev, + "device connected is more than %d\n", + MAX_USB_DEVICE_U8500); + + status = -ENOTCONN; /* Don't retry */ + goto loop; + } +#endif } /* reset (non-USB 3.0 devices) and get descriptor */ @@ -3145,7 +3349,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, goto loop_disable; } } - + /* check for devices running slower than they could */ if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200 && udev->speed == USB_SPEED_FULL @@ -3203,7 +3407,7 @@ loop: !(hcd->driver->port_handed_over)(hcd, port1)) dev_err(hub_dev, "unable to enumerate USB device on port %d\n", port1); - + done: hub_port_disable(hub, port1, 1); if (hcd->driver->relinquish_port && !hub->hdev->parent) @@ -3329,7 +3533,7 @@ static void hub_events(void) * EM interference sometimes causes badly * shielded USB devices to be shutdown by * the hub, this hack enables them again. - * Works at least with mouse driver. + * Works at least with mouse driver. */ if (!(portstatus & USB_PORT_STAT_ENABLE) && !connect_change @@ -3367,7 +3571,7 @@ static void hub_events(void) "resume on port %d, status %d\n", i, ret); } - + if (portchange & USB_PORT_STAT_C_OVERCURRENT) { dev_err (hub_dev, "over-current change on port %d\n", @@ -3646,7 +3850,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev) if (ret < 0) goto re_enumerate; - + /* Device might have changed firmware (DFU or similar) */ if (descriptors_changed(udev, &descriptor)) { dev_info(&udev->dev, "device firmware changed\n"); @@ -3719,7 +3923,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev) done: return 0; - + re_enumerate: hub_port_logical_disconnect(parent_hub, port1); return -ENODEV; diff --git a/drivers/usb/core/notify.c b/drivers/usb/core/notify.c index 7542dce3f5a..b76a8a8961e 100644 --- a/drivers/usb/core/notify.c +++ b/drivers/usb/core/notify.c @@ -45,11 +45,17 @@ EXPORT_SYMBOL_GPL(usb_unregister_notify); void usb_notify_add_device(struct usb_device *udev) { +#ifdef CONFIG_ARCH_U8500 + usb_device_count++; +#endif blocking_notifier_call_chain(&usb_notifier_list, USB_DEVICE_ADD, udev); } void usb_notify_remove_device(struct usb_device *udev) { +#ifdef CONFIG_ARCH_U8500 + usb_device_count--; +#endif /* Protect against simultaneous usbfs open */ mutex_lock(&usbfs_mutex); blocking_notifier_call_chain(&usb_notifier_list, diff --git a/drivers/usb/core/otg_whitelist.h b/drivers/usb/core/otg_whitelist.h index e8cdce571bb..9cbac092e77 100644 --- a/drivers/usb/core/otg_whitelist.h +++ b/drivers/usb/core/otg_whitelist.h @@ -43,12 +43,43 @@ static struct usb_device_id whitelist_table [] = { { USB_DEVICE(0x0525, 0xa4a0), }, #endif +#if defined(CONFIG_USB_OTG_20) +{ USB_DEVICE_INFO(8, 6, 80) },/* Mass Storage Devices */ +#endif +{ USB_DEVICE(0x4CC, 0x2323), },/* U8500 Board */ +{ USB_DEVICE(0x4D9, 0x1702), },/* HP Keyboard */ +{ USB_DEVICE(0x4E8, 0x1F06), },/* USB Hard disk */ +{ USB_DEVICE(0x46D, 0x0A0C), },/*USB Headset */ +{ USB_DEVICE(0x93A, 0x2510), },/* USB mouse */ + { } /* Terminating entry */ }; +/* The TEST_MODE Definition for OTG as per 6.4 of OTG Rev 2.0 */ + +#ifdef CONFIG_USB_OTG +#define USB_OTG_TEST_MODE_VID 0x1A0A +#define USB_OTG_TEST_SE0_NAK_PID 0x0101 +#define USB_OTG_TEST_J_PID 0x0102 +#define USB_OTG_TEST_K_PID 0x0103 +#define USB_OTG_TEST_PACKET_PID 0x0104 +#define USB_OTG_TEST_HS_HOST_PORT_SUSPEND_RESUME_PID 0x0106 +#define USB_OTG_TEST_SINGLE_STEP_GET_DEV_DESC_PID 0x0107 +#define USB_OTG_TEST_SINGLE_STEP_GET_DEV_DESC_DATA_PID 0x0108 + +#define USB_OTG_TEST_SE0_NAK 0x01 +#define USB_OTG_TEST_J 0x02 +#define USB_OTG_TEST_K 0x03 +#define USB_OTG_TEST_PACKET 0x04 +#endif + static int is_targeted(struct usb_device *dev) { struct usb_device_id *id = whitelist_table; +#ifdef CONFIG_USB_OTG_20 + u8 number_configs = 0; + u8 number_interface = 0; +#endif /* possible in developer configs only! */ if (!dev->bus->otg_port) @@ -97,6 +128,33 @@ static int is_targeted(struct usb_device *dev) } /* add other match criteria here ... */ +#ifdef CONFIG_USB_OTG_20 +/* Checking class,subclass and protocal at interface level */ +for (number_configs = dev->descriptor.bNumConfigurations; + number_configs > 0; number_configs--) + for (number_interface = dev->config->desc.bNumInterfaces; + number_interface > 0; number_interface--) + for (id = whitelist_table; id->match_flags; id++) { + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) && + (id->bDeviceClass != + dev->config->intf_cache[number_interface-1] + ->altsetting[0].desc.bInterfaceClass)) + continue; + if ((id->match_flags & + USB_DEVICE_ID_MATCH_DEV_SUBCLASS) + && (id->bDeviceSubClass != + dev->config->intf_cache[number_interface-1] + ->altsetting[0].desc.bInterfaceSubClass)) + continue; + if ((id->match_flags & + USB_DEVICE_ID_MATCH_DEV_PROTOCOL) + && (id->bDeviceProtocol != + dev->config->intf_cache[number_interface-1] + ->altsetting[0].desc.bInterfaceProtocol)) + continue; + return 1; + } +#endif /* OTG MESSAGE: report errors here, customize to match your product */ diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index cd882203ad3..4e9bc71440e 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -1,5 +1,8 @@ #include <linux/pm.h> +#ifdef CONFIG_ARCH_U8500 +extern int usb_device_count; +#endif /* Functions local to drivers/usb/core/ */ extern int usb_create_sysfs_dev_files(struct usb_device *dev); @@ -72,6 +75,17 @@ static inline int usb_port_resume(struct usb_device *udev, pm_message_t msg) #endif +#ifdef CONFIG_USB_OTG +#ifdef CONFIG_USB_OTG_20 +extern void usb_hnp_polling_work(struct work_struct *work); +#else +static inline void usb_hnp_polling_work(struct work_struct *work) +{ +} +#endif +#endif + + #ifdef CONFIG_USB_SUSPEND extern void usb_autosuspend_device(struct usb_device *udev); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 591ae9fde19..70c5fe66931 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -836,6 +836,49 @@ config USB_G_PRINTER For more information, see Documentation/usb/gadget_printer.txt which includes sample code for accessing the device file. +config USB_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_ACM + boolean "Android gadget ACM serial function" + depends on USB_ANDROID + help + Provides ACM serial function for android gadget driver. + +config USB_ANDROID_ADB + boolean "Android gadget adb function" + depends on USB_ANDROID + help + Provides adb function for android gadget driver. + +config USB_ANDROID_MASS_STORAGE + boolean "Android gadget mass storage function" + depends on USB_ANDROID && SWITCH + help + Provides USB mass storage function for android gadget driver. + +config USB_ANDROID_MTP + boolean "Android MTP function" + depends on USB_ANDROID + help + Provides Media Transfer Protocol (MTP) support for android gadget driver. + +config USB_ANDROID_ETHER + boolean "Android gadget Ethernet function" + depends on USB_ANDROID + help + Provides CDC Ethernat (ECM: Ethernet Control Model) or + RNDIS (Remote NDIS) for android gadget driver. + + ECM: The drivers are not provided by Microsoft. + + RNDIS: The drivers are provided by Microsoft. + config USB_CDC_COMPOSITE tristate "CDC Composite Device (Ethernet and ACM)" depends on NET @@ -928,4 +971,64 @@ config USB_G_WEBCAM endchoice +choice + prompt "Ether Mode" + depends on USB_ANDROID_ETHER + +config USB_ANDROID_ECM + boolean "Android gadget ECM ether function" + depends on USB_ANDROID + help + Provides CDC Ethernet (ECM) ether for android gadget driver. + + ECM and RNDIS should be mutually exclusive as both classes are + serving the same purpose. + +config USB_ANDROID_RNDIS + boolean "Android gadget RNDIS ethernet function" + depends on USB_ANDROID + help + Microsoft Windows XP bundles the "Remote NDIS" (RNDIS) protocol, + and Microsoft provides redistributable binary RNDIS drivers for + older versions of Windows. + + If you say "y" here, the Ethernet gadget driver will try to provide + a second device configuration, supporting RNDIS to talk to such + Microsoft USB hosts. + + To make MS-Windows work with this, use Documentation/usb/linux.inf + as the "driver info file". For versions of MS-Windows older than + XP, you'll need to download drivers from Microsoft's website; a URL + is given in comments found in that info file. + +config USB_ANDROID_RNDIS_WCEIS + boolean "Use Windows Internet Sharing Class/SubClass/Protocol" + depends on USB_ANDROID_RNDIS + help + Causes the driver to look like a Windows-compatible Internet + Sharing device, so Windows auto-detects it. + + If you enable this option, the device is no longer CDC ethernet + compatible. + +endchoice + +config USB_OTG_13 + bool "OTG 1.3 USB SUPPORT" + select PM_RUNTIME + select USB_OTG_WHITELIST + select USB_SUSPEND + help + Enabling the whitelist (Target Peripheral List-TPL) and runtime power + management at system level and usb level for OTG 1.3. + +config USB_OTG_20 + bool "OTG 2.0 USB SUPPORT" + select PM_RUNTIME + select USB_OTG_WHITELIST + select USB_SUSPEND + help + Enabling the whitelist (Target Peripheral List-TPL) and runtime power + management at system level and usb level for OTG 2.0. + endif # USB_GADGET diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 9bcde110feb..84a72fb0773 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -63,4 +63,11 @@ obj-$(CONFIG_USB_G_HID) += g_hid.o 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_ANDROID) += android.o +obj-$(CONFIG_USB_ANDROID_ACM) += f_acm.o u_serial.o +obj-$(CONFIG_USB_ANDROID_ADB) += f_adb.o +obj-$(CONFIG_USB_ANDROID_ECM) += f_ecm.o u_ether.o +obj-$(CONFIG_USB_ANDROID_MASS_STORAGE) += f_mass_storage.o +obj-$(CONFIG_USB_ANDROID_MTP) += f_mtp.o +obj-$(CONFIG_USB_ANDROID_RNDIS) += f_rndis.o u_ether.o diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c new file mode 100644 index 00000000000..1b316f092bf --- /dev/null +++ b/drivers/usb/gadget/android.c @@ -0,0 +1,478 @@ +/* + * 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; + u8 bound; + + 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, +}; + +#if defined(CONFIG_USB_OTG_20) || defined(CONFIG_USB_OTG_13) +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; +#endif + +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); + } + dev->bound = 1; +} + +static void rebind_functions(struct android_dev *dev) + +{ + struct android_usb_function *android_f; + struct usb_function *f; + + dev->config->next_interface_id = 0; + list_for_each_entry(f, &dev->config->functions, list) { + if (f->disabled) + continue; + + /* rebind our functions if they are enabled */ + android_f = get_function(f->name); + if (android_f && android_f->rebind_config) + android_f->rebind_config(dev->config, f); + else + printk(KERN_ERR "function %s not found in rebind_functions\n", + f->name); + } + dev->config->interface[dev->config->next_interface_id] = NULL; +} + +static int android_bind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + + printk(KERN_DEBUG "android_bind_config\n"); +#if defined(CONFIG_USB_OTG_20) || defined(CONFIG_USB_OTG_13) + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } +#endif + 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 = CONFIG_USB_GADGET_VBUS_DRAW / 2, +}; + +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; + + + /* + * As per USB compliance update, a device that is actively drawing + * more than 100mA from USB must report itself as bus-powered in + * the GetStatus(DEVICE) call. + */ + if (android_config_driver.bMaxPower <= + (USB_SELF_POWER_VBUS_MAX_DRAW / 2)) { + android_config_driver.bmAttributes = + USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER; + usb_gadget_set_selfpowered(gadget); + } else + android_config_driver.bmAttributes = USB_CONFIG_ATT_ONE; + + /* 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); + } + + 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") + || !strcmp(func->name, "adb") + || !strcmp(func->name, "acm")) { + 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; + if (dev->bound) + rebind_functions(dev); + 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; + dev->bound = 0; + 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); diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 391d169f8d0..5b20f6ba268 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -24,9 +24,10 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/device.h> - +#include <linux/kdev_t.h> +#include <linux/delay.h> #include <linux/usb/composite.h> - +#include <linux/switch.h> /* * The code in this file is utility code, used to build a gadget driver @@ -71,6 +72,56 @@ MODULE_PARM_DESC(iSerialNumber, "SerialNumber string"); /*-------------------------------------------------------------------------*/ +static ssize_t enable_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_function *f = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", !f->disabled); +} + +static ssize_t enable_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_function *f = dev_get_drvdata(dev); + struct usb_composite_driver *driver = f->config->cdev->driver; + int value; + + sscanf(buf, "%d", &value); + if (driver->enable_function) + driver->enable_function(f, value); + else + usb_function_set_enabled(f, value); + + return size; +} + +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store); + +void usb_function_set_enabled(struct usb_function *f, int enabled) +{ + f->disabled = !enabled; + kobject_uevent(&f->dev->kobj, KOBJ_CHANGE); +} + + +void usb_composite_force_reset(struct usb_composite_dev *cdev) +{ + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + /* force reenumeration */ + if (cdev && cdev->gadget && cdev->gadget->speed != USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(&cdev->lock, flags); + + usb_gadget_disconnect(cdev->gadget); + msleep(10); + usb_gadget_connect(cdev->gadget); + } else { + spin_unlock_irqrestore(&cdev->lock, flags); + } +} + /** * usb_add_function() - add a function to a configuration * @config: the configuration @@ -88,15 +139,30 @@ MODULE_PARM_DESC(iSerialNumber, "SerialNumber string"); int usb_add_function(struct usb_configuration *config, struct usb_function *function) { + struct usb_composite_dev *cdev = config->cdev; int value = -EINVAL; + int index; - DBG(config->cdev, "adding '%s'/%p to config '%s'/%p\n", + DBG(cdev, "adding '%s'/%p to config '%s'/%p\n", function->name, function, config->label, config); if (!function->set_alt || !function->disable) goto done; + index = atomic_inc_return(&cdev->driver->function_count); + function->dev = device_create(cdev->driver->class, NULL, + MKDEV(0, index), NULL, function->name); + if (IS_ERR(function->dev)) + return PTR_ERR(function->dev); + + value = device_create_file(function->dev, &dev_attr_enable); + if (value < 0) { + device_destroy(cdev->driver->class, MKDEV(0, index)); + return value; + } + dev_set_drvdata(function->dev, function); + function->config = config; list_add_tail(&function->list, &config->functions); @@ -122,7 +188,7 @@ int usb_add_function(struct usb_configuration *config, done: if (value) - DBG(config->cdev, "adding '%s'/%p --> %d\n", + DBG(cdev, "adding '%s'/%p --> %d\n", function->name, function, value); return value; } @@ -232,17 +298,19 @@ static int config_buf(struct usb_configuration *config, enum usb_device_speed speed, void *buf, u8 type) { struct usb_config_descriptor *c = buf; + struct usb_interface_descriptor *intf; void *next = buf + USB_DT_CONFIG_SIZE; int len = USB_BUFSIZ - USB_DT_CONFIG_SIZE; struct usb_function *f; int status; + int interfaceCount = 0; + u8 *dest; /* write the config descriptor */ c = buf; c->bLength = USB_DT_CONFIG_SIZE; c->bDescriptorType = type; - /* wTotalLength is written later */ - c->bNumInterfaces = config->next_interface_id; + /* wTotalLength and bNumInterfaces are written later */ c->bConfigurationValue = config->bConfigurationValue; c->iConfiguration = config->iConfiguration; c->bmAttributes = USB_CONFIG_ATT_ONE | config->bmAttributes; @@ -261,23 +329,40 @@ static int config_buf(struct usb_configuration *config, /* add each function's descriptors */ list_for_each_entry(f, &config->functions, list) { struct usb_descriptor_header **descriptors; + struct usb_descriptor_header *descriptor; if (speed == USB_SPEED_HIGH) descriptors = f->hs_descriptors; else descriptors = f->descriptors; - if (!descriptors) + if (f->disabled || !descriptors || descriptors[0] == NULL) continue; status = usb_descriptor_fillbuf(next, len, (const struct usb_descriptor_header **) descriptors); if (status < 0) return status; + + /* set interface numbers dynamically */ + dest = next; + while ((descriptor = *descriptors++) != NULL) { + intf = (struct usb_interface_descriptor *)dest; + if (intf->bDescriptorType == USB_DT_INTERFACE) { + /* don't increment bInterfaceNumber for alternate settings */ + if (intf->bAlternateSetting == 0) + intf->bInterfaceNumber = interfaceCount++; + else + intf->bInterfaceNumber = interfaceCount - 1; + } + dest += intf->bLength; + } + len -= status; next += status; } len = next - buf; c->wTotalLength = cpu_to_le16(len); + c->bNumInterfaces = interfaceCount; return len; } @@ -424,6 +509,8 @@ static int set_config(struct usb_composite_dev *cdev, if (!f) break; + if (f->disabled) + continue; /* * Record which endpoints are used by the function. This is used @@ -463,6 +550,8 @@ 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); + + schedule_work(&cdev->switch_work); return result; } @@ -717,6 +806,14 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) struct usb_function *f = NULL; u8 endp; + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + if (!cdev->connected) { + cdev->connected = 1; + schedule_work(&cdev->switch_work); + } + spin_unlock_irqrestore(&cdev->lock, flags); /* partial re-init of the response message; the function or the * gadget might need to intercept e.g. a control-OUT completion * when we delegate to it. @@ -759,6 +856,21 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) case USB_DT_STRING: value = get_string(cdev, req->buf, w_index, w_value & 0xff); + + /* Allow functions to handle USB_DT_STRING. + * This is required for MTP. + */ + if (value < 0) { + struct usb_configuration *cfg; + list_for_each_entry(cfg, &cdev->configs, list) { + if (cfg && cfg->setup) { + value = cfg->setup(cfg, ctrl); + if (value >= 0) + break; + } + } + } + if (value >= 0) value = min(w_length, (u16) value); break; @@ -784,11 +896,11 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) case USB_REQ_GET_CONFIGURATION: if (ctrl->bRequestType != USB_DIR_IN) goto unknown; - if (cdev->config) + if (cdev->config) { *(u8 *)req->buf = cdev->config->bConfigurationValue; - else + value = min(w_length, (u16) 1); + } else *(u8 *)req->buf = 0; - value = min(w_length, (u16) 1); break; /* function drivers must handle get/set altsetting; if there's @@ -838,6 +950,8 @@ unknown: */ switch (ctrl->bRequestType & USB_RECIP_MASK) { case USB_RECIP_INTERFACE: + if (!cdev->config || w_index >= MAX_CONFIG_INTERFACES) + break; f = cdev->config->interface[intf]; break; @@ -862,6 +976,25 @@ unknown: value = c->setup(c, ctrl); } + /* If the vendor request is not processed (value < 0), + * call all device registered configure setup callbacks + * to process it. + * This is used to handle the following cases: + * - vendor request is for the device and arrives before + * setconfiguration. + * - Some devices are required to handle vendor request before + * setconfiguration such as MTP, USBNET. + */ + + if (value < 0) { + struct usb_configuration *cfg; + + list_for_each_entry(cfg, &cdev->configs, list) { + if (cfg && cfg->setup) + value = cfg->setup(cfg, ctrl); + } + } + goto done; } @@ -887,12 +1020,19 @@ static void composite_disconnect(struct usb_gadget *gadget) struct usb_composite_dev *cdev = get_gadget_data(gadget); unsigned long flags; +#ifdef CONFIG_USB_OTG_20 + gadget->host_request = 0; +#endif + /* REVISIT: should we have config and device level * disconnect callbacks? */ spin_lock_irqsave(&cdev->lock, flags); if (cdev->config) reset_config(cdev); + + cdev->connected = 0; + schedule_work(&cdev->switch_work); spin_unlock_irqrestore(&cdev->lock, flags); } @@ -910,6 +1050,25 @@ static ssize_t composite_show_suspended(struct device *dev, static DEVICE_ATTR(suspended, 0444, composite_show_suspended, NULL); +#ifdef CONFIG_USB_OTG_20 +static ssize_t composite_set_host_request(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + int value; + + if (sscanf(buf, "%d", &value) != 1) + return -EINVAL; + + gadget->host_request = !!value; + return count; + +} + +static DEVICE_ATTR(host_request, S_IWUSR, NULL, composite_set_host_request); +#endif + static void composite_unbind(struct usb_gadget *gadget) { @@ -954,6 +1113,13 @@ composite_unbind(struct usb_gadget *gadget) kfree(cdev->req->buf); usb_ep_free_request(gadget->ep0, cdev->req); } + + switch_dev_unregister(&cdev->sw_connected); + switch_dev_unregister(&cdev->sw_config); +#ifdef CONFIG_USB_OTG_20 + device_remove_file(&gadget->dev, &dev_attr_host_request); +#endif + kfree(cdev); set_gadget_data(gadget, NULL); device_remove_file(&gadget->dev, &dev_attr_suspended); @@ -982,6 +1148,30 @@ string_override(struct usb_gadget_strings **tab, u8 id, const char *s) } } +static void +composite_switch_work(struct work_struct *data) +{ + struct usb_composite_dev *cdev = + container_of(data, struct usb_composite_dev, switch_work); + struct usb_configuration *config = cdev->config; + int connected; + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->connected != cdev->sw_connected.state) { + connected = cdev->connected; + spin_unlock_irqrestore(&cdev->lock, flags); + switch_set_state(&cdev->sw_connected, connected); + } else { + spin_unlock_irqrestore(&cdev->lock, flags); +} + + if (config) + switch_set_state(&cdev->sw_config, config->bConfigurationValue); + else + switch_set_state(&cdev->sw_config, 0); +} + static int composite_bind(struct usb_gadget *gadget) { struct usb_composite_dev *cdev; @@ -1009,7 +1199,13 @@ static int composite_bind(struct usb_gadget *gadget) cdev->bufsiz = USB_BUFSIZ; cdev->driver = composite; - usb_gadget_set_selfpowered(gadget); + /* + * As per USB compliance update, a device that is actively drawing + * more than 100mA from USB must report itself as bus-powered in + * the GetStatus(DEVICE) call. + */ + if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW) + usb_gadget_set_selfpowered(gadget); /* interface and string IDs start at zero via kzalloc. * we force endpoints to start unassigned; few controller @@ -1033,6 +1229,24 @@ static int composite_bind(struct usb_gadget *gadget) if (status < 0) goto fail; + + cdev->sw_connected.name = "usb_connected"; + status = switch_dev_register(&cdev->sw_connected); + if (status < 0) + goto fail; + cdev->sw_config.name = "usb_configuration"; + status = switch_dev_register(&cdev->sw_config); + if (status < 0) + goto fail; + INIT_WORK(&cdev->switch_work, composite_switch_work); + +#ifdef CONFIG_USB_OTG_20 + status = device_create_file(&gadget->dev, &dev_attr_host_request); + if (status) + DBG(cdev, "unable to create host_request sysfs file\n"); +#endif + + cdev->desc = *composite->dev; cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket; @@ -1107,6 +1321,23 @@ composite_resume(struct usb_gadget *gadget) cdev->suspended = 0; } +static int +composite_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct usb_function *f = dev_get_drvdata(dev); + + if (!f) { + /* this happens when the device is first created */ + return 0; + } + + if (add_uevent_var(env, "FUNCTION=%s", f->name)) + return -ENOMEM; + if (add_uevent_var(env, "ENABLED=%d", !f->disabled)) + return -ENOMEM; + return 0; +} + /*-------------------------------------------------------------------------*/ static struct usb_gadget_driver composite_driver = { @@ -1152,6 +1383,11 @@ int usb_composite_register(struct usb_composite_driver *driver) composite_driver.driver.name = driver->name; composite = driver; + driver->class = class_create(THIS_MODULE, "usb_composite"); + if (IS_ERR(driver->class)) + return PTR_ERR(driver->class); + driver->class->dev_uevent = composite_uevent; + return usb_gadget_register_driver(&composite_driver); } diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c index 8a832488ccd..dc2bd3af7d8 100644 --- a/drivers/usb/gadget/epautoconf.c +++ b/drivers/usb/gadget/epautoconf.c @@ -2,6 +2,7 @@ * epautoconf.c -- endpoint autoconfiguration for usb gadget drivers * * Copyright (C) 2004 David Brownell + * Copyright (C) 2009 ST Ericsson * * 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 @@ -79,6 +80,12 @@ ep_matches ( if (USB_ENDPOINT_XFER_CONTROL == type) return 0; +#if defined(CONFIG_ARCH_U8500) + /* 512 size endpoints are scarce, so leave them for bulk type */ + if (ep->maxpacket == 512 && USB_ENDPOINT_XFER_INT == type) + return 0; +#endif + /* some other naming convention */ if ('e' != ep->name[0]) return 0; @@ -287,10 +294,22 @@ struct usb_ep *usb_ep_autoconfig ( /* Second, look at endpoints until an unclaimed one looks usable */ list_for_each_entry (ep, &gadget->ep_list, ep_list) { - if (ep_matches (gadget, ep, desc)) - return ep; - } +#if (defined(CONFIG_ARCH_U8500) && !defined(CONFIG_MUSB_PIO_ONLY)) + if (!strcmp(((gadget->dev).driver)->name, "g_file_storage")) { + if ((strcmp(ep->name, "ep1in") == 0) || + (strcmp(ep->name, "ep1out") == 0)) { + if (ep_matches (gadget, ep, desc)) + return ep; + } + } else { +#endif + if (ep_matches (gadget, ep, desc)) + return ep; +#if (defined(CONFIG_ARCH_U8500) && !defined(CONFIG_MUSB_PIO_ONLY)) + } +#endif + } /* Fail */ return NULL; } diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c index d47a123f15a..7cc8228bf49 100644 --- a/drivers/usb/gadget/f_acm.c +++ b/drivers/usb/gadget/f_acm.c @@ -17,6 +17,7 @@ #include <linux/slab.h> #include <linux/kernel.h> #include <linux/device.h> +#include <linux/usb/android_composite.h> #include "u_serial.h" #include "gadget_chips.h" @@ -111,7 +112,7 @@ acm_iad_descriptor = { .bInterfaceCount = 2, // control + data .bFunctionClass = USB_CLASS_COMM, .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, - .bFunctionProtocol = USB_CDC_PROTO_NONE, + .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER, /* .iFunction = DYNAMIC */ }; @@ -405,10 +406,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 +419,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 @@ -782,3 +783,110 @@ int acm_bind_config(struct usb_configuration *c, u8 port_num) kfree(acm); return status; } + +#ifdef CONFIG_USB_ANDROID_ACM + +int acm_function_bind_config(struct usb_configuration *c) +{ + int ret = acm_bind_config(c, 0); + if (ret == 0) + gserial_setup(c->cdev->gadget, 1); + return ret; +} + +static int acm_function_rebind_config(struct usb_configuration *c, + struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_acm *acm = func_to_acm(f); + int status1, status2 = 0; + struct usb_descriptor_header **desc = f->descriptors; + + /* re-allocate instance-specific interface IDs, and patch descriptors */ + status1 = usb_interface_id(c, f); + if (status1 < 0) + goto fail; + acm->ctrl_id = status1; + + acm_iad_descriptor.bFirstInterface = status1; + acm_control_interface_desc.bInterfaceNumber = status1; + acm_union_desc .bMasterInterface0 = status1; + + ((struct usb_interface_assoc_descriptor *)desc[0]) + ->bFirstInterface = status1; + ((struct usb_interface_descriptor *)desc[1]) + ->bInterfaceNumber = status1; + ((struct usb_cdc_union_desc *)desc[5]) + ->bMasterInterface0 = status1; + + status2 = usb_interface_id(c, f); + if (status2 < 0) + goto fail; + acm->data_id = status2; + + acm_data_interface_desc.bInterfaceNumber = status2; + acm_union_desc.bSlaveInterface0 = status2; + acm_call_mgmt_descriptor.bDataInterface = status2; + + ((struct usb_interface_descriptor *)desc[7]) + ->bInterfaceNumber = status2; + ((struct usb_cdc_union_desc *)desc[5]) + ->bSlaveInterface0 = status2; + ((struct usb_cdc_call_mgmt_descriptor *)desc[3]) + ->bDataInterface = status2; + + if (gadget_is_dualspeed(c->cdev->gadget)) { + desc = f->hs_descriptors; + + ((struct usb_interface_assoc_descriptor *)desc[0]) + ->bFirstInterface = status1; + ((struct usb_interface_descriptor *)desc[1]) + ->bInterfaceNumber = status1; + ((struct usb_cdc_union_desc *)desc[5]) + ->bMasterInterface0 = status1; + + ((struct usb_interface_descriptor *)desc[7]) + ->bInterfaceNumber = status2; + ((struct usb_cdc_union_desc *)desc[5]) + ->bSlaveInterface0 = status2; + ((struct usb_cdc_call_mgmt_descriptor *)desc[3]) + ->bDataInterface = status2; + } + + return 0; + +fail: + if (acm->notify_req) + gs_free_req(acm->notify, acm->notify_req); + + /* we might as well release our claims on endpoints */ + if (acm->notify) + acm->notify->driver_data = NULL; + if (acm->port.out) + acm->port.out->driver_data = NULL; + if (acm->port.in) + acm->port.in->driver_data = NULL; + + ERROR(cdev, "%s/%p: can't re-bind, err %d %d\n", + f->name, f, status1, status2); + + if (status2 < 0) + status1 = status2; + return status1; +} + +static struct android_usb_function acm_function = { + .name = "acm", + .bind_config = acm_function_bind_config, + .rebind_config = acm_function_rebind_config, +}; + +static int __init init(void) +{ + printk(KERN_INFO "f_acm init\n"); + android_register_function(&acm_function); + return 0; +} +module_init(init); + +#endif /* CONFIG_USB_ANDROID_ACM */ diff --git a/drivers/usb/gadget/f_adb.c b/drivers/usb/gadget/f_adb.c new file mode 100644 index 00000000000..1319b158d72 --- /dev/null +++ b/drivers/usb/gadget/f_adb.c @@ -0,0 +1,670 @@ +/* + * 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. + * + */ + +/* #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/device.h> +#include <linux/miscdevice.h> + +#include <linux/usb/android_composite.h> + +#define BULK_BUFFER_SIZE 4096 + +/* number of tx requests to allocate */ +#define TX_REQ_MAX 4 + +static const char 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, +}; + + +/* temporary variable used between adb_open() and adb_gadget_bind() */ +static struct adb_dev *_adb_dev; + +static atomic_t adb_enable_excl; + +static inline struct adb_dev *func_to_dev(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 _lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void _unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +void 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 *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; + + 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 __init 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, 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, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_in; + 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_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int r = count, xfer; + int ret; + + DBG(cdev, "adb_read(%d)\n", count); + + if (count > BULK_BUFFER_SIZE) + return -EINVAL; + + if (_lock(&dev->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(dev->online || dev->error)) { + DBG(cdev, "adb_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + (dev->online || dev->error)); + if (ret < 0) { + _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) { + DBG(cdev, "adb_read: failed to queue req %p (%d)\n", req, ret); + r = -EIO; + dev->error = 1; + 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) { + dev->error = 1; + r = ret; + 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; + + DBG(cdev, "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: + _unlock(&dev->read_excl); + DBG(cdev, "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_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + DBG(cdev, "adb_write(%d)\n", count); + + if (_lock(&dev->write_excl)) + return -EBUSY; + + while (count > 0) { + if (dev->error) { + DBG(cdev, "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 = req_get(dev, &dev->tx_idle)) || dev->error)); + + if (ret < 0) { + r = ret; + break; + } + + if (req != 0) { + 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_ATOMIC); + if (ret < 0) { + DBG(cdev, "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) + req_put(dev, &dev->tx_idle, req); + + _unlock(&dev->write_excl); + DBG(cdev, "adb_write returning %d\n", r); + return r; +} + +static int adb_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "adb_open\n"); + if (_lock(&_adb_dev->open_excl)) + return -EBUSY; + + fp->private_data = _adb_dev; + + /* clear the error latch */ + _adb_dev->error = 0; + + return 0; +} + +static int adb_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "adb_release\n"); + _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 = shortname, + .fops = &adb_fops, +}; + +static int adb_enable_open(struct inode *ip, struct file *fp) +{ + if (atomic_inc_return(&adb_enable_excl) != 1) { + atomic_dec(&adb_enable_excl); + return -EBUSY; + } + + printk(KERN_INFO "enabling adb\n"); + android_enable_function(&_adb_dev->function, 1); + + return 0; +} + +static int adb_enable_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "disabling adb\n"); + android_enable_function(&_adb_dev->function, 0); + atomic_dec(&adb_enable_excl); + return 0; +} + +static const struct file_operations adb_enable_fops = { + .owner = THIS_MODULE, + .open = adb_enable_open, + .release = adb_enable_release, +}; + +static struct miscdevice adb_enable_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "android_adb_enable", + .fops = &adb_enable_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_dev(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 = 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_dev(f); + struct usb_request *req; + + spin_lock_irq(&dev->lock); + + adb_request_free(dev->rx_req, dev->ep_out); + while ((req = req_get(dev, &dev->tx_idle))) + adb_request_free(req, dev->ep_in); + + dev->online = 0; + dev->error = 1; + spin_unlock_irq(&dev->lock); + + misc_deregister(&adb_device); + misc_deregister(&adb_enable_device); + kfree(_adb_dev); + _adb_dev = NULL; +} + +static int adb_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct adb_dev *dev = func_to_dev(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_dev(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "adb_function_disable\n"); + 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; + int ret; + + printk(KERN_INFO "adb_bind_config\n"); + + 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); + + 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; + + /* start disabled */ + dev->function.disabled = 1; + + /* _adb_dev must be set before calling usb_gadget_register_driver */ + _adb_dev = dev; + + ret = misc_register(&adb_device); + if (ret) + goto err1; + ret = misc_register(&adb_enable_device); + if (ret) + goto err2; + + ret = usb_add_function(c, &dev->function); + if (ret) + goto err3; + + return 0; + +err3: + misc_deregister(&adb_enable_device); +err2: + misc_deregister(&adb_device); +err1: + kfree(dev); + printk(KERN_ERR "adb gadget driver failed to initialize\n"); + return ret; +} + +static int adb_rebind_config(struct usb_configuration *c, + struct usb_function *f) +{ + int id; + + /* re-allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + adb_interface_desc.bInterfaceNumber = id; + + return 0; +} + +static struct android_usb_function adb_function = { + .name = "adb", + .bind_config = adb_bind_config, + .rebind_config = adb_rebind_config, +}; + +static int __init init(void) +{ + printk(KERN_INFO "f_adb init\n"); + android_register_function(&adb_function); + return 0; +} +module_init(init); diff --git a/drivers/usb/gadget/f_ecm.c b/drivers/usb/gadget/f_ecm.c index 544257a89ed..0f038a87a91 100644 --- a/drivers/usb/gadget/f_ecm.c +++ b/drivers/usb/gadget/f_ecm.c @@ -26,6 +26,11 @@ #include <linux/device.h> #include <linux/etherdevice.h> +#ifdef CONFIG_USB_ANDROID_ECM +#include <linux/usb/android_composite.h> +#include <linux/platform_device.h> +#endif + #include "u_ether.h" @@ -113,6 +118,19 @@ static inline unsigned ecm_bitrate(struct usb_gadget *g) /* interface descriptor: */ +static struct usb_interface_assoc_descriptor +ecm_iad_descriptor = { + .bLength = sizeof ecm_iad_descriptor, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + /* .bFirstInterface = DYNAMIC, */ + .bInterfaceCount = 2, /* control + data */ + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bFunctionProtocol = USB_CDC_PROTO_NONE, + /* .iFunction = DYNAMIC */ +}; + static struct usb_interface_descriptor ecm_control_intf = { .bLength = sizeof ecm_control_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -215,6 +233,7 @@ static struct usb_endpoint_descriptor fs_ecm_out_desc = { static struct usb_descriptor_header *ecm_fs_function[] = { /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_iad_descriptor, (struct usb_descriptor_header *) &ecm_control_intf, (struct usb_descriptor_header *) &ecm_header_desc, (struct usb_descriptor_header *) &ecm_union_desc, @@ -260,6 +279,7 @@ static struct usb_endpoint_descriptor hs_ecm_out_desc = { static struct usb_descriptor_header *ecm_hs_function[] = { /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_iad_descriptor, (struct usb_descriptor_header *) &ecm_control_intf, (struct usb_descriptor_header *) &ecm_header_desc, (struct usb_descriptor_header *) &ecm_union_desc, @@ -280,6 +300,7 @@ static struct usb_string ecm_string_defs[] = { [0].s = "CDC Ethernet Control Model (ECM)", [1].s = NULL /* DYNAMIC */, [2].s = "CDC Ethernet Data", + [3].s = "CDC ECM", { } /* end of list */ }; @@ -293,6 +314,10 @@ static struct usb_gadget_strings *ecm_strings[] = { NULL, }; +#ifdef CONFIG_USB_ANDROID_ECM +static struct usb_ether_platform_data *ecm_pdata; +#endif + /*-------------------------------------------------------------------------*/ static void ecm_do_notify(struct f_ecm *ecm) @@ -466,10 +491,10 @@ static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) usb_ep_disable(ecm->notify); } else { VDBG(cdev, "init ecm ctrl %d\n", intf); - ecm->notify_desc = ep_choose(cdev->gadget, - ecm->hs.notify, - ecm->fs.notify); } + ecm->notify_desc = ep_choose(cdev->gadget, + ecm->hs.notify, + ecm->fs.notify); usb_ep_enable(ecm->notify, ecm->notify_desc); ecm->notify->driver_data = ecm; @@ -485,11 +510,11 @@ static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (!ecm->port.in) { DBG(cdev, "init ecm\n"); - ecm->port.in = ep_choose(cdev->gadget, - ecm->hs.in, ecm->fs.in); - ecm->port.out = ep_choose(cdev->gadget, - ecm->hs.out, ecm->fs.out); } + ecm->port.in = ep_choose(cdev->gadget, + ecm->hs.in, ecm->fs.in); + ecm->port.out = ep_choose(cdev->gadget, + ecm->hs.out, ecm->fs.out); /* CDC Ethernet only sends data in non-default altsettings. * Changing altsettings resets filters, statistics, etc. @@ -610,6 +635,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) if (status < 0) goto fail; ecm->ctrl_id = status; + ecm_iad_descriptor.bFirstInterface = status; ecm_control_intf.bInterfaceNumber = status; ecm_union_desc.bMasterInterface0 = status; @@ -795,6 +821,13 @@ ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) return status; ecm_string_defs[1].id = status; ecm_desc.iMACAddress = status; + + /* IAD label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + ecm_string_defs[3].id = status; + ecm_iad_descriptor.iFunction = status; } /* allocate and initialize one new instance */ @@ -828,3 +861,138 @@ ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) } return status; } + +#ifdef CONFIG_USB_ANDROID_ECM +static int __init ecm_probe(struct platform_device *pdev) +{ + printk(KERN_INFO "ecm_probe\n"); + ecm_pdata = pdev->dev.platform_data; + return 0; +} + +static struct platform_driver ecm_platform_driver = { + .driver = { .name = "cdc_ethernet", }, +}; + +int ecm_function_bind_config(struct usb_configuration *c) +{ + int ret; + + if (!ecm_pdata) { + printk(KERN_ERR "ecm_pdata null in ecm_function_bind_config\n"); + return -1; + } + + printk(KERN_INFO + "ecm_function_bind_config MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", + ecm_pdata->ethaddr[0], ecm_pdata->ethaddr[1], + ecm_pdata->ethaddr[2], ecm_pdata->ethaddr[3], + ecm_pdata->ethaddr[4], ecm_pdata->ethaddr[5]); + + ret = gether_setup(c->cdev->gadget, ecm_pdata->ethaddr); + if (ret == 0) + ret = ecm_bind_config(c, ecm_pdata->ethaddr); + return ret; +} + +static int ecm_function_rebind_config(struct usb_configuration *c, + struct usb_function *f) +{ + + struct usb_composite_dev *cdev = c->cdev; + struct f_ecm *ecm = func_to_ecm(f); + int status1, status2 = 0; + struct usb_descriptor_header **desc = f->descriptors; + + /* re-allocate instance-specific interface IDs */ + status1 = usb_interface_id(c, f); + if (status1 < 0) + goto fail; + ecm->ctrl_id = status1; + + ecm_iad_descriptor.bFirstInterface = status1; + ecm_control_intf.bInterfaceNumber = status1; + ecm_union_desc.bMasterInterface0 = status1; + + ((struct usb_interface_assoc_descriptor *)desc[0]) + ->bFirstInterface = status1; + ((struct usb_interface_descriptor *)desc[1]) + ->bInterfaceNumber = status1; + ((struct usb_cdc_union_desc *)desc[3]) + ->bMasterInterface0 = status1; + + status2 = usb_interface_id(c, f); + if (status2 < 0) + goto fail; + ecm->data_id = status2; + + ecm_data_nop_intf.bInterfaceNumber = status2; + ecm_data_intf.bInterfaceNumber = status2; + ecm_union_desc.bSlaveInterface0 = status2; + + ((struct usb_interface_descriptor *)desc[6]) + ->bInterfaceNumber = status2; + ((struct usb_interface_descriptor *)desc[7]) + ->bInterfaceNumber = status2; + ((struct usb_cdc_union_desc *)desc[3]) + ->bSlaveInterface0 = status2; + + if (gadget_is_dualspeed(c->cdev->gadget)) { + desc = f->hs_descriptors; + ((struct usb_interface_assoc_descriptor *)desc[0]) + ->bFirstInterface = status1; + ((struct usb_interface_descriptor *)desc[1]) + ->bInterfaceNumber = status1; + ((struct usb_cdc_union_desc *)desc[3]) + ->bMasterInterface0 = status1; + + ((struct usb_interface_descriptor *)desc[6]) + ->bInterfaceNumber = status2; + ((struct usb_interface_descriptor *)desc[7]) + ->bInterfaceNumber = status2; + ((struct usb_cdc_union_desc *)desc[3]) + ->bSlaveInterface0 = status2; + } + + return 0; + +fail: + if (f->descriptors) + usb_free_descriptors(f->descriptors); + + if (ecm->notify_req) { + kfree(ecm->notify_req->buf); + usb_ep_free_request(ecm->notify, ecm->notify_req); + } + + /* we might as well release our claims on endpoints */ + if (ecm->notify) + ecm->notify->driver_data = NULL; + if (ecm->port.out) + ecm->port.out_ep->driver_data = NULL; + if (ecm->port.in) + ecm->port.in_ep->driver_data = NULL; + + ERROR(cdev, "%s: can't re-bind, err %d %d\n", + f->name, status1, status2); + + if (status2 < 0) + status1 = status2; + return status1; +} + +static struct android_usb_function ecm_function = { + .name = "cdc_ethernet", + .bind_config = ecm_function_bind_config, + .rebind_config = ecm_function_rebind_config, +}; + +static int __init init(void) +{ + printk(KERN_INFO "f_ecm init\n"); + platform_driver_probe(&ecm_platform_driver, ecm_probe); + android_register_function(&ecm_function); + return 0; +} +module_init(init); +#endif /* CONFIG_USB_ANDROID_ECM */ diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 4ce899c9b16..1a08f15c4b8 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -295,7 +295,12 @@ #include "gadget_chips.h" +#ifdef CONFIG_USB_ANDROID_MASS_STORAGE +#include <linux/usb/android_composite.h> +#include <linux/platform_device.h> +#define FUNCTION_NAME "usb_mass_storage" +#endif /*------------------------------------------------------------------------*/ @@ -408,6 +413,10 @@ struct fsg_config { u16 release; char can_stall; + +#ifdef CONFIG_USB_ANDROID_MASS_STORAGE + struct platform_device *pdev; +#endif }; @@ -454,19 +463,6 @@ static int exception_in_progress(struct fsg_common *common) return common->state > FSG_STATE_IDLE; } -/* Make bulk-out requests be divisible by the maxpacket size */ -static void set_bulk_out_req_length(struct fsg_common *common, - struct fsg_buffhd *bh, unsigned int length) -{ - unsigned int rem; - - bh->bulk_out_intended_length = length; - rem = length % common->bulk_out_maxpacket; - if (rem > 0) - length += common->bulk_out_maxpacket - rem; - bh->outreq->length = length; -} - /*-------------------------------------------------------------------------*/ static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) @@ -564,10 +560,9 @@ static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) struct fsg_buffhd *bh = req->context; dump_msg(common, "bulk-out", req->buf, req->actual); - if (req->status || req->actual != bh->bulk_out_intended_length) + if (req->status || req->actual != req->length) DBG(common, "%s --> %d, %u/%u\n", __func__, - req->status, req->actual, - bh->bulk_out_intended_length); + req->status, req->actual, req->length); if (req->status == -ECONNRESET) /* Request was cancelled */ usb_ep_fifo_flush(ep); @@ -609,6 +604,7 @@ static int fsg_setup(struct usb_function *f, /* Raise an exception to stop the current operation * and reinitialize our state. */ DBG(fsg, "bulk reset request\n"); + fsg->common->ep0req->length = 0; raise_exception(fsg->common, FSG_STATE_RESET); return DELAYED_STATUS; @@ -939,7 +935,6 @@ static int do_write(struct fsg_common *common) /* amount is always divisible by 512, hence by * the bulk-out maxpacket size */ bh->outreq->length = amount; - bh->bulk_out_intended_length = amount; bh->outreq->short_not_ok = 1; START_TRANSFER_OR(common, bulk_out, bh->outreq, &bh->outreq_busy, &bh->state) @@ -1343,30 +1338,7 @@ static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh) limit = 65535; /* Should really be FSG_BUFLEN */ } - /* No block descriptors */ - - /* The mode pages, in numerical order. The only page we support - * is the Caching page. */ - if (page_code == 0x08 || all_pages) { - valid_page = 1; - buf[0] = 0x08; /* Page code */ - buf[1] = 10; /* Page length */ - memset(buf+2, 0, 10); /* None of the fields are changeable */ - - if (!changeable_values) { - buf[2] = 0x04; /* Write cache enable, */ - /* Read cache not disabled */ - /* No cache retention priorities */ - put_unaligned_be16(0xffff, &buf[4]); - /* Don't disable prefetch */ - /* Minimum prefetch = 0 */ - put_unaligned_be16(0xffff, &buf[8]); - /* Maximum prefetch */ - put_unaligned_be16(0xffff, &buf[10]); - /* Maximum prefetch ceiling */ - } - buf += 12; - } + valid_page = 1; /* Check that a valid page was requested and the mode data length * isn't too long. */ @@ -1602,7 +1574,6 @@ static int throw_away_data(struct fsg_common *common) /* amount is always divisible by 512, hence by * the bulk-out maxpacket size */ bh->outreq->length = amount; - bh->bulk_out_intended_length = amount; bh->outreq->short_not_ok = 1; START_TRANSFER_OR(common, bulk_out, bh->outreq, &bh->outreq_busy, &bh->state) @@ -2253,8 +2224,8 @@ static int get_next_command(struct fsg_common *common) } /* Queue a request to read a Bulk-only CBW */ - set_bulk_out_req_length(common, bh, USB_BULK_CB_WRAP_LEN); - bh->outreq->short_not_ok = 1; + bh->outreq->length = USB_BULK_CB_WRAP_LEN; + bh->outreq->short_not_ok = 0; START_TRANSFER_OR(common, bulk_out, bh->outreq, &bh->outreq_busy, &bh->state) /* Don't know what to do if common->fsg is NULL */ @@ -2717,7 +2688,13 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common, curlun->ro = lcfg->cdrom || lcfg->ro; curlun->removable = lcfg->removable; curlun->dev.release = fsg_lun_release; + +#ifdef CONFIG_USB_ANDROID_MASS_STORAGE + /* use "usb_mass_storage" platform device as parent */ + curlun->dev.parent = &cfg->pdev->dev; +#else curlun->dev.parent = &gadget->dev; +#endif /* curlun->dev.driver = &fsg_driver.driver; XXX */ dev_set_drvdata(&curlun->dev, &common->filesem); dev_set_name(&curlun->dev, @@ -3001,7 +2978,11 @@ static int fsg_add(struct usb_composite_dev *cdev, if (unlikely(!fsg)) return -ENOMEM; +#ifdef CONFIG_USB_ANDROID_MASS_STORAGE + fsg->function.name = FUNCTION_NAME; +#else fsg->function.name = FSG_DRIVER_DESC; +#endif fsg->function.strings = fsg_strings_array; fsg->function.bind = fsg_bind; fsg->function.unbind = fsg_unbind; @@ -3118,3 +3099,91 @@ fsg_common_from_params(struct fsg_common *common, return fsg_common_init(common, cdev, &cfg); } +#ifdef CONFIG_USB_ANDROID_MASS_STORAGE + +static struct fsg_config fsg_cfg; + +static int fsg_probe(struct platform_device *pdev) +{ + struct usb_mass_storage_platform_data *pdata = pdev->dev.platform_data; + int i, nluns; + + printk(KERN_INFO "fsg_probe pdev: %p, pdata: %p\n", pdev, pdata); + if (!pdata) + return -1; + + nluns = pdata->nluns; + if (nluns > FSG_MAX_LUNS) + nluns = FSG_MAX_LUNS; + fsg_cfg.nluns = nluns; + for (i = 0; i < nluns; i++) + fsg_cfg.luns[i].removable = 1; + + fsg_cfg.vendor_name = pdata->vendor; + fsg_cfg.product_name = pdata->product; + fsg_cfg.release = pdata->release; + fsg_cfg.can_stall = 0; + fsg_cfg.pdev = pdev; + + return 0; +} + +static struct platform_driver fsg_platform_driver = { + .driver = { .name = FUNCTION_NAME, }, + .probe = fsg_probe, +}; + +int mass_storage_bind_config(struct usb_configuration *c) +{ + struct fsg_common *common = fsg_common_init(NULL, c->cdev, &fsg_cfg); + if (IS_ERR(common)) + return -1; + return fsg_add(c->cdev, c, common); +} + +static int mass_storage_rebind_config(struct usb_configuration *c, + struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct usb_gadget *gadget = c->cdev->gadget; + int i; + + fsg->gadget = gadget; + + /* New interface */ + i = usb_interface_id(c, f); + if (i < 0) + return i; + fsg_intf_desc.bInterfaceNumber = i; + fsg->interface_number = i; + +#ifndef FSG_NO_OTG + ((struct usb_interface_assoc_descriptor *)f->descriptors[1] + ->bFirstInterface = i; +#else + ((struct usb_interface_assoc_descriptor *)f->descriptors[0]) + ->bFirstInterface = i; +#endif + + return 0; +} + +static struct android_usb_function mass_storage_function = { + .name = FUNCTION_NAME, + .bind_config = mass_storage_bind_config, + .rebind_config = mass_storage_rebind_config, +}; + +static int __init init(void) +{ + int rc; + printk(KERN_INFO "f_mass_storage init\n"); + rc = platform_driver_register(&fsg_platform_driver); + if (rc != 0) + return rc; + android_register_function(&mass_storage_function); + return 0; +}module_init(init); + +#endif /* CONFIG_USB_ANDROID_MASS_STORAGE */ + diff --git a/drivers/usb/gadget/f_mtp.c b/drivers/usb/gadget/f_mtp.c new file mode 100644 index 00000000000..2c61a261347 --- /dev/null +++ b/drivers/usb/gadget/f_mtp.c @@ -0,0 +1,1273 @@ +/* + * 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/kthread.h> +#include <linux/freezer.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/android_composite.h> +#include <linux/usb/f_mtp.h> + +#define 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 + +/* IO Thread commands */ +#define ANDROID_THREAD_QUIT 1 +#define ANDROID_THREAD_SEND_FILE 2 +#define ANDROID_THREAD_RECEIVE_FILE 3 + +/* 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 shortname[] = "mtp_usb"; + +struct mtp_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + /* appear as MTP or PTP when enumerating */ + int interface_mode; + + 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; + + struct list_head tx_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]; + struct usb_request *intr_req; + int rx_done; + + /* synchronize access to interrupt endpoint */ + struct mutex intr_mutex; + /* true if interrupt endpoint is busy */ + int intr_busy; + + /* for our file IO thread */ + struct task_struct *thread; + /* current command for IO thread (or zero for none) */ + int thread_command; + struct file *thread_file; + loff_t thread_file_offset; + size_t thread_file_length; + /* used to wait for thread to complete current command */ + struct completion thread_wait; + /* result from current command */ + int thread_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_dev(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 _lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void _unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void 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 *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; + + 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; + + DBG(dev->cdev, "mtp_complete_intr status: %d actual: %d\n", req->status, req->actual); + dev->intr_busy = 0; + if (req->status != 0) + dev->state = STATE_ERROR; + + wake_up(&dev->intr_wq); +} + +static int __init 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, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_in; + req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_out, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_out; + dev->rx_req[i] = req; + } + req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_intr; + dev->intr_req = 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 > 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; + 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 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); + + while (count > 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 = req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY)); + 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) { + 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) + 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; +} + +static int mtp_send_file(struct mtp_dev *dev, struct file *filp, + loff_t offset, size_t count) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + int r = count, xfer, ret; + + DBG(cdev, "mtp_send_file(%lld %d)\n", offset, count); + + while (count > 0) { + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY); + if (!req) { + r = ret; + break; + } + + if (count > BULK_BUFFER_SIZE) + xfer = BULK_BUFFER_SIZE; + else + xfer = count; + ret = vfs_read(filp, req->buf, xfer, &offset); + if (ret < 0) { + r = ret; + break; + } + xfer = ret; + + 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); + 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) + req_put(dev, &dev->tx_idle, req); + + DBG(cdev, "mtp_write returning %d\n", r); + return r; +} + +static int mtp_receive_file(struct mtp_dev *dev, struct file *filp, + loff_t offset, size_t count) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *read_req = NULL, *write_req = NULL; + int r = count; + int ret; + int cur_buf = 0; + + DBG(cdev, "mtp_receive_file(%d)\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 > BULK_BUFFER_SIZE + ? 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; + } + count -= ret; + } + + 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 (ret < 0 || dev->state != STATE_BUSY) { + r = ret; + break; + } + count -= read_req->actual; + write_req = read_req; + read_req = NULL; + } + } + + DBG(cdev, "mtp_read returning %d\n", r); + return r; +} + +/* Kernel thread for handling file IO operations */ +static int mtp_thread(void *data) +{ + struct mtp_dev *dev = (struct mtp_dev *)data; + struct usb_composite_dev *cdev = dev->cdev; + int flags; + + DBG(cdev, "mtp_thread started\n"); + + while (1) { + /* wait for a command */ + while (1) { + try_to_freeze(); + set_current_state(TASK_INTERRUPTIBLE); + if (dev->thread_command != 0) + break; + schedule(); + } + __set_current_state(TASK_RUNNING); + + if (dev->thread_command == ANDROID_THREAD_QUIT) { + DBG(cdev, "ANDROID_THREAD_QUIT\n"); + dev->thread_result = 0; + goto done; + } + + if (dev->thread_command == ANDROID_THREAD_SEND_FILE) + flags = O_RDONLY | O_LARGEFILE; + else + flags = O_WRONLY | O_LARGEFILE | O_CREAT; + + if (dev->thread_command == ANDROID_THREAD_SEND_FILE) { + dev->thread_result = mtp_send_file(dev, + dev->thread_file, + dev->thread_file_offset, + dev->thread_file_length); + } else { + dev->thread_result = mtp_receive_file(dev, + dev->thread_file, + dev->thread_file_offset, + dev->thread_file_length); + } + + if (dev->thread_file) { + fput(dev->thread_file); + dev->thread_file = NULL; + } + dev->thread_command = 0; + complete(&dev->thread_wait); + } + +done: + DBG(cdev, "android_thread done\n"); + complete_and_exit(&dev->thread_wait, 0); +} + +static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event) +{ + struct usb_request *req; + 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; + + mutex_lock(&dev->intr_mutex); + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->intr_wq, !dev->intr_busy || dev->state == STATE_OFFLINE); + if (ret < 0) + goto done; + if (dev->state == STATE_OFFLINE) { + ret = -ENODEV; + goto done; + } + req = dev->intr_req; + if (copy_from_user(req->buf, (void __user *)event->data, length)) { + ret = -EFAULT; + goto done; + } + req->length = length; + dev->intr_busy = 1; + ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL); + if (ret) + dev->intr_busy = 0; + +done: + mutex_unlock(&dev->intr_mutex); + 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; + + switch (code) { + case MTP_SEND_FILE: + case MTP_RECEIVE_FILE: + { + struct mtp_file_range mfr; + + 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); + + if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) { + ret = -EFAULT; + goto fail; + } + filp = fget(mfr.fd); + if (!filp) { + ret = -EBADF; + goto fail; + } + + dev->thread_file = filp; + dev->thread_file_offset = mfr.offset; + dev->thread_file_length = mfr.length; + + if (code == MTP_SEND_FILE) + dev->thread_command = ANDROID_THREAD_SEND_FILE; + else + dev->thread_command = ANDROID_THREAD_RECEIVE_FILE; + + /* wake up the thread */ + init_completion(&dev->thread_wait); + wake_up_process(dev->thread); + + /* wait for the thread to complete the command */ + wait_for_completion(&dev->thread_wait); + ret = dev->thread_result; + DBG(dev->cdev, "thread returned %d\n", ret); + break; + } + case MTP_SET_INTERFACE_MODE: + if (value == MTP_INTERFACE_MODE_MTP || + value == MTP_INTERFACE_MODE_PTP) { + dev->interface_mode = value; + if (value == MTP_INTERFACE_MODE_PTP) { + 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; + } + ret = 0; + } + 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))) + return -EFAULT; + else + return mtp_send_event(dev, &event); + } + } + +fail: + if (filp) + fput(filp); + 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); + 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 (_lock(&_mtp_dev->open_excl)) + return -EBUSY; + + _mtp_dev->thread = kthread_create(mtp_thread, _mtp_dev, "f_mtp"); + if (IS_ERR(_mtp_dev->thread)) + return -ENOMEM; + + /* 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"); + + /* tell the thread to quit */ + if (_mtp_dev->thread) { + _mtp_dev->thread_command = ANDROID_THREAD_QUIT; + init_completion(&_mtp_dev->thread_wait); + wake_up_process(_mtp_dev->thread); + wait_for_completion(&_mtp_dev->thread_wait); + } + + _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 = shortname, + .fops = &mtp_fops, +}; + +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_dev(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 = 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_dev(f); + struct usb_request *req; + int i; + + spin_lock_irq(&dev->lock); + while ((req = 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); + mtp_request_free(dev->intr_req, dev->ep_intr); + dev->state = STATE_OFFLINE; + spin_unlock_irq(&dev->lock); + wake_up(&dev->intr_wq); + + misc_deregister(&mtp_device); + kfree(_mtp_dev); + _mtp_dev = NULL; +} + +static int mtp_function_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct mtp_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = dev->cdev; + 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; + + /* do nothing if we are disabled */ + if (dev->function.disabled) + return value; + + VDBG(cdev, "mtp_function_setup " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + /* Handle MTP OS string */ + if (dev->interface_mode == MTP_INTERFACE_MODE_MTP + && 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); + /* return here since composite.c will send for us */ + return value; + } + 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 (dev->interface_mode == MTP_INTERFACE_MODE_MTP + && 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); + } + } + 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_BUSY + || 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__); + } + + 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 mtp_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct mtp_dev *dev = func_to_dev(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_dev(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); + wake_up(&dev->intr_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int mtp_bind_config(struct usb_configuration *c) +{ + struct mtp_dev *dev; + int ret; + + printk(KERN_INFO "mtp_bind_config\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* 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; + } + + spin_lock_init(&dev->lock); + init_completion(&dev->thread_wait); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + init_waitqueue_head(&dev->intr_wq); + atomic_set(&dev->open_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + mutex_init(&dev->intr_mutex); + + dev->cdev = c->cdev; + dev->function.name = "mtp"; + dev->function.strings = mtp_strings, + 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.setup = mtp_function_setup; + dev->function.set_alt = mtp_function_set_alt; + dev->function.disable = mtp_function_disable; + + /* MTP mode by default */ + dev->interface_mode = MTP_INTERFACE_MODE_MTP; + + /* _mtp_dev must be set before calling usb_gadget_register_driver */ + _mtp_dev = dev; + + ret = misc_register(&mtp_device); + if (ret) + goto err1; + + ret = usb_add_function(c, &dev->function); + if (ret) + goto err2; + + return 0; + +err2: + misc_deregister(&mtp_device); +err1: + kfree(dev); + printk(KERN_ERR "mtp gadget driver failed to initialize\n"); + return ret; +} + +static struct android_usb_function mtp_function = { + .name = "mtp", + .bind_config = mtp_bind_config, +}; + +static int __init init(void) +{ + printk(KERN_INFO "f_mtp init\n"); + android_register_function(&mtp_function); + return 0; +} +module_init(init); diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index 882484a4039..6e6489b4958 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -26,8 +26,9 @@ #include <linux/slab.h> #include <linux/kernel.h> -#include <linux/device.h> +#include <linux/platform_device.h> #include <linux/etherdevice.h> +#include <linux/usb/android_composite.h> #include <asm/atomic.h> @@ -129,9 +130,16 @@ static struct usb_interface_descriptor rndis_control_intf = { /* .bInterfaceNumber = DYNAMIC */ /* status endpoint is optional; this could be patched later */ .bNumEndpoints = 1, +#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS + /* "Wireless" RNDIS; auto-detected by Windows */ + .bInterfaceClass = USB_CLASS_WIRELESS_CONTROLLER, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 3, +#else .bInterfaceClass = USB_CLASS_COMM, .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR, +#endif /* .iInterface = DYNAMIC */ }; @@ -190,9 +198,16 @@ rndis_iad_descriptor = { .bFirstInterface = 0, /* XXX, hardcoded */ .bInterfaceCount = 2, // control + data +#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS + /* "Wireless" RNDIS; auto-detected by Windows */ + .bFunctionClass = USB_CLASS_WIRELESS_CONTROLLER, + .bFunctionSubClass = 1, + .bFunctionProtocol = 3, +#else .bFunctionClass = USB_CLASS_COMM, - .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET, - .bFunctionProtocol = USB_CDC_PROTO_NONE, + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, + .bFunctionProtocol = USB_CDC_ACM_PROTO_VENDOR, +#endif /* .iFunction = DYNAMIC */ }; @@ -304,6 +319,10 @@ static struct usb_gadget_strings *rndis_strings[] = { NULL, }; +#ifdef CONFIG_USB_ANDROID_RNDIS +static struct usb_ether_platform_data *rndis_pdata; +#endif + /*-------------------------------------------------------------------------*/ static struct sk_buff *rndis_add_header(struct gether *port, @@ -487,10 +506,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; @@ -504,11 +523,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; @@ -590,6 +609,33 @@ static void rndis_close(struct gether *geth) rndis_signal_disconnect(rndis->config); } +static rndis_release(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_rndis *rndis = func_to_rndis(f); + int status; + struct usb_ep *ep; + + if (gadget_is_dualspeed(c->cdev->gadget) && f->hs_descriptors) + usb_free_descriptors(f->hs_descriptors); + if (f->descriptors) + usb_free_descriptors(f->descriptors); + + if (rndis->notify_req) { + kfree(rndis->notify_req->buf); + usb_ep_free_request(rndis->notify, rndis->notify_req); + } + + /* we might as well release our claims on endpoints */ + if (rndis->notify) + rndis->notify->driver_data = NULL; + if (rndis->port.out) + rndis->port.out_ep->driver_data = NULL; + if (rndis->port.in) + rndis->port.in_ep->driver_data = NULL; + +} + /*-------------------------------------------------------------------------*/ /* ethernet function driver setup/binding */ @@ -707,11 +753,12 @@ 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; +#ifdef CONFIG_USB_ANDROID_RNDIS + if (rndis_pdata) { + if (rndis_set_param_vendor(rndis->config, rndis_pdata->vendorID, + rndis_pdata->vendorDescr)) + goto fail; + } #endif /* NOTE: all that is done without knowing or caring about @@ -726,23 +773,7 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) return 0; fail: - if (gadget_is_dualspeed(c->cdev->gadget) && f->hs_descriptors) - usb_free_descriptors(f->hs_descriptors); - if (f->descriptors) - usb_free_descriptors(f->descriptors); - - if (rndis->notify_req) { - kfree(rndis->notify_req->buf); - usb_ep_free_request(rndis->notify, rndis->notify_req); - } - - /* we might as well release our claims on endpoints */ - if (rndis->notify) - rndis->notify->driver_data = NULL; - if (rndis->port.out) - rndis->port.out_ep->driver_data = NULL; - if (rndis->port.in) - rndis->port.in_ep->driver_data = NULL; + rndis_release(c, f); ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); @@ -850,6 +881,11 @@ rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) rndis->port.func.setup = rndis_setup; rndis->port.func.disable = rndis_disable; +#ifdef CONFIG_USB_ANDROID_RNDIS + /* start disabled */ + rndis->port.func.disabled = 1; +#endif + status = usb_add_function(c, &rndis->port.func); if (status) { kfree(rndis); @@ -858,3 +894,122 @@ fail: } return status; } + +#ifdef CONFIG_USB_ANDROID_RNDIS +#include "rndis.c" + +static int rndis_probe(struct platform_device *pdev) +{ + rndis_pdata = pdev->dev.platform_data; + return 0; +} + +static struct platform_driver rndis_platform_driver = { + .driver = { .name = "rndis", }, + .probe = rndis_probe, +}; + +int rndis_function_bind_config(struct usb_configuration *c) +{ + int ret; + + if (!rndis_pdata) { + printk(KERN_ERR "rndis_pdata null in rndis_function_bind_config\n"); + return -1; + } + + printk(KERN_INFO + "rndis_function_bind_config MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", + rndis_pdata->ethaddr[0], rndis_pdata->ethaddr[1], + rndis_pdata->ethaddr[2], rndis_pdata->ethaddr[3], + rndis_pdata->ethaddr[4], rndis_pdata->ethaddr[5]); + + ret = gether_setup(c->cdev->gadget, rndis_pdata->ethaddr); + if (ret == 0) + ret = rndis_bind_config(c, rndis_pdata->ethaddr); + return ret; +} + +static int rndis_function_rebind_config(struct usb_configuration *c, + struct usb_function *f) +{ + + struct usb_composite_dev *cdev = c->cdev; + struct f_rndis *rndis = func_to_rndis(f); + int status1, status2 = 0; + struct usb_descriptor_header **desc = f->descriptors; + + /* re-allocate instance-specific interface IDs */ + status1 = usb_interface_id(c, f); + + if (status1 < 0) + goto fail; + rndis->ctrl_id = status1; + + rndis_iad_descriptor.bFirstInterface = status1; + rndis_control_intf.bInterfaceNumber = status1; + rndis_union_desc.bMasterInterface0 = status1; + + ((struct usb_interface_assoc_descriptor *)desc[0]) + ->bFirstInterface = status1; + ((struct usb_interface_descriptor *)desc[1]) + ->bInterfaceNumber = status1; + ((struct usb_cdc_union_desc *)desc[5]) + ->bMasterInterface0 = status1; + + status2 = usb_interface_id(c, f); + if (status2 < 0) + goto fail; + rndis->data_id = status2; + + rndis_data_intf.bInterfaceNumber = status2; + rndis_union_desc.bSlaveInterface0 = status2; + + ((struct usb_interface_descriptor *)desc[7]) + ->bInterfaceNumber = status2; + ((struct usb_cdc_union_desc *)desc[5]) + ->bSlaveInterface0 = status2; + + if (gadget_is_dualspeed(c->cdev->gadget)) { + desc = f->hs_descriptors; + ((struct usb_interface_assoc_descriptor *) + desc[0])->bFirstInterface = status1; + ((struct usb_interface_descriptor *) + desc[1])->bInterfaceNumber = status1; + ((struct usb_cdc_union_desc *) + desc[3])->bMasterInterface0 = status1; + + ((struct usb_interface_descriptor *) + desc[7])->bInterfaceNumber = status2; + ((struct usb_cdc_union_desc *) + desc[5])->bSlaveInterface0 = status2; + } + + return 0; + +fail: + rndis_release(c, f); + + ERROR(cdev, "%s: can't re-bind, err %d %d\n", f->name, + status1, status2); + if (status2 < 0) + status1 = status2; + return status1; +} + +static struct android_usb_function rndis_function = { + .name = "rndis", + .bind_config = rndis_function_bind_config, + .rebind_config = rndis_function_rebind_config, +}; + +static int __init init(void) +{ + printk(KERN_INFO "f_rndis init\n"); + platform_driver_register(&rndis_platform_driver); + android_register_function(&rndis_function); + return 0; +} +module_init(init); + +#endif /* CONFIG_USB_ANDROID_RNDIS */ diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c index b49d86e3e45..8ef770214b2 100644 --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -486,19 +486,6 @@ static int exception_in_progress(struct fsg_dev *fsg) return (fsg->state > FSG_STATE_IDLE); } -/* Make bulk-out requests be divisible by the maxpacket size */ -static void set_bulk_out_req_length(struct fsg_dev *fsg, - struct fsg_buffhd *bh, unsigned int length) -{ - unsigned int rem; - - bh->bulk_out_intended_length = length; - rem = length % fsg->bulk_out_maxpacket; - if (rem > 0) - length += fsg->bulk_out_maxpacket - rem; - bh->outreq->length = length; -} - static struct fsg_dev *the_fsg; static struct usb_gadget_driver fsg_driver; @@ -719,10 +706,10 @@ static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) struct fsg_buffhd *bh = req->context; dump_msg(fsg, "bulk-out", req->buf, req->actual); - if (req->status || req->actual != bh->bulk_out_intended_length) + if (req->status || req->actual != req->length) DBG(fsg, "%s --> %d, %u/%u\n", __func__, req->status, req->actual, - bh->bulk_out_intended_length); + req->length); if (req->status == -ECONNRESET) // Request was cancelled usb_ep_fifo_flush(ep); @@ -1337,8 +1324,7 @@ static int do_write(struct fsg_dev *fsg) /* amount is always divisible by 512, hence by * the bulk-out maxpacket size */ - bh->outreq->length = bh->bulk_out_intended_length = - amount; + bh->outreq->length = amount; bh->outreq->short_not_ok = 1; start_transfer(fsg, fsg->bulk_out, bh->outreq, &bh->outreq_busy, &bh->state); @@ -1909,6 +1895,18 @@ static int halt_bulk_in_endpoint(struct fsg_dev *fsg) return -EINTR; rc = usb_ep_set_halt(fsg->bulk_in); } +#ifdef CONFIG_ARCH_U8500 + /* temporary HACK: There is a problem using mass storage with + * the musb driver. The problem is that the status command + * wrapper block gets queued in hardware before the clear-stall + * is executed. When the clear stall gets executed the status + * command wrapper block gets dropped and the USB host OS + * starts hanging. We are not sure where the proper place to + * fix this bug is. Until further, add a delay here so that + * the USB host gets time to execute the clear-stall request. + */ + msleep_interruptible(100); /* temporary HACK */ +#endif return rc; } @@ -1998,8 +1996,7 @@ static int throw_away_data(struct fsg_dev *fsg) /* amount is always divisible by 512, hence by * the bulk-out maxpacket size */ - bh->outreq->length = bh->bulk_out_intended_length = - amount; + bh->outreq->length = amount; bh->outreq->short_not_ok = 1; start_transfer(fsg, fsg->bulk_out, bh->outreq, &bh->outreq_busy, &bh->state); @@ -2676,8 +2673,8 @@ static int get_next_command(struct fsg_dev *fsg) } /* Queue a request to read a Bulk-only CBW */ - set_bulk_out_req_length(fsg, bh, USB_BULK_CB_WRAP_LEN); - bh->outreq->short_not_ok = 1; + bh->outreq->length = USB_BULK_CB_WRAP_LEN; + bh->outreq->short_not_ok = 0; start_transfer(fsg, fsg->bulk_out, bh->outreq, &bh->outreq_busy, &bh->state); diff --git a/drivers/usb/gadget/multi.c b/drivers/usb/gadget/multi.c index a930d7fd7e7..fe1ed688518 100644 --- a/drivers/usb/gadget/multi.c +++ b/drivers/usb/gadget/multi.c @@ -107,7 +107,11 @@ static struct usb_otg_descriptor otg_descriptor = { /* REVISIT SRP-only hardware is possible, although * it would not be called "OTG" ... */ +#ifndef USB_OTG_20 .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +#else + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP | USB_OTG_ADP, +#endif }; static const struct usb_descriptor_header *otg_desc[] = { diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c index 04c462ff0ea..744291dd5ab 100644 --- a/drivers/usb/gadget/storage_common.c +++ b/drivers/usb/gadget/storage_common.c @@ -312,12 +312,6 @@ struct fsg_buffhd { #endif enum fsg_buffer_state state; struct fsg_buffhd *next; - - /* The NetChip 2280 is faster, and handles some protocol faults - * better, if we don't submit any short bulk-out read requests. - * So we will record the intended request length here. */ - unsigned int bulk_out_intended_length; - struct usb_request *inreq; int inreq_busy; struct usb_request *outreq; @@ -750,10 +744,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') diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c index 1da755a1c85..8bccb6211db 100644 --- a/drivers/usb/gadget/u_ether.c +++ b/drivers/usb/gadget/u_ether.c @@ -947,7 +947,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 3c8c0c9f9d7..99e4aa3b137 100644 --- a/drivers/usb/gadget/u_ether.h +++ b/drivers/usb/gadget/u_ether.h @@ -105,7 +105,7 @@ int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]); int ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]); int eem_bind_config(struct usb_configuration *c); -#ifdef USB_ETH_RNDIS +#if defined(USB_ETH_RNDIS) || defined(CONFIG_USB_ANDROID_RNDIS) int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]); diff --git a/drivers/usb/gadget/zero.c b/drivers/usb/gadget/zero.c index 807280d069f..a7c60c23265 100644 --- a/drivers/usb/gadget/zero.c +++ b/drivers/usb/gadget/zero.c @@ -292,7 +292,10 @@ static int __init zero_bind(struct usb_composite_dev *cdev) device_desc.iSerialNumber = id; setup_timer(&autoresume_timer, zero_autoresume, (unsigned long) cdev); - +#ifdef CONFIG_USB_OTG_20 + if (gadget_is_otg2(cdev->gadget)) + otg_descriptor.bcdOTG = __constant_cpu_to_le16(0x0200); +#endif /* Register primary, then secondary configuration. Note that * SH3 only allows one config... */ diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index cfd38edfcf9..8d5caddc03e 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -41,6 +41,7 @@ config USB_MUSB_SOC default y if ARCH_OMAP4 default y if (BF54x && !BF544) default y if (BF52x && !BF522 && !BF523) + default y if ARCH_U8500 comment "DaVinci 35x and 644x USB support" depends on USB_MUSB_HDRC && ARCH_DAVINCI_DMx @@ -57,6 +58,17 @@ comment "OMAP 44xx high speed USB support" comment "Blackfin high speed USB Support" depends on USB_MUSB_HDRC && ((BF54x && !BF544) || (BF52x && !BF522 && !BF523)) +comment "U8500 USB support" + depends on USB_MUSB_HDRC && ARCH_U8500 + +config U8500_USB_HS_OTG + boolean "USB HS-OTG support" + depends on USB_MUSB_HDRC && ARCH_U8500 + default y + help + Say Y here if you want to enable support for the U8500 USB + high speed on-the-go USB interface with external ULPI transceiver. + config USB_TUSB6010 boolean "TUSB 6010 support" depends on USB_MUSB_HDRC && !USB_MUSB_SOC diff --git a/drivers/usb/musb/Makefile b/drivers/usb/musb/Makefile index 9705f716386..bddb323c3f2 100644 --- a/drivers/usb/musb/Makefile +++ b/drivers/usb/musb/Makefile @@ -26,6 +26,10 @@ ifeq ($(CONFIG_ARCH_OMAP4),y) musb_hdrc-objs += omap2430.o endif +ifeq ($(CONFIG_ARCH_U8500),y) + musb_hdrc-objs += stm_musb.o +endif + ifeq ($(CONFIG_BF54x),y) musb_hdrc-objs += blackfin.o endif @@ -67,6 +71,7 @@ ifneq ($(CONFIG_MUSB_PIO_ONLY),y) endif endif endif + musb_hdrc-objs += stm_musb_dma.o endif diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 3b795c56221..ee10bf1f031 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -4,6 +4,7 @@ * Copyright 2005 Mentor Graphics Corporation * Copyright (C) 2005-2006 by Texas Instruments * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2009 ST Ericsson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -106,12 +107,16 @@ #endif #include "musb_core.h" - +#ifdef CONFIG_ARCH_U8500 +#include "ste_config.h" +#endif #ifdef CONFIG_ARCH_DAVINCI #include "davinci.h" #endif +#include <mach/stm_musb.h> + #define TA_WAIT_BCON(m) max_t(int, (m)->a_wait_bcon, OTG_TIME_A_WAIT_BCON) @@ -459,6 +464,29 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, DBG(3, "<== Power=%02x, DevCtl=%02x, int_usb=0x%x\n", power, devctl, int_usb); + /* + * XXX The following code has been inserted here as a temporary hack + * to get some USB events directly from the USB hardware. This code + * and callbacks should eventually be integrated into the generic + * USB gadget stack. + */ + if (!(devctl & MUSB_DEVCTL_HM)) { + if (int_usb & MUSB_INTR_RESET) { + if (power & MUSB_POWER_HSMODE) + ab8500_bm_usb_state_changed_wrapper( + AB8500_BM_USB_STATE_RESET_HS); + else + ab8500_bm_usb_state_changed_wrapper( + AB8500_BM_USB_STATE_RESET_FS); + } + if (int_usb & MUSB_INTR_RESUME) + ab8500_bm_usb_state_changed_wrapper( + AB8500_BM_USB_STATE_RESUME); + else if (int_usb & MUSB_INTR_SUSPEND) + ab8500_bm_usb_state_changed_wrapper( + AB8500_BM_USB_STATE_SUSPEND); + } + /* in host mode, the peripheral may issue remote wakeup. * in peripheral mode, the host may resume the link. * spurious RESUME irqs happen too, paired with SUSPEND. @@ -492,7 +520,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, (USB_PORT_STAT_C_SUSPEND << 16) | MUSB_PORT_STAT_RESUME; musb->rh_timer = jiffies - + msecs_to_jiffies(20); + + msecs_to_jiffies(30); musb->xceiv->state = OTG_STATE_A_HOST; musb->is_active = 1; @@ -987,6 +1015,10 @@ void musb_start(struct musb *musb) } musb_platform_enable(musb); musb_writeb(regs, MUSB_DEVCTL, devctl); +#if defined(CONFIG_PM) && defined(CONFIG_ARCH_U8500) + musb_save_context(musb); + clk_disable(musb->clock); +#endif } @@ -995,6 +1027,9 @@ static void musb_generic_disable(struct musb *musb) void __iomem *mbase = musb->mregs; u16 temp; +#if defined(CONFIG_PM) && defined(CONFIG_ARCH_U8500) + clk_enable(musb->clock); +#endif /* disable interrupts */ musb_writeb(mbase, MUSB_INTRUSBE, 0); musb_writew(mbase, MUSB_INTRTXE, 0); @@ -1008,6 +1043,9 @@ static void musb_generic_disable(struct musb *musb) temp = musb_readw(mbase, MUSB_INTRTX); temp = musb_readw(mbase, MUSB_INTRRX); +#if defined(CONFIG_PM) && defined(CONFIG_ARCH_U8500) + clk_disable(musb->clock); +#endif } /* @@ -1067,7 +1105,11 @@ static void musb_shutdown(struct platform_device *pdev) || defined(CONFIG_ARCH_OMAP4) static ushort __initdata fifo_mode = 4; #else +#ifndef CONFIG_ARCH_U8500 static ushort __initdata fifo_mode = 2; +#else +static ushort __initdata fifo_mode = 5; +#endif #endif /* "modprobe ... fifo_mode=1" etc */ @@ -1150,8 +1192,8 @@ static struct musb_fifo_cfg __initdata mode_4_cfg[] = { /* mode 5 - fits in 8KB */ static struct musb_fifo_cfg __initdata mode_5_cfg[] = { -{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, -{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, .mode = BUF_DOUBLE, }, { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, { .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, { .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, }, @@ -1536,7 +1578,7 @@ static int __init musb_core_init(u16 musb_type, struct musb *musb) /*-------------------------------------------------------------------------*/ #if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3430) || \ - defined(CONFIG_ARCH_OMAP4) + defined(CONFIG_ARCH_OMAP4) || defined(CONFIG_ARCH_U8500) static irqreturn_t generic_interrupt(int irq, void *__hci) { @@ -1591,6 +1633,14 @@ irqreturn_t musb_interrupt(struct musb *musb) } #endif + /** + * HACK for detecting the AX8817X series Ethernet over USB + * Adapters for U8500 platform + */ +#if (defined(CONFIG_ARCH_U8500) && defined(CONFIG_USB_NET_AX8817X)) + mdelay(10); +#endif + /* the core can interrupt us for multiple reasons; docs have * a generic interrupt flowchart to follow */ @@ -2417,26 +2467,42 @@ static int musb_suspend(struct device *dev) spin_lock_irqsave(&musb->lock, flags); if (is_peripheral_active(musb)) { - /* FIXME force disconnect unless we know USB will wake + /* + * FIXME force disconnect unless we know USB will wake * the system up quickly enough to respond ... + * For ux500 platform if usb is connected return busy + * state */ + if (musb->is_active == 1) { + spin_unlock_irqrestore(&musb->lock, flags); + return -EBUSY; + } } else if (is_host_active(musb)) { - /* we know all the children are suspended; sometimes + /* + * we know all the children are suspended; sometimes * they will even be wakeup-enabled. + * For ux500 platform if usb is connected return busy + * state */ + if (musb->is_active == 1) { + spin_unlock_irqrestore(&musb->lock, flags); + return -EBUSY; + } } +#ifndef CONFIG_ARCH_U8500 musb_save_context(musb); if (musb->set_clock) musb->set_clock(musb->clock, 0); else clk_disable(musb->clock); +#endif spin_unlock_irqrestore(&musb->lock, flags); return 0; } -static int musb_resume_noirq(struct device *dev) +static int musb_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct musb *musb = dev_to_musb(&pdev->dev); @@ -2444,12 +2510,14 @@ static int musb_resume_noirq(struct device *dev) if (!musb->clock) return 0; +#ifndef CONFIG_ARCH_U8500 if (musb->set_clock) musb->set_clock(musb->clock, 1); else clk_enable(musb->clock); musb_restore_context(musb); +#endif /* for static cmos like DaVinci, register values were preserved * unless for some reason the whole soc powered down or the USB @@ -2460,7 +2528,7 @@ static int musb_resume_noirq(struct device *dev) static const struct dev_pm_ops musb_dev_pm_ops = { .suspend = musb_suspend, - .resume_noirq = musb_resume_noirq, + .resume = musb_resume, }; #define MUSB_DEV_PM_OPS (&musb_dev_pm_ops) @@ -2513,10 +2581,18 @@ static int __init musb_init(void) return platform_driver_probe(&musb_driver, musb_probe); } +#ifndef CONFIG_ARCH_U8500 /* make us init after usbcore and i2c (transceivers, regulators, etc) * and before usb gadget and host-side drivers start to register */ fs_initcall(musb_init); +#else +/* with fs_initcall the dma controller driver was loaded after mentor IP + * driver so when DMA is enabled, it will break as DMA controller driver is + * not loaded. This has been done to correct the order + */ +module_init(musb_init); +#endif static void __exit musb_cleanup(void) { diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 91d67794e35..c037939a8a2 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -72,8 +72,6 @@ struct musb_ep; #include <linux/usb/hcd.h> #include "musb_host.h" - - #ifdef CONFIG_USB_MUSB_OTG #define is_peripheral_enabled(musb) ((musb)->board_mode != MUSB_HOST) @@ -204,6 +202,7 @@ enum musb_g_ep0_state { #define OTG_TIME_A_WAIT_BCON 1100 /* min 1 second */ #define OTG_TIME_A_AIDL_BDIS 200 /* min 200 msec */ #define OTG_TIME_B_ASE0_BRST 100 /* min 3.125 ms */ +#define USB_SUSP_DET_DURATION 5 /* suspend time 5ms */ /*************************** REGISTER ACCESS ********************************/ @@ -492,8 +491,10 @@ extern void musb_platform_save_context(struct musb *musb, extern void musb_platform_restore_context(struct musb *musb, struct musb_context_registers *musb_context); #else -#define musb_platform_save_context(m, x) do {} while (0) -#define musb_platform_restore_context(m, x) do {} while (0) +static inline void musb_platform_save_context(struct musb *musb, + struct musb_context_registers *musb_context) { } +static inline void musb_platform_restore_context(struct musb *musb, + struct musb_context_registers *musb_context) { } #endif #endif @@ -598,18 +599,31 @@ extern void musb_hnp_stop(struct musb *musb); extern int musb_platform_set_mode(struct musb *musb, u8 musb_mode); +#ifdef CONFIG_PM +void musb_save_context(struct musb *); +extern void musb_restore_context(struct musb *musb); +#endif + #if defined(CONFIG_USB_TUSB6010) || defined(CONFIG_BLACKFIN) || \ defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3) || \ - defined(CONFIG_ARCH_OMAP4) + defined(CONFIG_ARCH_OMAP4) || defined(CONFIG_ARCH_U8500) extern void musb_platform_try_idle(struct musb *musb, unsigned long timeout); #else -#define musb_platform_try_idle(x, y) do {} while (0) +static inline void musb_platform_try_idle(struct musb *musb, unsigned long timeout) { } #endif #if defined(CONFIG_USB_TUSB6010) || defined(CONFIG_BLACKFIN) extern int musb_platform_get_vbus_status(struct musb *musb); #else -#define musb_platform_get_vbus_status(x) 0 +static inline int musb_platform_get_vbus_status(struct musb *musb) { return 0; } +#endif + +#if defined(CONFIG_ARCH_U8500) +extern void musb_platform_device_en(int enable); +extern void musb_platform_session_req(void); +#else +static inline void musb_platform_device_en(int enable) { } +static inline void musb_platform_session_req(void) { } #endif extern int __init musb_platform_init(struct musb *musb, void *board_data); diff --git a/drivers/usb/musb/musb_dma.h b/drivers/usb/musb/musb_dma.h index 916065ba9e7..a114af6c330 100644 --- a/drivers/usb/musb/musb_dma.h +++ b/drivers/usb/musb/musb_dma.h @@ -36,7 +36,7 @@ #define __MUSB_DMA_H__ struct musb_hw_ep; - +struct musb_request; /* * DMA Controller Abstraction * @@ -91,6 +91,10 @@ struct musb_hw_ep; # endif #endif +#ifdef CONFIG_USB_U8500_DMA +#undef USE_MODE1 +#endif + /* * DMA channel status ... updated by the dma controller driver whenever that * status changes, and protected by the overall controller spinlock. @@ -155,6 +159,10 @@ dma_channel_status(struct dma_channel *c) * @channel_release: call this to release a DMA channel * @channel_abort: call this to abort a pending DMA transaction, * returning it to FREE (but allocated) state + * @is_compatible:allow dma code to indicate incompatibility + * with usb request. Gadget musb driver call this api, if + * available, before dma mappings to avoid any unnecessary + * mapping operations. * * Controllers manage dma channels. */ @@ -169,6 +177,8 @@ struct dma_controller { dma_addr_t dma_addr, u32 length); int (*channel_abort)(struct dma_channel *); + int (*is_compatible)(struct dma_channel *channel, + struct musb_request *); }; /* called after channel_program(), may indicate a fault */ diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 6fca870e957..9ac40c4bab9 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -5,6 +5,7 @@ * Copyright (C) 2005-2006 by Texas Instruments * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2009 MontaVista Software, Inc. <source@mvista.com> + * Copyright (C) 2009 ST Ericsson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -43,11 +44,14 @@ #include <linux/moduleparam.h> #include <linux/stat.h> #include <linux/dma-mapping.h> +#include <asm/cacheflush.h> #include <linux/slab.h> #include "musb_core.h" - - +#ifdef CONFIG_ARCH_U8500 +#include "ste_config.h" +#endif +#define USB_ENABLE_PHY 1 /* MUSB PERIPHERAL status 3-mar-2006: * * - EP0 seems solid. It passes both USBCV and usbtest control cases. @@ -91,6 +95,8 @@ */ /* ----------------------------------------------------------------------- */ +#define is_buffer_mapped(req) (is_dma_capable() && \ + (req->map_state != UN_MAPPED)) /* * Immediately complete a request. @@ -109,6 +115,7 @@ __acquires(ep->musb->lock) struct musb_request *req; struct musb *musb; int busy = ep->busy; + int dma_len; req = to_musb_request(request); @@ -119,20 +126,34 @@ __acquires(ep->musb->lock) ep->busy = 1; spin_unlock(&musb->lock); +#ifdef CONFIG_ARM + /* If DMA is enabled we need to flush all data we read. */ + if (ep->hw_ep->musb->controller->dma_mask != 0) + dmac_flush_range((const u8 *)req->request.buf, + (const u8 *)req->request.buf + req->request.length); +#endif +#ifdef CONFIG_USB_U8500_DMA + if (!(req->request.actual < ep->packet_sz) + && is_buffer_mapped(req) + && (ep->packet_sz >= DMA_PACKET_THRESHOLD)) { +#else if (is_dma_capable()) { - if (req->mapped) { +#endif + dma_len = req->request.actual - (req->request.actual % 512) ; + + if (req->map_state == MUSB_MAPPED) { dma_unmap_single(musb->controller, req->request.dma, - req->request.length, + req->request.actual - dma_len, req->tx ? DMA_TO_DEVICE : DMA_FROM_DEVICE); req->request.dma = DMA_ADDR_INVALID; - req->mapped = 0; - } else if (req->request.dma != DMA_ADDR_INVALID) + req->map_state = UN_MAPPED; + } else if (req->map_state == PRE_MAPPED) dma_sync_single_for_cpu(musb->controller, req->request.dma, - req->request.length, + req->request.actual - dma_len, req->tx ? DMA_TO_DEVICE : DMA_FROM_DEVICE); @@ -209,7 +230,20 @@ static void nuke(struct musb_ep *ep, const int status) static inline int max_ep_writesize(struct musb *musb, struct musb_ep *ep) { +#ifdef CONFIG_USB_U8500_DMA + /* + * In case of full speed mode with double buffering, bulk split + * does not work as defined. Hence in case of FS, return the EP + * size instead of EP FIFO size. + */ + u8 power; + + power = musb_readb(musb->mregs, MUSB_POWER); + + if (can_bulk_split(musb, ep->type) && (power & MUSB_POWER_HSMODE)) +#else if (can_bulk_split(musb, ep->type)) +#endif return ep->hw_ep->max_packet_sz_tx; else return ep->packet_sz; @@ -267,12 +301,17 @@ static void txstate(struct musb *musb, struct musb_request *req) int use_dma = 0; musb_ep = req->ep; - +#ifdef CONFIG_USB_U8500_DMA + if (musb_ep->dma) { +#endif /* we shouldn't get here while DMA is active ... but we do ... */ if (dma_channel_status(musb_ep->dma) == MUSB_DMA_STATUS_BUSY) { DBG(4, "dma pending...\n"); return; } +#ifdef CONFIG_USB_U8500_DMA + } +#endif /* read TXCSR before */ csr = musb_readw(epio, MUSB_TXCSR); @@ -298,10 +337,25 @@ static void txstate(struct musb *musb, struct musb_request *req) csr); #ifndef CONFIG_MUSB_PIO_ONLY +#ifdef CONFIG_USB_U8500_DMA + if (musb_ep->dma && is_buffer_mapped(req)) { +#else if (is_dma_capable() && musb_ep->dma) { +#endif struct dma_controller *c = musb->dma_controller; use_dma = (request->dma != DMA_ADDR_INVALID); +#ifdef CONFIG_USB_U8500_DMA + if (request->length >= DMA_PACKET_THRESHOLD) { + csr |= (MUSB_TXCSR_AUTOSET| + MUSB_TXCSR_DMAENAB + | MUSB_TXCSR_DMAMODE + | MUSB_TXCSR_MODE); + csr &= ~MUSB_TXCSR_P_UNDERRUN; + musb_writew(epio, MUSB_TXCSR, csr); + } + use_dma = use_dma && c->channel_program(musb_ep->dma, musb_ep->packet_sz, DMA_MODE_1, request->dma, request->length); +#endif /* MUSB_TXCSR_P_ISO is still set correctly */ @@ -422,6 +476,7 @@ void musb_g_tx(struct musb *musb, u8 epnum) struct musb_ep *musb_ep = &musb->endpoints[epnum].ep_in; void __iomem *epio = musb->endpoints[epnum].regs; struct dma_channel *dma; + u16 bytes_left = 0; musb_ep_select(mbase, epnum); request = next_request(musb_ep); @@ -463,13 +518,41 @@ void musb_g_tx(struct musb *musb, u8 epnum) u8 is_dma = 0; if (dma && (csr & MUSB_TXCSR_DMAENAB)) { + int count = 0; is_dma = 1; + + /* ensure writebuffer is empty */ + csr = musb_readw(epio, MUSB_TXCSR); + bytes_left = request->length + -musb_ep->dma->actual_len; + for (count = 0; count < MAX_COUNT; count++) { + if (!(csr&MUSB_TXCSR_FIFONOTEMPTY)) + break; + csr = musb_readw(epio, MUSB_TXCSR); + } + csr |= MUSB_TXCSR_P_WZC_BITS; +#ifdef CONFIG_ARCH_U8500 + csr &= ~(MUSB_TXCSR_DMAENAB | MUSB_TXCSR_MODE + | MUSB_TXCSR_AUTOSET + | MUSB_TXCSR_P_UNDERRUN + | MUSB_TXCSR_DMAMODE + | MUSB_TXCSR_TXPKTRDY); +#else csr &= ~(MUSB_TXCSR_DMAENAB | MUSB_TXCSR_P_UNDERRUN | - MUSB_TXCSR_TXPKTRDY); + MUSB_TXCSR_DMAMODE | MUSB_TXCSR_TXPKTRDY); +#endif musb_writew(epio, MUSB_TXCSR, csr); - /* Ensure writebuffer is empty. */ - csr = musb_readw(epio, MUSB_TXCSR); + if (bytes_left) { + musb_write_fifo(musb_ep->hw_ep, + bytes_left, + (u8 *) (request->buf + + musb_ep->dma->actual_len)); + musb_writew(epio, MUSB_TXCSR, + MUSB_TXCSR_TXPKTRDY); + request->actual += bytes_left; + } + request->actual += musb_ep->dma->actual_len; DBG(4, "TXCSR%d %04x, DMA off, len %zu, req %p\n", epnum, csr, musb_ep->dma->actual_len, request); @@ -502,25 +585,33 @@ void musb_g_tx(struct musb *musb, u8 epnum) } /* ... or if not, then complete it. */ - musb_g_giveback(musb_ep, request, 0); + if (request->actual == request->length) { + musb_g_giveback(musb_ep, request, 0); - /* - * Kickstart next transfer if appropriate; - * the packet that just completed might not - * be transmitted for hours or days. - * REVISIT for double buffering... - * FIXME revisit for stalls too... - */ - musb_ep_select(mbase, epnum); - csr = musb_readw(epio, MUSB_TXCSR); - if (csr & MUSB_TXCSR_FIFONOTEMPTY) - return; - - request = musb_ep->desc ? next_request(musb_ep) : NULL; - if (!request) { - DBG(4, "%s idle now\n", - musb_ep->end_point.name); - return; + /* + * Kickstart next transfer if appropriate; + * the packet that just completed might not + * be transmitted for hours or days. + * REVISIT for double buffering... + * FIXME revisit for stalls too... + */ + musb_ep_select(mbase, epnum); + csr = musb_readw(epio, MUSB_TXCSR); +#ifdef CONFIG_ARCH_U8500 + if ((csr & MUSB_TXCSR_FIFONOTEMPTY) && + !(musb_ep->hw_ep->tx_double_buffered)) +#else + if (csr & MUSB_TXCSR_FIFONOTEMPTY) +#endif + return; + + request = + musb_ep->desc ? next_request(musb_ep) : NULL; + if (!request) { + DBG(4, "%s idle now\n", + musb_ep->end_point.name); + return; + } } } @@ -616,8 +707,14 @@ static void rxstate(struct musb *musb, struct musb_request *req) if (csr & MUSB_RXCSR_RXPKTRDY) { len = musb_readw(epio, MUSB_RXCOUNT); if (request->actual < request->length) { +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_U8500_DMA) + #ifdef CONFIG_USB_INVENTRA_DMA if (is_dma_capable() && musb_ep->dma) { +#else + if (is_buffer_mapped(req) && + (len >= DMA_PACKET_THRESHOLD)) { +#endif struct dma_controller *c; struct dma_channel *channel; int use_dma = 0; @@ -646,44 +743,54 @@ static void rxstate(struct musb *musb, struct musb_request *req) * then becomes usable as a runtime "use mode 1" hint... */ - csr |= MUSB_RXCSR_DMAENAB; -#ifdef USE_MODE1 - csr |= MUSB_RXCSR_AUTOCLEAR; - /* csr |= MUSB_RXCSR_DMAMODE; */ - - /* this special sequence (enabling and then - * disabling MUSB_RXCSR_DMAMODE) is required - * to get DMAReq to activate - */ - musb_writew(epio, MUSB_RXCSR, - csr | MUSB_RXCSR_DMAMODE); -#endif - musb_writew(epio, MUSB_RXCSR, csr); - if (request->actual < request->length) { int transfer_size = 0; -#ifdef USE_MODE1 - transfer_size = min(request->length, - channel->max_len); -#else - transfer_size = len; -#endif - if (transfer_size <= musb_ep->packet_sz) - musb_ep->dma->desired_mode = 0; - else + + /* In case first packet is short */ + if (len < musb_ep->packet_sz) + transfer_size = len; + else if (request->short_not_ok) { + + csr &= ~MUSB_RXCSR_DMAMODE; + csr |= MUSB_RXCSR_AUTOCLEAR; + csr |= MUSB_RXCSR_DMAENAB; + musb_writew(epio, + MUSB_RXCSR, csr); + + transfer_size = + min(request->length - + request->actual, + channel->max_len); + + csr |= MUSB_RXCSR_DMAMODE; + musb_writew(epio, + MUSB_RXCSR, csr); musb_ep->dma->desired_mode = 1; + } else { + csr &= ~MUSB_RXCSR_DMAMODE; + csr |= MUSB_RXCSR_DMAENAB; + musb_writew(epio, + MUSB_RXCSR, csr); + + transfer_size = + min(request->length - + request->actual, + (unsigned)len); + + musb_ep->dma->desired_mode = 0; + } use_dma = c->channel_program( - channel, - musb_ep->packet_sz, - channel->desired_mode, - request->dma - + request->actual, - transfer_size); - } + channel, + musb_ep->packet_sz, + channel->desired_mode, + request->dma + + request->actual, + transfer_size); - if (use_dma) - return; + if (use_dma) + return; + } } #endif /* Mentor's DMA */ @@ -743,6 +850,7 @@ void musb_g_rx(struct musb *musb, u8 epnum) struct musb_ep *musb_ep = &musb->endpoints[epnum].ep_out; void __iomem *epio = musb->endpoints[epnum].regs; struct dma_channel *dma; + u16 bytes_left = 0; musb_ep_select(mbase, epnum); @@ -791,7 +899,7 @@ void musb_g_rx(struct musb *musb, u8 epnum) | MUSB_RXCSR_DMAMODE); musb_writew(epio, MUSB_RXCSR, MUSB_RXCSR_P_WZC_BITS | csr); - + csr = musb_readw(epio, MUSB_RXCSR); request->actual += musb_ep->dma->actual_len; DBG(4, "RXCSR%d %04x, dma off, %04x, len %zu, req %p\n", @@ -799,7 +907,9 @@ void musb_g_rx(struct musb *musb, u8 epnum) musb_readw(epio, MUSB_RXCSR), musb_ep->dma->actual_len, request); -#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_TUSB_OMAP_DMA) +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_TUSB_OMAP_DMA) \ + || defined(CONFIG_USB_U8500_DMA) + /* Autoclear doesn't clear RxPktRdy for short packets */ if ((dma->desired_mode == 0) || (dma->actual_len @@ -963,6 +1073,7 @@ static int musb_gadget_enable(struct usb_ep *ep, * for some reason you run out of channels here. */ if (is_dma_capable() && musb->dma_controller) { + struct dma_controller *c = musb->dma_controller; musb_ep->dma = c->channel_alloc(c, hw_ep, @@ -1102,6 +1213,8 @@ static int musb_gadget_queue(struct usb_ep *ep, struct usb_request *req, struct musb *musb; int status = 0; unsigned long lockflags; + int compatible = 0; + struct dma_controller *dma; if (!ep || !req) return -EINVAL; @@ -1110,6 +1223,7 @@ static int musb_gadget_queue(struct usb_ep *ep, struct usb_request *req, musb_ep = to_musb_ep(ep); musb = musb_ep->musb; + dma = musb->dma_controller; request = to_musb_request(req); request->musb = musb; @@ -1125,7 +1239,15 @@ static int musb_gadget_queue(struct usb_ep *ep, struct usb_request *req, request->epnum = musb_ep->current_epnum; request->tx = musb_ep->is_in; - if (is_dma_capable() && musb_ep->dma) { + request->map_state = UN_MAPPED; + + if (musb_ep->dma && dma) { + if (dma->is_compatible) + compatible = dma->is_compatible(musb_ep->dma, request); + + } + + if (is_dma_capable() && musb_ep->dma && compatible) { if (request->request.dma == DMA_ADDR_INVALID) { request->request.dma = dma_map_single( musb->controller, @@ -1134,7 +1256,7 @@ static int musb_gadget_queue(struct usb_ep *ep, struct usb_request *req, request->tx ? DMA_TO_DEVICE : DMA_FROM_DEVICE); - request->mapped = 1; + request->map_state = MUSB_MAPPED; } else { dma_sync_single_for_device(musb->controller, request->request.dma, @@ -1142,12 +1264,12 @@ static int musb_gadget_queue(struct usb_ep *ep, struct usb_request *req, request->tx ? DMA_TO_DEVICE : DMA_FROM_DEVICE); - request->mapped = 0; + request->map_state = PRE_MAPPED; } } else if (!req->buf) { return -ENODATA; } else - request->mapped = 0; + request->map_state = UN_MAPPED; spin_lock_irqsave(&musb->lock, lockflags); @@ -1417,6 +1539,10 @@ static int musb_gadget_wakeup(struct usb_gadget *gadget) u8 power, devctl; int retries; + + musb_platform_device_en(USB_ENABLE_PHY); + musb_platform_session_req(); + spin_lock_irqsave(&musb->lock, flags); switch (musb->xceiv->state) { @@ -1894,6 +2020,7 @@ EXPORT_SYMBOL(usb_gadget_unregister_driver); void musb_g_resume(struct musb *musb) { musb->is_suspended = 0; + stm_prcmu_qos_handler(SET_OPP); switch (musb->xceiv->state) { case OTG_STATE_B_IDLE: break; @@ -1940,6 +2067,7 @@ void musb_g_suspend(struct musb *musb) WARNING("unhandled SUSPEND transition (%s)\n", otg_state_string(musb)); } + stm_prcmu_qos_handler(!SET_OPP); } /* Called during SRP */ diff --git a/drivers/usb/musb/musb_gadget.h b/drivers/usb/musb/musb_gadget.h index c8b140325d8..20d6b064542 100644 --- a/drivers/usb/musb/musb_gadget.h +++ b/drivers/usb/musb/musb_gadget.h @@ -34,6 +34,11 @@ #ifndef __MUSB_GADGET_H #define __MUSB_GADGET_H +enum buffer_map_state { + UN_MAPPED = 0, + PRE_MAPPED, + MUSB_MAPPED +}; struct musb_request { struct usb_request request; @@ -41,7 +46,7 @@ struct musb_request { struct musb *musb; u8 tx; /* endpoint direction */ u8 epnum; - u8 mapped; + enum buffer_map_state map_state; }; static inline struct musb_request *to_musb_request(struct usb_request *req) diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index 877d20b1dff..25cbeadb8b0 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -45,6 +45,9 @@ #include "musb_core.h" #include "musb_host.h" +#ifdef CONFIG_ARCH_U8500 +#include "ste_config.h" +#endif /* MUSB HOST status 22-mar-2006 * @@ -106,24 +109,41 @@ static void musb_ep_program(struct musb *musb, u8 epnum, static void musb_h_tx_flush_fifo(struct musb_hw_ep *ep) { void __iomem *epio = ep->regs; + void __iomem *regs = ep->musb->mregs; u16 csr; - u16 lastcsr = 0; - int retries = 1000; + u8 addr; + int retries = 3000; /* 3ms */ + /* + * NOTE: We are using a hack here because the FIFO-FLUSH + * bit is broken in hardware! The hack consists of changing + * the TXFUNCADDR to an unused device address and waiting + * for any pending USB packets to hit the 3-strikes and your + * gone rule. + */ + addr = musb_readb(regs, MUSB_BUSCTL_OFFSET(ep->epnum, MUSB_TXFUNCADDR)); csr = musb_readw(epio, MUSB_TXCSR); while (csr & MUSB_TXCSR_FIFONOTEMPTY) { - if (csr != lastcsr) - DBG(3, "Host TX FIFONOTEMPTY csr: %02x\n", csr); - lastcsr = csr; - csr |= MUSB_TXCSR_FLUSHFIFO; - musb_writew(epio, MUSB_TXCSR, csr); + musb_writeb(regs, MUSB_BUSCTL_OFFSET(ep->epnum, + MUSB_TXFUNCADDR), 127); csr = musb_readw(epio, MUSB_TXCSR); - if (WARN(retries-- < 1, - "Could not flush host TX%d fifo: csr: %04x\n", - ep->epnum, csr)) - return; - mdelay(1); + retries--; + if (retries == 0) { + /* can happen if the USB clocks are OFF */ + DBG(3, "Could not flush host TX%d " + "fifo: csr=0x%04x\n", ep->epnum, csr); + break; + } + udelay(1); } + /* clear any errors */ + csr &= ~(MUSB_TXCSR_H_ERROR + | MUSB_TXCSR_H_RXSTALL + | MUSB_TXCSR_H_NAKTIMEOUT); + musb_writew(epio, MUSB_TXCSR, csr); + + /* restore endpoint address */ + musb_writeb(regs, MUSB_BUSCTL_OFFSET(ep->epnum, MUSB_TXFUNCADDR), addr); } static void musb_h_ep0_flush_fifo(struct musb_hw_ep *ep) @@ -175,9 +195,16 @@ static inline void musb_h_tx_dma_start(struct musb_hw_ep *ep) /* NOTE: no locks here; caller should lock and select EP */ txcsr = musb_readw(ep->regs, MUSB_TXCSR); + +#ifdef CONFIG_USB_U8500_DMA + txcsr |= MUSB_TXCSR_TXPKTRDY | MUSB_TXCSR_DMAENAB | + MUSB_TXCSR_H_WZC_BITS; + txcsr |= MUSB_TXCSR_DMAMODE; +#else txcsr |= MUSB_TXCSR_DMAENAB | MUSB_TXCSR_H_WZC_BITS; if (is_cppi_enabled()) txcsr |= MUSB_TXCSR_DMAMODE; +#endif musb_writew(ep->regs, MUSB_TXCSR, txcsr); } @@ -290,8 +317,13 @@ start: if (!hw_ep->tx_channel) musb_h_tx_start(hw_ep); +#ifdef CONFIG_USB_U8500_DMA + else + musb_h_tx_dma_start(hw_ep); +#else else if (is_cppi_enabled() || tusb_dma_omap()) musb_h_tx_dma_start(hw_ep); +#endif } } @@ -323,8 +355,15 @@ __acquires(musb->lock) urb->actual_length, urb->transfer_buffer_length ); + usb_hcd_unlink_urb_from_ep(musb_to_hcd(musb), urb); spin_unlock(&musb->lock); +#ifdef CONFIG_USB_U8500_DMA + /* Make it safe to call this routine more than once */ + urb->transfer_flags &= ~(URB_SETUP_MAP_SINGLE | URB_SETUP_MAP_LOCAL | + URB_DMA_MAP_SG | URB_DMA_MAP_PAGE | + URB_DMA_MAP_SINGLE | URB_MAP_LOCAL); +#endif usb_hcd_giveback_urb(musb_to_hcd(musb), urb, status); spin_lock(&musb->lock); } @@ -627,7 +666,35 @@ static bool musb_tx_dma_program(struct dma_controller *dma, u16 csr; u8 mode; -#ifdef CONFIG_USB_INVENTRA_DMA +#ifdef CONFIG_USB_U8500_DMA + if (length > channel->max_len) + length = channel->max_len; + + csr = musb_readw(epio, MUSB_TXCSR); + if (length > pkt_size) { + mode = 1; + csr |= MUSB_TXCSR_DMAMODE | MUSB_TXCSR_DMAENAB; + /* autoset shouldn't be set in high bandwidth */ + if (qh->hb_mult == 1) + csr |= MUSB_TXCSR_AUTOSET; + } else { + mode = 0; + csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAMODE); + csr |= MUSB_TXCSR_DMAENAB; /* against programmer's guide */ + } + channel->desired_mode = mode; + musb_writew(epio, MUSB_TXCSR, csr); + + channel->actual_len = 0; + + /* + * TX uses "RNDIS" mode automatically but needs help + * to identify the zero-length-final-packet case. + */ + mode = (urb->transfer_flags & URB_ZERO_PACKET) ? 1 : 0; + +#else +#ifdef CONFIG_USB_INVENTRA_DMA if (length > channel->max_len) length = channel->max_len; @@ -657,6 +724,7 @@ static bool musb_tx_dma_program(struct dma_controller *dma, */ mode = (urb->transfer_flags & URB_ZERO_PACKET) ? 1 : 0; #endif +#endif qh->segsize = length; @@ -842,7 +910,6 @@ static void musb_ep_program(struct musb *musb, u8 epnum, } /* kick things off */ - if ((is_cppi_enabled() || tusb_dma_omap()) && dma_channel) { /* candidate for DMA */ if (dma_channel) { @@ -872,7 +939,6 @@ static void musb_ep_program(struct musb *musb, u8 epnum, csr |= MUSB_RXCSR_DMAENAB; } } - csr |= MUSB_RXCSR_H_REQPKT; DBG(7, "RXCSR%d := %04x\n", epnum, csr); musb_writew(hw_ep->regs, MUSB_RXCSR, csr); @@ -1141,7 +1207,9 @@ void musb_host_tx(struct musb *musb, u8 epnum) DBG(3, "TX 3strikes on ep=%d\n", epnum); status = -ETIMEDOUT; - + } else if (tx_csr & MUSB_TXCSR_TXPKTRDY) { + /* BUSY - can happen during USB transfer cancel */ + return; } else if (tx_csr & MUSB_TXCSR_H_NAKTIMEOUT) { DBG(6, "TX end=%d device not responding\n", epnum); @@ -1437,6 +1505,9 @@ void musb_host_rx(struct musb *musb, u8 epnum) u32 status; struct dma_channel *dma; +#if defined(CONFIG_USB_U8500_DMA) + u16 tmp_val; +#endif musb_ep_select(mbase, epnum); urb = next_urb(qh); @@ -1527,7 +1598,6 @@ void musb_host_rx(struct musb *musb, u8 epnum) done = true; goto finish; } - if (unlikely(dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY)) { /* SHOULD NEVER HAPPEN ... but at least DaVinci has done it */ ERR("RX%d dma busy, csr %04x\n", epnum, rx_csr); @@ -1541,7 +1611,7 @@ void musb_host_rx(struct musb *musb, u8 epnum) /* FIXME this is _way_ too much in-line logic for Mentor DMA */ -#ifndef CONFIG_USB_INVENTRA_DMA +#if !defined(CONFIG_USB_INVENTRA_DMA) && !defined(CONFIG_USB_U8500_DMA) if (rx_csr & MUSB_RXCSR_H_REQPKT) { /* REVISIT this happened for a while on some short reads... * the cleanup still needs investigation... looks bad... @@ -1564,6 +1634,7 @@ void musb_host_rx(struct musb *musb, u8 epnum) MUSB_RXCSR_H_WZC_BITS | rx_csr); } #endif + if (dma && (rx_csr & MUSB_RXCSR_DMAENAB)) { xfer_len = dma->actual_len; @@ -1573,7 +1644,7 @@ void musb_host_rx(struct musb *musb, u8 epnum) | MUSB_RXCSR_RXPKTRDY); musb_writew(hw_ep->regs, MUSB_RXCSR, val); -#ifdef CONFIG_USB_INVENTRA_DMA +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_U8500_DMA) if (usb_pipeisoc(pipe)) { struct usb_iso_packet_descriptor *d; @@ -1629,7 +1700,7 @@ void musb_host_rx(struct musb *musb, u8 epnum) } /* we are expecting IN packets */ -#ifdef CONFIG_USB_INVENTRA_DMA +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_U8500_DMA) if (dma) { struct dma_controller *c; u16 rx_count; @@ -1713,6 +1784,14 @@ void musb_host_rx(struct musb *musb, u8 epnum) */ val = musb_readw(epio, MUSB_RXCSR); + +#if defined(CONFIG_USB_U8500_DMA) + /* retain the original value, + *which will be used to reset CSR + */ + tmp_val = val; +#endif + val &= ~MUSB_RXCSR_H_REQPKT; if (dma->desired_mode == 0) @@ -1741,9 +1820,13 @@ void musb_host_rx(struct musb *musb, u8 epnum) hw_ep->rx_channel = NULL; dma = NULL; /* REVISIT reset CSR */ + +#if defined(CONFIG_USB_U8500_DMA) + musb_writew(epio, MUSB_RXCSR, tmp_val); +#endif } } -#endif /* Mentor DMA */ +#endif /* Mentor DMA || U8500 DMA */ if (!dma) { done = musb_host_packet_rx(musb, urb, diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c index 92e85e027cf..663dda191dd 100644 --- a/drivers/usb/musb/musb_virthub.c +++ b/drivers/usb/musb/musb_virthub.c @@ -84,8 +84,9 @@ static void musb_port_suspend(struct musb *musb, bool do_suspend) && musb->xceiv->host->b_hnp_enable; if (musb->is_active) mod_timer(&musb->otg_timer, jiffies - + msecs_to_jiffies( - OTG_TIME_A_AIDL_BDIS)); + + msecs_to_jiffies(( + OTG_TIME_A_AIDL_BDIS + + USB_SUSP_DET_DURATION))); musb_platform_try_idle(musb, 0); break; #ifdef CONFIG_USB_MUSB_OTG diff --git a/drivers/usb/musb/ste_config.h b/drivers/usb/musb/ste_config.h new file mode 100644 index 00000000000..daa336369fa --- /dev/null +++ b/drivers/usb/musb/ste_config.h @@ -0,0 +1,52 @@ +/* + * + * Copyright (C) 2009 ST-Ericsson SA + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __STE_CONFIG_H__ +#define __STE_CONFIG_H__ + +#if (defined(CONFIG_ARCH_U8500) && !defined(CONFIG_MUSB_PIO_ONLY)) +#define CONFIG_USB_U8500_DMA +#endif +#define U8500_DMA_END_POINTS 7 +#define DMA_MODE_0 0 +#define DMA_MODE_1 1 +#define DMA_PACKET_THRESHOLD 512 +#define RX_END_POINT_OFFSET 6 +#define DELAY_IN_MICROSECONDS 10 +#define MAX_COUNT 35000 +void stm_prcmu_qos_handler(int); +#define SET_OPP 1 + +enum nmdk_dma_tx_rx_channel { + TX_CHANNEL_1 = 0, + TX_CHANNEL_2, + TX_CHANNEL_3, + TX_CHANNEL_4, + TX_CHANNEL_5, + TX_CHANNEL_6, + TX_CHANNEL_7, + RX_CHANNEL_1, + RX_CHANNEL_2, + RX_CHANNEL_3, + RX_CHANNEL_4, + RX_CHANNEL_5, + RX_CHANNEL_6, + RX_CHANNEL_7 +}; +#endif + diff --git a/drivers/usb/musb/stm_musb.c b/drivers/usb/musb/stm_musb.c new file mode 100644 index 00000000000..5ce3c4a75dc --- /dev/null +++ b/drivers/usb/musb/stm_musb.c @@ -0,0 +1,638 @@ +/* + * Copyright (C) 2009 STMicroelectronics + * Copyright (C) 2009 ST-Ericsson SA + * + * 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. + */ + +/** @file stm_musb.c + * @brief This file contains the USB controller and Phy initialization + * with default as interrupt mode was implemented + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/mfd/ab8500/ab8500-bm.h> +#include <mach/stm_musb.h> +#include <mach/musb_db8500.h> +#include <mach/prcmu-fw-api.h> +#include "musb_core.h" + +static u8 ulpi_read_register(struct musb *musb, u8 address); +static u8 ulpi_write_register(struct musb *musb, u8 address, u8 data); +/* callback argument for AB8500 callback functions */ +static struct musb *musb_status; +static spinlock_t musb_ulpi_spinlock; +static unsigned musb_power; +#ifdef CONFIG_USB_OTG_20 +static int userrequest; +#endif +static struct workqueue_struct *stm_usb_power_wq; +static struct work_struct stm_prcmu_qos; +static int musb_qos_req; + +#define PERI5_CLK_ENABLE 1 +#define PERI5_CLK_DISABLE 0 + +/** + * musb_set_session() - Start the USB session + * + * This function is used to start the USB sessios in USB host mode + * once the A cable is plugged in + */ +void musb_set_session(void) +{ + u8 val; + void __iomem *regs; + + if (musb_status == NULL) { + printk(KERN_ERR "Error: devctl session cannot be set\n"); + return; + } + regs = musb_status->mregs; + val = musb_readb(regs, MUSB_DEVCTL); + musb_writeb(regs, MUSB_DEVCTL, val | MUSB_DEVCTL_SESSION); +} +EXPORT_SYMBOL(musb_set_session); + + +void stm_prcmu_qos_handler(int value) +{ + if (value) + musb_qos_req = 100; + else + musb_qos_req = 50; + queue_work(stm_usb_power_wq, &stm_prcmu_qos); +} +EXPORT_SYMBOL(stm_prcmu_qos_handler); + +static void stm_prcmu_qos_work(struct work_struct *work) +{ + if (musb_qos_req == 100) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, + "musb_qos", 100); + } else { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, + "musb_qos", 50); + prcmu_release_usb_wakeup_state(); + } +} + +void stm_set_peripheral_clock(int enable) +{ + if (enable) { + if (musb_status->set_clock) + musb_status->set_clock(musb_status->clock, 1); + else + clk_enable(musb_status->clock); + } else { + if (musb_status->set_clock) + musb_status->set_clock(musb_status->clock, 0); + else + clk_disable(musb_status->clock); + } +} + +#ifdef CONFIG_PM +void stm_musb_context(int enable) +{ + void __iomem *regs; + + if (enable) { + stm_set_peripheral_clock(PERI5_CLK_ENABLE); + musb_restore_context(musb_status); + regs = musb_status->mregs; + } else + stm_set_peripheral_clock(PERI5_CLK_DISABLE); +} +EXPORT_SYMBOL(stm_musb_context); +#endif + +void +ab8500_bm_usb_state_changed_wrapper(u8 bm_usb_state) +{ + if ((bm_usb_state == AB8500_BM_USB_STATE_RESET_HS) || + (bm_usb_state == AB8500_BM_USB_STATE_RESET_FS)) { + musb_power = 0; + } + + /* + * TODO: Instead of using callbacks, we should be using notify + * to tell the battery manager when there is s state change + */ + ab8500_charger_usb_state_changed(bm_usb_state, musb_power); +} + +#ifdef CONFIG_USB_OTG_20 +int musb_adp(void) +{ + if (userrequest == 0) + return 0; + else + return 1; +} +EXPORT_SYMBOL(musb_adp); +#endif + +/* Sys interfaces */ +static struct kobject *usbstatus_kobj; +static ssize_t usb_cable_status + (struct kobject *kobj, struct attribute *attr, char *buf) +{ + u8 is_active = 0; + + if (strcmp(attr->name, "cable_connect") == 0) { + is_active = musb_status->is_active; + sprintf(buf, "%d\n", is_active); + } + return strlen(buf); +} + +static struct attribute usb_cable_connect_attribute = \ + {.name = "cable_connect", .mode = S_IRUGO}; +static struct attribute *usb_status[] = { + &usb_cable_connect_attribute, + NULL +}; + +struct sysfs_ops usb_sysfs_ops = { + .show = usb_cable_status, +}; + +static struct kobj_type ktype_usbstatus = { + .sysfs_ops = &usb_sysfs_ops, + .default_attrs = usb_status, +}; + +/* + * A structure was declared as global for timer in USB host mode + */ +static struct timer_list notify_timer; + +/* TODO: Remove or use!! */ +#ifdef UNUSED_USB_STUFF + +/** + * ulpi_read_register() - Read the usb register from address writing into ULPI + * @musb: struct musb pointer. + * @address: address for reading from ULPI register of USB + * + * This function read the value from the specific address in USB host mode. + */ +static u8 ulpi_read_register(struct musb *musb, u8 address) +{ + void __iomem *mbase = musb->mregs; + unsigned long flags; + int count = 200; + u8 val; + + spin_lock_irqsave(&musb_ulpi_spinlock, flags); + + /* set ULPI register address */ + musb_writeb(mbase, OTG_UREGADDR, address); + + /* request a read access */ + val = musb_readb(mbase, OTG_UREGCTRL); + val |= OTG_UREGCTRL_URW; + musb_writeb(mbase, OTG_UREGCTRL, val); + + /* perform access */ + val = musb_readb(mbase, OTG_UREGCTRL); + val |= OTG_UREGCTRL_REGREQ; + musb_writeb(mbase, OTG_UREGCTRL, val); + + /* wait for completion with a time-out */ + do { + udelay(10); + val = musb_readb(mbase, OTG_UREGCTRL); + count--; + } while (!(val & OTG_UREGCTRL_REGCMP) && (count > 0)); + + /* check for time-out */ + if (!(val & OTG_UREGCTRL_REGCMP) && (count == 0)) { + spin_unlock_irqrestore(&musb_ulpi_spinlock, flags); + if (printk_ratelimit()) + printk(KERN_ALERT "U8500 USB : ULPI read timed out\n"); + return 0; + } + + /* acknowledge completion */ + val &= ~OTG_UREGCTRL_REGCMP; + musb_writeb(mbase, OTG_UREGCTRL, val); + + /* get data */ + val = musb_readb(mbase, OTG_UREGDATA); + spin_unlock_irqrestore(&musb_ulpi_spinlock, flags); + + return val; +} +#endif + +/** + * ulpi_write_register() - Write to a usb phy's ULPI register + * using the Mentor ULPI wrapper functionality + * @musb: struct musb pointer. + * @address: address of ULPI register + * @data: data for ULPI register + * This function writes the value given by data to the specific address + */ +static u8 ulpi_write_register(struct musb *musb, u8 address, u8 data) +{ + void __iomem *mbase = musb->mregs; + unsigned long flags; + int count = 200; + u8 val; + + spin_lock_irqsave(&musb_ulpi_spinlock, flags); + + /* First write to ULPI wrapper registers */ + /* set ULPI register address */ + musb_writeb(mbase, OTG_UREGADDR, address); + + /* request a write access */ + val = musb_readb(mbase, OTG_UREGCTRL); + val &= ~OTG_UREGCTRL_URW; + musb_writeb(mbase, OTG_UREGCTRL, val); + + /* Write data to ULPI wrapper data register */ + musb_writeb(mbase, OTG_UREGDATA, data); + + /* perform access */ + val = musb_readb(mbase, OTG_UREGCTRL); + val |= OTG_UREGCTRL_REGREQ; + musb_writeb(mbase, OTG_UREGCTRL, val); + + /* wait for completion with a time-out */ + do { + udelay(10); + val = musb_readb(mbase, OTG_UREGCTRL); + count--; + } while (!(val & OTG_UREGCTRL_REGCMP) && (count > 0)); + + /* check for time-out */ + if (!(val & OTG_UREGCTRL_REGCMP) && (count == 0)) { + spin_unlock_irqrestore(&musb_ulpi_spinlock, flags); + if (printk_ratelimit()) + printk(KERN_ALERT "U8500 USB : ULPI write timed out\n"); + return 0; + } + + /* acknowledge completion */ + val &= ~OTG_UREGCTRL_REGCMP; + musb_writeb(mbase, OTG_UREGCTRL, val); + + spin_unlock_irqrestore(&musb_ulpi_spinlock, flags); + + return 0; + +} + + +/** + * musb_stm_hs_otg_init() - Initialize the USB for paltform specific. + * @musb: struct musb pointer. + * + * This function initialize the USB with the given musb structure information. + */ +int __init musb_stm_hs_otg_init(struct musb *musb) +{ + u8 val; + + if (musb->clock) + clk_enable(musb->clock); + + /* enable ULPI interface */ + val = musb_readb(musb->mregs, OTG_TOPCTRL); + val |= OTG_TOPCTRL_MODE_ULPI; + musb_writeb(musb->mregs, OTG_TOPCTRL, val); + + /* do soft reset */ + val = musb_readb(musb->mregs, 0x7F); + val |= 0x2; + musb_writeb(musb->mregs, 0x7F, val); + + return 0; +} +/** + * musb_stm_fs_init() - Initialize the file system for the USB. + * @musb: struct musb pointer. + * + * This function initialize the file system of USB. + */ +int __init musb_stm_fs_init(struct musb *musb) +{ + return 0; +} +/** + * musb_platform_enable() - Enable the USB. + * @musb: struct musb pointer. + * + * This function enables the USB. + */ +void musb_platform_enable(struct musb *musb) +{ +} +/** + * musb_platform_disable() - Disable the USB. + * @musb: struct musb pointer. + * + * This function disables the USB. + */ +void musb_platform_disable(struct musb *musb) +{ +} + +/** + * musb_platform_try_idle() - Check the USB state active or not. + * @musb: struct musb pointer. + * @timeout: set the timeout to keep the host in idle mode. + * + * This function keeps the USB host in idle state based on the musb inforamtion. + */ +void musb_platform_try_idle(struct musb *musb, unsigned long timeout) +{ + if (musb->board_mode != MUSB_PERIPHERAL) { + unsigned long default_timeout = + jiffies + msecs_to_jiffies(10); + static unsigned long last_timer; + + if (timeout == 0) + timeout = default_timeout; + + /* Never idle if active, or when VBUS + timeout is not set as host */ + if (musb->is_active || ((musb->a_wait_bcon == 0) + && (musb->xceiv->state == OTG_STATE_A_WAIT_BCON))) { + DBG(4, "%s active, deleting timer\n", + otg_state_string(musb)); + del_timer(¬ify_timer); + last_timer = jiffies; + return; + } + + if (time_after(last_timer, timeout)) { + if (!timer_pending(¬ify_timer)) + last_timer = timeout; + else { + DBG(4, + "Longer idle timer already pending,ignoring\n"); + return; + } + } + last_timer = timeout; + + DBG(4, "%s inactive, for idle timer for %lu ms\n", + otg_state_string(musb), + (unsigned long)jiffies_to_msecs(timeout - jiffies)); + mod_timer(¬ify_timer, timeout); + } +} + +/** + * set_vbus() - Set the Vbus for the USB. + * @musb: struct musb pointer. + * @is_on: set Vbus for USB or not. + * + * This function set the Vbus for USB. + */ +static void set_vbus(struct musb *musb, int is_on) +{ + u8 devctl; + /* HDRC controls CPEN, but beware current surges during device + * connect. They can trigger transient overcurrent conditions + * that must be ignored. + */ + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + if (is_on) { + musb->is_active = 1; + musb->xceiv->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + devctl |= MUSB_DEVCTL_SESSION; + + MUSB_HST_MODE(musb); + } else { + musb->is_active = 0; + + /* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and + * jumping right to B_IDLE... + */ + + musb->xceiv->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; + devctl &= ~MUSB_DEVCTL_SESSION; + + MUSB_DEV_MODE(musb); + } + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + DBG(1, "VBUS %s, devctl %02x " + /* otg %3x conf %08x prcm %08x */ "\n", + otg_state_string(musb), + musb_readb(musb->mregs, MUSB_DEVCTL)); + if (!is_on) { + /* Discahrge the VBUS */ + if (musb_status == NULL) + return; + ulpi_write_register(musb_status, ULPI_OCTRL, 0x08); + } +} +/** + * set_power() - Set the power for the USB transceiver. + * @x: struct usb_transceiver pointer. + * @mA: set mA power for USB. + * + * This function set the power for the USB. + */ +static int set_power(struct otg_transceiver *x, unsigned mA) +{ + if (mA > 100) { + /* AB V2 has eye diagram issues when drawing more + * than 100mA from VBUS.So setting charging current + * to 100mA in case of standard host + */ + if (musb_get_abx500_rev() < 0x30) + mA = 100; + else + mA = 300; + } + musb_power = mA; + DBG(1, "Set VBUS Power = %d mA\n", mA); + ab8500_bm_usb_state_changed_wrapper( + AB8500_BM_USB_STATE_CONFIGURED); + return 0; +} +/** + * musb_platform_set_mode() - Set the mode for the USB driver. + * @musb: struct musb pointer. + * @musb_mode: usb mode. + * + * This function set the mode for the USB. + */ +int musb_platform_set_mode(struct musb *musb, u8 musb_mode) +{ + u8 devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + switch (musb_mode) { + case MUSB_HOST: + otg_set_host(musb->xceiv, musb->xceiv->host); + break; + case MUSB_PERIPHERAL: + otg_set_peripheral(musb->xceiv, musb->xceiv->gadget); + break; + case MUSB_OTG: + break; + default: + return -EINVAL; + } + return 0; +} +/** + * funct_host_notify_timer() - Initialize the timer for USB host driver. + * @data: usb host data. + * + * This function runs the timer for the USB host mode. + */ +static void funct_host_notify_timer(unsigned long data) +{ + struct musb *musb = (void *)data; + unsigned long flags; + u8 devctl; + + spin_lock_irqsave(&musb->lock, flags); + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + switch (musb->xceiv->state) { + case OTG_STATE_A_WAIT_BCON: + devctl &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) { + musb->xceiv->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } else { + musb->xceiv->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + } +#ifdef CONFIG_PM + stm_musb_context(USB_DISABLE); +#endif + + break; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); + DBG(1, "otg_state %s devctl %d\n", otg_state_string(musb), devctl); +} + +/** + * musb_platform_init() - Initialize the platform USB driver. + * @musb: struct musb pointer. + * + * This function initialize the USB controller and Phy. + */ +int __init musb_platform_init(struct musb *musb, void *board_data) +{ + int ret; + + usb_nop_xceiv_register(); + + musb->xceiv = otg_get_transceiver(); + if (!musb->xceiv) { + pr_err("U8500 USB : no transceiver configured\n"); + ret = -ENODEV; + goto done; + } + + ret = musb_stm_hs_otg_init(musb); + if (ret < 0) + goto done; + if (is_host_enabled(musb)) + musb->board_set_vbus = set_vbus; + if (is_peripheral_enabled(musb)) + musb->xceiv->set_power = set_power; + + ret = musb_phy_en(musb->board_mode); + if (ret < 0) + goto done; + + if (musb_status == NULL) { + musb_status = musb; + spin_lock_init(&musb_ulpi_spinlock); + } + + /* Registering usb device for sysfs */ + usbstatus_kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL); + + if (usbstatus_kobj == NULL) + ret = -ENOMEM; + usbstatus_kobj->ktype = &ktype_usbstatus; + kobject_init(usbstatus_kobj, usbstatus_kobj->ktype); + + ret = kobject_set_name(usbstatus_kobj, "usb_status"); + if (ret) + kfree(usbstatus_kobj); + + ret = kobject_add(usbstatus_kobj, NULL, "usb_status"); + if (ret) + kfree(usbstatus_kobj); + + if (musb->board_mode != MUSB_PERIPHERAL) { + init_timer(¬ify_timer); + notify_timer.expires = jiffies + msecs_to_jiffies(1000); + notify_timer.function = funct_host_notify_timer; + notify_timer.data = (unsigned long)musb; + add_timer(¬ify_timer); + } + + stm_usb_power_wq = create_singlethread_workqueue( + "stm_usb_power_wq"); + if (stm_usb_power_wq == NULL) + return -ENOMEM; + + INIT_WORK(&stm_prcmu_qos, stm_prcmu_qos_work); + + ret = musb_force_detect(musb->board_mode); + if (ret < 0) + goto done; + return 0; + +done: + usb_nop_xceiv_unregister(); + return ret; +} +/** + * musb_platform_exit() - unregister the platform USB driver. + * @musb: struct musb pointer. + * + * This function unregisters the USB controller. + */ +int musb_platform_exit(struct musb *musb) +{ + musb->clock = 0; + + if (musb->board_mode != MUSB_PERIPHERAL) + del_timer_sync(¬ify_timer); + + usb_nop_xceiv_unregister(); + + musb_status = NULL; + + return 0; +} diff --git a/drivers/usb/musb/stm_musb_dma.c b/drivers/usb/musb/stm_musb_dma.c new file mode 100644 index 00000000000..468e96bdcea --- /dev/null +++ b/drivers/usb/musb/stm_musb_dma.c @@ -0,0 +1,722 @@ +/* Copyright (C) 2009 ST-Ericsson SA + * Copyright (C) 2009 STMicroelectronics + * + * 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/pfn.h> +#include "musb_core.h" +#include "ste_config.h" +#include "stm_musb_dma.h" +#include <plat/ste_dma40.h> +#include <mach/ste-dma40-db8500.h> + +/* + * U8500 system DMA used for USB can't transfer less + * than max packet size of Buldk EP which is 512 + */ +#define U8500_USB_DMA_MIN_TRANSFER_SIZE 512 + +/** + * dma_controller_start() - creates the logical channels pool and registers callbacks + * @c: pointer to DMA Controller + * + * This function requests the logical channels from the DMA driver and creates + * logical channels based on event lines and also registers the callbacks which + * are invoked after data transfer in the transmit or receive direction. +*/ + +static int dma_controller_start(struct dma_controller *c) +{ + struct musb_dma_controller *controller = container_of(c, + struct musb_dma_controller, controller); + struct musb_dma_channel *musb_channel = NULL; + struct stedma40_chan_cfg *info; + u8 bit; + struct dma_channel *channel = NULL; + /*bit 0 for receive and bit 1 for transmit*/ +#ifndef CONFIG_USB_U8500_DMA + for (bit = 0; bit < 2; bit++) { +#else + for (bit = 0; bit < (U8500_DMA_END_POINTS*2); bit++) { +#endif + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + musb_channel = &(controller->channel[bit]); + info = kzalloc(sizeof(struct stedma40_chan_cfg), GFP_KERNEL); + if (!info) { + ERR("could not allocate dma info structure\n"); + return -1; + } + musb_channel->info = info; + musb_channel->controller = controller; +#ifdef CONFIG_USB_U8500_DMA + info->high_priority = true; +#else + info->mode = STEDMA40_MODE_PHYSICAL; + info->high_priority = true; +#endif + +#ifndef CONFIG_USB_U8500_DMA + if (bit) { +#else + if ((bit <= TX_CHANNEL_7)) { +#endif + int dst_dev_type; + + info->dir = STEDMA40_MEM_TO_PERIPH; + info->src_dev_type = STEDMA40_DEV_SRC_MEMORY; + +#ifdef CONFIG_USB_U8500_DMA + switch (bit) { + + case TX_CHANNEL_1: + dst_dev_type = DB8500_DMA_DEV38_USB_OTG_OEP_1_9; + break; + case TX_CHANNEL_2: + dst_dev_type = + DB8500_DMA_DEV37_USB_OTG_OEP_2_10; + break; + case TX_CHANNEL_3: + dst_dev_type = + DB8500_DMA_DEV36_USB_OTG_OEP_3_11; + break; + case TX_CHANNEL_4: + dst_dev_type = + DB8500_DMA_DEV19_USB_OTG_OEP_4_12; + break; + case TX_CHANNEL_5: + dst_dev_type = + DB8500_DMA_DEV18_USB_OTG_OEP_5_13; + break; + case TX_CHANNEL_6: + dst_dev_type = + DB8500_DMA_DEV17_USB_OTG_OEP_6_14; + break; + case TX_CHANNEL_7: + dst_dev_type = + DB8500_DMA_DEV16_USB_OTG_OEP_7_15; + break; + + } + + info->dst_dev_type = dst_dev_type; +#endif + + } else { + int src_dev_type; + + info->dir = STEDMA40_PERIPH_TO_MEM; + +#ifdef CONFIG_USB_U8500_DMA + switch (bit) { + case RX_CHANNEL_1: + src_dev_type = DB8500_DMA_DEV38_USB_OTG_IEP_1_9; + break; + case RX_CHANNEL_2: + src_dev_type = + DB8500_DMA_DEV37_USB_OTG_IEP_2_10; + break; + case RX_CHANNEL_3: + src_dev_type = + DB8500_DMA_DEV36_USB_OTG_IEP_3_11; + break; + case RX_CHANNEL_4: + src_dev_type = + DB8500_DMA_DEV19_USB_OTG_IEP_4_12; + break; + case RX_CHANNEL_5: + src_dev_type = + DB8500_DMA_DEV18_USB_OTG_IEP_5_13; + break; + case RX_CHANNEL_6: + src_dev_type = + DB8500_DMA_DEV17_USB_OTG_IEP_6_14; + break; + case RX_CHANNEL_7: + src_dev_type = + DB8500_DMA_DEV16_USB_OTG_IEP_7_15; + break; + } + + info->src_dev_type = src_dev_type; +#endif + info->dst_dev_type = STEDMA40_DEV_DST_MEMORY; + } + info->src_info.data_width = STEDMA40_WORD_WIDTH; + info->src_info.psize = STEDMA40_PSIZE_LOG_16; + + info->dst_info.data_width = STEDMA40_WORD_WIDTH ; + info->dst_info.psize = STEDMA40_PSIZE_LOG_16; + musb_channel->is_pipe_allocated = 1; + channel = &(musb_channel->channel); + channel->private_data = musb_channel; + channel->status = MUSB_DMA_STATUS_FREE; + channel->max_len = 0x10000; + /* Tx => mode 1; Rx => mode 0 */ + channel->desired_mode = bit; + channel->actual_len = 0; + + musb_channel->dma_chan = dma_request_channel(mask, + stedma40_filter, + info); + if (!musb_channel->dma_chan) + ERR("dma pipe can't be allocated\n"); +#ifndef CONFIG_USB_U8500_DMA + /* Tx => mode 1; Rx => mode 0 */ + if (bit) { +#else + if ((bit <= TX_CHANNEL_7)) { +#endif + INIT_WORK(&musb_channel->channel_data_tx, + musb_channel_work_tx); + DBG(2, "channel allocated for TX, %s\n", + dma_chan_name(musb_channel->dma_chan)); + } else { + INIT_WORK(&musb_channel->channel_data_rx, + musb_channel_work_rx); + DBG(2, "channel allocated for RX, %s\n", + dma_chan_name(musb_channel->dma_chan)); + } + + } + return 0; +} + +static void dma_channel_release(struct dma_channel *channel); + +/** + * dma_controller_stop() - releases all the channels and frees the DMA pipes + * @c: pointer to DMA controller + * + * This function frees all of the logical channels and frees the DMA pipes +*/ + +static int dma_controller_stop(struct dma_controller *c) +{ + struct musb_dma_controller *controller = container_of(c, + struct musb_dma_controller, controller); + struct musb_dma_channel *musb_channel; + struct dma_channel *channel; + u8 bit; +#ifndef CONFIG_USB_U8500_DMA + for (bit = 0; bit < 2; bit++) { +#else + for (bit = 0; bit < (U8500_DMA_END_POINTS*2); bit++) { +#endif + channel = &controller->channel[bit].channel; + musb_channel = channel->private_data; + dma_channel_release(channel); + if (musb_channel->info) { + dma_release_channel(musb_channel->dma_chan); + kfree(musb_channel->info); + musb_channel->info = NULL; + } + } + + return 0; +} + +/** + * dma_controller_allocate() - allocates the DMA channels + * @c: pointer to DMA controller + * @hw_ep: pointer to endpoint + * @transmit: transmit or receive direction + * + * This function allocates the DMA channel and initializes + * the channel +*/ + +static struct dma_channel *dma_channel_allocate(struct dma_controller *c, + struct musb_hw_ep *hw_ep, u8 transmit) +{ + struct musb_dma_controller *controller = container_of(c, + struct musb_dma_controller, controller); + struct musb_dma_channel *musb_channel = NULL; + struct dma_channel *channel = NULL; + u8 bit; + +#ifndef CONFIG_USB_U8500_DMA + + /*bit 0 for receive and bit 1 for transmit*/ + for (bit = 0; bit < 2; bit++) { + if (!(controller->used_channels & (1 << bit))) { + + if ((transmit && !bit)) + continue; + if ((!transmit && bit)) + break; +#else + if (hw_ep->epnum > 0 + && hw_ep->epnum <= U8500_DMA_END_POINTS) { + if (transmit) + bit = hw_ep->epnum - 1; + else + bit = + hw_ep->epnum + RX_END_POINT_OFFSET; + } else + return NULL; +#endif + controller->used_channels |= (1 << bit); + musb_channel = &(controller->channel[bit]); + musb_channel->idx = bit; + musb_channel->epnum = hw_ep->epnum; + musb_channel->hw_ep = hw_ep; + musb_channel->transmit = transmit; + musb_channel->is_pipe_allocated = 1; + channel = &(musb_channel->channel); +#ifndef CONFIG_USB_U8500_DMA + break; + } + } +#endif + return channel; +} + +/** + * dma_channel_release() - releases the DMA channel + * @channel: channel to be released + * + * This function releases the DMA channel + * +*/ + +static void dma_channel_release(struct dma_channel *channel) +{ + struct musb_dma_channel *musb_channel = channel->private_data; + channel->actual_len = 0; + musb_channel->start_addr = 0; + musb_channel->len = 0; + + DBG(2, "enter\n"); + musb_channel->controller->used_channels &= + ~(1 << musb_channel->idx); + + channel->status = MUSB_DMA_STATUS_FREE; + if (musb_channel->is_pipe_allocated) + musb_channel->is_pipe_allocated = 0; + DBG(2, "exit\n"); +} + +/** + * configure_channel() - configures the source, destination addresses and + * starts the transfer + * @channel: pointer to DMA channel + * @packet_sz: packet size + * @mode: Dma mode + * @dma_addr: DMA source address for transmit direction + * or DMA destination address for receive direction + * @len: length + * This function configures the source and destination addresses for DMA + * operation and initiates the DMA transfer +*/ + +static bool configure_channel(struct dma_channel *channel, + u16 packet_sz, u8 mode, + dma_addr_t dma_addr, u32 len) +{ + struct musb_dma_channel *musb_channel = channel->private_data; + struct musb_hw_ep *hw_ep = musb_channel->hw_ep; + struct musb *musb = hw_ep->musb; + void __iomem *mbase = musb->mregs; + u32 dma_count; + struct dma_chan *dma_chan = musb_channel->dma_chan; + struct dma_async_tx_descriptor *dma_desc; + enum dma_data_direction direction; + struct scatterlist sg; + +#ifndef CONFIG_USB_U8500_DMA + struct musb_qh *qh; + struct urb *urb; +#endif + unsigned int usb_fifo_addr = + (unsigned int)(MUSB_FIFO_OFFSET(hw_ep->epnum) + mbase); + +#ifndef CONFIG_USB_U8500_DMA + if (musb_channel->transmit) + qh = hw_ep->out_qh; + else + qh = hw_ep->in_qh; + urb = next_urb(qh); +#endif + + dma_count = len - (len % packet_sz); + musb_channel->cur_len = dma_count; + usb_fifo_addr = + U8500_USBOTG_BASE + ((unsigned int)usb_fifo_addr & 0xFFFF); + +#ifndef CONFIG_USB_U8500_DMA + if (!(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) { + if (musb_channel->transmit) + dma_addr = musb->tx_dma_phy; + else + dma_addr = musb->rx_dma_phy; + } +#endif + + stedma40_set_dev_addr(dma_chan, usb_fifo_addr, usb_fifo_addr); + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(dma_addr)), dma_count, + offset_in_page(dma_addr)); + sg_dma_address(&sg) = dma_addr; + sg_dma_len(&sg) = dma_count; + + direction = musb_channel->transmit ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + dma_desc = dma_chan->device-> + device_prep_slave_sg(dma_chan, &sg, 1, direction, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!dma_desc) + return false; + + dma_desc->callback = musb_channel->transmit ? + musb_tx_dma_controller_handler : + musb_rx_dma_controller_handler; + dma_desc->callback_param = channel; + dma_desc->tx_submit(dma_desc); + dma_async_issue_pending(dma_chan); + + return true; +} + +/** + * dma_channel_program() - Configures the channel and initiates transfer + * @channel: pointer to DMA channel + * @packet_sz: packet size + * @mode: mode + * @dma_addr: physical address of memory + * @len: length + * + * This function configures the channel and initiates the DMA transfer +*/ + +static int dma_channel_program(struct dma_channel *channel, + u16 packet_sz, u8 mode, + dma_addr_t dma_addr, u32 len) +{ + struct musb_dma_channel *musb_channel = channel->private_data; + bool ret; + + BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN || + channel->status == MUSB_DMA_STATUS_BUSY); + if (len < U8500_USB_DMA_MIN_TRANSFER_SIZE) + return false; + if (!musb_channel->transmit && len < packet_sz) + return false; + channel->actual_len = 0; + musb_channel->start_addr = dma_addr; + musb_channel->len = len; + musb_channel->max_packet_sz = packet_sz; + channel->status = MUSB_DMA_STATUS_BUSY; + + + if ((mode == 1) && (len >= packet_sz)) + ret = configure_channel(channel, packet_sz, 1, dma_addr, len); + else + ret = configure_channel(channel, packet_sz, 0, dma_addr, len); + return ret; +} + +/** + * dma_channel_abort() - aborts the DMA transfer + * @channel: pointer to DMA channel. + * + * This function aborts the DMA transfer. +*/ + +static int dma_channel_abort(struct dma_channel *channel) +{ + struct musb_dma_channel *musb_channel = channel->private_data; + void __iomem *mbase = musb_channel->controller->base; + u16 csr; + if (channel->status == MUSB_DMA_STATUS_BUSY) { + if (musb_channel->transmit) { + + csr = musb_readw(mbase, + MUSB_EP_OFFSET(musb_channel->epnum, + MUSB_TXCSR)); + csr &= ~(MUSB_TXCSR_AUTOSET | + MUSB_TXCSR_DMAENAB | + MUSB_TXCSR_DMAMODE); + musb_writew(mbase, + MUSB_EP_OFFSET(musb_channel->epnum, MUSB_TXCSR), + csr); + } else { + csr = musb_readw(mbase, + MUSB_EP_OFFSET(musb_channel->epnum, + MUSB_RXCSR)); + csr &= ~(MUSB_RXCSR_AUTOCLEAR | + MUSB_RXCSR_DMAENAB | + MUSB_RXCSR_DMAMODE); + musb_writew(mbase, + MUSB_EP_OFFSET(musb_channel->epnum, MUSB_RXCSR), + csr); + } + + if (musb_channel->is_pipe_allocated) { + musb_channel->dma_chan->device-> + device_control(musb_channel->dma_chan, DMA_TERMINATE_ALL, 0); + channel->status = MUSB_DMA_STATUS_FREE; + } + } + return 0; +} + +#include <linux/usb/composite.h> + +static int dma_is_compatible(struct dma_channel *channel, + struct musb_request *req) +{ + + struct musb_dma_channel *musb_channel = channel->private_data; + struct musb_hw_ep *hw_ep = musb_channel->hw_ep; + struct musb *musb = hw_ep->musb; + struct usb_descriptor_header **descriptors; + struct usb_function *f; + + struct usb_gadget *gadget = &musb->g; + + struct usb_composite_dev *cdev = get_gadget_data(gadget); + + list_for_each_entry(f, &cdev->config->functions, list) { + + if (!strcmp(f->name, "cdc_ethernet") || + !strcmp(f->name, "rndis") || + !strcmp(f->name, "adb")) { + + if (gadget->speed == USB_SPEED_HIGH) + descriptors = f->hs_descriptors; + else + descriptors = f->descriptors; + + for (; *descriptors; ++descriptors) { + struct usb_endpoint_descriptor *ep; + + if ((*descriptors)->bDescriptorType != + USB_DT_ENDPOINT) + continue; + + ep = (struct usb_endpoint_descriptor *) + *descriptors; + + if (ep->bEndpointAddress == req->epnum) + return 0; + } + + } + } + + if (req->request.length < U8500_USB_DMA_MIN_TRANSFER_SIZE) + return 0; + + return 1; +} + +/** + * musb_rx_dma_controller_handler() - callback invoked when the data is received in the receive direction + * @private_data: DMA channel + * + * This callback is invoked when the DMA transfer is completed + * in the receive direction. +*/ +void musb_rx_dma_controller_handler(void *private_data) +{ + struct dma_channel *channel = (struct dma_channel *)private_data; + struct musb_dma_channel *musb_channel = channel->private_data; +#ifndef CONFIG_USB_U8500_DMA + struct musb_hw_ep *hw_ep = musb_channel->hw_ep; + struct musb *musb = hw_ep->musb; + void __iomem *mbase = musb->mregs; + unsigned long flags, pio; + unsigned int rxcsr; + struct musb_qh *qh = hw_ep->in_qh; + struct urb *urb; + spin_lock_irqsave(&musb->lock, flags); + urb = next_urb(qh); + musb_ep_select(mbase, hw_ep->epnum); + channel->actual_len = musb_channel->cur_len; + pio = musb_channel->len - channel->actual_len; + if (!(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) { + memcpy(urb->transfer_buffer, + (void *)musb->rx_dma_log, channel->actual_len); + musb_memcpy(urb->transfer_buffer, + (void *)musb->rx_dma_log, channel->actual_len); + } + if (!pio) { + channel->status = MUSB_DMA_STATUS_FREE; + musb_dma_completion(musb, musb_channel->epnum, + musb_channel->transmit); + } + spin_unlock_irqrestore(&musb->lock, flags); +#else + schedule_work(&musb_channel->channel_data_rx); +#endif +} + +/** + * musb_tx_dma_controller_handler() - callback invoked on the transmit direction DMA data transfer + * @private_data: pointer to DMA channel. + * + * This callback is invoked when the DMA tranfer is completed + * in the transmit direction +*/ + +void musb_tx_dma_controller_handler(void *private_data) +{ + struct dma_channel *channel = (struct dma_channel *)private_data; + struct musb_dma_channel *musb_channel = channel->private_data; +#ifndef CONFIG_USB_U8500_DMA + struct musb_hw_ep *hw_ep = musb_channel->hw_ep; + struct musb *musb = hw_ep->musb; + void __iomem *mbase = musb->mregs; + unsigned long flags, pio; + unsigned int txcsr; + struct musb_qh *qh = hw_ep->out_qh; + struct urb *urb; + spin_lock_irqsave(&musb->lock, flags); + musb_ep_select(mbase, hw_ep->epnum); + channel->actual_len = musb_channel->cur_len; + pio = musb_channel->len - channel->actual_len; + if (!pio) { + channel->status = MUSB_DMA_STATUS_FREE; + musb_dma_completion(musb, musb_channel->epnum, + musb_channel->transmit); + } + if (pio) { + channel->status = MUSB_DMA_STATUS_FREE; + urb = next_urb(qh); + qh->offset += channel->actual_len; + buf = urb->transfer_buffer + qh->offset; + musb_write_fifo(hw_ep, pio, buf); + qh->segsize = pio; + musb_writew(hw_ep->regs, MUSB_TXCSR, MUSB_TXCSR_TXPKTRDY); + } + spin_unlock_irqrestore(&musb->lock, flags); +#else + schedule_work(&musb_channel->channel_data_tx); +#endif +} + +/** + * dma_controller_destroy() - deallocates the DMA controller + * @c: pointer to dma controller. + * + * This function deallocates the DMA controller. +*/ + +void dma_controller_destroy(struct dma_controller *c) +{ + struct musb_dma_controller *controller = container_of(c, + struct musb_dma_controller, controller); + struct musb_dma_channel *musb_channel = NULL; + u8 bit; + if (!controller) + return; + for (bit = 0; bit < MUSB_HSDMA_CHANNELS; bit++) + musb_channel = &(controller->channel[bit]); + if (controller->irq) + free_irq(controller->irq, c); + + kfree(controller); +} + +/** + * musb_channel_work_tx() - Invoked by worker thread + * @data: worker queue data + * + * This function is invoked by worker thread when the DMA transfer + * is completed in the transmit direction. +*/ + +static void musb_channel_work_tx(struct work_struct *data) +{ + struct musb_dma_channel *musb_channel = container_of(data, + struct musb_dma_channel, channel_data_tx); + struct musb_hw_ep *hw_ep = musb_channel->hw_ep; + struct musb *musb = hw_ep->musb; + unsigned long flags; + spin_lock_irqsave(&musb->lock, flags); + musb_channel->channel.actual_len = musb_channel->cur_len; + musb_channel->channel.status = MUSB_DMA_STATUS_FREE; + musb_ep_select(musb->mregs, hw_ep->epnum); + musb_dma_completion(musb, musb_channel->epnum, + musb_channel->transmit); + spin_unlock_irqrestore(&musb->lock, flags); +} + +/** + * musb_channel_work_tx() - Invoked by worker thread + * @data: worker queue data + * + * This function is invoked by worker thread when the + * DMA transfer is completed in the receive direction. +*/ + +static void musb_channel_work_rx(struct work_struct *data) +{ + struct musb_dma_channel *musb_channel = container_of(data, + struct musb_dma_channel, channel_data_rx); + struct musb_hw_ep *hw_ep = musb_channel->hw_ep; + struct musb *musb = hw_ep->musb; + void __iomem *mbase = musb->mregs; + unsigned long flags; + spin_lock_irqsave(&musb->lock, flags); + musb_channel->channel.actual_len = musb_channel->cur_len; + musb_channel->channel.status = MUSB_DMA_STATUS_FREE; + musb_ep_select(mbase, hw_ep->epnum); + musb_dma_completion(musb, musb_channel->epnum, + musb_channel->transmit); + spin_unlock_irqrestore(&musb->lock, flags); +} + +/** + * dma_controller_create() - creates the dma controller and initializes callbacks + * + * @musb: pointer to mentor core driver data instance| + * @base: base address of musb registers. + * + * This function creates the DMA controller and initializes the callbacks + * that are invoked from the Mentor IP core. +*/ + +struct dma_controller *__init +dma_controller_create(struct musb *musb, void __iomem *base) +{ + struct musb_dma_controller *controller; + + controller = kzalloc(sizeof(*controller), GFP_KERNEL); + if (!controller) + return NULL; + + controller->channel_count = MUSB_HSDMA_CHANNELS; + controller->private_data = musb; + controller->base = base; + + controller->controller.start = dma_controller_start; + controller->controller.stop = dma_controller_stop; + controller->controller.channel_alloc = dma_channel_allocate; + controller->controller.channel_release = dma_channel_release; + controller->controller.channel_program = dma_channel_program; + controller->controller.channel_abort = dma_channel_abort; + controller->controller.is_compatible = dma_is_compatible; + + return &controller->controller; +} diff --git a/drivers/usb/musb/stm_musb_dma.h b/drivers/usb/musb/stm_musb_dma.h new file mode 100644 index 00000000000..753cf08a882 --- /dev/null +++ b/drivers/usb/musb/stm_musb_dma.h @@ -0,0 +1,59 @@ +/* + * + * Copyright (C) 2009 ST-Ericsson SA + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __STM_MUSB_DMA_H__ +#define __STM_MUSB_DMA_H__ + +#define MUSB_HSDMA_CHANNELS 16 +struct musb_dma_controller; +struct dma_chan; +struct stedma40_chan_cfg; +struct musb_dma_channel { + struct dma_channel channel; + struct musb_dma_controller *controller; + struct stedma40_chan_cfg *info; + struct musb_hw_ep *hw_ep; + struct work_struct channel_data_tx; + struct work_struct channel_data_rx; + u32 start_addr; + u32 len; + u32 is_pipe_allocated; + u16 max_packet_sz; + u8 idx; + struct dma_chan *dma_chan; + unsigned int cur_len; + u8 epnum; + u8 last_xfer; + u8 transmit; +}; + +struct musb_dma_controller { + struct dma_controller controller; + struct musb_dma_channel channel[MUSB_HSDMA_CHANNELS]; + void *private_data; + void __iomem *base; + u8 channel_count; + u8 used_channels; + u8 irq; +}; +void musb_rx_dma_controller_handler(void *private_data); +void musb_tx_dma_controller_handler(void *private_data); +static void musb_channel_work_tx(struct work_struct *data); +static void musb_channel_work_rx(struct work_struct *data); +#endif + |