USB: refactor code for enabling/disabling remote wakeup
[pandora-kernel.git] / drivers / usb / core / hub.c
index 4191db3..c376c8b 100644 (file)
@@ -668,6 +668,15 @@ resubmit:
 static inline int
 hub_clear_tt_buffer (struct usb_device *hdev, u16 devinfo, u16 tt)
 {
+       /* Need to clear both directions for control ep */
+       if (((devinfo >> 11) & USB_ENDPOINT_XFERTYPE_MASK) ==
+                       USB_ENDPOINT_XFER_CONTROL) {
+               int status = usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+                               HUB_CLEAR_TT_BUFFER, USB_RT_PORT,
+                               devinfo ^ 0x8000, tt, NULL, 0, 1000);
+               if (status)
+                       return status;
+       }
        return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
                               HUB_CLEAR_TT_BUFFER, USB_RT_PORT, devinfo,
                               tt, NULL, 0, 1000);
@@ -1455,11 +1464,10 @@ static int hub_configure(struct usb_hub *hub,
         * and battery-powered root hubs (may provide just 8 mA).
         */
        ret = usb_get_status(hdev, USB_RECIP_DEVICE, 0, &hubstatus);
-       if (ret < 2) {
+       if (ret) {
                message = "can't get hub status";
                goto fail;
        }
-       le16_to_cpus(&hubstatus);
        hcd = bus_to_hcd(hdev->bus);
        if (hdev == hdev->bus->root_hub) {
                if (hcd->power_budget > 0)
@@ -2829,25 +2837,65 @@ void usb_enable_ltm(struct usb_device *udev)
 }
 EXPORT_SYMBOL_GPL(usb_enable_ltm);
 
-#ifdef CONFIG_PM
 /*
- * usb_disable_function_remotewakeup - disable usb3.0
- * device's function remote wakeup
+ * usb_enable_remote_wakeup - enable remote wakeup for a device
  * @udev: target device
  *
- * Assume there's only one function on the USB 3.0
- * device and disable remote wake for the first
- * interface. FIXME if the interface association
- * descriptor shows there's more than one function.
+ * For USB-2 devices: Set the device's remote wakeup feature.
+ *
+ * For USB-3 devices: Assume there's only one function on the device and
+ * enable remote wake for the first interface.  FIXME if the interface
+ * association descriptor shows there's more than one function.
  */
-static int usb_disable_function_remotewakeup(struct usb_device *udev)
+static int usb_enable_remote_wakeup(struct usb_device *udev)
 {
-       return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+       if (udev->speed < USB_SPEED_SUPER)
+               return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                               USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
+                               USB_DEVICE_REMOTE_WAKEUP, 0, NULL, 0,
+                               USB_CTRL_SET_TIMEOUT);
+       else
+               return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                               USB_REQ_SET_FEATURE, USB_RECIP_INTERFACE,
+                               USB_INTRF_FUNC_SUSPEND,
+                               USB_INTRF_FUNC_SUSPEND_RW |
+                                       USB_INTRF_FUNC_SUSPEND_LP,
+                               NULL, 0, USB_CTRL_SET_TIMEOUT);
+}
+
+/*
+ * usb_disable_remote_wakeup - disable remote wakeup for a device
+ * @udev: target device
+ *
+ * For USB-2 devices: Clear the device's remote wakeup feature.
+ *
+ * For USB-3 devices: Assume there's only one function on the device and
+ * disable remote wake for the first interface.  FIXME if the interface
+ * association descriptor shows there's more than one function.
+ */
+static int usb_disable_remote_wakeup(struct usb_device *udev)
+{
+       if (udev->speed < USB_SPEED_SUPER)
+               return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                               USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE,
+                               USB_DEVICE_REMOTE_WAKEUP, 0, NULL, 0,
+                               USB_CTRL_SET_TIMEOUT);
+       else
+               return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
                                USB_REQ_CLEAR_FEATURE, USB_RECIP_INTERFACE,
                                USB_INTRF_FUNC_SUSPEND, 0, NULL, 0,
                                USB_CTRL_SET_TIMEOUT);
 }
 
