Merge branch 'pm-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael...
[pandora-kernel.git] / drivers / usb / core / hub.c
index a428aa0..96f05b2 100644 (file)
@@ -1636,11 +1636,6 @@ void usb_disconnect(struct usb_device **pdev)
        int                     i;
        struct usb_hcd          *hcd = bus_to_hcd(udev->bus);
 
-       if (!udev) {
-               pr_debug ("%s nodev\n", __func__);
-               return;
-       }
-
        /* mark the device as inactive, so any further urb submissions for
         * this device (and any of its children) will fail immediately.
         * this quiesces everything except pending urbs.
@@ -2030,11 +2025,23 @@ static unsigned hub_is_wusb(struct usb_hub *hub)
 
 #define HUB_ROOT_RESET_TIME    50      /* times are in msec */
 #define HUB_SHORT_RESET_TIME   10
+#define HUB_BH_RESET_TIME      50
 #define HUB_LONG_RESET_TIME    200
 #define HUB_RESET_TIMEOUT      500
 
+static int hub_port_reset(struct usb_hub *hub, int port1,
+                       struct usb_device *udev, unsigned int delay, bool warm);
+
+/* Is a USB 3.0 port in the Inactive state? */
+static bool hub_port_inactive(struct usb_hub *hub, u16 portstatus)
+{
+       return hub_is_superspeed(hub->hdev) &&
+               (portstatus & USB_PORT_STAT_LINK_STATE) ==
+               USB_SS_PORT_LS_SS_INACTIVE;
+}
+
 static int hub_port_wait_reset(struct usb_hub *hub, int port1,
-                               struct usb_device *udev, unsigned int delay)
+                       struct usb_device *udev, unsigned int delay, bool warm)
 {
        int delay_time, ret;
        u16 portstatus;
@@ -2051,28 +2058,71 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
                if (ret < 0)
                        return ret;
 
-               /* Device went away? */
-               if (!(portstatus & USB_PORT_STAT_CONNECTION))
-                       return -ENOTCONN;
-
-               /* bomb out completely if the connection bounced */
-               if ((portchange & USB_PORT_STAT_C_CONNECTION))
-                       return -ENOTCONN;
-
-               /* if we`ve finished resetting, then break out of the loop */
-               if (!(portstatus & USB_PORT_STAT_RESET) &&
-                   (portstatus & USB_PORT_STAT_ENABLE)) {
-                       if (hub_is_wusb(hub))
-                               udev->speed = USB_SPEED_WIRELESS;
-                       else if (hub_is_superspeed(hub->hdev))
-                               udev->speed = USB_SPEED_SUPER;
-                       else if (portstatus & USB_PORT_STAT_HIGH_SPEED)
-                               udev->speed = USB_SPEED_HIGH;
-                       else if (portstatus & USB_PORT_STAT_LOW_SPEED)
-                               udev->speed = USB_SPEED_LOW;
-                       else
-                               udev->speed = USB_SPEED_FULL;
-                       return 0;
+               /*
+                * Some buggy devices require a warm reset to be issued even
+                * when the port appears not to be connected.
+                */
+               if (!warm) {
+                       /*
+                        * Some buggy devices can cause an NEC host controller
+                        * to transition to the "Error" state after a hot port
+                        * reset.  This will show up as the port state in
+                        * "Inactive", and the port may also report a
+                        * disconnect.  Forcing a warm port reset seems to make
+                        * the device work.
+                        *
+                        * See https://bugzilla.kernel.org/show_bug.cgi?id=41752
+                        */
+                       if (hub_port_inactive(hub, portstatus)) {
+                               int ret;
+
+                               if ((portchange & USB_PORT_STAT_C_CONNECTION))
+                                       clear_port_feature(hub->hdev, port1,
+                                                       USB_PORT_FEAT_C_CONNECTION);
+                               if (portchange & USB_PORT_STAT_C_LINK_STATE)
+                                       clear_port_feature(hub->hdev, port1,
+                                                       USB_PORT_FEAT_C_PORT_LINK_STATE);
+                               if (portchange & USB_PORT_STAT_C_RESET)
+                                       clear_port_feature(hub->hdev, port1,
+                                                       USB_PORT_FEAT_C_RESET);
+                               dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n",
+                                               port1);
+                               ret = hub_port_reset(hub, port1,
+                                               udev, HUB_BH_RESET_TIME,
+                                               true);
+                               if ((portchange & USB_PORT_STAT_C_CONNECTION))
+                                       clear_port_feature(hub->hdev, port1,
+                                                       USB_PORT_FEAT_C_CONNECTION);
+                               return ret;
+                       }
+                       /* Device went away? */
+                       if (!(portstatus & USB_PORT_STAT_CONNECTION))
+                               return -ENOTCONN;
+
+                       /* bomb out completely if the connection bounced */
+                       if ((portchange & USB_PORT_STAT_C_CONNECTION))
+                               return -ENOTCONN;
+
+                       /* if we`ve finished resetting, then break out of
+                        * the loop
+                        */
+                       if (!(portstatus & USB_PORT_STAT_RESET) &&
+                           (portstatus & USB_PORT_STAT_ENABLE)) {
+                               if (hub_is_wusb(hub))
+                                       udev->speed = USB_SPEED_WIRELESS;
+                               else if (hub_is_superspeed(hub->hdev))
+                                       udev->speed = USB_SPEED_SUPER;
+                               else if (portstatus & USB_PORT_STAT_HIGH_SPEED)
+                                       udev->speed = USB_SPEED_HIGH;
+                               else if (portstatus & USB_PORT_STAT_LOW_SPEED)
+                                       udev->speed = USB_SPEED_LOW;
+                               else
+                                       udev->speed = USB_SPEED_FULL;
+                               return 0;
+                       }
+               } else {
+                       if (portchange & USB_PORT_STAT_C_BH_RESET)
+                               return 0;
                }
 
                /* switch to the long delay after two short delay failures */
@@ -2080,35 +2130,84 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
                        delay = HUB_LONG_RESET_TIME;
 
                dev_dbg (hub->intfdev,
-                       "port %d not reset yet, waiting %dms\n",
-                       port1, delay);
+                       "port %d not %sreset yet, waiting %dms\n",
+                       port1, warm ? "warm " : "", delay);
        }
 
        return -EBUSY;
 }
 
