]> git.ipfire.org Git - people/arne_f/kernel.git/commitdiff
xhci: rework bus_resume and check ports are suspended before resuming them.
authorMathias Nyman <mathias.nyman@linux.intel.com>
Wed, 16 Aug 2017 11:23:26 +0000 (14:23 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 16 Aug 2017 22:26:26 +0000 (15:26 -0700)
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: Mathias Nyman <mathias.nyman@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci-hub.c

index 00721e8807ab472f1e886fff791e7bbbaeec3a13..4bc6f4201340f3dbad8a5da35f5fe60d963e3b4b 100644 (file)
@@ -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]);
-       }
-
-       if (need_usb2_u3_exit) {
-               spin_unlock_irqrestore(&xhci->lock, flags);
-               msleep(USB_RESUME_TIMEOUT);
-               spin_lock_irqsave(&xhci->lock, flags);
+               /* 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]);
        }
 
-       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);