summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/host/xhci-ext-caps.h1
-rw-r--r--drivers/usb/host/xhci.c204
-rw-r--r--drivers/usb/host/xhci.h21
-rw-r--r--include/linux/usb.h2
4 files changed, 164 insertions, 64 deletions
diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h
index 377f4242dabb..8d7a1324e2f3 100644
--- a/drivers/usb/host/xhci-ext-caps.h
+++ b/drivers/usb/host/xhci-ext-caps.h
@@ -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*/
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 31ce422af00a..3d34a0eed088 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -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)
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 01325710424c..c306849eb299 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -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
diff --git a/include/linux/usb.h b/include/linux/usb.h
index b424e5318f20..fe444989668a 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -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;