]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
xhci: Add Intel U1/U2 timeout policy.
authorSarah Sharp <sarah.a.sharp@linux.intel.com>
Wed, 16 May 2012 20:36:24 +0000 (13:36 -0700)
committerSarah Sharp <sarah.a.sharp@linux.intel.com>
Fri, 18 May 2012 22:42:04 +0000 (15:42 -0700)
All Intel xHCI host controllers support USB 3.0 Link Power Management.

The Panther Point xHCI host controller needs the xHCI driver to
calculate the U1 and U2 timeout values, because it will blindly accept a
MEL that would cause scheduling issues.

The Lynx Point xHCI host controller will reject MEL values that are too
high, but internally it implements the same algorithm that is needed for
Panther Point xHCI.

Simplify the code paths by just having the xHCI driver calculate what
the U1/U2 timeouts should be.  Comments on the policy are in the code.

Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
drivers/usb/host/xhci-pci.c
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h

index 890ba735dee7dd08d7003e5124ca5dae64224593..18b231b0c5d373afbec7efde87faed879ca26889 100644 (file)
@@ -84,6 +84,10 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
        /* AMD PLL quirk */
        if (pdev->vendor == PCI_VENDOR_ID_AMD && usb_amd_find_chipset_info())
                xhci->quirks |= XHCI_AMD_PLL_FIX;
