summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorHenry Lin <henryl@nvidia.com>2013-07-12 11:36:55 +0800
committerHarshada Kale <hkale@nvidia.com>2013-07-23 08:11:57 -0700
commit93eb2637dae56488d6e3dc980c04f0020239b3be (patch)
tree12c60aa6ef04f85f2d529c7603b1162d1ee80fde /drivers/usb
parent16e29c11104e053f219ebafd6259cebf367f40f7 (diff)
xhci: tegra: fix race between remote and host wake for usb2 devices
For usb2 devices, a race condition between remote and host wake was observed during remote wake from LP0. If usb2 remote wake's port status change event interrupt happens while the hub driver is resuming the same usb2 port for host wake, the hub driver may disconnect the usb2 port and cause the resuming_ports flag for usb2 remote wake not cleared properly. Then, the system cannot go to LP0 with "xhci_bus_suspend failed -16" error message. This patch fix the race by letting remote wake being completed before hub driver performs host resume for usb2 port. Bug 1318548 Change-Id: I59c032527f9adf02a6e4f589f022033940b1d494 Signed-off-by: Henry Lin <henryl@nvidia.com> Reviewed-on: http://git-master/r/248179 Reviewed-by: Harshada Kale <hkale@nvidia.com> Tested-by: Harshada Kale <hkale@nvidia.com>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/host/xhci-tegra.c43
1 files changed, 37 insertions, 6 deletions
diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c
index 3822e3452f24..6eaae5765569 100644
--- a/drivers/usb/host/xhci-tegra.c
+++ b/drivers/usb/host/xhci-tegra.c
@@ -289,6 +289,7 @@ struct tegra_xhci_hcd {
/* number of retires to handle oc */
u32 no_of_oc_retries;
+ unsigned long usb2_rh_remote_wakeup_ports; /* one bit per port */
unsigned long usb3_rh_remote_wakeup_ports; /* one bit per port */
/* firmware loading related */
struct tegra_xhci_firmware firmware;
@@ -2253,10 +2254,11 @@ static void ss_partition_elpg_exit_work(struct work_struct *work)
}
/* read pmc WAKE2_STATUS register to know if SS port caused remote wake */
-static void update_remote_wakeup_ports_pmc(struct tegra_xhci_hcd *tegra)
+static void update_remote_wakeup_ports(struct tegra_xhci_hcd *tegra)
{
struct xhci_hcd *xhci = tegra->xhci;
u32 wake2_status;
+ int port;
#define PMC_WAKE2_STATUS 0x168
#define PADCTL_WAKE (1 << (58 - 32)) /* PADCTL is WAKE#58 */
@@ -2270,6 +2272,15 @@ static void update_remote_wakeup_ports_pmc(struct tegra_xhci_hcd *tegra)
tegra_usb_pmc_reg_write(PMC_WAKE2_STATUS, PADCTL_WAKE);
}
+ /* set all usb2 ports with RESUME link state as wakup ports */
+ for (port = 0; port < xhci->num_usb2_ports; port++) {
+ u32 portsc = xhci_readl(xhci, xhci->usb2_ports[port]);
+ if ((portsc & PORT_PLS_MASK) == XDEV_RESUME)
+ set_bit(port, &tegra->usb2_rh_remote_wakeup_ports);
+ }
+
+ xhci_dbg(xhci, "%s: usb2 roothub remote_wakeup_ports 0x%lx\n",
+ __func__, tegra->usb2_rh_remote_wakeup_ports);
xhci_dbg(xhci, "%s: usb3 roothub remote_wakeup_ports 0x%lx\n",
__func__, tegra->usb3_rh_remote_wakeup_ports);
}
@@ -2284,18 +2295,26 @@ static void wait_remote_wakeup_ports(struct usb_hcd *hcd)
__le32 __iomem **port_array;
unsigned char *rh;
unsigned int retry = 64;
+ struct xhci_bus_state *bus_state;
+ bus_state = &xhci->bus_state[hcd_index(hcd)];
if (hcd == xhci->shared_hcd) {
port_array = xhci->usb3_ports;
num_ports = xhci->num_usb3_ports;
remote_wakeup_ports = &tegra->usb3_rh_remote_wakeup_ports;
rh = "usb3 roothub";
- } else
- return;
+ } else {
+ port_array = xhci->usb2_ports;
+ num_ports = xhci->num_usb2_ports;
+ remote_wakeup_ports = &tegra->usb2_rh_remote_wakeup_ports;
+ rh = "usb2 roothub";
+ }
while (*remote_wakeup_ports && retry--) {
for_each_set_bit(port, remote_wakeup_ports, num_ports) {
+ bool can_continue;
+
portsc = xhci_readl(xhci, port_array[port]);
if (!(portsc & PORT_CONNECT)) {
@@ -2304,11 +2323,23 @@ static void wait_remote_wakeup_ports(struct usb_hcd *hcd)
continue;
}
- if ((portsc & PORT_PLS_MASK) == XDEV_U0)
+ if (hcd == xhci->shared_hcd) {
+ can_continue =
+ (portsc & PORT_PLS_MASK) == XDEV_U0;
+ } else {
+ unsigned long flags;
+
+ spin_lock_irqsave(&xhci->lock, flags);
+ can_continue =
+ test_bit(port, &bus_state->resuming_ports);
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ }
+
+ if (can_continue)
clear_bit(port, remote_wakeup_ports);
else
xhci_dbg(xhci, "%s: %s port %d status 0x%x\n",
- __func__, rh, port, portsc);
+ __func__, rh, port, portsc);
}
if (*remote_wakeup_ports)
@@ -2508,7 +2539,7 @@ tegra_xhci_host_partition_elpg_exit(struct tegra_xhci_hcd *tegra)
goto out;
}
- update_remote_wakeup_ports_pmc(tegra);
+ update_remote_wakeup_ports(tegra);
if (tegra->hs_wake_event)
tegra->hs_wake_event = false;