+/* Count of wakeup-enabled devices at or below udev */
+static unsigned wakeup_enabled_descendants(struct usb_device *udev)
+{
+       struct usb_hub *hub = usb_hub_to_struct_hub(udev);
+
+       return udev->do_remote_wakeup +
+                       (hub ? hub->wakeup_enabled_descendants : 0);
+}
+
 /*
  * usb_port_suspend - suspend a usb device's upstream port
  * @udev: device that's no longer in active use, not a root hub
@@ -2888,8 +2936,8 @@ static int usb_disable_function_remotewakeup(struct usb_device *udev)
  * Linux (2.6) currently has NO mechanisms to initiate that:  no khubd
  * timer, no SRP, no requests through sysfs.
  *
- * If Runtime PM isn't enabled or used, non-SuperSpeed devices really get
- * suspended only when their bus goes into global suspend (i.e., the root
+ * If Runtime PM isn't enabled or used, non-SuperSpeed devices may not get
+ * suspended until their bus goes into global suspend (i.e., the root
  * hub is suspended).  Nevertheless, we change @udev->state to
  * USB_STATE_SUSPENDED as this is the device's "logical" state.  The actual
  * upstream port setting is stored in @udev->port_is_suspended.
@@ -2912,27 +2960,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
         * we don't explicitly enable it here.
         */
        if (udev->do_remote_wakeup) {
-               if (!hub_is_superspeed(hub->hdev)) {
-                       status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
-                                       USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
-                                       USB_DEVICE_REMOTE_WAKEUP, 0,
-                                       NULL, 0,
-                                       USB_CTRL_SET_TIMEOUT);
-               } else {
-                       /* Assume there's only one function on the USB 3.0
-                        * device and enable remote wake for the first
-                        * interface. FIXME if the interface association
-                        * descriptor shows there's more than one function.
-                        */
-                       status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
-                                       USB_REQ_SET_FEATURE,
-                                       USB_RECIP_INTERFACE,
-                                       USB_INTRF_FUNC_SUSPEND,
-                                       USB_INTRF_FUNC_SUSPEND_RW |
-                                       USB_INTRF_FUNC_SUSPEND_LP,
-                                       NULL, 0,
-                                       USB_CTRL_SET_TIMEOUT);
-               }
+               status = usb_enable_remote_wakeup(udev);
                if (status) {
                        dev_dbg(&udev->dev, "won't remote wakeup, status %d\n",
                                        status);
@@ -2960,15 +2988,21 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
        /* see 7.1.7.6 */
        if (hub_is_superspeed(hub->hdev))
                status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U3);
-       else if (PMSG_IS_AUTO(msg))
-               status = set_port_feature(hub->hdev, port1,
-                                               USB_PORT_FEAT_SUSPEND);
+
        /*
         * For system suspend, we do not need to enable the suspend feature
         * on individual USB-2 ports.  The devices will automatically go
         * into suspend a few ms after the root hub stops sending packets.
         * The USB 2.0 spec calls this "global suspend".
+        *
+        * However, many USB hubs have a bug: They don't relay wakeup requests
+        * from a downstream port if the port's suspend feature isn't on.
+        * Therefore we will turn on the suspend feature if udev or any of its
+        * descendants is enabled for remote wakeup.
         */
