>From d3055401927f70e4de7717138b4c8ecad8b8223a Mon Sep 17 00:00:00 2001 From: Libin Yang <libin.yang@xxxxxxx> Date: Wed, 3 Mar 2010 13:50:41 +0800 Subject: [PATCH 3/5] 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. Signed-off-by: Crane Cai <crane.cai@xxxxxxx> Signed-off-by: Libin Yang <libin.yang@xxxxxxx> --- drivers/usb/host/xhci-hcd.c | 2 +- drivers/usb/host/xhci-hub.c | 148 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-pci.c | 2 + drivers/usb/host/xhci.h | 10 +++ 4 files changed, 161 insertions(+), 1 deletions(-) diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 4cb69e0..fe6a113 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -223,7 +223,7 @@ int xhci_init(struct usb_hcd *hcd) * * xhci->lock must be held by caller. */ -static void xhci_work(struct xhci_hcd *xhci) +void xhci_work(struct xhci_hcd *xhci) { u32 temp; u64 temp_64; diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index ffc2635..db048ad 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) { @@ -508,3 +512,147 @@ 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; + + xhci_dbg(xhci, "suspend root hub\n"); + + port = HCS_MAX_PORTS(xhci->hcs_params1); + spin_lock_irq(&xhci->lock); + xhci->bus_suspended = 0; + while (port--) { + /* suspend the port if the port is not suspended */ + u32 __iomem *addr; + u32 t1, t2; + + addr = &xhci->op_regs->port_status_base + + NUM_PORT_REGS * (port & 0xff); + t1 = xhci_readl(xhci, addr); + if (DEV_SUPERSPEED(t1)) + t1 &= ~(PORT_RWC_BITS | PORT_CEC); + else + t1 &= ~PORT_RWC_BITS; + t2 = t1; + if ((t1 & PORT_PE) && !(t1 & PORT_PLS_MASK)) { + xhci_dbg(xhci, "port %d not suspended\n", port); + t2 = xhci_port_state_to_neutral(t2); + 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; + + 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_irq(&xhci->lock); + return 0; +} + +int xhci_bus_resume(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int port; + u32 temp; + u8 resume_needed; + + if (time_before(jiffies, xhci->next_statechange)) + msleep(5); + + spin_lock_irq(&xhci->lock); + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + spin_unlock_irq(&xhci->lock); + 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; + 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)) { + temp &= ~PORT_PLS_MASK; + resume_needed = 1; + } + 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); + } + } + + if (resume_needed) { + spin_unlock_irq(&xhci->lock); + msleep(20); + spin_lock_irq(&xhci->lock); + } + + (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_irq(&xhci->lock); + return 0; +} + +#else + +#define xhci_bus_suspend NULL +#define xhci_bus_resume NULL + +#endif diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 21030a9..b071d9b 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -147,6 +147,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 4c65d1e..fd092ca 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 @@ -1096,6 +1098,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) @@ -1263,6 +1270,7 @@ void xhci_quiesce(struct xhci_hcd *xhci); int xhci_halt(struct xhci_hcd *xhci); int xhci_reset(struct xhci_hcd *xhci); int xhci_init(struct usb_hcd *hcd); +void xhci_work(struct xhci_hcd *xhci); int xhci_run(struct usb_hcd *hcd); void xhci_stop(struct usb_hcd *hcd); void xhci_shutdown(struct usb_hcd *hcd); @@ -1329,6 +1337,8 @@ void 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); u32 xhci_stop_endpoints(struct xhci_hcd *xhci, u16 port, int stop, unsigned long *flags); -- 1.6.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html