| From 3e8d6d85adedc59115a564c0a54b36e42087c4d9 Mon Sep 17 00:00:00 2001 |
| From: Alan Stern <stern@rowland.harvard.edu> |
| Date: Thu, 13 Feb 2014 15:49:17 -0500 |
| Subject: USB: EHCI: add delay during suspend to prevent erroneous wakeups |
| |
| From: Alan Stern <stern@rowland.harvard.edu> |
| |
| commit 3e8d6d85adedc59115a564c0a54b36e42087c4d9 upstream. |
| |
| High-speed USB connections revert back to full-speed signalling when |
| the device goes into suspend. This takes several milliseconds, and |
| during that time it's not possible to tell reliably whether the device |
| has been disconnected. |
| |
| On some platforms, the Wake-On-Disconnect circuitry gets confused |
| during this intermediate state. It generates a false wakeup signal, |
| which can prevent the controller from going to sleep. |
| |
| To avoid this problem, this patch adds a 5-ms delay to the |
| ehci_bus_suspend() routine if any ports have to switch over to |
| full-speed signalling. (Actually, the delay was already present for |
| devices using a particular kind of PHY power management; the patch |
| merely causes the delay to be used more widely.) |
| |
| Signed-off-by: Alan Stern <stern@rowland.harvard.edu> |
| Reviewed-by: Peter Chen <Peter.Chen@freescale.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/usb/host/ehci-hub.c | 26 ++++++++++++++++++++++---- |
| 1 file changed, 22 insertions(+), 4 deletions(-) |
| |
| --- a/drivers/usb/host/ehci-hub.c |
| +++ b/drivers/usb/host/ehci-hub.c |
| @@ -238,6 +238,7 @@ static int ehci_bus_suspend (struct usb_ |
| int port; |
| int mask; |
| int changed; |
| + bool fs_idle_delay; |
| |
| ehci_dbg(ehci, "suspend root hub\n"); |
| |
| @@ -272,6 +273,7 @@ static int ehci_bus_suspend (struct usb_ |
| ehci->bus_suspended = 0; |
| ehci->owned_ports = 0; |
| changed = 0; |
| + fs_idle_delay = false; |
| port = HCS_N_PORTS(ehci->hcs_params); |
| while (port--) { |
| u32 __iomem *reg = &ehci->regs->port_status [port]; |
| @@ -300,16 +302,32 @@ static int ehci_bus_suspend (struct usb_ |
| } |
| |
| if (t1 != t2) { |
| + /* |
| + * On some controllers, Wake-On-Disconnect will |
| + * generate false wakeup signals until the bus |
| + * switches over to full-speed idle. For their |
| + * sake, add a delay if we need one. |
| + */ |
| + if ((t2 & PORT_WKDISC_E) && |
| + ehci_port_speed(ehci, t2) == |
| + USB_PORT_STAT_HIGH_SPEED) |
| + fs_idle_delay = true; |
| ehci_writel(ehci, t2, reg); |
| changed = 1; |
| } |
| } |
| + spin_unlock_irq(&ehci->lock); |
| + |
| + if ((changed && ehci->has_tdi_phy_lpm) || fs_idle_delay) { |
| + /* |
| + * Wait for HCD to enter low-power mode or for the bus |
| + * to switch to full-speed idle. |
| + */ |
| + usleep_range(5000, 5500); |
| + } |
| |
| if (changed && ehci->has_tdi_phy_lpm) { |
| - spin_unlock_irq(&ehci->lock); |
| - msleep(5); /* 5 ms for HCD to enter low-power mode */ |
| spin_lock_irq(&ehci->lock); |
| - |
| port = HCS_N_PORTS(ehci->hcs_params); |
| while (port--) { |
| u32 __iomem *hostpc_reg = &ehci->regs->hostpc[port]; |
| @@ -322,8 +340,8 @@ static int ehci_bus_suspend (struct usb_ |
| port, (t3 & HOSTPC_PHCD) ? |
| "succeeded" : "failed"); |
| } |
| + spin_unlock_irq(&ehci->lock); |
| } |
| - spin_unlock_irq(&ehci->lock); |
| |
| /* Apparently some devices need a >= 1-uframe delay here */ |
| if (ehci->bus_suspended) |