diff options
author | Andiry Xu <andiry.xu@amd.com> | 2010-10-14 16:23:03 +0200 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-10-22 19:22:13 +0200 |
commit | 9777e3ce907d4cb5a513902a87ecd03b52499569 (patch) | |
tree | a2b28eeeaddd39d7c8cdd59f6ddbfa694d40d5ba /drivers/usb/host/xhci-hub.c | |
parent | USB: xHCI: port remote wakeup implementation (diff) | |
download | linux-9777e3ce907d4cb5a513902a87ecd03b52499569.tar.xz linux-9777e3ce907d4cb5a513902a87ecd03b52499569.zip |
USB: xHCI: bus power management implementation
This patch implements xHCI bus suspend/resume function hook.
In the patch it goes through all the ports and suspend/resume
the ports if needed.
If any port is in remote wakeup, abort bus suspend as what ehci/ohci do.
Signed-off-by: Libin Yang <libin.yang@amd.com>
Signed-off-by: Crane Cai <crane.cai@amd.com>
Signed-off-by: Andiry Xu <andiry.xu@amd.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/host/xhci-hub.c')
-rw-r--r-- | drivers/usb/host/xhci-hub.c | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 8163f17e7043..7f2f63cb6c53 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -24,6 +24,10 @@ #include "xhci.h" +#define PORT_WAKE_BITS (PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E) +#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \ + PORT_RC | PORT_PLC | PORT_PE) + static void xhci_hub_descriptor(struct xhci_hcd *xhci, struct usb_hub_descriptor *desc) { @@ -560,3 +564,187 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) spin_unlock_irqrestore(&xhci->lock, flags); return status ? retval : 0; } + +#ifdef CONFIG_PM + +int xhci_bus_suspend(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int port; + unsigned long flags; + + xhci_dbg(xhci, "suspend root hub\n"); + + spin_lock_irqsave(&xhci->lock, flags); + + if (hcd->self.root_hub->do_remote_wakeup) { + port = HCS_MAX_PORTS(xhci->hcs_params1); + while (port--) { + if (xhci->resume_done[port] != 0) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "suspend failed because " + "port %d is resuming\n", + port + 1); + return -EBUSY; + } + } + } + + port = HCS_MAX_PORTS(xhci->hcs_params1); + xhci->bus_suspended = 0; + while (port--) { + /* suspend the port if the port is not suspended */ + u32 __iomem *addr; + u32 t1, t2; + int slot_id; + + addr = &xhci->op_regs->port_status_base + + NUM_PORT_REGS * (port & 0xff); + t1 = xhci_readl(xhci, addr); + t2 = xhci_port_state_to_neutral(t1); + + if ((t1 & PORT_PE) && !(t1 & PORT_PLS_MASK)) { + xhci_dbg(xhci, "port %d not suspended\n", port); + slot_id = xhci_find_slot_id_by_port(xhci, port + 1); + if (slot_id) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_stop_device(xhci, slot_id, 1); + spin_lock_irqsave(&xhci->lock, flags); + } + t2 &= ~PORT_PLS_MASK; + t2 |= PORT_LINK_STROBE | XDEV_U3; + set_bit(port, &xhci->bus_suspended); + } + if (hcd->self.root_hub->do_remote_wakeup) { + if (t1 & PORT_CONNECT) { + t2 |= PORT_WKOC_E | PORT_WKDISC_E; + t2 &= ~PORT_WKCONN_E; + } else { + t2 |= PORT_WKOC_E | PORT_WKCONN_E; + t2 &= ~PORT_WKDISC_E; + } + } else + t2 &= ~PORT_WAKE_BITS; + + t1 = xhci_port_state_to_neutral(t1); + if (t1 != t2) + xhci_writel(xhci, t2, addr); + + if (DEV_HIGHSPEED(t1)) { + /* enable remote wake up for USB 2.0 */ + u32 __iomem *addr; + u32 tmp; + + addr = &xhci->op_regs->port_power_base + + NUM_PORT_REGS * (port & 0xff); + tmp = xhci_readl(xhci, addr); + tmp |= PORT_RWE; + xhci_writel(xhci, tmp, addr); + } + } + hcd->state = HC_STATE_SUSPENDED; + xhci->next_statechange = jiffies + msecs_to_jiffies(10); + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; +} + +int xhci_bus_resume(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int port; + u32 temp; + unsigned long flags; + + xhci_dbg(xhci, "resume root hub\n"); + + if (time_before(jiffies, xhci->next_statechange)) + msleep(5); + + spin_lock_irqsave(&xhci->lock, flags); + if (!HCD_HW_ACCESSIBLE(hcd)) { + spin_unlock_irqrestore(&xhci->lock, flags); + return -ESHUTDOWN; + } + + /* delay the irqs */ + temp = xhci_readl(xhci, &xhci->op_regs->command); + temp &= ~CMD_EIE; + xhci_writel(xhci, temp, &xhci->op_regs->command); + + port = HCS_MAX_PORTS(xhci->hcs_params1); + while (port--) { + /* Check whether need resume ports. If needed + resume port and disable remote wakeup */ + u32 __iomem *addr; + u32 temp; + int slot_id; + + addr = &xhci->op_regs->port_status_base + + NUM_PORT_REGS * (port & 0xff); + temp = xhci_readl(xhci, addr); + if (DEV_SUPERSPEED(temp)) + temp &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS); + else + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); + if (test_bit(port, &xhci->bus_suspended) && + (temp & PORT_PLS_MASK)) { + if (DEV_SUPERSPEED(temp)) { + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | XDEV_U0; + xhci_writel(xhci, temp, addr); + } else { + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | XDEV_RESUME; + xhci_writel(xhci, temp, addr); + + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(20); + spin_lock_irqsave(&xhci->lock, flags); + + temp = xhci_readl(xhci, addr); + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | XDEV_U0; + xhci_writel(xhci, temp, addr); + } + slot_id = xhci_find_slot_id_by_port(xhci, port + 1); + if (slot_id) + xhci_ring_device(xhci, slot_id); + } else + xhci_writel(xhci, temp, addr); + + if (DEV_HIGHSPEED(temp)) { + /* disable remote wake up for USB 2.0 */ + u32 __iomem *addr; + u32 tmp; + + addr = &xhci->op_regs->port_power_base + + NUM_PORT_REGS * (port & 0xff); + tmp = xhci_readl(xhci, addr); + tmp &= ~PORT_RWE; + xhci_writel(xhci, tmp, addr); + } + } + + (void) xhci_readl(xhci, &xhci->op_regs->command); + + xhci->next_statechange = jiffies + msecs_to_jiffies(5); + hcd->state = HC_STATE_RUNNING; + /* re-enable irqs */ + temp = xhci_readl(xhci, &xhci->op_regs->command); + temp |= CMD_EIE; + xhci_writel(xhci, temp, &xhci->op_regs->command); + temp = xhci_readl(xhci, &xhci->op_regs->command); + + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; +} + +#else + +#define xhci_bus_suspend NULL +#define xhci_bus_resume NULL + +#endif |