+static void hub_port_finish_reset(struct usb_hub *hub, int port1,
+                       struct usb_device *udev, int *status, bool warm)
+{
+       switch (*status) {
+       case 0:
+               if (!warm) {
+                       struct usb_hcd *hcd;
+                       /* TRSTRCY = 10 ms; plus some extra */
+                       msleep(10 + 40);
+                       update_devnum(udev, 0);
+                       hcd = bus_to_hcd(udev->bus);
+                       if (hcd->driver->reset_device) {
+                               *status = hcd->driver->reset_device(hcd, udev);
+                               if (*status < 0) {
+                                       dev_err(&udev->dev, "Cannot reset "
+                                                       "HCD device state\n");
+                                       break;
+                               }
+                       }
+               }
+               /* FALL THROUGH */
+       case -ENOTCONN:
+       case -ENODEV:
+               clear_port_feature(hub->hdev,
+                               port1, USB_PORT_FEAT_C_RESET);
+               /* FIXME need disconnect() for NOTATTACHED device */
+               if (warm) {
+                       clear_port_feature(hub->hdev, port1,
+                                       USB_PORT_FEAT_C_BH_PORT_RESET);
+                       clear_port_feature(hub->hdev, port1,
+                                       USB_PORT_FEAT_C_PORT_LINK_STATE);
+               } else {
+                       usb_set_device_state(udev, *status
+                                       ? USB_STATE_NOTATTACHED
+                                       : USB_STATE_DEFAULT);
+               }
+               break;
+       }
+}
+
+/* Handle port reset and port warm(BH) reset (for USB3 protocol ports) */
 static int hub_port_reset(struct usb_hub *hub, int port1,
-                               struct usb_device *udev, unsigned int delay)
+                       struct usb_device *udev, unsigned int delay, bool warm)
 {
        int i, status;
-       struct usb_hcd *hcd;
 
-       hcd = bus_to_hcd(udev->bus);
-       /* Block EHCI CF initialization during the port reset.
-        * Some companion controllers don't like it when they mix.
-        */
-       down_read(&ehci_cf_port_reset_rwsem);
+       if (!warm) {
+               /* Block EHCI CF initialization during the port reset.
+                * Some companion controllers don't like it when they mix.
+                */
+               down_read(&ehci_cf_port_reset_rwsem);
+       } else {
+               if (!hub_is_superspeed(hub->hdev)) {
+                       dev_err(hub->intfdev, "only USB3 hub support "
+                                               "warm reset\n");
+                       return -EINVAL;
+               }
+       }
 
        /* Reset the port */
        for (i = 0; i < PORT_RESET_TRIES; i++) {
-               status = set_port_feature(hub->hdev,
-                               port1, USB_PORT_FEAT_RESET);
-               if (status)
+               status = set_port_feature(hub->hdev, port1, (warm ?
+                                       USB_PORT_FEAT_BH_PORT_RESET :
+                                       USB_PORT_FEAT_RESET));
+               if (status) {
                        dev_err(hub->intfdev,
-                                       "cannot reset port %d (err = %d)\n",
-                                       port1, status);
-               else {
-                       status = hub_port_wait_reset(hub, port1, udev, delay);
+                                       "cannot %sreset port %d (err = %d)\n",
+                                       warm ? "warm " : "", port1, status);
+               } else {
+                       status = hub_port_wait_reset(hub, port1, udev, delay,
+                                                               warm);
                        if (status && status != -ENOTCONN)
                                dev_dbg(hub->intfdev,
                                                "port_wait_reset: err = %d\n",
@@ -2116,34 +2215,14 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
                }
 
                /* return on disconnect or reset */
-               switch (status) {
-               case 0:
-                       /* TRSTRCY = 10 ms; plus some extra */
-                       msleep(10 + 40);
-                       update_devnum(udev, 0);
-                       if (hcd->driver->reset_device) {
-                               status = hcd->driver->reset_device(hcd, udev);
-                               if (status < 0) {
-                                       dev_err(&udev->dev, "Cannot reset "
-                                                       "HCD device state\n");
-                                       break;
-                               }
-                       }
-                       /* FALL THROUGH */
-               case -ENOTCONN:
-               case -ENODEV:
-                       clear_port_feature(hub->hdev,
-                               port1, USB_PORT_FEAT_C_RESET);
-                       /* FIXME need disconnect() for NOTATTACHED device */
-                       usb_set_device_state(udev, status
-                                       ? USB_STATE_NOTATTACHED
-                                       : USB_STATE_DEFAULT);
+               if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
+                       hub_port_finish_reset(hub, port1, udev, &status, warm);
                        goto done;
                }
 
                dev_dbg (hub->intfdev,
-                       "port %d not enabled, trying reset again...\n",
-                       port1);
+                       "port %d not enabled, trying %sreset again...\n",
+                       port1, warm ? "warm " : "");
                delay = HUB_LONG_RESET_TIME;
        }
 
@@ -2151,45 +2230,11 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
                "Cannot enable port %i.  Maybe the USB cable is bad?\n",
                port1);
 
- done:
-       up_read(&ehci_cf_port_reset_rwsem);
-       return status;
-}
-
-/* Warm reset a USB3 protocol port */
-static int hub_port_warm_reset(struct usb_hub *hub, int port)
-{
-       int ret;
-       u16 portstatus, portchange;
-
-       if (!hub_is_superspeed(hub->hdev)) {
-               dev_err(hub->intfdev, "only USB3 hub support warm reset\n");
-               return -EINVAL;
-       }
-
-       /* Warm reset the port */
-       ret = set_port_feature(hub->hdev,
-                               port, USB_PORT_FEAT_BH_PORT_RESET);
-       if (ret) {
-               dev_err(hub->intfdev, "cannot warm reset port %d\n", port);
-               return ret;
-       }
-
-       msleep(20);
-       ret = hub_port_status(hub, port, &portstatus, &portchange);
-
-       if (portchange & USB_PORT_STAT_C_RESET)
-               clear_port_feature(hub->hdev, port, USB_PORT_FEAT_C_RESET);
-
-       if (portchange & USB_PORT_STAT_C_BH_RESET)
-               clear_port_feature(hub->hdev, port,
-                                       USB_PORT_FEAT_C_BH_PORT_RESET);
-
-       if (portchange & USB_PORT_STAT_C_LINK_STATE)
-               clear_port_feature(hub->hdev, port,
-                                       USB_PORT_FEAT_C_PORT_LINK_STATE);
+done:
+       if (!warm)
+               up_read(&ehci_cf_port_reset_rwsem);
 
-       return ret;
+       return status;
 }
 
 /* Check if a port is power on */
@@ -2324,8 +2369,6 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
        int             port1 = udev->portnum;
        int             status;
 
-       // dev_dbg(hub->intfdev, "suspend port %d\n", port1);
-
        /* enable remote wakeup when appropriate; this lets the device
         * wake up the upstream hub (including maybe the root hub).
         *
@@ -2342,11 +2385,15 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
                        dev_dbg(&udev->dev, "won't remote wakeup, status %d\n",
                                        status);
                        /* bail if autosuspend is requested */
-                       if (msg.event & PM_EVENT_AUTO)
+                       if (PMSG_IS_AUTO(msg))
                                return status;
                }
        }
 
+       /* disable USB2 hardware LPM */
+       if (udev->usb2_hw_lpm_enabled == 1)
+               usb_set_usb2_hardware_lpm(udev, 0);
+
        /* see 7.1.7.6 */
        if (hub_is_superspeed(hub->hdev))
                status = set_port_feature(hub->hdev,
@@ -2367,12 +2414,13 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
                                USB_CTRL_SET_TIMEOUT);
 
                /* System sleep transitions should never fail */
-               if (!(msg.event & PM_EVENT_AUTO))
+               if (!PMSG_IS_AUTO(msg))
                        status = 0;
        } else {
                /* device has up to 10 msec to fully suspend */
-               dev_dbg(&udev->dev, "usb %ssuspend\n",
-                               (msg.event & PM_EVENT_AUTO ? "auto-" : ""));
+               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);
                msleep(10);
        }
@@ -2523,7 +2571,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
        } else {
                /* drive resume for at least 20 msec */
                dev_dbg(&udev->dev, "usb %sresume\n",
-                               (msg.event & PM_EVENT_AUTO ? "auto-" : ""));
+                               (PMSG_IS_AUTO(msg) ? "auto-" : ""));
                msleep(25);
 
                /* Virtual root hubs can trigger on GET_PORT_STATUS to
@@ -2558,7 +2606,12 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
        if (status < 0) {
                dev_dbg(&udev->dev, "can't resume, status %d\n", status);
                hub_port_logical_disconnect(hub, port1);
+       } else  {
+               /* Try to enable USB2 hardware LPM */
+               if (udev->usb2_hw_lpm_capable == 1)
+                       usb_set_usb2_hardware_lpm(udev, 1);
        }
+
        return status;
 }
 
