diff options
author | Henry Lin <henryl@nvidia.com> | 2013-07-12 11:36:55 +0800 |
---|---|---|
committer | Harshada Kale <hkale@nvidia.com> | 2013-07-23 08:11:57 -0700 |
commit | 93eb2637dae56488d6e3dc980c04f0020239b3be (patch) | |
tree | 12c60aa6ef04f85f2d529c7603b1162d1ee80fde /drivers/usb | |
parent | 16e29c11104e053f219ebafd6259cebf367f40f7 (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.c | 43 |
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; |