|
@@ -189,9 +189,216 @@ static int usb_device_supports_lpm(struct usb_device *udev)
|
|
|
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)
|
|
|
{
|
|
@@ -3226,8 +3433,10 @@ 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 (!retval) {
|
|
|
udev->lpm_capable = usb_device_supports_lpm(udev);
|
|
|
+ usb_set_lpm_parameters(udev);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
retval = 0;
|