| From 1c3285798797058421f167d3ae49b1d938d0dbc0 Mon Sep 17 00:00:00 2001 |
| From: Mathias Nyman <mathias.nyman@linux.intel.com> |
| Date: Thu, 23 May 2013 17:14:28 +0300 |
| Subject: usb: xhci: check usb2 port capabilities before adding hw link PM |
| support |
| |
| Hardware link powermanagement in usb2 is a per-port capability. |
| Previously support for hw lpm was enabled for all ports if any usb2 port supported it. |
| |
| Now instead cache the capability values and check them for each port individually |
| |
| Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> |
| Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> |
| (cherry picked from commit b630d4b9d05ba2e66878ca4614946d0f950d4111) |
| Signed-off-by: Benson Leung <bleung@chromium.org> |
| |
| |
| Reviewed-on: https://chromium-review.googlesource.com/169210 |
| Reviewed-by: Julius Werner <jwerner@chromium.org> |
| Commit-Queue: Benson Leung <bleung@chromium.org> |
| Tested-by: Benson Leung <bleung@chromium.org> |
| Signed-off-by: Darren Hart <dvhart@linux.intel.com> |
| --- |
| drivers/usb/host/xhci-mem.c | 33 +++++++++++++++++++++++++++++---- |
| drivers/usb/host/xhci.c | 27 ++++++++++++++++++++++++++- |
| drivers/usb/host/xhci.h | 3 +++ |
| 3 files changed, 58 insertions(+), 5 deletions(-) |
| |
| --- a/drivers/usb/host/xhci-mem.c |
| +++ b/drivers/usb/host/xhci-mem.c |
| @@ -1860,6 +1860,7 @@ no_bw: |
| kfree(xhci->usb3_ports); |
| kfree(xhci->port_array); |
| kfree(xhci->rh_bw); |
| + kfree(xhci->ext_caps); |
| |
| xhci->usb2_ports = NULL; |
| xhci->usb3_ports = NULL; |
| @@ -2052,7 +2053,7 @@ static void xhci_set_hc_event_deq(struct |
| } |
| |
| 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; |
| @@ -2077,6 +2078,10 @@ static void xhci_add_in_port(struct xhci |
| /* 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)) { |
| @@ -2134,10 +2139,11 @@ static void xhci_add_in_port(struct xhci |
| */ |
| 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)); |
| @@ -2170,13 +2176,32 @@ static int xhci_setup_port_arrays(struct |
| * 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) |
| --- a/drivers/usb/host/xhci.c |
| +++ b/drivers/usb/host/xhci.c |
| @@ -4063,15 +4063,40 @@ int xhci_set_usb2_hardware_lpm(struct us |
| 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; |
| ret = xhci_set_usb2_hardware_lpm(hcd, udev, 1); |
| if (!ret) |
| --- a/drivers/usb/host/xhci.h |
| +++ b/drivers/usb/host/xhci.h |
| @@ -1548,6 +1548,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; |