summaryrefslogtreecommitdiffstats
path: root/drivers/usb/host/xhci.c
diff options
context:
space:
mode:
authorSarah Sharp <sarah.a.sharp@linux.intel.com>2012-05-16 13:36:24 -0700
committerSarah Sharp <sarah.a.sharp@linux.intel.com>2012-05-18 15:42:04 -0700
commite3567d2c15a7a8e2f992a5f7c7683453ca406d82 (patch)
tree2442117f7358e50f49cfecd030a6017d392f474c /drivers/usb/host/xhci.c
parent3b3db026414bba1c8f45c49d5eeaefd48d66e1ae (diff)
xhci: Add Intel U1/U2 timeout policy.
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>
Diffstat (limited to 'drivers/usb/host/xhci.c')
-rw-r--r--drivers/usb/host/xhci.c133
1 files changed, 133 insertions, 0 deletions
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 518d002d54c..4ceba145fa8 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -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;
}