diff options
Diffstat (limited to 'drivers/usb/host/xhci-hub.c')
-rw-r--r-- | drivers/usb/host/xhci-hub.c | 184 |
1 files changed, 118 insertions, 66 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 00721e8807ab..2a90229be7a6 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * xHCI host controller driver * @@ -5,19 +6,6 @@ * * Author: Sarah Sharp * Some code borrowed from the Linux EHCI driver. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ @@ -112,7 +100,7 @@ static int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf, /* If PSI table exists, add the custom speed attributes from it */ if (usb3_1 && xhci->usb3_rhub.psi_count) { - u32 ssp_cap_base, bm_attrib, psi; + u32 ssp_cap_base, bm_attrib, psi, psi_mant, psi_exp; int offset; ssp_cap_base = USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE; @@ -139,6 +127,15 @@ static int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf, for (i = 0; i < xhci->usb3_rhub.psi_count; i++) { psi = xhci->usb3_rhub.psi[i]; psi &= ~USB_SSP_SUBLINK_SPEED_RSVD; + psi_exp = XHCI_EXT_PORT_PSIE(psi); + psi_mant = XHCI_EXT_PORT_PSIM(psi); + + /* Shift to Gbps and set SSP Link BIT(14) if 10Gpbs */ + for (; psi_exp < 3; psi_exp++) + psi_mant /= 1000; + if (psi_mant >= 10) + psi |= BIT(14); + if ((psi & PLT_MASK) == PLT_SYM) { /* Symmetric, create SSA RX and TX from one PSI entry */ put_unaligned_le32(psi, &buf[offset]); @@ -411,14 +408,25 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend) GFP_NOWAIT); if (!command) { spin_unlock_irqrestore(&xhci->lock, flags); - xhci_free_command(xhci, cmd); - return -ENOMEM; + ret = -ENOMEM; + goto cmd_cleanup; + } + + ret = xhci_queue_stop_endpoint(xhci, command, slot_id, + i, suspend); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, command); + goto cmd_cleanup; } - xhci_queue_stop_endpoint(xhci, command, slot_id, i, - suspend); } } - xhci_queue_stop_endpoint(xhci, cmd, slot_id, 0, suspend); + ret = xhci_queue_stop_endpoint(xhci, cmd, slot_id, 0, suspend); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + goto cmd_cleanup; + } + xhci_ring_cmd_db(xhci); spin_unlock_irqrestore(&xhci->lock, flags); @@ -430,6 +438,8 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend) xhci_warn(xhci, "Timeout while waiting for stop endpoint command\n"); ret = -ETIME; } + +cmd_cleanup: xhci_free_command(xhci, cmd); return ret; } @@ -612,7 +622,10 @@ static int xhci_enter_test_mode(struct xhci_hcd *xhci, xhci_dbg(xhci, "Disable all slots\n"); spin_unlock_irqrestore(&xhci->lock, *flags); for (i = 1; i <= HCS_MAX_SLOTS(xhci->hcs_params1); i++) { - retval = xhci_disable_slot(xhci, NULL, i); + if (!xhci->devs[i]) + continue; + + retval = xhci_disable_slot(xhci, i); if (retval) xhci_err(xhci, "Failed to disable slot %d, %d. Enter test mode anyway\n", i, retval); @@ -1179,6 +1192,39 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; } + /* + * For xHCI 1.1 according to section 4.19.1.2.4.1 a + * root hub port's transition to compliance mode upon + * detecting LFPS timeout may be controlled by an + * Compliance Transition Enabled (CTE) flag (not + * software visible). This flag is set by writing 0xA + * to PORTSC PLS field which will allow transition to + * compliance mode the next time LFPS timeout is + * encountered. A warm reset will clear it. + * + * The CTE flag is only supported if the HCCPARAMS2 CTC + * flag is set, otherwise, the compliance substate is + * automatically entered as on 1.0 and prior. + */ + if (link_state == USB_SS_PORT_LS_COMP_MOD) { + if (!HCC2_CTC(xhci->hcc_params2)) { + xhci_dbg(xhci, "CTC flag is 0, port already supports entering compliance mode\n"); + break; + } + + if ((temp & PORT_CONNECT)) { + xhci_warn(xhci, "Can't set compliance mode when port is connected\n"); + goto error; + } + + xhci_dbg(xhci, "Enable compliance mode transition for port %d\n", + wIndex); + xhci_set_link_state(xhci, port_array, wIndex, + link_state); + temp = readl(port_array[wIndex]); + break; + } + /* Software should not attempt to set * port link state above '3' (U3) and the port * must be enabled. @@ -1319,6 +1365,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case USB_PORT_FEAT_C_SUSPEND: bus_state->port_c_suspend &= ~(1 << wIndex); + /* fall through */ case USB_PORT_FEAT_C_RESET: case USB_PORT_FEAT_C_BH_PORT_RESET: case USB_PORT_FEAT_C_CONNECTION: @@ -1473,9 +1520,6 @@ int xhci_bus_suspend(struct usb_hcd *hcd) t2 |= PORT_WKOC_E | PORT_WKCONN_E; t2 &= ~PORT_WKDISC_E; } - if ((xhci->quirks & XHCI_U2_DISABLE_WAKE) && - (hcd->speed < HCD_USB3)) - t2 &= ~PORT_WAKE_BITS; } else t2 &= ~PORT_WAKE_BITS; @@ -1521,15 +1565,14 @@ static bool xhci_port_missing_cas_quirk(int port_index, int xhci_bus_resume(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); - int max_ports, port_index; - __le32 __iomem **port_array; struct xhci_bus_state *bus_state; - u32 temp; + __le32 __iomem **port_array; unsigned long flags; - unsigned long port_was_suspended = 0; - bool need_usb2_u3_exit = false; + int max_ports, port_index; int slot_id; int sret; + u32 next_state; + u32 temp, portsc; max_ports = xhci_get_ports(hcd, &port_array); bus_state = &xhci->bus_state[hcd_index(hcd)]; @@ -1548,68 +1591,77 @@ int xhci_bus_resume(struct usb_hcd *hcd) temp &= ~CMD_EIE; writel(temp, &xhci->op_regs->command); + /* bus specific resume for ports we suspended at bus_suspend */ + if (hcd->speed >= HCD_USB3) + next_state = XDEV_U0; + else + next_state = XDEV_RESUME; + port_index = max_ports; while (port_index--) { - /* Check whether need resume ports. If needed - resume port and disable remote wakeup */ - u32 temp; - - temp = readl(port_array[port_index]); + portsc = readl(port_array[port_index]); /* warm reset CAS limited ports stuck in polling/compliance */ if ((xhci->quirks & XHCI_MISSING_CAS) && (hcd->speed >= HCD_USB3) && xhci_port_missing_cas_quirk(port_index, port_array)) { xhci_dbg(xhci, "reset stuck port %d\n", port_index); + clear_bit(port_index, &bus_state->bus_suspended); continue; } - if (DEV_SUPERSPEED_ANY(temp)) - temp &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS); - else - temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); - if (test_bit(port_index, &bus_state->bus_suspended) && - (temp & PORT_PLS_MASK)) { - set_bit(port_index, &port_was_suspended); - if (!DEV_SUPERSPEED_ANY(temp)) { - xhci_set_link_state(xhci, port_array, - port_index, XDEV_RESUME); - need_usb2_u3_exit = true; + /* resume if we suspended the link, and it is still suspended */ + if (test_bit(port_index, &bus_state->bus_suspended)) + switch (portsc & PORT_PLS_MASK) { + case XDEV_U3: + portsc = xhci_port_state_to_neutral(portsc); + portsc &= ~PORT_PLS_MASK; + portsc |= PORT_LINK_STROBE | next_state; + break; + case XDEV_RESUME: + /* resume already initiated */ + break; + default: + /* not in a resumeable state, ignore it */ + clear_bit(port_index, + &bus_state->bus_suspended); + break; } - } else - writel(temp, port_array[port_index]); + /* disable wake for all ports, write new link state if needed */ + portsc &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS); + writel(portsc, port_array[port_index]); } - if (need_usb2_u3_exit) { - spin_unlock_irqrestore(&xhci->lock, flags); - msleep(USB_RESUME_TIMEOUT); - spin_lock_irqsave(&xhci->lock, flags); - } - - port_index = max_ports; - while (port_index--) { - if (!(port_was_suspended & BIT(port_index))) - continue; - /* Clear PLC to poll it later after XDEV_U0 */ - xhci_test_and_clear_bit(xhci, port_array, port_index, PORT_PLC); - xhci_set_link_state(xhci, port_array, port_index, XDEV_U0); + /* USB2 specific resume signaling delay and U0 link state transition */ + if (hcd->speed < HCD_USB3) { + if (bus_state->bus_suspended) { + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(USB_RESUME_TIMEOUT); + spin_lock_irqsave(&xhci->lock, flags); + } + for_each_set_bit(port_index, &bus_state->bus_suspended, + BITS_PER_LONG) { + /* Clear PLC to poll it later for U0 transition */ + xhci_test_and_clear_bit(xhci, port_array, port_index, + PORT_PLC); + xhci_set_link_state(xhci, port_array, port_index, + XDEV_U0); + } } - port_index = max_ports; - while (port_index--) { - if (!(port_was_suspended & BIT(port_index))) - continue; - /* Poll and Clear PLC */ + /* poll for U0 link state complete, both USB2 and USB3 */ + for_each_set_bit(port_index, &bus_state->bus_suspended, BITS_PER_LONG) { sret = xhci_handshake(port_array[port_index], PORT_PLC, PORT_PLC, 10 * 1000); - if (sret) + if (sret) { xhci_warn(xhci, "port %d resume PLC timeout\n", port_index); + continue; + } xhci_test_and_clear_bit(xhci, port_array, port_index, PORT_PLC); slot_id = xhci_find_slot_id_by_port(hcd, xhci, port_index + 1); if (slot_id) xhci_ring_device(xhci, slot_id); } - (void) readl(&xhci->op_regs->command); bus_state->next_statechange = jiffies + msecs_to_jiffies(5); |