From 9777e3ce907d4cb5a513902a87ecd03b52499569 Mon Sep 17 00:00:00 2001 From: Andiry Xu Date: Thu, 14 Oct 2010 07:23:03 -0700 Subject: [PATCH] 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 Signed-off-by: Crane Cai Signed-off-by: Andiry Xu Signed-off-by: Sarah Sharp Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 188 ++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-mem.c | 1 + drivers/usb/host/xhci-pci.c | 2 + drivers/usb/host/xhci.h | 9 ++ 4 files changed, 200 insertions(+) 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 diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index fd888bc0422b..202770676da3 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1445,6 +1445,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) scratchpad_free(xhci); xhci->page_size = 0; xhci->page_shift = 0; + xhci->bus_suspended = 0; } static int xhci_test_trb_in_td(struct xhci_hcd *xhci, diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index aefc3496376a..3865f8c6f647 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -162,6 +162,8 @@ static const struct hc_driver xhci_pci_hc_driver = { /* Root hub support */ .hub_control = xhci_hub_control, .hub_status_data = xhci_hub_status_data, + .bus_suspend = xhci_bus_suspend, + .bus_resume = xhci_bus_resume, }; /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index ca4a923dc810..196e21fb36ff 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -357,6 +357,8 @@ struct xhci_op_regs { #define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) /* Bits 24:31 for port testing */ +/* USB2 Protocol PORTSPMSC */ +#define PORT_RWE (1 << 0x3) /** * struct xhci_intr_reg - Interrupt Register Set @@ -1191,6 +1193,11 @@ struct xhci_hcd { #endif /* Host controller watchdog timer structures */ unsigned int xhc_state; + + unsigned long bus_suspended; + unsigned long next_statechange; + + u32 command; /* Host controller is dying - not responding to commands. "I'm not dead yet!" * * xHC interrupts have been disabled and a watchdog timer will (or has already) @@ -1460,6 +1467,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); +int xhci_bus_suspend(struct usb_hcd *hcd); +int xhci_bus_resume(struct usb_hcd *hcd); u32 xhci_port_state_to_neutral(u32 state); int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port); void xhci_ring_device(struct xhci_hcd *xhci, int slot_id); -- 2.30.2