[PATCH 3/4 v5] xHCI: bus power management implementation

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

 



>From 3c3182ea986bf152ff2d70921bbea40d0d66059c Mon Sep 17 00:00:00 2001
From: Andiry Xu <andiry.xu@xxxxxxx>
Date: Mon, 26 Jul 2010 15:05:49 +0800
Subject: [PATCH 3/4] 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@xxxxxxx>
Signed-off-by: Crane Cai <crane.cai@xxxxxxx>
Signed-off-by: Andiry Xu <andiry.xu@xxxxxxx>
---
 drivers/usb/host/xhci-hub.c |  193 +++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/xhci-pci.c |    2 +
 drivers/usb/host/xhci.h     |    9 ++
 3 files changed, 204 insertions(+), 0 deletions(-)

diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 48c8024..7a098fa 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)
 {
@@ -559,3 +563,192 @@ 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);
+			if (!slot_id) {
+				xhci_dbg(xhci, "slot_id is zero\n");
+				return -ENODEV;
+			}
+			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 (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) {
+		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);
+			if (!slot_id) {
+				xhci_dbg(xhci, "slot_id is zero\n");
+				return -ENODEV;
+			}
+			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-pci.c b/drivers/usb/host/xhci-pci.c
index 11482b6..b0e6120 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -153,6 +153,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 7ba5743..79497a2 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
@@ -1171,6 +1173,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)
@@ -1443,6 +1450,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);
-- 
1.7.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