Commit 48f79939 authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[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: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <greg@kroah.com>
parent 13c3fe8a
...@@ -1381,6 +1381,32 @@ static void hcd_endpoint_disable (struct usb_device *udev, int endpoint) ...@@ -1381,6 +1381,32 @@ static void hcd_endpoint_disable (struct usb_device *udev, int endpoint)
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
#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. /* called by khubd, rmmod, apmd, or other thread for hcd-private cleanup.
* we're guaranteed that the device is fully quiesced. also, that each * we're guaranteed that the device is fully quiesced. also, that each
* endpoint has been hcd_endpoint_disabled. * endpoint has been hcd_endpoint_disabled.
...@@ -1435,6 +1461,10 @@ struct usb_operations usb_hcd_operations = { ...@@ -1435,6 +1461,10 @@ struct usb_operations usb_hcd_operations = {
.buffer_alloc = hcd_buffer_alloc, .buffer_alloc = hcd_buffer_alloc,
.buffer_free = hcd_buffer_free, .buffer_free = hcd_buffer_free,
.disable = hcd_endpoint_disable, .disable = hcd_endpoint_disable,
#ifdef CONFIG_USB_SUSPEND
.hub_suspend = hcd_hub_suspend,
.hub_resume = hcd_hub_resume,
#endif
}; };
EXPORT_SYMBOL (usb_hcd_operations); EXPORT_SYMBOL (usb_hcd_operations);
......
...@@ -152,6 +152,10 @@ struct usb_operations { ...@@ -152,6 +152,10 @@ struct usb_operations {
void *addr, dma_addr_t dma); void *addr, dma_addr_t dma);
void (*disable)(struct usb_device *udev, int bEndpointAddress); 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 */ /* each driver provides one of these, and hardware init support */
...@@ -173,6 +177,9 @@ struct hc_driver { ...@@ -173,6 +177,9 @@ struct hc_driver {
int (*reset) (struct usb_hcd *hcd); int (*reset) (struct usb_hcd *hcd);
int (*start) (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 */ /* called after all devices were suspended */
int (*suspend) (struct usb_hcd *hcd, u32 state); int (*suspend) (struct usb_hcd *hcd, u32 state);
...@@ -203,6 +210,8 @@ struct hc_driver { ...@@ -203,6 +210,8 @@ struct hc_driver {
int (*hub_control) (struct usb_hcd *hcd, int (*hub_control) (struct usb_hcd *hcd,
u16 typeReq, u16 wValue, u16 wIndex, u16 typeReq, u16 wValue, u16 wIndex,
char *buf, u16 wLength); 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); extern void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs);
......
...@@ -1237,6 +1237,33 @@ static int hub_port_disable(struct usb_device *hdev, int port) ...@@ -1237,6 +1237,33 @@ static int hub_port_disable(struct usb_device *hdev, int port)
return ret; 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: /* USB 2.0 spec, 7.1.7.3 / fig 7-29:
* *
* Between connect detection and reset signaling there must be a delay * 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) ...@@ -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 /* root hub ports have a slightly longer reset period
* (from USB 2.0 spec, section 7.1.7.5) * (from USB 2.0 spec, section 7.1.7.5)
*/ */
if (!hdev->parent) if (!hdev->parent) {
delay = HUB_ROOT_RESET_TIME; 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 */ /* Some low speed devices have problems with the quick delay, so */
/* be a bit pessimistic with those devices. RHbug #23670 */ /* be a bit pessimistic with those devices. RHbug #23670 */
...@@ -1508,16 +1538,25 @@ hub_power_remaining (struct usb_hub *hub) ...@@ -1508,16 +1538,25 @@ hub_power_remaining (struct usb_hub *hub)
for (i = 0; i < hdev->maxchild; i++) { for (i = 0; i < hdev->maxchild; i++) {
struct usb_device *udev = hdev->children[i]; struct usb_device *udev = hdev->children[i];
int delta; int delta, ceiling;
if (!udev) if (!udev)
continue; 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) if (udev->actconfig)
delta = udev->actconfig->desc.bMaxPower; delta = udev->actconfig->desc.bMaxPower;
else else
delta = 50; delta = ceiling;
// dev_dbg(&udev->dev, "budgeted %dmA\n", 2 * delta); // 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; remaining -= delta;
} }
if (remaining < 0) { if (remaining < 0) {
...@@ -1814,11 +1853,17 @@ static void hub_events(void) ...@@ -1814,11 +1853,17 @@ static void hub_events(void)
} }
if (portchange & USB_PORT_STAT_C_SUSPEND) { 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, dev_dbg (hub_dev,
"suspend change on port %d\n", "resume on port %d, status %d\n",
i + 1); i + 1, ret);
clear_port_feature(hdev, if (ret < 0)
i + 1, USB_PORT_FEAT_C_SUSPEND); ret = hub_port_disable(hdev, i);
} }
if (portchange & USB_PORT_STAT_C_OVERCURRENT) { if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
...@@ -1905,6 +1950,8 @@ static struct usb_driver hub_driver = { ...@@ -1905,6 +1950,8 @@ static struct usb_driver hub_driver = {
.name = "hub", .name = "hub",
.probe = hub_probe, .probe = hub_probe,
.disconnect = hub_disconnect, .disconnect = hub_disconnect,
.suspend = hub_suspend,
.resume = hub_resume,
.ioctl = hub_ioctl, .ioctl = hub_ioctl,
.id_table = hub_id_table, .id_table = hub_id_table,
}; };
......
...@@ -1221,13 +1221,15 @@ void usb_buffer_unmap_sg (struct usb_device *dev, unsigned pipe, ...@@ -1221,13 +1221,15 @@ void usb_buffer_unmap_sg (struct usb_device *dev, unsigned pipe,
usb_pipein (pipe) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); 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_interface *intf;
struct usb_driver *driver; struct usb_driver *driver;
if (dev->driver == &usb_generic_driver)
return usb_suspend_device (to_usb_device(dev), state);
if ((dev->driver == NULL) || if ((dev->driver == NULL) ||
(dev->driver == &usb_generic_driver) ||
(dev->driver_data == &usb_generic_driver_data)) (dev->driver_data == &usb_generic_driver_data))
return 0; return 0;
...@@ -1239,13 +1241,16 @@ static int usb_device_suspend(struct device *dev, u32 state) ...@@ -1239,13 +1241,16 @@ static int usb_device_suspend(struct device *dev, u32 state)
return 0; return 0;
} }
static int usb_device_resume(struct device *dev) static int usb_generic_resume(struct device *dev)
{ {
struct usb_interface *intf; struct usb_interface *intf;
struct usb_driver *driver; 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) || if ((dev->driver == NULL) ||
(dev->driver == &usb_generic_driver) ||
(dev->driver_data == &usb_generic_driver_data)) (dev->driver_data == &usb_generic_driver_data))
return 0; return 0;
...@@ -1261,8 +1266,8 @@ struct bus_type usb_bus_type = { ...@@ -1261,8 +1266,8 @@ struct bus_type usb_bus_type = {
.name = "usb", .name = "usb",
.match = usb_device_match, .match = usb_device_match,
.hotplug = usb_hotplug, .hotplug = usb_hotplug,
.suspend = usb_device_suspend, .suspend = usb_generic_suspend,
.resume = usb_device_resume, .resume = usb_generic_resume,
}; };
#ifndef MODULE #ifndef MODULE
......
...@@ -647,7 +647,7 @@ static int ehci_suspend (struct usb_hcd *hcd, u32 state) ...@@ -647,7 +647,7 @@ static int ehci_suspend (struct usb_hcd *hcd, u32 state)
msleep (100); msleep (100);
#ifdef CONFIG_USB_SUSPEND #ifdef CONFIG_USB_SUSPEND
(void) usb_suspend_device (hcd->self.root_hub); (void) usb_suspend_device (hcd->self.root_hub, state);
#else #else
/* FIXME lock root hub */ /* FIXME lock root hub */
(void) ehci_hub_suspend (hcd); (void) ehci_hub_suspend (hcd);
...@@ -1036,6 +1036,8 @@ static const struct hc_driver ehci_driver = { ...@@ -1036,6 +1036,8 @@ static const struct hc_driver ehci_driver = {
*/ */
.hub_status_data = ehci_hub_status_data, .hub_status_data = ehci_hub_status_data,
.hub_control = ehci_hub_control, .hub_control = ehci_hub_control,
.hub_suspend = ehci_hub_suspend,
.hub_resume = ehci_hub_resume,
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
...@@ -563,6 +563,10 @@ static const struct hc_driver ohci_omap_hc_driver = { ...@@ -563,6 +563,10 @@ static const struct hc_driver ohci_omap_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
#ifdef CONFIG_USB_SUSPEND
.hub_suspend = ohci_hub_suspend,
.hub_resume = ohci_hub_resume,
#endif
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
...@@ -125,7 +125,7 @@ static int ohci_pci_suspend (struct usb_hcd *hcd, u32 state) ...@@ -125,7 +125,7 @@ static int ohci_pci_suspend (struct usb_hcd *hcd, u32 state)
msleep (100); msleep (100);
#ifdef CONFIG_USB_SUSPEND #ifdef CONFIG_USB_SUSPEND
(void) usb_suspend_device (hcd->self.root_hub); (void) usb_suspend_device (hcd->self.root_hub, state);
#else #else
down (&hcd->self.root_hub->serialize); down (&hcd->self.root_hub->serialize);
(void) ohci_hub_suspend (hcd); (void) ohci_hub_suspend (hcd);
...@@ -238,6 +238,10 @@ static const struct hc_driver ohci_pci_hc_driver = { ...@@ -238,6 +238,10 @@ static const struct hc_driver ohci_pci_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
#ifdef CONFIG_USB_SUSPEND
.hub_suspend = ohci_hub_suspend,
.hub_resume = ohci_hub_resume,
#endif
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
...@@ -346,6 +346,10 @@ static const struct hc_driver ohci_sa1111_hc_driver = { ...@@ -346,6 +346,10 @@ static const struct hc_driver ohci_sa1111_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
#ifdef CONFIG_USB_SUSPEND
.hub_suspend = ohci_hub_suspend,
.hub_resume = ohci_hub_resume,
#endif
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
...@@ -241,6 +241,9 @@ struct usb_bus { ...@@ -241,6 +241,9 @@ struct usb_bus {
struct device *controller; /* host/master side hardware */ struct device *controller; /* host/master side hardware */
int busnum; /* Bus number (in order of reg) */ int busnum; /* Bus number (in order of reg) */
char *bus_name; /* stable id (PCI slot_name etc) */ 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 */ 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, ...@@ -936,6 +939,11 @@ extern int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length, void *data, int len, int *actual_length,
int timeout); 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 */ /* wrappers around usb_control_msg() for the most common standard requests */
extern int usb_get_descriptor(struct usb_device *dev, unsigned char desctype, extern int usb_get_descriptor(struct usb_device *dev, unsigned char desctype,
unsigned char descindex, void *buf, int size); unsigned char descindex, void *buf, int size);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment