USB: Checking the wrong variable in usb_disable_lpm()
[pandora-kernel.git] / drivers / usb / core / hub.c
index ec6c97d..25a7422 100644 (file)
@@ -177,6 +177,228 @@ static struct usb_hub *hdev_to_hub(struct usb_device *hdev)
        return usb_get_intfdata(hdev->actconfig->interface[0]);
 }
 
+static int usb_device_supports_lpm(struct usb_device *udev)
+{
+       /* USB 2.1 (and greater) devices indicate LPM support through
+        * their USB 2.0 Extended Capabilities BOS descriptor.
+        */
+       if (udev->speed == USB_SPEED_HIGH) {
+               if (udev->bos->ext_cap &&
+                       (USB_LPM_SUPPORT &
+                        le32_to_cpu(udev->bos->ext_cap->bmAttributes)))
+                       return 1;
+               return 0;
+       }
+
+       /* All USB 3.0 must support LPM, but we need their max exit latency
+        * information from the SuperSpeed Extended Capabilities BOS descriptor.
+        */
+       if (!udev->bos->ss_cap) {
+               dev_warn(&udev->dev, "No LPM exit latency info found.  "
+                               "Power management will be impacted.\n");
+               return 0;
+       }
+       if (udev->parent->lpm_capable)
+               return 1;
+
+       dev_warn(&udev->dev, "Parent hub missing LPM exit latency info.  "
+                       "Power management will be impacted.\n");
+       return 0;
+}
+
+/*
+ * Set the Maximum Exit Latency (MEL) for the host to initiate a transition from
+ * either U1 or U2.
+ */
+static void usb_set_lpm_mel(struct usb_device *udev,
+               struct usb3_lpm_parameters *udev_lpm_params,
+               unsigned int udev_exit_latency,
+               struct usb_hub *hub,
+               struct usb3_lpm_parameters *hub_lpm_params,
+               unsigned int hub_exit_latency)
+{
+       unsigned int total_mel;
+       unsigned int device_mel;
+       unsigned int hub_mel;
+
+       /*
+        * Calculate the time it takes to transition all links from the roothub
+        * to the parent hub into U0.  The parent hub must then decode the
+        * packet (hub header decode latency) to figure out which port it was
+        * bound for.
+        *
+        * The Hub Header decode latency is expressed in 0.1us intervals (0x1
+        * means 0.1us).  Multiply that by 100 to get nanoseconds.
+        */
+       total_mel = hub_lpm_params->mel +
+               (hub->descriptor->u.ss.bHubHdrDecLat * 100);
+
+       /*
+        * How long will it take to transition the downstream hub's port into
+        * U0?  The greater of either the hub exit latency or the device exit
+        * latency.
+        *
+        * The BOS U1/U2 exit latencies are expressed in 1us intervals.
+        * Multiply that by 1000 to get nanoseconds.
+        */
+       device_mel = udev_exit_latency * 1000;
+       hub_mel = hub_exit_latency * 1000;
+       if (device_mel > hub_mel)
+               total_mel += device_mel;
+       else
+               total_mel += hub_mel;
+
+       udev_lpm_params->mel = total_mel;
+}
+
+/*
+ * Set the maximum Device to Host Exit Latency (PEL) for the device to initiate
+ * a transition from either U1 or U2.
+ */
+static void usb_set_lpm_pel(struct usb_device *udev,
+               struct usb3_lpm_parameters *udev_lpm_params,
+               unsigned int udev_exit_latency,
+               struct usb_hub *hub,
+               struct usb3_lpm_parameters *hub_lpm_params,
+               unsigned int hub_exit_latency,
+               unsigned int port_to_port_exit_latency)
+{
+       unsigned int first_link_pel;
+       unsigned int hub_pel;
+
+       /*
+        * First, the device sends an LFPS to transition the link between the
+        * device and the parent hub into U0.  The exit latency is the bigger of
+        * the device exit latency or the hub exit latency.
+        */
+       if (udev_exit_latency > hub_exit_latency)
+               first_link_pel = udev_exit_latency * 1000;
+       else
+               first_link_pel = hub_exit_latency * 1000;
+
+       /*
+        * When the hub starts to receive the LFPS, there is a slight delay for
+        * it to figure out that one of the ports is sending an LFPS.  Then it
+        * will forward the LFPS to its upstream link.  The exit latency is the
+        * delay, plus the PEL that we calculated for this hub.
+        */
+       hub_pel = port_to_port_exit_latency * 1000 + hub_lpm_params->pel;
+
+       /*
+        * According to figure C-7 in the USB 3.0 spec, the PEL for this device
+        * is the greater of the two exit latencies.
+        */
+       if (first_link_pel > hub_pel)
+               udev_lpm_params->pel = first_link_pel;
+       else
+               udev_lpm_params->pel = hub_pel;
+}
+
+/*
+ * Set the System Exit Latency (SEL) to indicate the total worst-case time from
+ * when a device initiates a transition to U0, until when it will receive the
+ * first packet from the host controller.
+ *
+ * Section C.1.5.1 describes the four components to this:
+ *  - t1: device PEL
+ *  - t2: time for the ERDY to make it from the device to the host.
+ *  - t3: a host-specific delay to process the ERDY.
+ *  - t4: time for the packet to make it from the host to the device.
+ *
+ * t3 is specific to both the xHCI host and the platform the host is integrated
+ * into.  The Intel HW folks have said it's negligible, FIXME if a different
+ * vendor says otherwise.
+ */
+static void usb_set_lpm_sel(struct usb_device *udev,
+               struct usb3_lpm_parameters *udev_lpm_params)
+{
+       struct usb_device *parent;
+       unsigned int num_hubs;
+       unsigned int total_sel;
+
+       /* t1 = device PEL */
+       total_sel = udev_lpm_params->pel;
+       /* How many external hubs are in between the device & the root port. */
+       for (parent = udev->parent, num_hubs = 0; parent->parent;
+                       parent = parent->parent)
+               num_hubs++;
+       /* t2 = 2.1us + 250ns * (num_hubs - 1) */
+       if (num_hubs > 0)
+               total_sel += 2100 + 250 * (num_hubs - 1);
+
+       /* t4 = 250ns * num_hubs */
+       total_sel += 250 * num_hubs;
+
+       udev_lpm_params->sel = total_sel;
+}
+
+static void usb_set_lpm_parameters(struct usb_device *udev)
+{
+       struct usb_hub *hub;
+       unsigned int port_to_port_delay;
+       unsigned int udev_u1_del;
+       unsigned int udev_u2_del;
+       unsigned int hub_u1_del;
+       unsigned int hub_u2_del;
+
+       if (!udev->lpm_capable || udev->speed != USB_SPEED_SUPER)
+               return;
+
+       hub = hdev_to_hub(udev->parent);
+       /* It doesn't take time to transition the roothub into U0, since it
+        * doesn't have an upstream link.
+        */
+       if (!hub)
+               return;
+
+       udev_u1_del = udev->bos->ss_cap->bU1devExitLat;
+       udev_u2_del = udev->bos->ss_cap->bU2DevExitLat;
+       hub_u1_del = udev->parent->bos->ss_cap->bU1devExitLat;
+       hub_u2_del = udev->parent->bos->ss_cap->bU2DevExitLat;
+
+       usb_set_lpm_mel(udev, &udev->u1_params, udev_u1_del,
+                       hub, &udev->parent->u1_params, hub_u1_del);
+
+       usb_set_lpm_mel(udev, &udev->u2_params, udev_u2_del,
+                       hub, &udev->parent->u2_params, hub_u2_del);
+
+       /*
+        * Appendix C, section C.2.2.2, says that there is a slight delay from
+        * when the parent hub notices the downstream port is trying to
+        * transition to U0 to when the hub initiates a U0 transition on its
+        * upstream port.  The section says the delays are tPort2PortU1EL and
+        * tPort2PortU2EL, but it doesn't define what they are.
+        *
+        * The hub chapter, sections 10.4.2.4 and 10.4.2.5 seem to be talking
+        * about the same delays.  Use the maximum delay calculations from those
+        * sections.  For U1, it's tHubPort2PortExitLat, which is 1us max.  For
+        * U2, it's tHubPort2PortExitLat + U2DevExitLat - U1DevExitLat.  I
+        * assume the device exit latencies they are talking about are the hub
+        * exit latencies.
+        *
+        * What do we do if the U2 exit latency is less than the U1 exit
+        * latency?  It's possible, although not likely...
+        */
+       port_to_port_delay = 1;
+
+       usb_set_lpm_pel(udev, &udev->u1_params, udev_u1_del,
+                       hub, &udev->parent->u1_params, hub_u1_del,
+                       port_to_port_delay);
+
+       if (hub_u2_del > hub_u1_del)
+               port_to_port_delay = 1 + hub_u2_del - hub_u1_del;
+       else
+               port_to_port_delay = 1 + hub_u1_del;
+
+       usb_set_lpm_pel(udev, &udev->u2_params, udev_u2_del,
+                       hub, &udev->parent->u2_params, hub_u2_del,
+                       port_to_port_delay);
+
+       /* Now that we've got PEL, calculate SEL. */
+       usb_set_lpm_sel(udev, &udev->u1_params);
+       usb_set_lpm_sel(udev, &udev->u2_params);
+}
+
 /* USB 2.0 spec Section 11.24.4.5 */
 static int get_hub_descriptor(struct usb_device *hdev, void *data)
 {
@@ -2480,6 +2702,12 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
        if (udev->usb2_hw_lpm_enabled == 1)
                usb_set_usb2_hardware_lpm(udev, 0);
 
+       if (usb_unlocked_disable_lpm(udev)) {
+               dev_err(&udev->dev, "%s Failed to disable LPM before suspend\n.",
+                               __func__);
+               return -ENOMEM;
+       }
+
        /* see 7.1.7.6 */
        if (hub_is_superspeed(hub->hdev))
                status = set_port_feature(hub->hdev,
@@ -2499,6 +2727,13 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
                                NULL, 0,
                                USB_CTRL_SET_TIMEOUT);
 
+               /* Try to enable USB2 hardware LPM again */
+               if (udev->usb2_hw_lpm_capable == 1)
+                       usb_set_usb2_hardware_lpm(udev, 1);
+
+               /* Try to enable USB3 LPM again */
+               usb_unlocked_enable_lpm(udev);
+
                /* System sleep transitions should never fail */
                if (!PMSG_IS_AUTO(msg))
                        status = 0;
@@ -2696,6 +2931,9 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
                /* Try to enable USB2 hardware LPM */
                if (udev->usb2_hw_lpm_capable == 1)
                        usb_set_usb2_hardware_lpm(udev, 1);
+
+               /* Try to enable USB3 LPM */
+               usb_unlocked_enable_lpm(udev);
        }
 
        return status;
@@ -2824,11 +3062,429 @@ void usb_root_hub_lost_power(struct usb_device *rhdev)
 }
 EXPORT_SYMBOL_GPL(usb_root_hub_lost_power);
 
+static const char * const usb3_lpm_names[]  = {
+       "U0",
+       "U1",
+       "U2",
+       "U3",
+};
+
+/*
+ * Send a Set SEL control transfer to the device, prior to enabling
+ * device-initiated U1 or U2.  This lets the device know the exit latencies from
+ * the time the device initiates a U1 or U2 exit, to the time it will receive a
+ * packet from the host.
+ *
+ * This function will fail if the SEL or PEL values for udev are greater than
+ * the maximum allowed values for the link state to be enabled.
+ */
+static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state)
+{
+       struct usb_set_sel_req *sel_values;
+       unsigned long long u1_sel;
+       unsigned long long u1_pel;
+       unsigned long long u2_sel;
+       unsigned long long u2_pel;
+       int ret;
+
+       /* Convert SEL and PEL stored in ns to us */
+       u1_sel = DIV_ROUND_UP(udev->u1_params.sel, 1000);
+       u1_pel = DIV_ROUND_UP(udev->u1_params.pel, 1000);
+       u2_sel = DIV_ROUND_UP(udev->u2_params.sel, 1000);
+       u2_pel = DIV_ROUND_UP(udev->u2_params.pel, 1000);
+
+       /*
+        * Make sure that the calculated SEL and PEL values for the link
+        * state we're enabling aren't bigger than the max SEL/PEL
+        * value that will fit in the SET SEL control transfer.
+        * Otherwise the device would get an incorrect idea of the exit
+        * latency for the link state, and could start a device-initiated
+        * U1/U2 when the exit latencies are too high.
+        */
+       if ((state == USB3_LPM_U1 &&
+                               (u1_sel > USB3_LPM_MAX_U1_SEL_PEL ||
+                                u1_pel > USB3_LPM_MAX_U1_SEL_PEL)) ||
+                       (state == USB3_LPM_U2 &&
+                        (u2_sel > USB3_LPM_MAX_U2_SEL_PEL ||
+                         u2_pel > USB3_LPM_MAX_U2_SEL_PEL))) {
+               dev_dbg(&udev->dev, "Device-initiated %s disabled due "
+                               "to long SEL %llu ms or PEL %llu ms\n",
+                               usb3_lpm_names[state], u1_sel, u1_pel);
+               return -EINVAL;
+       }
+
+       /*
+        * If we're enabling device-initiated LPM for one link state,
+        * but the other link state has a too high SEL or PEL value,
+        * just set those values to the max in the Set SEL request.
+        */
+       if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL)
+               u1_sel = USB3_LPM_MAX_U1_SEL_PEL;
+
+       if (u1_pel > USB3_LPM_MAX_U1_SEL_PEL)
+               u1_pel = USB3_LPM_MAX_U1_SEL_PEL;
+
+       if (u2_sel > USB3_LPM_MAX_U2_SEL_PEL)
+               u2_sel = USB3_LPM_MAX_U2_SEL_PEL;
+
+       if (u2_pel > USB3_LPM_MAX_U2_SEL_PEL)
+               u2_pel = USB3_LPM_MAX_U2_SEL_PEL;
+
+       /*
+        * usb_enable_lpm() can be called as part of a failed device reset,
+        * which may be initiated by an error path of a mass storage driver.
+        * Therefore, use GFP_NOIO.
+        */
+       sel_values = kmalloc(sizeof *(sel_values), GFP_NOIO);
+       if (!sel_values)
+               return -ENOMEM;
+
+       sel_values->u1_sel = u1_sel;
+       sel_values->u1_pel = u1_pel;
+       sel_values->u2_sel = cpu_to_le16(u2_sel);
+       sel_values->u2_pel = cpu_to_le16(u2_pel);
+
+       ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                       USB_REQ_SET_SEL,
+                       USB_RECIP_DEVICE,
+                       0, 0,
+                       sel_values, sizeof *(sel_values),
+                       USB_CTRL_SET_TIMEOUT);
+       kfree(sel_values);
+       return ret;
+}
+
+/*
+ * Enable or disable device-initiated U1 or U2 transitions.
+ */
+static int usb_set_device_initiated_lpm(struct usb_device *udev,
+               enum usb3_link_state state, bool enable)
+{
+       int ret;
+       int feature;
+
+       switch (state) {
+       case USB3_LPM_U1:
+               feature = USB_DEVICE_U1_ENABLE;
+               break;
+       case USB3_LPM_U2:
+               feature = USB_DEVICE_U2_ENABLE;
+               break;
+       default:
+               dev_warn(&udev->dev, "%s: Can't %s non-U1 or U2 state.\n",
+                               __func__, enable ? "enable" : "disable");
+               return -EINVAL;
+       }
+
+       if (udev->state != USB_STATE_CONFIGURED) {
+               dev_dbg(&udev->dev, "%s: Can't %s %s state "
+                               "for unconfigured device.\n",
+                               __func__, enable ? "enable" : "disable",
+                               usb3_lpm_names[state]);
+               return 0;
+       }
+
+       if (enable) {
+               /*
+                * First, let the device know about the exit latencies
+                * associated with the link state we're about to enable.
+                */
+               ret = usb_req_set_sel(udev, state);
+               if (ret < 0) {
+                       dev_warn(&udev->dev, "Set SEL for device-initiated "
+                                       "%s failed.\n", usb3_lpm_names[state]);
+                       return -EBUSY;
+               }
+               /*
+                * Now send the control transfer to enable device-initiated LPM
+                * for either U1 or U2.
+                */
+               ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                               USB_REQ_SET_FEATURE,
+                               USB_RECIP_DEVICE,
+                               feature,
+                               0, NULL, 0,
+                               USB_CTRL_SET_TIMEOUT);
+       } else {
+               ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                               USB_REQ_CLEAR_FEATURE,
+                               USB_RECIP_DEVICE,
+                               feature,
+                               0, NULL, 0,
+                               USB_CTRL_SET_TIMEOUT);
+       }
+       if (ret < 0) {
+               dev_warn(&udev->dev, "%s of device-initiated %s failed.\n",
+                               enable ? "Enable" : "Disable",
+                               usb3_lpm_names[state]);
+               return -EBUSY;
+       }
+       return 0;
+}
+
+static int usb_set_lpm_timeout(struct usb_device *udev,
+               enum usb3_link_state state, int timeout)
+{
+       int ret;
+       int feature;
+
+       switch (state) {
+       case USB3_LPM_U1:
+               feature = USB_PORT_FEAT_U1_TIMEOUT;
+               break;
+       case USB3_LPM_U2:
+               feature = USB_PORT_FEAT_U2_TIMEOUT;
+               break;
+       default:
+               dev_warn(&udev->dev, "%s: Can't set timeout for non-U1 or U2 state.\n",
+                               __func__);
+               return -EINVAL;
+       }
+
+       if (state == USB3_LPM_U1 && timeout > USB3_LPM_U1_MAX_TIMEOUT &&
+                       timeout != USB3_LPM_DEVICE_INITIATED) {
+               dev_warn(&udev->dev, "Failed to set %s timeout to 0x%x, "
+                               "which is a reserved value.\n",
+                               usb3_lpm_names[state], timeout);
+               return -EINVAL;
+       }
+
+       ret = set_port_feature(udev->parent,
+                       USB_PORT_LPM_TIMEOUT(timeout) | udev->portnum,
+                       feature);
+       if (ret < 0) {
+               dev_warn(&udev->dev, "Failed to set %s timeout to 0x%x,"
+                               "error code %i\n", usb3_lpm_names[state],
+                               timeout, ret);
+               return -EBUSY;
+       }
+       if (state == USB3_LPM_U1)
+               udev->u1_params.timeout = timeout;
+       else
+               udev->u2_params.timeout = timeout;
+       return 0;
+}
+
+/*
+ * Enable the hub-initiated U1/U2 idle timeouts, and enable device-initiated
+ * U1/U2 entry.
+ *
+ * We will attempt to enable U1 or U2, but there are no guarantees that the
+ * control transfers to set the hub timeout or enable device-initiated U1/U2
+ * will be successful.
+ *
+ * If we cannot set the parent hub U1/U2 timeout, we attempt to let the xHCI
+ * driver know about it.  If that call fails, it should be harmless, and just
+ * take up more slightly more bus bandwidth for unnecessary U1/U2 exit latency.
+ */
+static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
+               enum usb3_link_state state)
+{
+       int timeout;
+
+       /* We allow the host controller to set the U1/U2 timeout internally
+        * first, so that it can change its schedule to account for the
+        * additional latency to send data to a device in a lower power
+        * link state.
+        */
+       timeout = hcd->driver->enable_usb3_lpm_timeout(hcd, udev, state);
+
+       /* xHCI host controller doesn't want to enable this LPM state. */
+       if (timeout == 0)
+               return;
+
+       if (timeout < 0) {
+               dev_warn(&udev->dev, "Could not enable %s link state, "
+                               "xHCI error %i.\n", usb3_lpm_names[state],
+                               timeout);
+               return;
+       }
+
+       if (usb_set_lpm_timeout(udev, state, timeout))
+               /* If we can't set the parent hub U1/U2 timeout,
+                * device-initiated LPM won't be allowed either, so let the xHCI
+                * host know that this link state won't be enabled.
+                */
+               hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state);
+
+       /* Only a configured device will accept the Set Feature U1/U2_ENABLE */
+       else if (udev->actconfig)
+               usb_set_device_initiated_lpm(udev, state, true);
+
+}
+
+/*
+ * Disable the hub-initiated U1/U2 idle timeouts, and disable device-initiated
+ * U1/U2 entry.
+ *
+ * If this function returns -EBUSY, the parent hub will still allow U1/U2 entry.
+ * If zero is returned, the parent will not allow the link to go into U1/U2.
+ *
+ * If zero is returned, device-initiated U1/U2 entry may still be enabled, but
+ * it won't have an effect on the bus link state because the parent hub will
+ * still disallow device-initiated U1/U2 entry.
+ *
+ * If zero is returned, the xHCI host controller may still think U1/U2 entry is
+ * possible.  The result will be slightly more bus bandwidth will be taken up
+ * (to account for U1/U2 exit latency), but it should be harmless.
+ */
+static int usb_disable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
+               enum usb3_link_state state)
+{
+       int feature;
+
+       switch (state) {
+       case USB3_LPM_U1:
+               feature = USB_PORT_FEAT_U1_TIMEOUT;
+               break;
+       case USB3_LPM_U2:
+               feature = USB_PORT_FEAT_U2_TIMEOUT;
+               break;
+       default:
+               dev_warn(&udev->dev, "%s: Can't disable non-U1 or U2 state.\n",
+                               __func__);
+               return -EINVAL;
+       }
+
+       if (usb_set_lpm_timeout(udev, state, 0))
+               return -EBUSY;
+
+       usb_set_device_initiated_lpm(udev, state, false);
+
+       if (hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state))
+               dev_warn(&udev->dev, "Could not disable xHCI %s timeout, "
+                               "bus schedule bandwidth may be impacted.\n",
+                               usb3_lpm_names[state]);
+       return 0;
+}
+
+/*
+ * Disable hub-initiated and device-initiated U1 and U2 entry.
+ * Caller must own the bandwidth_mutex.
+ *
+ * This will call usb_enable_lpm() on failure, which will decrement
+ * lpm_disable_count, and will re-enable LPM if lpm_disable_count reaches zero.
+ */
+int usb_disable_lpm(struct usb_device *udev)
+{
+       struct usb_hcd *hcd;
+
+       if (!udev || !udev->parent ||
+                       udev->speed != USB_SPEED_SUPER ||
+                       !udev->lpm_capable)
+               return 0;
+
+       hcd = bus_to_hcd(udev->bus);
+       if (!hcd || !hcd->driver->disable_usb3_lpm_timeout)
+               return 0;
+
+       udev->lpm_disable_count++;
+       if ((udev->u1_params.timeout == 0 && udev->u2_params.timeout == 0))
+               return 0;
+
+       /* If LPM is enabled, attempt to disable it. */
+       if (usb_disable_link_state(hcd, udev, USB3_LPM_U1))
+               goto enable_lpm;
+       if (usb_disable_link_state(hcd, udev, USB3_LPM_U2))
+               goto enable_lpm;
+
+       return 0;
+
+enable_lpm:
+       usb_enable_lpm(udev);
+       return -EBUSY;
+}
+EXPORT_SYMBOL_GPL(usb_disable_lpm);
+
+/* Grab the bandwidth_mutex before calling usb_disable_lpm() */
+int usb_unlocked_disable_lpm(struct usb_device *udev)
+{
+       struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+       int ret;
+
+       if (!hcd)
+               return -EINVAL;
+
+       mutex_lock(hcd->bandwidth_mutex);
+       ret = usb_disable_lpm(udev);
+       mutex_unlock(hcd->bandwidth_mutex);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm);
+
+/*
+ * Attempt to enable device-initiated and hub-initiated U1 and U2 entry.  The
+ * xHCI host policy may prevent U1 or U2 from being enabled.
+ *
+ * Other callers may have disabled link PM, so U1 and U2 entry will be disabled
+ * until the lpm_disable_count drops to zero.  Caller must own the
+ * bandwidth_mutex.
+ */
+void usb_enable_lpm(struct usb_device *udev)
+{
+       struct usb_hcd *hcd;
+
+       if (!udev || !udev->parent ||
+                       udev->speed != USB_SPEED_SUPER ||
+                       !udev->lpm_capable)
+               return;
+
+       udev->lpm_disable_count--;
+       hcd = bus_to_hcd(udev->bus);
+       /* Double check that we can both enable and disable LPM.
+        * Device must be configured to accept set feature U1/U2 timeout.
+        */
+       if (!hcd || !hcd->driver->enable_usb3_lpm_timeout ||
+                       !hcd->driver->disable_usb3_lpm_timeout)
+               return;
+
+       if (udev->lpm_disable_count > 0)
+               return;
+
+       usb_enable_link_state(hcd, udev, USB3_LPM_U1);
+       usb_enable_link_state(hcd, udev, USB3_LPM_U2);
+}
+EXPORT_SYMBOL_GPL(usb_enable_lpm);
+
+/* Grab the bandwidth_mutex before calling usb_enable_lpm() */
+void usb_unlocked_enable_lpm(struct usb_device *udev)
+{
+       struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+
+       if (!hcd)
+               return;
+
+       mutex_lock(hcd->bandwidth_mutex);
+       usb_enable_lpm(udev);
+       mutex_unlock(hcd->bandwidth_mutex);
+}
+EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm);
+
+
 #else  /* CONFIG_PM */
 
 #define hub_suspend            NULL
 #define hub_resume             NULL
 #define hub_reset_resume       NULL