+       else if (PMSG_IS_AUTO(msg) || wakeup_enabled_descendants(udev) > 0)
+               status = set_port_feature(hub->hdev, port1,
+                               USB_PORT_FEAT_SUSPEND);
        else {
                really_suspend = false;
                status = 0;
@@ -2977,19 +3011,8 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
                dev_dbg(hub->intfdev, "can't suspend port %d, status %d\n",
                                port1, status);
                /* paranoia:  "should not happen" */
-               if (udev->do_remote_wakeup) {
-                       if (!hub_is_superspeed(hub->hdev)) {
-                               (void) usb_control_msg(udev,
-                                               usb_sndctrlpipe(udev, 0),
-                                               USB_REQ_CLEAR_FEATURE,
-                                               USB_RECIP_DEVICE,
-                                               USB_DEVICE_REMOTE_WAKEUP, 0,
-                                               NULL, 0,
-                                               USB_CTRL_SET_TIMEOUT);
-                       } else
-                               (void) usb_disable_function_remotewakeup(udev);
-
-               }
+               if (udev->do_remote_wakeup)
+                       (void) usb_disable_remote_wakeup(udev);
 
                /* Try to enable USB2 hardware LPM again */
                if (udev->usb2_hw_lpm_capable == 1)
@@ -3003,15 +3026,16 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
                if (!PMSG_IS_AUTO(msg))
                        status = 0;
        } else {
-               /* device has up to 10 msec to fully suspend */
                dev_dbg(&udev->dev, "usb %ssuspend, wakeup %d\n",
                                (PMSG_IS_AUTO(msg) ? "auto-" : ""),
                                udev->do_remote_wakeup);
-               usb_set_device_state(udev, USB_STATE_SUSPENDED);
                if (really_suspend) {
                        udev->port_is_suspended = 1;
+
+                       /* device has up to 10 msec to fully suspend */
                        msleep(10);
                }
+               usb_set_device_state(udev, USB_STATE_SUSPENDED);
        }
 
        /*
@@ -3077,8 +3101,6 @@ static int finish_port_resume(struct usb_device *udev)
        if (status == 0) {
                devstatus = 0;
                status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus);
-               if (status >= 0)
-                       status = (status > 0 ? 0 : -ENODEV);
 
                /* If a normal resume failed, try doing a reset-resume */
                if (status && !udev->reset_resume && udev->persist_enabled) {
@@ -3098,24 +3120,15 @@ static int finish_port_resume(struct usb_device *udev)
         * udev->reset_resume
         */
        } else if (udev->actconfig && !udev->reset_resume) {
-               if (!hub_is_superspeed(udev->parent)) {
-                       le16_to_cpus(&devstatus);
+               if (udev->speed < USB_SPEED_SUPER) {
                        if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP))
-                               status = usb_control_msg(udev,
-                                               usb_sndctrlpipe(udev, 0),
-                                               USB_REQ_CLEAR_FEATURE,
-                                               USB_RECIP_DEVICE,
-                                               USB_DEVICE_REMOTE_WAKEUP, 0,
-                                               NULL, 0,
-                                               USB_CTRL_SET_TIMEOUT);
+                               status = usb_disable_remote_wakeup(udev);
                } else {
                        status = usb_get_status(udev, USB_RECIP_INTERFACE, 0,
                                        &devstatus);
-                       le16_to_cpus(&devstatus);
                        if (!status && devstatus & (USB_INTRF_STAT_FUNC_RW_CAP
                                        | USB_INTRF_STAT_FUNC_RW))
-                               status =
-                                       usb_disable_function_remotewakeup(udev);
+                               status = usb_disable_remote_wakeup(udev);
                }
 
                if (status)
@@ -3249,8 +3262,6 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
        return status;
 }
 
-#endif /* CONFIG_PM */
-
 #ifdef CONFIG_PM_RUNTIME
 
 /* caller has locked udev */
@@ -3293,7 +3304,11 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
        unsigned                port1;
        int                     status;
 
-       /* Warn if children aren't already suspended */
+       /*
+        * Warn if children aren't already suspended.
+        * Also, add up the number of wakeup-enabled descendants.
+        */
+       hub->wakeup_enabled_descendants = 0;
        for (port1 = 1; port1 <= hdev->maxchild; port1++) {
                struct usb_device       *udev;
 
@@ -3303,6 +3318,9 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
                        if (PMSG_IS_AUTO(msg))
                                return -EBUSY;
                }
+               if (udev)
+                       hub->wakeup_enabled_descendants +=
+                                       wakeup_enabled_descendants(udev);
        }
 
        if (hdev->do_remote_wakeup && hub->quirk_check_port_auto_suspend) {
@@ -3811,7 +3829,8 @@ EXPORT_SYMBOL_GPL(usb_disable_ltm);
 
 void usb_enable_ltm(struct usb_device *udev) { }
 EXPORT_SYMBOL_GPL(usb_enable_ltm);
-#endif
+
+#endif /* CONFIG_PM */
 
 
 /* USB 2.0 spec, 7.1.7.3 / fig 7-29:
@@ -4451,11 +4470,10 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
 
                        status = usb_get_status(udev, USB_RECIP_DEVICE, 0,
                                        &devstat);
-                       if (status < 2) {
+                       if (status) {
                                dev_dbg(&udev->dev, "get status %d ?\n", status);
                                goto loop_disable;
                        }
-                       le16_to_cpus(&devstat);
                        if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) {
                                dev_err(&udev->dev,
                                        "can't connect bus-powered hub "
@@ -4766,7 +4784,8 @@ static void hub_events(void)
                                        hub->ports[i - 1]->child;
 
                                dev_dbg(hub_dev, "warm reset port %d\n", i);
-                               if (!udev) {
+                               if (!udev || !(portstatus &
+                                               USB_PORT_STAT_CONNECTION)) {
                                        status = hub_port_reset(hub, i,
                                                        NULL, HUB_BH_RESET_TIME,
                                                        true);
@@ -4776,8 +4795,8 @@ static void hub_events(void)
                                        usb_lock_device(udev);
                                        status = usb_reset_device(udev);
                                        usb_unlock_device(udev);
+                                       connect_change = 0;
                                }
-                               connect_change = 0;
                        }
 
                        if (connect_change)