[PATCH RFC 1/5] xhci: port power management implementation

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

 



>From 858d0e67059852385be474a32b08c72cf4adbe75 Mon Sep 17 00:00:00 2001
From: Crane Cai <crane.cai@xxxxxxx>
Date: Tue, 2 Mar 2010 14:16:07 +0800
Subject: [PATCH 1/5] xhci: port power management implementation

Add software trigger USB device suspend resume function hook.
Do port suspend & resume in terms of xHCI spec 4.15.
System trigger suspend/resume via:
    echo suspend > /sys/bus/usb/devices/../power/level
    echo on > /sys/bus/usb/devices/../power/level
Tested with USB storage device & mouse.

Ref: USB device remote wake need another patch to implement. For details of
how USB subsystem do power management, please see:
    Documentation/usb/power-management.txt

Signed-off-by: Crane Cai <crane.cai@xxxxxxx>
Signed-off-by: Libin Yang <libin.yang@xxxxxxx>
---
 drivers/usb/host/xhci-hub.c  |  144 +++++++++++++++++++++++++++++++++++++++++-
 drivers/usb/host/xhci-ring.c |    2 +-
 drivers/usb/host/xhci.h      |    9 +++
 3 files changed, 153 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 208b805..0f5f0db 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -128,6 +128,76 @@ static u32 xhci_port_state_to_neutral(u32 state)
 	/* Save read-only status and port state */
 	return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS);
 }
+/*
+ * stop endpoints before the port suspend, and ring the bell before resume.
+ * caller need xhci lock and may release it to finish commands.
+ */
+static u32 xhci_stop_endpoints(struct xhci_hcd *xhci, u16 port, int stop,
+	unsigned long *flags)
+{
+	int i, slot_id;
+	struct xhci_container_ctx *out_ctx;
+	struct xhci_slot_ctx *slot_ctx;
+	struct xhci_ep_ctx *ep_ctx;
+	struct xhci_virt_ep *ep;
+
+	slot_id = 0;
+	if (!xhci->devs)
+		return 0;
+	for (i = 0; i < MAX_HC_SLOTS; i++) {
+		if (!xhci->devs[i])
+			continue;
+		out_ctx = xhci->devs[i]->out_ctx;
+		slot_ctx = xhci_get_slot_ctx(xhci, out_ctx);
+		if (GET_SLOT_STATE(slot_ctx->dev_state) == 3 &&
+			ROOT_HUB_PORT(slot_ctx->dev_info2) == port) {
+			slot_id = i;
+			break;
+		}
+	}
+
+	if (!slot_id)
+		return 0;
+
+	if (stop)
+		goto stop;
+	else
+		goto run;
+stop:
+	for (i = 0; i < 31; i++) {
+		ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, i);
+		if ((ep_ctx->ep_info & EP_STATE_MASK) != EP_STATE_RUNNING)
+			continue;
+		ep = &xhci->devs[slot_id]->eps[i];
+		if (!(ep->ep_state & EP_HALT_PENDING)) {
+			ep->ep_state |= EP_HALT_PENDING;
+			ep->stop_cmds_pending++;
+			ep->stop_cmd_timer.expires = jiffies +
+				XHCI_STOP_EP_CMD_TIMEOUT * HZ;
+			add_timer(&ep->stop_cmd_timer);
+			xhci_queue_stop_endpoint(xhci, slot_id, i);
+			xhci_ring_cmd_db(xhci);
+		}
+	}
+	spin_unlock_irqrestore(&xhci->lock, *flags);
+	for (i = 0; i < 31; i++) {
+		ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, i);
+		if ((ep_ctx->ep_info & EP_STATE_MASK) == EP_STATE_RUNNING) {
+			i = 0;
+			msleep(1);
+		}
+	}
+	spin_lock_irqsave(&xhci->lock, *flags);
+	return 0;
+
+run:
+	for (i = 0; i < 31; i++) {
+		ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, i);
+		if ((ep_ctx->ep_info & EP_STATE_MASK) == EP_STATE_STOPPED)
+			ring_ep_doorbell(xhci, slot_id, i);
+	}
+	return 0;
+}
 
 static void xhci_disable_port(struct xhci_hcd *xhci, u16 wIndex,
 		u32 __iomem *addr, u32 port_status)
@@ -162,6 +232,10 @@ static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue,
 		status = PORT_PEC;
 		port_change_bit = "enable/disable";
 		break;
+	case USB_PORT_FEAT_C_SUSPEND:
+		status = PORT_PLC;
+		port_change_bit = "suspend/resume";
+		break;
 	default:
 		/* Should never happen */
 		return;
@@ -211,9 +285,17 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
 		if ((temp & PORT_OCC))
 			status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
 		/*
-		 * FIXME ignoring suspend, reset, and USB 2.1/3.0 specific
+		 * FIXME ignoring reset and USB 2.1/3.0 specific
 		 * changes
 		 */
