usb: xhci: add USB2 Link power management BESL support
authorMathias Nyman <mathias.nyman@linux.intel.com>
Thu, 23 May 2013 14:14:30 +0000 (17:14 +0300)
committerSarah Sharp <sarah.a.sharp@linux.intel.com>
Wed, 5 Jun 2013 23:48:24 +0000 (16:48 -0700)
usb 2.0 devices with link power managment (LPM) can describe their idle link
timeouts either in BESL or HIRD format, so far xHCI has only supported HIRD but
later xHCI errata add BESL support as well

BESL timeouts need to inform exit latency changes with an evaluate
context command the same way USB 3.0 link PM code does.
The same xhci_change_max_exit_latency() function is used as with USB3
but code is pulled out from #ifdef CONFIG_PM as USB2.0 BESL LPM
funcionality does not depend on CONFIG_PM.

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
drivers/usb/host/xhci-ext-caps.h
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h
include/linux/usb.h

index 377f4242dabb8cac56456258506b0c3982495b2a..8d7a1324e2f3e7e3859c7574d23cadd3d873c92d 100644 (file)
@@ -71,6 +71,7 @@
 
 /* USB 2.0 xHCI 1.0 hardware LMP capability - section 7.2.2.1.3.2 */
 #define XHCI_HLC               (1 << 19)
+#define XHCI_BLC               (1 << 19)
 
 /* command register values to disable interrupts and halt the HC */
 /* start/stop HC execution - do not write unless HC is halted*/
index 31ce422af00a8ae8796b0b50f11e262cf0d9b8c2..3d34a0eed0880fe8254989f107224e94f98b071a 100644 (file)
@@ -3815,6 +3815,56 @@ int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1)
        return raw_port;
 }
 
+/*
+ * Issue an Evaluate Context command to change the Maximum Exit Latency in the
+ * slot context.  If that succeeds, store the new MEL in the xhci_virt_device.
+ */
+static int xhci_change_max_exit_latency(struct xhci_hcd *xhci,
+                       struct usb_device *udev, u16 max_exit_latency)
+{
+       struct xhci_virt_device *virt_dev;
+       struct xhci_command *command;
+       struct xhci_input_control_ctx *ctrl_ctx;
+       struct xhci_slot_ctx *slot_ctx;
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&xhci->lock, flags);
+       if (max_exit_latency == xhci->devs[udev->slot_id]->current_mel) {
+               spin_unlock_irqrestore(&xhci->lock, flags);
+               return 0;
+       }
+
+       /* Attempt to issue an Evaluate Context command to change the MEL. */
+       virt_dev = xhci->devs[udev->slot_id];
+       command = xhci->lpm_command;
+       xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx);
+       spin_unlock_irqrestore(&xhci->lock, flags);
+
+       ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx);
+       ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG);
+       slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx);
+       slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT));
+       slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency);
+
+       xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n");
+       xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id);
+       xhci_dbg_ctx(xhci, command->in_ctx, 0);
+
+       /* Issue and wait for the evaluate context command. */
+       ret = xhci_configure_endpoint(xhci, udev, command,
+                       true, true);
+       xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id);
+       xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0);
+
+       if (!ret) {
+               spin_lock_irqsave(&xhci->lock, flags);
+               virt_dev->current_mel = max_exit_latency;
+               spin_unlock_irqrestore(&xhci->lock, flags);
+       }
+       return ret;
+}
+
 #ifdef CONFIG_PM_RUNTIME
 
 /* BESL to HIRD Encoding array for USB2 LPM */
@@ -3856,6 +3906,28 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci,
        return besl;
 }
 
