[PATCH RFC 3/5] xHCI: bus power management implementation

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



>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

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux