On Wed, Mar 03, 2010 at 06:07:55PM +0800, Libin wrote: > >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. I thought the USB core ensured that all devices were suspended before suspending the root hub. Alan, can you confirm? Sarah Sharp > 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