+/* Calculate BESLD, L1 timeout and HIRDM for USB2 PORTHLPMC */
+static int xhci_calculate_usb2_hw_lpm_params(struct usb_device *udev)
+{
+       u32 field;
+       int l1;
+       int besld = 0;
+       int hirdm = 0;
+
+       field = le32_to_cpu(udev->bos->ext_cap->bmAttributes);
+
+       /* xHCI l1 is set in steps of 256us, xHCI 1.0 section 5.4.11.2 */
+       l1 = XHCI_L1_TIMEOUT / 256;
+
+       /* device has preferred BESLD */
+       if (field & USB_BESL_DEEP_VALID) {
+               besld = USB_GET_BESL_DEEP(field);
+               hirdm = 1;
+       }
+
+       return PORT_BESLD(besld) | PORT_L1_TIMEOUT(l1) | PORT_HIRDM(hirdm);
+}
+
 static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd,
                                        struct usb_device *udev)
 {
@@ -3988,11 +4060,12 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
 {
        struct xhci_hcd *xhci = hcd_to_xhci(hcd);
        __le32 __iomem  **port_array;
-       __le32 __iomem  *pm_addr;
-       u32             temp;
+       __le32 __iomem  *pm_addr, *hlpm_addr;
+       u32             pm_val, hlpm_val, field;
        unsigned int    port_num;
        unsigned long   flags;
-       int             hird;
+       int             hird, exit_latency;
+       int             ret;
 
        if (hcd->speed == HCD_USB3 || !xhci->hw_lpm_support ||
                        !udev->lpm_capable)
@@ -4010,23 +4083,73 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
        port_array = xhci->usb2_ports;
        port_num = udev->portnum - 1;
        pm_addr = port_array[port_num] + PORTPMSC;
-       temp = xhci_readl(xhci, pm_addr);
+       pm_val = xhci_readl(xhci, pm_addr);
+       hlpm_addr = port_array[port_num] + PORTHLPMC;
+       field = le32_to_cpu(udev->bos->ext_cap->bmAttributes);
 
        xhci_dbg(xhci, "%s port %d USB2 hardware LPM\n",
                        enable ? "enable" : "disable", port_num);
 
-       hird = xhci_calculate_hird_besl(xhci, udev);
-
        if (enable) {
-               temp &= ~PORT_HIRD_MASK;
-               temp |= PORT_HIRD(hird) | PORT_RWE;
-               xhci_writel(xhci, temp, pm_addr);
-               temp = xhci_readl(xhci, pm_addr);
-               temp |= PORT_HLE;
-               xhci_writel(xhci, temp, pm_addr);
+               /* Host supports BESL timeout instead of HIRD */
+               if (udev->usb2_hw_lpm_besl_capable) {
+                       /* if device doesn't have a preferred BESL value use a
+                        * default one which works with mixed HIRD and BESL
+                        * systems. See XHCI_DEFAULT_BESL definition in xhci.h
+                        */
+                       if ((field & USB_BESL_SUPPORT) &&
+                           (field & USB_BESL_BASELINE_VALID))
+                               hird = USB_GET_BESL_BASELINE(field);
+                       else
+                               hird = XHCI_DEFAULT_BESL;
+
+                       exit_latency = xhci_besl_encoding[hird];
+                       spin_unlock_irqrestore(&xhci->lock, flags);
+
+                       /* USB 3.0 code dedicate one xhci->lpm_command->in_ctx
+                        * input context for link powermanagement evaluate
+                        * context commands. It is protected by hcd->bandwidth
+                        * mutex and is shared by all devices. We need to set
+                        * the max ext latency in USB 2 BESL LPM as well, so
+                        * use the same mutex and xhci_change_max_exit_latency()
+                        */
+                       mutex_lock(hcd->bandwidth_mutex);
+                       ret = xhci_change_max_exit_latency(xhci, udev,
+                                                          exit_latency);
+                       mutex_unlock(hcd->bandwidth_mutex);
+
+                       if (ret < 0)
+                               return ret;
+                       spin_lock_irqsave(&xhci->lock, flags);
+
+                       hlpm_val = xhci_calculate_usb2_hw_lpm_params(udev);
+                       xhci_writel(xhci, hlpm_val, hlpm_addr);
+                       /* flush write */
+                       xhci_readl(xhci, hlpm_addr);
+               } else {
+                       hird = xhci_calculate_hird_besl(xhci, udev);
+               }
+
+               pm_val &= ~PORT_HIRD_MASK;
+               pm_val |= PORT_HIRD(hird) | PORT_RWE;
+               xhci_writel(xhci, pm_val, pm_addr);
+               pm_val = xhci_readl(xhci, pm_addr);
+               pm_val |= PORT_HLE;
+               xhci_writel(xhci, pm_val, pm_addr);
+               /* flush write */
+               xhci_readl(xhci, pm_addr);
        } else {
-               temp &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK);
-               xhci_writel(xhci, temp, pm_addr);
+               pm_val &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK);
+               xhci_writel(xhci, pm_val, pm_addr);
+               /* flush write */
+               xhci_readl(xhci, pm_addr);
+               if (udev->usb2_hw_lpm_besl_capable) {
+                       spin_unlock_irqrestore(&xhci->lock, flags);
+                       mutex_lock(hcd->bandwidth_mutex);
+                       xhci_change_max_exit_latency(xhci, udev, 0);
+                       mutex_unlock(hcd->bandwidth_mutex);
+                       return 0;
+               }
        }
 
        spin_unlock_irqrestore(&xhci->lock, flags);