+       if (pdev->vendor == PCI_VENDOR_ID_INTEL) {
+               xhci->quirks |= XHCI_LPM_SUPPORT;
+               xhci->quirks |= XHCI_INTEL_HOST;
+       }
        if (pdev->vendor == PCI_VENDOR_ID_INTEL &&
                        pdev->device == PCI_DEVICE_ID_INTEL_PANTHERPOINT_XHCI) {
                xhci->quirks |= XHCI_SPURIOUS_SUCCESS;
index 518d002d54ccebb75df4e9e1810fc656cca3a6e1..4ceba145fa88e100a75715a460161db4a69d5c24 100644 (file)
@@ -3839,6 +3839,13 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
 
 /*---------------------- USB 3.0 Link PM functions ------------------------*/
 
+/* Service interval in nanoseconds = 2^(bInterval - 1) * 125us * 1000ns / 1us */
+static unsigned long long xhci_service_interval_to_ns(
+               struct usb_endpoint_descriptor *desc)
+{
+       return (1 << (desc->bInterval - 1)) * 125 * 1000;
+}
+
 static u16 xhci_get_timeout_no_hub_lpm(struct usb_device *udev,
                enum usb3_link_state state)
 {
@@ -3881,12 +3888,112 @@ static u16 xhci_get_timeout_no_hub_lpm(struct usb_device *udev,
        return USB3_LPM_DISABLED;
 }
 
+/* Returns the hub-encoded U1 timeout value.
+ * The U1 timeout should be the maximum of the following values:
+ *  - For control endpoints, U1 system exit latency (SEL) * 3
+ *  - For bulk endpoints, U1 SEL * 5
+ *  - For interrupt endpoints:
+ *    - Notification EPs, U1 SEL * 3
+ *    - Periodic EPs, max(105% of bInterval, U1 SEL * 2)
+ *  - For isochronous endpoints, max(105% of bInterval, U1 SEL * 2)
+ */
+static u16 xhci_calculate_intel_u1_timeout(struct usb_device *udev,
+               struct usb_endpoint_descriptor *desc)
+{
+       unsigned long long timeout_ns;
+       int ep_type;
+       int intr_type;
+
+       ep_type = usb_endpoint_type(desc);
+       switch (ep_type) {
+       case USB_ENDPOINT_XFER_CONTROL:
+               timeout_ns = udev->u1_params.sel * 3;
+               break;
+       case USB_ENDPOINT_XFER_BULK:
+               timeout_ns = udev->u1_params.sel * 5;
+               break;
+       case USB_ENDPOINT_XFER_INT:
+               intr_type = usb_endpoint_interrupt_type(desc);
+               if (intr_type == USB_ENDPOINT_INTR_NOTIFICATION) {
+                       timeout_ns = udev->u1_params.sel * 3;
+                       break;
+               }
+               /* Otherwise the calculation is the same as isoc eps */
+       case USB_ENDPOINT_XFER_ISOC:
+               timeout_ns = xhci_service_interval_to_ns(desc);
+               timeout_ns = DIV_ROUND_UP(timeout_ns * 105, 100);
+               if (timeout_ns < udev->u1_params.sel * 2)
+                       timeout_ns = udev->u1_params.sel * 2;
+               break;
+       default:
+               return 0;
+       }
+
+       /* The U1 timeout is encoded in 1us intervals. */
+       timeout_ns = DIV_ROUND_UP(timeout_ns, 1000);
+       /* Don't return a timeout of zero, because that's USB3_LPM_DISABLED. */
+       if (timeout_ns == USB3_LPM_DISABLED)
+               timeout_ns++;
+
+       /* If the necessary timeout value is bigger than what we can set in the
+        * USB 3.0 hub, we have to disable hub-initiated U1.
+        */
+       if (timeout_ns <= USB3_LPM_U1_MAX_TIMEOUT)
+               return timeout_ns;
+       dev_dbg(&udev->dev, "Hub-initiated U1 disabled "
+                       "due to long timeout %llu ms\n", timeout_ns);
+       return xhci_get_timeout_no_hub_lpm(udev, USB3_LPM_U1);
+}
+
+/* Returns the hub-encoded U2 timeout value.
+ * The U2 timeout should be the maximum of:
+ *  - 10 ms (to avoid the bandwidth impact on the scheduler)
+ *  - largest bInterval of any active periodic endpoint (to avoid going
+ *    into lower power link states between intervals).
+ *  - the U2 Exit Latency of the device
+ */
+static u16 xhci_calculate_intel_u2_timeout(struct usb_device *udev,
+               struct usb_endpoint_descriptor *desc)
+{
+       unsigned long long timeout_ns;
+       unsigned long long u2_del_ns;
+
+       timeout_ns = 10 * 1000 * 1000;
+
+       if ((usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc)) &&
+                       (xhci_service_interval_to_ns(desc) > timeout_ns))
+               timeout_ns = xhci_service_interval_to_ns(desc);
+
+       u2_del_ns = udev->bos->ss_cap->bU2DevExitLat * 1000;
+       if (u2_del_ns > timeout_ns)
+               timeout_ns = u2_del_ns;
+
+       /* The U2 timeout is encoded in 256us intervals */
+       timeout_ns = DIV_ROUND_UP(timeout_ns, 256 * 1000);
+       /* If the necessary timeout value is bigger than what we can set in the
+        * USB 3.0 hub, we have to disable hub-initiated U2.
+        */
+       if (timeout_ns <= USB3_LPM_U2_MAX_TIMEOUT)
+               return timeout_ns;
+       dev_dbg(&udev->dev, "Hub-initiated U2 disabled "
+                       "due to long timeout %llu ms\n", timeout_ns);
+       return xhci_get_timeout_no_hub_lpm(udev, USB3_LPM_U2);
+}
+
 static u16 xhci_call_host_update_timeout_for_endpoint(struct xhci_hcd *xhci,
                struct usb_device *udev,
                struct usb_endpoint_descriptor *desc,
                enum usb3_link_state state,
                u16 *timeout)
 {
+       if (state == USB3_LPM_U1) {
+               if (xhci->quirks & XHCI_INTEL_HOST)
+                       return xhci_calculate_intel_u1_timeout(udev, desc);
+       } else {
+               if (xhci->quirks & XHCI_INTEL_HOST)
+                       return xhci_calculate_intel_u2_timeout(udev, desc);
+       }
+
        return USB3_LPM_DISABLED;
 }
 
@@ -3932,10 +4039,36 @@ static int xhci_update_timeout_for_interface(struct xhci_hcd *xhci,
        return 0;
 }
 
+static int xhci_check_intel_tier_policy(struct usb_device *udev,
+               enum usb3_link_state state)
+{
+       struct usb_device *parent;
+       unsigned int num_hubs;
+
+       if (state == USB3_LPM_U2)
+               return 0;
+
+       /* Don't enable U1 if the device is on a 2nd tier hub or lower. */
+       for (parent = udev->parent, num_hubs = 0; parent->parent;
+                       parent = parent->parent)
+               num_hubs++;
+
+       if (num_hubs < 2)
+               return 0;
+
+       dev_dbg(&udev->dev, "Disabling U1 link state for device"
+                       " below second-tier hub.\n");
+       dev_dbg(&udev->dev, "Plug device into first-tier hub "
+                       "to decrease power consumption.\n");
+       return -E2BIG;
+}
+
 static int xhci_check_tier_policy(struct xhci_hcd *xhci,
                struct usb_device *udev,
                enum usb3_link_state state)
 {
+       if (xhci->quirks & XHCI_INTEL_HOST)
+               return xhci_check_intel_tier_policy(udev, state);
        return -EINVAL;
 }
 
index d55b3678c8b884a3b4e76f0b746a5c889cd6fa41..de3d6e3e57be4b12e840f5a168b439b151fe84fc 100644 (file)
@@ -1489,6 +1489,7 @@ struct xhci_hcd {
 #define XHCI_AMD_0x96_HOST     (1 << 9)
 #define XHCI_TRUST_TX_LENGTH   (1 << 10)
 #define XHCI_LPM_SUPPORT       (1 << 11)
+#define XHCI_INTEL_HOST                (1 << 12)
        unsigned int            num_active_eps;
        unsigned int            limit_active_eps;
        /* There are two roothubs to keep track of bus suspend info for */