From 48f799390fa21f19fd92ecda4c3fa87b44f44f54 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Wed, 14 Jul 2004 01:07:48 -0700 Subject: [PATCH] [PATCH] USB: usb host side updates, mostly for suspend This adds some of the infrastructure needed to support some more USB capabilities: - CONFIG_USB_SUSPEND, so Linux can put individual devices into the USB "suspend" state. They can (sometimes) use "remote wakeup" to resume the host; or they can each be resumed by the host. + New usbcore device selective suspend/resume APIs * Define them, as stubs for now * Call them on the paths sysfs uses (renamed functions) + HCD support * Define root hub suspend calls; delegate them to HCDs. * OHCI and EHCI can suspend/resume root hubs that way. * Not called yet, until suspend/resume calls exist - CONFIG_USB_OTG, which depends on the selective suspend APIs to allow devices to switch roles (host to peripheral, etc). This patch just adds a few key flags in usb_bus, needed by usbcore (during enumeration) and by HCD and OTG controllers on OTG-capable boards. - Related bugfix: power budgeting is supposed to place a 100mA per port (non-OTG) for bus-powered devices. This patch changes no behavior; later patches will do that, and they'll be smaller because of this. Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 30 +++++++++++++++++ drivers/usb/core/hcd.h | 9 +++++ drivers/usb/core/hub.c | 61 ++++++++++++++++++++++++++++++---- drivers/usb/core/usb.c | 17 ++++++---- drivers/usb/host/ehci-hcd.c | 4 ++- drivers/usb/host/ohci-omap.c | 4 +++ drivers/usb/host/ohci-pci.c | 6 +++- drivers/usb/host/ohci-sa1111.c | 4 +++ include/linux/usb.h | 8 +++++ 9 files changed, 128 insertions(+), 15 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 8692bb1ea850d..cd2268ad1caf6 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1381,6 +1381,32 @@ rescan: /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_SUSPEND + +static int hcd_hub_suspend (struct usb_bus *bus) +{ + struct usb_hcd *hcd; + + hcd = container_of (bus, struct usb_hcd, self); + if (hcd->driver->hub_suspend) + return hcd->driver->hub_suspend (hcd); + return 0; +} + +static int hcd_hub_resume (struct usb_bus *bus) +{ + struct usb_hcd *hcd; + + hcd = container_of (bus, struct usb_hcd, self); + if (hcd->driver->hub_resume) + return hcd->driver->hub_resume (hcd); + return 0; +} + +#endif + +/*-------------------------------------------------------------------------*/ + /* called by khubd, rmmod, apmd, or other thread for hcd-private cleanup. * we're guaranteed that the device is fully quiesced. also, that each * endpoint has been hcd_endpoint_disabled. @@ -1435,6 +1461,10 @@ struct usb_operations usb_hcd_operations = { .buffer_alloc = hcd_buffer_alloc, .buffer_free = hcd_buffer_free, .disable = hcd_endpoint_disable, +#ifdef CONFIG_USB_SUSPEND + .hub_suspend = hcd_hub_suspend, + .hub_resume = hcd_hub_resume, +#endif }; EXPORT_SYMBOL (usb_hcd_operations); diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 82827b15cc5e5..89b6fc2e35595 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -152,6 +152,10 @@ struct usb_operations { void *addr, dma_addr_t dma); void (*disable)(struct usb_device *udev, int bEndpointAddress); + + /* global suspend/resume of bus */ + int (*hub_suspend)(struct usb_bus *); + int (*hub_resume)(struct usb_bus *); }; /* each driver provides one of these, and hardware init support */ @@ -173,6 +177,9 @@ struct hc_driver { int (*reset) (struct usb_hcd *hcd); int (*start) (struct usb_hcd *hcd); + /* NOTE: these suspend/resume calls relate to the HC as + * a whole, not just the root hub; they're for bus glue. + */ /* called after all devices were suspended */ int (*suspend) (struct usb_hcd *hcd, u32 state); @@ -203,6 +210,8 @@ struct hc_driver { int (*hub_control) (struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); + int (*hub_suspend)(struct usb_hcd *); + int (*hub_resume)(struct usb_hcd *); }; extern void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index ead5b3cfef452..68e10d9c66ef8 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1237,6 +1237,33 @@ static int hub_port_disable(struct usb_device *hdev, int port) return ret; } +#ifdef CONFIG_USB_SUSPEND + + /* no USB_SUSPEND yet! */ + +#else /* !CONFIG_USB_SUSPEND */ + +int usb_suspend_device(struct usb_device *udev, u32 state) +{ + return 0; +} + +int usb_resume_device(struct usb_device *udev) +{ + return 0; +} + +#define hub_suspend 0 +#define hub_resume 0 +#define remote_wakeup(x) 0 + +#endif /* CONFIG_USB_SUSPEND */ + +EXPORT_SYMBOL(usb_suspend_device); +EXPORT_SYMBOL(usb_resume_device); + + + /* USB 2.0 spec, 7.1.7.3 / fig 7-29: * * Between connect detection and reset signaling there must be a delay @@ -1336,8 +1363,11 @@ hub_port_init (struct usb_device *hdev, struct usb_device *udev, int port) /* root hub ports have a slightly longer reset period * (from USB 2.0 spec, section 7.1.7.5) */ - if (!hdev->parent) + if (!hdev->parent) { delay = HUB_ROOT_RESET_TIME; + if (port + 1 == hdev->bus->otg_port) + hdev->bus->b_hnp_enable = 0; + } /* Some low speed devices have problems with the quick delay, so */ /* be a bit pessimistic with those devices. RHbug #23670 */ @@ -1508,16 +1538,25 @@ hub_power_remaining (struct usb_hub *hub) for (i = 0; i < hdev->maxchild; i++) { struct usb_device *udev = hdev->children[i]; - int delta; + int delta, ceiling; if (!udev) continue; + /* 100mA per-port ceiling, or 8mA for OTG ports */ + if (i != (udev->bus->otg_port - 1) || hdev->parent) + ceiling = 50; + else + ceiling = 4; + if (udev->actconfig) delta = udev->actconfig->desc.bMaxPower; else - delta = 50; + delta = ceiling; // dev_dbg(&udev->dev, "budgeted %dmA\n", 2 * delta); + if (delta > ceiling) + dev_warn(&udev->dev, "%dmA over %dmA budget!\n", + 2 * (delta - ceiling), 2 * ceiling); remaining -= delta; } if (remaining < 0) { @@ -1814,11 +1853,17 @@ static void hub_events(void) } if (portchange & USB_PORT_STAT_C_SUSPEND) { + clear_port_feature(hdev, i + 1, + USB_PORT_FEAT_C_SUSPEND); + if (hdev->children[i]) + ret = remote_wakeup(hdev->children[i]); + else + ret = -ENODEV; dev_dbg (hub_dev, - "suspend change on port %d\n", - i + 1); - clear_port_feature(hdev, - i + 1, USB_PORT_FEAT_C_SUSPEND); + "resume on port %d, status %d\n", + i + 1, ret); + if (ret < 0) + ret = hub_port_disable(hdev, i); } if (portchange & USB_PORT_STAT_C_OVERCURRENT) { @@ -1905,6 +1950,8 @@ static struct usb_driver hub_driver = { .name = "hub", .probe = hub_probe, .disconnect = hub_disconnect, + .suspend = hub_suspend, + .resume = hub_resume, .ioctl = hub_ioctl, .id_table = hub_id_table, }; diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 66137a2fda5a0..324e16cafdf3d 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1221,13 +1221,15 @@ void usb_buffer_unmap_sg (struct usb_device *dev, unsigned pipe, usb_pipein (pipe) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); } -static int usb_device_suspend(struct device *dev, u32 state) +static int usb_generic_suspend(struct device *dev, u32 state) { struct usb_interface *intf; struct usb_driver *driver; + if (dev->driver == &usb_generic_driver) + return usb_suspend_device (to_usb_device(dev), state); + if ((dev->driver == NULL) || - (dev->driver == &usb_generic_driver) || (dev->driver_data == &usb_generic_driver_data)) return 0; @@ -1239,13 +1241,16 @@ static int usb_device_suspend(struct device *dev, u32 state) return 0; } -static int usb_device_resume(struct device *dev) +static int usb_generic_resume(struct device *dev) { struct usb_interface *intf; struct usb_driver *driver; + /* devices resume through their hub */ + if (dev->driver == &usb_generic_driver) + return usb_resume_device (to_usb_device(dev)); + if ((dev->driver == NULL) || - (dev->driver == &usb_generic_driver) || (dev->driver_data == &usb_generic_driver_data)) return 0; @@ -1261,8 +1266,8 @@ struct bus_type usb_bus_type = { .name = "usb", .match = usb_device_match, .hotplug = usb_hotplug, - .suspend = usb_device_suspend, - .resume = usb_device_resume, + .suspend = usb_generic_suspend, + .resume = usb_generic_resume, }; #ifndef MODULE diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 506006be87d73..d3bb7b508df0d 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -647,7 +647,7 @@ static int ehci_suspend (struct usb_hcd *hcd, u32 state) msleep (100); #ifdef CONFIG_USB_SUSPEND - (void) usb_suspend_device (hcd->self.root_hub); + (void) usb_suspend_device (hcd->self.root_hub, state); #else /* FIXME lock root hub */ (void) ehci_hub_suspend (hcd); @@ -1036,6 +1036,8 @@ static const struct hc_driver ehci_driver = { */ .hub_status_data = ehci_hub_status_data, .hub_control = ehci_hub_control, + .hub_suspend = ehci_hub_suspend, + .hub_resume = ehci_hub_resume, }; /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index b82c9398585cb..16ccbbfe2c3e2 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -563,6 +563,10 @@ static const struct hc_driver ohci_omap_hc_driver = { */ .hub_status_data = ohci_hub_status_data, .hub_control = ohci_hub_control, +#ifdef CONFIG_USB_SUSPEND + .hub_suspend = ohci_hub_suspend, + .hub_resume = ohci_hub_resume, +#endif }; /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index 0e7f9f804a969..dc8bc13bf0aaa 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -125,7 +125,7 @@ static int ohci_pci_suspend (struct usb_hcd *hcd, u32 state) msleep (100); #ifdef CONFIG_USB_SUSPEND - (void) usb_suspend_device (hcd->self.root_hub); + (void) usb_suspend_device (hcd->self.root_hub, state); #else down (&hcd->self.root_hub->serialize); (void) ohci_hub_suspend (hcd); @@ -238,6 +238,10 @@ static const struct hc_driver ohci_pci_hc_driver = { */ .hub_status_data = ohci_hub_status_data, .hub_control = ohci_hub_control, +#ifdef CONFIG_USB_SUSPEND + .hub_suspend = ohci_hub_suspend, + .hub_resume = ohci_hub_resume, +#endif }; /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/ohci-sa1111.c b/drivers/usb/host/ohci-sa1111.c index 34cce497ee1e3..c3cd76aba1635 100644 --- a/drivers/usb/host/ohci-sa1111.c +++ b/drivers/usb/host/ohci-sa1111.c @@ -346,6 +346,10 @@ static const struct hc_driver ohci_sa1111_hc_driver = { */ .hub_status_data = ohci_hub_status_data, .hub_control = ohci_hub_control, +#ifdef CONFIG_USB_SUSPEND + .hub_suspend = ohci_hub_suspend, + .hub_resume = ohci_hub_resume, +#endif }; /*-------------------------------------------------------------------------*/ diff --git a/include/linux/usb.h b/include/linux/usb.h index ffd0ce9ac81a5..632e19947f436 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -241,6 +241,9 @@ struct usb_bus { struct device *controller; /* host/master side hardware */ int busnum; /* Bus number (in order of reg) */ char *bus_name; /* stable id (PCI slot_name etc) */ + u8 otg_port; /* 0, or number of OTG/HNP port */ + unsigned is_b_host:1; /* true during some HNP roleswitches */ + unsigned b_hnp_enable:1; /* OTG: did A-Host enable HNP? */ int devnum_next; /* Next open device number in round-robin allocation */ @@ -936,6 +939,11 @@ extern int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout); +/* selective suspend/resume */ +extern int usb_suspend_device(struct usb_device *dev, u32 state); +extern int usb_resume_device(struct usb_device *dev); + + /* wrappers around usb_control_msg() for the most common standard requests */ extern int usb_get_descriptor(struct usb_device *dev, unsigned char desctype, unsigned char descindex, void *buf, int size); -- 2.53.0