ChangeSet 1.1757.66.36, 2004/07/14 15:07:48-07:00, david-b@pacbell.net

[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 <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>


 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 -Nru a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
--- a/drivers/usb/core/hcd.c	2004-07-14 16:43:10 -07:00
+++ b/drivers/usb/core/hcd.c	2004-07-14 16:43:10 -07:00
@@ -1381,6 +1381,32 @@
 
 /*-------------------------------------------------------------------------*/
 
+#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 @@
 	.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 -Nru a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h
--- a/drivers/usb/core/hcd.h	2004-07-14 16:43:10 -07:00
+++ b/drivers/usb/core/hcd.h	2004-07-14 16:43:10 -07:00
@@ -152,6 +152,10 @@
 			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 @@
 	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 @@
 	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 -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
--- a/drivers/usb/core/hub.c	2004-07-14 16:43:10 -07:00
+++ b/drivers/usb/core/hub.c	2004-07-14 16:43:10 -07:00
@@ -1237,6 +1237,33 @@
 	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 @@
 	/* 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 @@
 
 	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 @@
 			}
 
 			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 @@
 	.name =		"hub",
 	.probe =	hub_probe,
 	.disconnect =	hub_disconnect,
+	.suspend =	hub_suspend,
+	.resume =	hub_resume,
 	.ioctl =	hub_ioctl,
 	.id_table =	hub_id_table,
 };
diff -Nru a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
--- a/drivers/usb/core/usb.c	2004-07-14 16:43:10 -07:00
+++ b/drivers/usb/core/usb.c	2004-07-14 16:43:10 -07:00
@@ -1221,13 +1221,15 @@
 			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 @@
 	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 @@
 	.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 -Nru a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
--- a/drivers/usb/host/ehci-hcd.c	2004-07-14 16:43:10 -07:00
+++ b/drivers/usb/host/ehci-hcd.c	2004-07-14 16:43:10 -07:00
@@ -647,7 +647,7 @@
 		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 @@
 	 */
 	.hub_status_data =	ehci_hub_status_data,
 	.hub_control =		ehci_hub_control,
+	.hub_suspend =		ehci_hub_suspend,
+	.hub_resume =		ehci_hub_resume,
 };
 
 /*-------------------------------------------------------------------------*/
diff -Nru a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c
--- a/drivers/usb/host/ohci-omap.c	2004-07-14 16:43:10 -07:00
+++ b/drivers/usb/host/ohci-omap.c	2004-07-14 16:43:10 -07:00
@@ -563,6 +563,10 @@
 	 */
 	.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 -Nru a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c
--- a/drivers/usb/host/ohci-pci.c	2004-07-14 16:43:10 -07:00
+++ b/drivers/usb/host/ohci-pci.c	2004-07-14 16:43:10 -07:00
@@ -125,7 +125,7 @@
 		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 @@
 	 */
 	.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 -Nru a/drivers/usb/host/ohci-sa1111.c b/drivers/usb/host/ohci-sa1111.c
--- a/drivers/usb/host/ohci-sa1111.c	2004-07-14 16:43:10 -07:00
+++ b/drivers/usb/host/ohci-sa1111.c	2004-07-14 16:43:10 -07:00
@@ -346,6 +346,10 @@
 	 */
 	.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 -Nru a/include/linux/usb.h b/include/linux/usb.h
--- a/include/linux/usb.h	2004-07-14 16:43:10 -07:00
+++ b/include/linux/usb.h	2004-07-14 16:43:10 -07:00
@@ -241,6 +241,9 @@
 	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 */
 
@@ -935,6 +938,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,