+		if ((temp & PORT_PLS_MASK) == XDEV_U3 ||
+			(temp & PORT_PLS_MASK) == XDEV_RESUME)
+			status |= 1 << USB_PORT_FEAT_SUSPEND;
+		if ((temp & PORT_PLS_MASK) == XDEV_U0
+			&& test_bit(wIndex, &xhci->suspended_ports)) {
+			clear_bit(wIndex, &xhci->suspended_ports);
+			set_bit(wIndex, &xhci->port_c_suspend);
+		}
 		if (temp & PORT_CONNECT) {
 			status |= 1 << USB_PORT_FEAT_CONNECTION;
 			status |= xhci_port_speed(temp);
@@ -226,6 +308,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
 			status |= 1 << USB_PORT_FEAT_RESET;
 		if (temp & PORT_POWER)
 			status |= 1 << USB_PORT_FEAT_POWER;
+		if (test_bit(wIndex, &xhci->port_c_suspend))
+			status |= 1 << USB_PORT_FEAT_C_SUSPEND;
 		xhci_dbg(xhci, "Get port status returned 0x%x\n", status);
 		put_unaligned(cpu_to_le32(status), (__le32 *) buf);
 		break;
@@ -238,6 +322,27 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
 		temp = xhci_readl(xhci, addr);
 		temp = xhci_port_state_to_neutral(temp);
 		switch (wValue) {
+		case USB_PORT_FEAT_SUSPEND:
+			temp = xhci_readl(xhci, addr);
+			if ((temp & PORT_PE) == 0 || (temp & PORT_RESET)
+				|| (temp & PORT_PLS_MASK) >= XDEV_U3) {
+				xhci_dbg(xhci, "goto error status = 0x%x\n",
+					 temp);
+				goto error;
+			}
+			xhci_stop_endpoints(xhci, wIndex + 1, 1, &flags);
+			temp = xhci_port_state_to_neutral(temp);
+			temp &= ~PORT_PLS_MASK;
+			temp |= PORT_LINK_STROBE | XDEV_U3;
+			xhci_writel(xhci, temp, addr);
+
+			spin_unlock_irqrestore(&xhci->lock, flags);
+			msleep(10); /* wait device to enter */
+			spin_lock_irqsave(&xhci->lock, flags);
+
+			temp = xhci_readl(xhci, addr);
+			set_bit(wIndex, &xhci->suspended_ports);
+			break;
 		case USB_PORT_FEAT_POWER:
 			/*
 			 * Turn on ports, even if there isn't per-port switching.
@@ -271,6 +376,43 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
 		temp = xhci_readl(xhci, addr);
 		temp = xhci_port_state_to_neutral(temp);
 		switch (wValue) {
+		case USB_PORT_FEAT_SUSPEND:
+			temp = xhci_readl(xhci, addr);
+			xhci_dbg(xhci, "clear USB_PORT_FEAT_SUSPEND\n");
+			xhci_dbg(xhci, "PORTSC %04x\n", temp);
+			if (temp & PORT_RESET)
+				goto error;
+			if (temp & XDEV_U3) {
+				if ((temp & PORT_PE) == 0)
+					goto error;
+				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);
+				}
+				set_bit(wIndex, &xhci->port_c_suspend);
+			}
+			xhci_stop_endpoints(xhci, wIndex + 1, 0, &flags);
+			break;
+		case USB_PORT_FEAT_C_SUSPEND:
+			clear_bit(wIndex, &xhci->port_c_suspend);
 		case USB_PORT_FEAT_C_RESET:
 		case USB_PORT_FEAT_C_CONNECTION:
 		case USB_PORT_FEAT_C_OVER_CURRENT:
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 6ba841b..71d0d5d 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -292,7 +292,7 @@ void xhci_ring_cmd_db(struct xhci_hcd *xhci)
 	xhci_readl(xhci, &xhci->dba->doorbell[0]);
 }
 
-static void ring_ep_doorbell(struct xhci_hcd *xhci,
+void ring_ep_doorbell(struct xhci_hcd *xhci,
 		unsigned int slot_id,
 		unsigned int ep_index)
 {
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index e5eb09b..1a16c5d 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -269,6 +269,10 @@ struct xhci_op_regs {
  * A read gives the current link PM state of the port,
  * a write with Link State Write Strobe set sets the link state.
  */
+#define PORT_PLS_MASK	(0xf << 5)
+#define XDEV_U0		(0x0 << 5)
+#define XDEV_U3		(0x3 << 5)
+#define XDEV_RESUME	(0xf << 5)
 /* true: port has power (see HCC_PPC) */
 #define PORT_POWER	(1 << 9)
 /* bits 10:13 indicate device speed:
@@ -1112,6 +1116,9 @@ struct xhci_hcd {
 	unsigned int		quirks;
 #define	XHCI_LINK_TRB_QUIRK	(1 << 0)
 #define XHCI_RESET_EP_QUIRK	(1 << 1)
+	unsigned long		port_c_suspend;		/* port suspend change*/
+	unsigned long		suspended_ports;	/* which ports are
+							   suspended */
 };
 
 /* For testing purposes */
@@ -1314,6 +1321,8 @@ void xhci_queue_config_ep_quirk(struct xhci_hcd *xhci,
 		unsigned int slot_id, unsigned int ep_index,
 		struct xhci_dequeue_state *deq_state);
 void xhci_stop_endpoint_command_watchdog(unsigned long arg);
+void ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id,
+		unsigned int ep_index);
 
 /* xHCI roothub code */
 int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex,
-- 
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