+
+int usb_disable_lpm(struct usb_device *udev)
+{
+       return 0;
+}
+EXPORT_SYMBOL_GPL(usb_disable_lpm);
+
+void usb_enable_lpm(struct usb_device *udev) { }
+EXPORT_SYMBOL_GPL(usb_enable_lpm);
+
+int usb_unlocked_disable_lpm(struct usb_device *udev)
+{
+       return 0;
+}
+EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm);
+
+void usb_unlocked_enable_lpm(struct usb_device *udev) { }
+EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm);
 #endif
 
 
@@ -3208,9 +3864,8 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
        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;
+                       udev->lpm_capable = usb_device_supports_lpm(udev);
+                       usb_set_lpm_parameters(udev);
                }
        }
 
@@ -4042,11 +4697,22 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
                goto done;
 
        mutex_lock(hcd->bandwidth_mutex);
+       /* Disable LPM while we reset the device and reinstall the alt settings.
+        * Device-initiated LPM settings, and system exit latency settings are
+        * cleared when the device is reset, so we have to set them up again.
+        */
+       ret = usb_disable_lpm(udev);
+       if (ret) {
+               dev_err(&udev->dev, "%s Failed to disable LPM\n.", __func__);
+               mutex_unlock(hcd->bandwidth_mutex);
+               goto done;
+       }
        ret = usb_hcd_alloc_bandwidth(udev, udev->actconfig, NULL, NULL);
        if (ret < 0) {
                dev_warn(&udev->dev,
                                "Busted HC?  Not enough HCD resources for "
                                "old configuration.\n");
+               usb_enable_lpm(udev);
                mutex_unlock(hcd->bandwidth_mutex);
                goto re_enumerate;
        }
@@ -4058,6 +4724,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
                dev_err(&udev->dev,
                        "can't restore configuration #%d (error=%d)\n",
                        udev->actconfig->desc.bConfigurationValue, ret);
+               usb_enable_lpm(udev);
                mutex_unlock(hcd->bandwidth_mutex);
                goto re_enumerate;
        }
@@ -4096,10 +4763,13 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
                                desc->bInterfaceNumber,
                                desc->bAlternateSetting,
                                ret);
+                       usb_unlocked_enable_lpm(udev);
                        goto re_enumerate;
                }
        }
 
+       /* Now that the alt settings are re-installed, enable LPM. */
+       usb_unlocked_enable_lpm(udev);
 done:
        return 0;