Commit a85c0f8d authored by Mathias Nyman's avatar Mathias Nyman Committed by Greg Kroah-Hartman

xhci: rework bus_resume and check ports are suspended before resuming them.

bus_resume() tried to resume the same ports the bus_suspend()
suspeded. This caused PLC timeouts in case a suspended device disconnected
and was not in a resumable state at bus_resume().

Add a check to make sure the link state is either U3 or resuming
before actually resuming the link.

At the same time do some other changes such as make sure we remove
wake on connect/disconnect/overcurrent also for the resuming ports,
and avoid extra portsc port register writes.

This improves resume time with 10ms in those PLC timeout cases where
devices disconnect at suspend/resume cycle.
Signed-off-by: default avatarMathias Nyman <mathias.nyman@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 74072bae
......@@ -1521,15 +1521,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 +1547,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) {
/* 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);
}
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);
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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment