| From 3021ef9ba9976ecfde9527caace0ee50534191d2 Mon Sep 17 00:00:00 2001 |
| From: Guoqing Zhang <guoqing.zhang@intel.com> |
| Date: Fri, 7 Apr 2017 17:56:54 +0300 |
| Subject: [PATCH 181/286] usb: xhci: Add port test modes support for usb2. |
| |
| For usb2 ports, the port test mode Test_J_State, Test_K_State, |
| Test_Packet, Test_SE0_NAK and Test_Force_En can be enabled |
| as described in usb2 spec. |
| |
| USB2 test mode is a required hardware feature for system integrators |
| validating their hardware according to USB spec, regarding signal |
| strength and stuff. It is purely a hardware test feature. |
| |
| Usually you need an oscilloscope and have to enable those test modes on |
| the hardware. This will send some specific test patterns on D+/D-. There |
| is no report available (in Linux itself) as it is purely externally |
| visible. Regular USB usage is not possible at that time. |
| Anyone (well access to e.g. /dev/bus/usb/001/001 provided) can use it by |
| sending appropriate USB_PORT_FEAT_TEST requests to the hub. |
| |
| [Add better commit message by Alexander Stein -Mathias] |
| Signed-off-by: Guoqing Zhang <guoqing.zhang@intel.com> |
| Cc: Alexander Stein <alexander.stein@systec-electronic.com> |
| Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| (cherry picked from commit 0f1d832ed1fb9527a4cc5fcb8511e85fd0a85185) |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| --- |
| drivers/usb/host/xhci-hub.c | 85 ++++++++++++++++++++++++++++++++++++++++++++ |
| drivers/usb/host/xhci.h | 2 + |
| 2 files changed, 87 insertions(+) |
| |
| --- a/drivers/usb/host/xhci-hub.c |
| +++ b/drivers/usb/host/xhci-hub.c |
| @@ -583,6 +583,77 @@ static void xhci_set_port_power(struct x |
| spin_lock_irqsave(&xhci->lock, flags); |
| } |
| |
| +static void xhci_port_set_test_mode(struct xhci_hcd *xhci, |
| + u16 test_mode, u16 wIndex) |
| +{ |
| + u32 temp; |
| + __le32 __iomem *addr; |
| + |
| + /* xhci only supports test mode for usb2 ports, i.e. xhci->main_hcd */ |
| + addr = xhci_get_port_io_addr(xhci->main_hcd, wIndex); |
| + temp = readl(addr + PORTPMSC); |
| + temp |= test_mode << PORT_TEST_MODE_SHIFT; |
| + writel(temp, addr + PORTPMSC); |
| + xhci->test_mode = test_mode; |
| + if (test_mode == TEST_FORCE_EN) |
| + xhci_start(xhci); |
| +} |
| + |
| +static int xhci_enter_test_mode(struct xhci_hcd *xhci, |
| + u16 test_mode, u16 wIndex) |
| +{ |
| + int i, retval; |
| + |
| + /* Disable all Device Slots */ |
| + xhci_dbg(xhci, "Disable all slots\n"); |
| + for (i = 1; i <= HCS_MAX_SLOTS(xhci->hcs_params1); i++) { |
| + retval = xhci_disable_slot(xhci, NULL, i); |
| + if (retval) |
| + xhci_err(xhci, "Failed to disable slot %d, %d. Enter test mode anyway\n", |
| + i, retval); |
| + } |
| + /* Put all ports to the Disable state by clear PP */ |
| + xhci_dbg(xhci, "Disable all port (PP = 0)\n"); |
| + /* Power off USB3 ports*/ |
| + for (i = 0; i < xhci->num_usb3_ports; i++) |
| + xhci_set_port_power(xhci, xhci->shared_hcd, i, false); |
| + /* Power off USB2 ports*/ |
| + for (i = 0; i < xhci->num_usb2_ports; i++) |
| + xhci_set_port_power(xhci, xhci->main_hcd, i, false); |
| + /* Stop the controller */ |
| + xhci_dbg(xhci, "Stop controller\n"); |
| + retval = xhci_halt(xhci); |
| + if (retval) |
| + return retval; |
| + /* Disable runtime PM for test mode */ |
| + pm_runtime_forbid(xhci_to_hcd(xhci)->self.controller); |
| + /* Set PORTPMSC.PTC field to enter selected test mode */ |
| + /* Port is selected by wIndex. port_id = wIndex + 1 */ |
| + xhci_dbg(xhci, "Enter Test Mode: %d, Port_id=%d\n", |
| + test_mode, wIndex + 1); |
| + xhci_port_set_test_mode(xhci, test_mode, wIndex); |
| + return retval; |
| +} |
| + |
| +static int xhci_exit_test_mode(struct xhci_hcd *xhci) |
| +{ |
| + int retval; |
| + |
| + if (!xhci->test_mode) { |
| + xhci_err(xhci, "Not in test mode, do nothing.\n"); |
| + return 0; |
| + } |
| + if (xhci->test_mode == TEST_FORCE_EN && |
| + !(xhci->xhc_state & XHCI_STATE_HALTED)) { |
| + retval = xhci_halt(xhci); |
| + if (retval) |
| + return retval; |
| + } |
| + pm_runtime_allow(xhci_to_hcd(xhci)->self.controller); |
| + xhci->test_mode = 0; |
| + return xhci_reset(xhci); |
| +} |
| + |
| void xhci_set_link_state(struct xhci_hcd *xhci, __le32 __iomem **port_array, |
| int port_id, u32 link_state) |
| { |
| @@ -941,6 +1012,7 @@ int xhci_hub_control(struct usb_hcd *hcd |
| u16 link_state = 0; |
| u16 wake_mask = 0; |
| u16 timeout = 0; |
| + u16 test_mode = 0; |
| |
| max_ports = xhci_get_ports(hcd, &port_array); |
| bus_state = &xhci->bus_state[hcd_index(hcd)]; |
| @@ -1014,6 +1086,8 @@ int xhci_hub_control(struct usb_hcd *hcd |
| link_state = (wIndex & 0xff00) >> 3; |
| if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK) |
| wake_mask = wIndex & 0xff00; |
| + if (wValue == USB_PORT_FEAT_TEST) |
| + test_mode = (wIndex & 0xff00) >> 8; |
| /* The MSB of wIndex is the U1/U2 timeout */ |
| timeout = (wIndex & 0xff00) >> 8; |
| wIndex &= 0xff; |
| @@ -1177,6 +1251,14 @@ int xhci_hub_control(struct usb_hcd *hcd |
| temp |= PORT_U2_TIMEOUT(timeout); |
| writel(temp, port_array[wIndex] + PORTPMSC); |
| break; |
| + case USB_PORT_FEAT_TEST: |
| + /* 4.19.6 Port Test Modes (USB2 Test Mode) */ |
| + if (hcd->speed != HCD_USB2) |
| + goto error; |
| + if (test_mode > TEST_FORCE_EN || test_mode < TEST_J) |
| + goto error; |
| + retval = xhci_enter_test_mode(xhci, test_mode, wIndex); |
| + break; |
| default: |
| goto error; |
| } |
| @@ -1244,6 +1326,9 @@ int xhci_hub_control(struct usb_hcd *hcd |
| case USB_PORT_FEAT_POWER: |
| xhci_set_port_power(xhci, hcd, wIndex, false); |
| break; |
| + case USB_PORT_FEAT_TEST: |
| + retval = xhci_exit_test_mode(xhci); |
| + break; |
| default: |
| goto error; |
| } |
| --- a/drivers/usb/host/xhci.h |
| +++ b/drivers/usb/host/xhci.h |
| @@ -425,6 +425,7 @@ struct xhci_op_regs { |
| #define PORT_L1DS_MASK (0xff << 8) |
| #define PORT_L1DS(p) (((p) & 0xff) << 8) |
| #define PORT_HLE (1 << 16) |
| +#define PORT_TEST_MODE_SHIFT 28 |
| |
| /* USB3 Protocol PORTLI Port Link Information */ |
| #define PORT_RX_LANES(p) (((p) >> 16) & 0xf) |
| @@ -1846,6 +1847,7 @@ struct xhci_hcd { |
| /* Compliance Mode Recovery Data */ |
| struct timer_list comp_mode_recovery_timer; |
| u32 port_status_u0; |
| + u16 test_mode; |
| /* Compliance Mode Timer Triggered every 2 seconds */ |
| #define COMP_MODE_RCVRY_MSECS 2000 |
| |