@@ -4068,6 +4191,9 @@ int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev)
                if (xhci->hw_lpm_support == 1 &&
                    xhci_check_usb2_port_capability(xhci, portnum, XHCI_HLC)) {
                        udev->usb2_hw_lpm_capable = 1;
+                       if (xhci_check_usb2_port_capability(xhci, portnum,
+                                                           XHCI_BLC))
+                               udev->usb2_hw_lpm_besl_capable = 1;
                        ret = xhci_set_usb2_hardware_lpm(hcd, udev, 1);
                        if (!ret)
                                udev->usb2_hw_lpm_enabled = 1;
@@ -4398,56 +4524,6 @@ static u16 xhci_calculate_lpm_timeout(struct usb_hcd *hcd,
        return timeout;
 }
 
-/*
- * Issue an Evaluate Context command to change the Maximum Exit Latency in the
- * slot context.  If that succeeds, store the new MEL in the xhci_virt_device.
- */
-static int xhci_change_max_exit_latency(struct xhci_hcd *xhci,
-                       struct usb_device *udev, u16 max_exit_latency)
-{
-       struct xhci_virt_device *virt_dev;
-       struct xhci_command *command;
-       struct xhci_input_control_ctx *ctrl_ctx;
-       struct xhci_slot_ctx *slot_ctx;
-       unsigned long flags;
-       int ret;
-
-       spin_lock_irqsave(&xhci->lock, flags);
-       if (max_exit_latency == xhci->devs[udev->slot_id]->current_mel) {
-               spin_unlock_irqrestore(&xhci->lock, flags);
-               return 0;
-       }
-
-       /* Attempt to issue an Evaluate Context command to change the MEL. */
-       virt_dev = xhci->devs[udev->slot_id];
-       command = xhci->lpm_command;
-       xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx);
-       spin_unlock_irqrestore(&xhci->lock, flags);
-
-       ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx);
-       ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG);
-       slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx);
-       slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT));
-       slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency);
-
-       xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n");
-       xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id);
-       xhci_dbg_ctx(xhci, command->in_ctx, 0);
-
-       /* Issue and wait for the evaluate context command. */
-       ret = xhci_configure_endpoint(xhci, udev, command,
-                       true, true);
-       xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id);
-       xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0);
-
-       if (!ret) {
-               spin_lock_irqsave(&xhci->lock, flags);
-               virt_dev->current_mel = max_exit_latency;
-               spin_unlock_irqrestore(&xhci->lock, flags);
-       }
-       return ret;
-}
-
 static int calculate_max_exit_latency(struct usb_device *udev,
                enum usb3_link_state state_changed,
                u16 hub_encoded_timeout)
index 01325710424c96d7e3f4a78c53f6215aa2bfde7d..c306849eb299027ca2cb75faacb8d795362ae7fd 100644 (file)
@@ -386,6 +386,27 @@ struct xhci_op_regs {
 #define        PORT_L1DS(p)            (((p) & 0xff) << 8)
 #define        PORT_HLE                (1 << 16)
 
+
+/* USB2 Protocol PORTHLPMC */
+#define PORT_HIRDM(p)((p) & 3)
+#define PORT_L1_TIMEOUT(p)(((p) & 0xff) << 2)
+#define PORT_BESLD(p)(((p) & 0xf) << 10)
+
+/* use 512 microseconds as USB2 LPM L1 default timeout. */
+#define XHCI_L1_TIMEOUT                512
+
+/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency.
+ * Safe to use with mixed HIRD and BESL systems (host and device) and is used
+ * by other operating systems.
+ *
+ * XHCI 1.0 errata 8/14/12 Table 13 notes:
+ * "Software should choose xHC BESL/BESLD field values that do not violate a
+ * device's resume latency requirements,
+ * e.g. not program values > '4' if BLC = '1' and a HIRD device is attached,
+ * or not program values < '4' if BLC = '0' and a BESL device is attached.
+ */
+#define XHCI_DEFAULT_BESL      4
+
 /**
  * struct xhci_intr_reg - Interrupt Register Set
  * @irq_pending:       IMAN - Interrupt Management Register.  Used to enable
index b424e5318f2070f4c707dc5eaf750e786be8d569..fe444989668a4306feae2149c4376089ddbf2881 100644 (file)
@@ -468,6 +468,7 @@ struct usb3_lpm_parameters {
  * @wusb: device is Wireless USB
  * @lpm_capable: device supports LPM
  * @usb2_hw_lpm_capable: device can perform USB2 hardware LPM
+ * @usb2_hw_lpm_besl_capable: device can perform USB2 hardware BESL LPM
  * @usb2_hw_lpm_enabled: USB2 hardware LPM enabled
  * @usb3_lpm_enabled: USB3 hardware LPM enabled
  * @string_langid: language ID for strings
@@ -538,6 +539,7 @@ struct usb_device {
        unsigned wusb:1;
        unsigned lpm_capable:1;
        unsigned usb2_hw_lpm_capable:1;
+       unsigned usb2_hw_lpm_besl_capable:1;
        unsigned usb2_hw_lpm_enabled:1;
        unsigned usb3_lpm_enabled:1;
        int string_langid;