@@ -2625,7 +2678,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
                udev = hdev->children [port1-1];
                if (udev && udev->can_submit) {
                        dev_warn(&intf->dev, "port %d nyet suspended\n", port1);
-                       if (msg.event & PM_EVENT_AUTO)
+                       if (PMSG_IS_AUTO(msg))
                                return -EBUSY;
                }
        }
@@ -2798,7 +2851,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
        int                     i, j, retval;
        unsigned                delay = HUB_SHORT_RESET_TIME;
        enum usb_device_speed   oldspeed = udev->speed;
-       char                    *speed, *type;
+       const char              *speed;
        int                     devnum = udev->devnum;
 
        /* root hub ports have a slightly longer reset period
@@ -2819,7 +2872,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
 
        /* Reset the device; full speed may morph to high speed */
        /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
-       retval = hub_port_reset(hub, port1, udev, delay);
+       retval = hub_port_reset(hub, port1, udev, delay, false);
        if (retval < 0)         /* error or disconnect */
                goto fail;
        /* success, speed is known */
@@ -2858,25 +2911,16 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
        default:
                goto fail;
        }
-       type = "";
-       switch (udev->speed) {
-       case USB_SPEED_LOW:     speed = "low";  break;
-       case USB_SPEED_FULL:    speed = "full"; break;
-       case USB_SPEED_HIGH:    speed = "high"; break;
-       case USB_SPEED_SUPER:
-                               speed = "super";
-                               break;
-       case USB_SPEED_WIRELESS:
-                               speed = "variable";
-                               type = "Wireless ";
-                               break;
-       default:                speed = "?";    break;
-       }
+
+       if (udev->speed == USB_SPEED_WIRELESS)
+               speed = "variable speed Wireless";
+       else
+               speed = usb_speed_string(udev->speed);
+
        if (udev->speed != USB_SPEED_SUPER)
                dev_info(&udev->dev,
-                               "%s %s speed %sUSB device number %d using %s\n",
-                               (udev->config) ? "reset" : "new", speed, type,
+                               "%s %s USB device number %d using %s\n",
+                               (udev->config) ? "reset" : "new", speed,
                                devnum, udev->bus->controller->driver->name);
 
        /* Set up TT records, if needed  */
@@ -2949,7 +2993,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
                                        buf->bMaxPacketSize0;
                        kfree(buf);
 
-                       retval = hub_port_reset(hub, port1, udev, delay);
+                       retval = hub_port_reset(hub, port1, udev, delay, false);
                        if (retval < 0)         /* error or disconnect */
                                goto fail;
                        if (oldspeed != udev->speed) {
@@ -3023,7 +3067,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
                i = 512;
        else
                i = udev->descriptor.bMaxPacketSize0;
-       if (le16_to_cpu(udev->ep0.desc.wMaxPacketSize) != i) {
+       if (usb_endpoint_maxp(&udev->ep0.desc) != i) {
                if (udev->speed == USB_SPEED_LOW ||
                                !(i == 8 || i == 16 || i == 32 || i == 64)) {
                        dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", i);
@@ -3047,6 +3091,15 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
                goto fail;
        }
 
+       if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) {
+               retval = usb_get_bos_descriptor(udev);
+               if (!retval) {
+                       if (udev->bos->ext_cap && (USB_LPM_SUPPORT &
+                               le32_to_cpu(udev->bos->ext_cap->bmAttributes)))
+                                       udev->lpm_capable = 1;
+               }
+       }
+
        retval = 0;
        /* notify HCD that we have a device connected and addressed */
        if (hcd->driver->update_device)
@@ -3570,7 +3623,8 @@ static void hub_events(void)
                                (portstatus & USB_PORT_STAT_LINK_STATE)
                                        == USB_SS_PORT_LS_SS_INACTIVE) {
                                dev_dbg(hub_dev, "warm reset port %d\n", i);
-                               hub_port_warm_reset(hub, i);
+                               hub_port_reset(hub, i, NULL,
+                                               HUB_BH_RESET_TIME, true);
                        }
 
                        if (connect_change)