diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-06-06 15:21:02 -0700 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-06-06 15:21:02 -0700 |
commit | 1c83d94ff646001f9ee83f0330a3933b55660927 (patch) | |
tree | 13733a673705e1fba9371eb328b15b7c2e81eea2 | |
parent | 3a57aa8161a06133bc3142327f3b2f1a9a50f6c8 (diff) | |
parent | 17f34867e98d2fb0c03918faab79efb989fa134b (diff) | |
download | op-kernel-dev-1c83d94ff646001f9ee83f0330a3933b55660927.zip op-kernel-dev-1c83d94ff646001f9ee83f0330a3933b55660927.tar.gz |
Merge tag 'for-usb-next-2013-06-06' of git://git.kernel.org/pub/scm/linux/kernel/git/sarah/xhci into usb-next
Sarah writes:
xHCI: USB 2.0 Link PM and misc cleanup patches
Hi Greg,
Here's six patches to be queued for 3.11.
The first four add support for a new type of host hardware-managed USB
2.0 Link Power Management. Hosts with BESL support, including Intel
Haswell ULT systems, will now be able to have USB 2.0 devices go into
the lower power link state (L1) in between packets. These patches have
been tested on Haswell ULT platforms with USB 2.0 webcams that support
Link PM.
The other two patches are clean up. One from Julius clarifies the xHCI
endpoint context debugging to make it consistent with standard endpoint
addresses, instead of xHCI endpoint context indexes. The one from Alex
changes the xHCI driver to be consistent about passing a void pointer to
the xHCI IRQ handler.
Sarah Sharp
-rw-r--r-- | Documentation/ABI/testing/sysfs-bus-usb | 27 | ||||
-rw-r--r-- | drivers/usb/core/sysfs.c | 54 | ||||
-rw-r--r-- | drivers/usb/host/xhci-dbg.c | 5 | ||||
-rw-r--r-- | drivers/usb/host/xhci-ext-caps.h | 1 | ||||
-rw-r--r-- | drivers/usb/host/xhci-hub.c | 16 | ||||
-rw-r--r-- | drivers/usb/host/xhci-mem.c | 33 | ||||
-rw-r--r-- | drivers/usb/host/xhci-ring.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/xhci.c | 251 | ||||
-rw-r--r-- | drivers/usb/host/xhci.h | 32 | ||||
-rw-r--r-- | include/linux/usb.h | 20 |
10 files changed, 356 insertions, 85 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index f093e59..9759b8c 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -236,3 +236,30 @@ Description: This attribute is to expose these information to user space. The file will read "hotplug", "wired" and "not used" if the information is available, and "unknown" otherwise. + +What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout +Date: May 2013 +Contact: Mathias Nyman <mathias.nyman@linux.intel.com> +Description: + USB 2.0 devices may support hardware link power management (LPM) + L1 sleep state. The usb2_lpm_l1_timeout attribute allows + tuning the timeout for L1 inactivity timer (LPM timer), e.g. + needed inactivity time before host requests the device to go to L1 sleep. + Useful for power management tuning. + Supported values are 0 - 65535 microseconds. + +What: /sys/bus/usb/devices/.../power/usb2_lpm_besl +Date: May 2013 +Contact: Mathias Nyman <mathias.nyman@linux.intel.com> +Description: + USB 2.0 devices that support hardware link power management (LPM) + L1 sleep state now use a best effort service latency value (BESL) to + indicate the best effort to resumption of service to the device after the + initiation of the resume event. + If the device does not have a preferred besl value then the host can select + one instead. This usb2_lpm_besl attribute allows to tune the host selected besl + value in order to tune power saving and service latency. + + Supported values are 0 - 15. + More information on how besl values map to microseconds can be found in + USB 2.0 ECN Errata for Link Power Management, section 4.10) diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index aa38db4..d9284b9 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -497,8 +497,62 @@ set_usb2_hardware_lpm(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(usb2_hardware_lpm, S_IRUGO | S_IWUSR, show_usb2_hardware_lpm, set_usb2_hardware_lpm); +static ssize_t +show_usb2_lpm_l1_timeout(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->l1_params.timeout); +} + +static ssize_t +set_usb2_lpm_l1_timeout(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + u16 timeout; + + if (kstrtou16(buf, 0, &timeout)) + return -EINVAL; + + udev->l1_params.timeout = timeout; + + return count; +} + +static DEVICE_ATTR(usb2_lpm_l1_timeout, S_IRUGO | S_IWUSR, + show_usb2_lpm_l1_timeout, set_usb2_lpm_l1_timeout); + +static ssize_t +show_usb2_lpm_besl(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->l1_params.besl); +} + +static ssize_t +set_usb2_lpm_besl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + u8 besl; + + if (kstrtou8(buf, 0, &besl) || besl > 15) + return -EINVAL; + + udev->l1_params.besl = besl; + + return count; +} + +static DEVICE_ATTR(usb2_lpm_besl, S_IRUGO | S_IWUSR, + show_usb2_lpm_besl, set_usb2_lpm_besl); + static struct attribute *usb2_hardware_lpm_attr[] = { &dev_attr_usb2_hardware_lpm.attr, + &dev_attr_usb2_lpm_l1_timeout.attr, + &dev_attr_usb2_lpm_besl.attr, NULL, }; static struct attribute_group usb2_hardware_lpm_attr_group = { diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 5f3a7c7..f2e7689 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -503,11 +503,14 @@ static void xhci_dbg_ep_ctx(struct xhci_hcd *xhci, if (last_ep < 31) last_ep_ctx = last_ep + 1; for (i = 0; i < last_ep_ctx; ++i) { + unsigned int epaddr = xhci_get_endpoint_address(i); struct xhci_ep_ctx *ep_ctx = xhci_get_ep_ctx(xhci, ctx, i); dma_addr_t dma = ctx->dma + ((unsigned long)ep_ctx - (unsigned long)ctx->bytes); - xhci_dbg(xhci, "Endpoint %02d Context:\n", i); + xhci_dbg(xhci, "%s Endpoint %02d Context (ep_index %02d):\n", + usb_endpoint_out(epaddr) ? "OUT" : "IN", + epaddr & USB_ENDPOINT_NUMBER_MASK, i); xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - ep_info\n", &ep_ctx->ep_info, (unsigned long long)dma, ep_ctx->ep_info); diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h index 377f424..8d7a132 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-hub.c b/drivers/usb/host/xhci-hub.c index 187a3ec..1d35459 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -867,18 +867,18 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_U1_TIMEOUT: if (hcd->speed != HCD_USB3) goto error; - temp = xhci_readl(xhci, port_array[wIndex] + 1); + temp = xhci_readl(xhci, port_array[wIndex] + PORTPMSC); temp &= ~PORT_U1_TIMEOUT_MASK; temp |= PORT_U1_TIMEOUT(timeout); - xhci_writel(xhci, temp, port_array[wIndex] + 1); + xhci_writel(xhci, temp, port_array[wIndex] + PORTPMSC); break; case USB_PORT_FEAT_U2_TIMEOUT: if (hcd->speed != HCD_USB3) goto error; - temp = xhci_readl(xhci, port_array[wIndex] + 1); + temp = xhci_readl(xhci, port_array[wIndex] + PORTPMSC); temp &= ~PORT_U2_TIMEOUT_MASK; temp |= PORT_U2_TIMEOUT(timeout); - xhci_writel(xhci, temp, port_array[wIndex] + 1); + xhci_writel(xhci, temp, port_array[wIndex] + PORTPMSC); break; default: goto error; @@ -1098,10 +1098,8 @@ int xhci_bus_suspend(struct usb_hcd *hcd) __le32 __iomem *addr; u32 tmp; - /* Add one to the port status register address to get - * the port power control register address. - */ - addr = port_array[port_index] + 1; + /* Get the port power control register address. */ + addr = port_array[port_index] + PORTPMSC; tmp = xhci_readl(xhci, addr); tmp |= PORT_RWE; xhci_writel(xhci, tmp, addr); @@ -1193,7 +1191,7 @@ int xhci_bus_resume(struct usb_hcd *hcd) /* Add one to the port status register address to get * the port power control register address. */ - addr = port_array[port_index] + 1; + addr = port_array[port_index] + PORTPMSC; tmp = xhci_readl(xhci, addr); tmp &= ~PORT_RWE; xhci_writel(xhci, tmp, addr); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 2cfc465..832f05e 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1852,6 +1852,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) kfree(xhci->usb3_ports); kfree(xhci->port_array); kfree(xhci->rh_bw); + kfree(xhci->ext_caps); xhci->page_size = 0; xhci->page_shift = 0; @@ -2039,7 +2040,7 @@ static void xhci_set_hc_event_deq(struct xhci_hcd *xhci) } static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, - __le32 __iomem *addr, u8 major_revision) + __le32 __iomem *addr, u8 major_revision, int max_caps) { u32 temp, port_offset, port_count; int i; @@ -2064,6 +2065,10 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, /* WTF? "Valid values are ‘1’ to MaxPorts" */ return; + /* cache usb2 port capabilities */ + if (major_revision < 0x03 && xhci->num_ext_caps < max_caps) + xhci->ext_caps[xhci->num_ext_caps++] = temp; + /* Check the host's USB2 LPM capability */ if ((xhci->hci_version == 0x96) && (major_revision != 0x03) && (temp & XHCI_L1C)) { @@ -2121,10 +2126,11 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, */ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) { - __le32 __iomem *addr; - u32 offset; + __le32 __iomem *addr, *tmp_addr; + u32 offset, tmp_offset; unsigned int num_ports; int i, j, port_index; + int cap_count = 0; addr = &xhci->cap_regs->hcc_params; offset = XHCI_HCC_EXT_CAPS(xhci_readl(xhci, addr)); @@ -2157,13 +2163,32 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) * See section 5.3.6 for offset calculation. */ addr = &xhci->cap_regs->hc_capbase + offset; + + tmp_addr = addr; + tmp_offset = offset; + + /* count extended protocol capability entries for later caching */ + do { + u32 cap_id; + cap_id = xhci_readl(xhci, tmp_addr); + if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) + cap_count++; + tmp_offset = XHCI_EXT_CAPS_NEXT(cap_id); + tmp_addr += tmp_offset; + } while (tmp_offset); + + xhci->ext_caps = kzalloc(sizeof(*xhci->ext_caps) * cap_count, flags); + if (!xhci->ext_caps) + return -ENOMEM; + while (1) { u32 cap_id; cap_id = xhci_readl(xhci, addr); if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) xhci_add_in_port(xhci, num_ports, addr, - (u8) XHCI_EXT_PORT_MAJOR(cap_id)); + (u8) XHCI_EXT_PORT_MAJOR(cap_id), + cap_count); offset = XHCI_EXT_CAPS_NEXT(cap_id); if (!offset || (xhci->num_usb2_ports + xhci->num_usb3_ports) == num_ports) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1969c00..e02b907 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2799,7 +2799,7 @@ hw_died: return IRQ_HANDLED; } -irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd) +irqreturn_t xhci_msi_irq(int irq, void *hcd) { return xhci_irq(hcd); } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index b4aa79d..8be34f8 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -218,7 +218,7 @@ static int xhci_setup_msi(struct xhci_hcd *xhci) return ret; } - ret = request_irq(pdev->irq, (irq_handler_t)xhci_msi_irq, + ret = request_irq(pdev->irq, xhci_msi_irq, 0, "xhci_hcd", xhci_to_hcd(xhci)); if (ret) { xhci_dbg(xhci, "disable MSI interrupt\n"); @@ -290,7 +290,7 @@ static int xhci_setup_msix(struct xhci_hcd *xhci) for (i = 0; i < xhci->msix_count; i++) { ret = request_irq(xhci->msix_entries[i].vector, - (irq_handler_t)xhci_msi_irq, + xhci_msi_irq, 0, "xhci_hcd", xhci_to_hcd(xhci)); if (ret) goto disable_msix; @@ -1111,6 +1111,16 @@ unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc) return index; } +/* The reverse operation to xhci_get_endpoint_index. Calculate the USB endpoint + * address from the XHCI endpoint index. + */ +unsigned int xhci_get_endpoint_address(unsigned int ep_index) +{ + unsigned int number = DIV_ROUND_UP(ep_index, 2); + unsigned int direction = ep_index % 2 ? USB_DIR_OUT : USB_DIR_IN; + return direction | number; +} + /* Find the flag for this endpoint (for use in the control context). Use the * endpoint index to create a bitmask. The slot context is bit 0, endpoint 0 is * bit 1, etc. @@ -3805,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 */ @@ -3846,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 = udev->l1_params.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) { @@ -3901,7 +3983,7 @@ static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd, * Check device's USB 2.0 extension descriptor to determine whether * HIRD or BESL shoule be used. See USB2.0 LPM errata. */ - pm_addr = port_array[port_num] + 1; + pm_addr = port_array[port_num] + PORTPMSC; hird = xhci_calculate_hird_besl(xhci, udev); temp = PORT_L1DS(udev->slot_id) | PORT_HIRD(hird); xhci_writel(xhci, temp, pm_addr); @@ -3978,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) @@ -3999,40 +4082,120 @@ 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] + 1; - temp = xhci_readl(xhci, pm_addr); + pm_addr = port_array[port_num] + PORTPMSC; + 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 = udev->l1_params.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); return 0; } +/* check if a usb2 port supports a given extened capability protocol + * only USB2 ports extended protocol capability values are cached. + * Return 1 if capability is supported + */ +static int xhci_check_usb2_port_capability(struct xhci_hcd *xhci, int port, + unsigned capability) +{ + u32 port_offset, port_count; + int i; + + for (i = 0; i < xhci->num_ext_caps; i++) { + if (xhci->ext_caps[i] & capability) { + /* port offsets starts at 1 */ + port_offset = XHCI_EXT_PORT_OFF(xhci->ext_caps[i]) - 1; + port_count = XHCI_EXT_PORT_COUNT(xhci->ext_caps[i]); + if (port >= port_offset && + port < port_offset + port_count) + return 1; + } + } + return 0; +} + int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); int ret; + int portnum = udev->portnum - 1; ret = xhci_usb2_software_lpm_test(hcd, udev); if (!ret) { xhci_dbg(xhci, "software LPM test succeed\n"); - if (xhci->hw_lpm_support == 1) { + if (xhci->hw_lpm_support == 1 && + xhci_check_usb2_port_capability(xhci, portnum, XHCI_HLC)) { udev->usb2_hw_lpm_capable = 1; + udev->l1_params.timeout = XHCI_L1_TIMEOUT; + udev->l1_params.besl = XHCI_DEFAULT_BESL; + 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; @@ -4363,56 +4526,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 29c978e..c306849 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -132,6 +132,11 @@ struct xhci_cap_regs { /* Number of registers per port */ #define NUM_PORT_REGS 4 +#define PORTSC 0 +#define PORTPMSC 1 +#define PORTLI 2 +#define PORTHLPMC 3 + /** * struct xhci_op_regs - xHCI Host Controller Operational Registers. * @command: USBCMD - xHC command register @@ -381,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 @@ -1532,6 +1558,9 @@ struct xhci_hcd { unsigned sw_lpm_support:1; /* support xHCI 1.0 spec USB2 hardware LPM */ unsigned hw_lpm_support:1; + /* cached usb2 extened protocol capabilites */ + u32 *ext_caps; + unsigned int num_ext_caps; /* Compliance Mode Recovery Data */ struct timer_list comp_mode_recovery_timer; u32 port_status_u0; @@ -1641,6 +1670,7 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci, struct usb_device *udev); unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc); +unsigned int xhci_get_endpoint_address(unsigned int ep_index); unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc); unsigned int xhci_get_endpoint_flag_from_index(unsigned int ep_index); unsigned int xhci_last_valid_endpoint(u32 added_ctxs); @@ -1745,7 +1775,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated); int xhci_get_frame(struct usb_hcd *hcd); irqreturn_t xhci_irq(struct usb_hcd *hcd); -irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd); +irqreturn_t xhci_msi_irq(int irq, void *hcd); int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev); void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev); int xhci_alloc_tt_info(struct xhci_hcd *xhci, diff --git a/include/linux/usb.h b/include/linux/usb.h index b424e53..a232b7e 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -394,6 +394,22 @@ enum usb_port_connect_type { }; /* + * USB 2.0 Link Power Management (LPM) parameters. + */ +struct usb2_lpm_parameters { + /* Best effort service latency indicate how long the host will drive + * resume on an exit from L1. + */ + unsigned int besl; + + /* Timeout value in microseconds for the L1 inactivity (LPM) timer. + * When the timer counts to zero, the parent hub will initiate a LPM + * transition to L1. + */ + int timeout; +}; + +/* * USB 3.0 Link Power Management (LPM) parameters. * * PEL and SEL are USB 3.0 Link PM latencies for device-initiated LPM exit. @@ -468,6 +484,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 @@ -487,6 +504,7 @@ struct usb3_lpm_parameters { * specific data for the device. * @slot_id: Slot ID assigned by xHCI * @removable: Device can be physically removed from this port + * @l1_params: best effor service latency for USB2 L1 LPM state, and L1 timeout. * @u1_params: exit latencies for USB3 U1 LPM state, and hub-initiated timeout. * @u2_params: exit latencies for USB3 U2 LPM state, and hub-initiated timeout. * @lpm_disable_count: Ref count used by usb_disable_lpm() and usb_enable_lpm() @@ -538,6 +556,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; @@ -566,6 +585,7 @@ struct usb_device { struct wusb_dev *wusb_dev; int slot_id; enum usb_device_removable removable; + struct usb2_lpm_parameters l1_params; struct usb3_lpm_parameters u1_params; struct usb3_lpm_parameters u2_params; unsigned lpm_disable_count; |