]> git.hungrycats.org Git - linux/commitdiff
[PATCH] USB: usb host side updates, mostly for suspend
authorDavid Brownell <david-b@pacbell.net>
Wed, 14 Jul 2004 08:07:48 +0000 (01:07 -0700)
committerGreg Kroah-Hartman <greg@kroah.com>
Wed, 14 Jul 2004 08:07:48 +0000 (01:07 -0700)
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
drivers/usb/core/hcd.h
drivers/usb/core/hub.c
drivers/usb/core/usb.c
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ohci-omap.c
drivers/usb/host/ohci-pci.c
drivers/usb/host/ohci-sa1111.c
include/linux/usb.h

index 8692bb1ea850d745ef63b53e629e891ed0e99de5..cd2268ad1caf612aa23b7daf0c2cdb6bed590121 100644 (file)
@@ -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);
 
index 82827b15cc5e59881fddd7aa10181dbae43e2dea..89b6fc2e35595d74822bd0754b4db72271e0a347 100644 (file)
@@ -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);
index ead5b3cfef452abaf3c2fc49b660f96d4b45bdba..68e10d9c66ef830ee209d4f7696853692fa41800 100644 (file)
@@ -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,
 };
index 66137a2fda5a0b7e0bd0be17d72265d45327d3ed..324e16cafdf3d4788de45ae01610847a377d7317 100644 (file)
@@ -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
index 506006be87d739262f7b365741d2a2c6277202e9..d3bb7b508df0dda44ebcc183a608ccbe7fb16d79 100644 (file)
@@ -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,
 };
 
 /*-------------------------------------------------------------------------*/
index b82c9398585cbeba9f82de8f156265d2809c0164..16ccbbfe2c3e2d978d6e1fa064e2e14eb4204a6d 100644 (file)
@@ -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
 };
 
 /*-------------------------------------------------------------------------*/
index 0e7f9f804a96950134440c3f84c858c69981c2c1..dc8bc13bf0aaa872860bef83aeb67ecb9dffcb28 100644 (file)
@@ -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
 };
 
 /*-------------------------------------------------------------------------*/
index 34cce497ee1e3081595629b79becf8b7a8e58f41..c3cd76aba1635519b6879a23caa279450fff505d 100644 (file)
@@ -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
 };
 
 /*-------------------------------------------------------------------------*/
index ffd0ce9ac81a59478b3a1a2208b226592b8c8af2..632e19947f436dafef7453adf6c0866f970ae0a5 100644 (file)
@@ -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);