[PATCH 2/3] EHCI: EHCI 1.1 addendum: Basic LPM feature support

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

 



From: Alek Du <alek.du@xxxxxxxxx>

With this patch, the LPM capable EHCI host controller can put device
into L1 sleep state which is a mode that can enter/exit quickly, and
reduce power consumption.

Signed-off-by: Jacob Pan <jacob.jun.pan@xxxxxxxxx>
Signed-off-by: Alek Du <alek.du@xxxxxxxxx>
---
 drivers/usb/host/ehci-hcd.c |   29 ++++++++++++++-
 drivers/usb/host/ehci-hub.c |    5 ++
 drivers/usb/host/ehci-lpm.c |   87 +++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/ehci-q.c   |   10 +++++
 drivers/usb/host/ehci.h     |    2 +
 5 files changed, 132 insertions(+), 1 deletions(-)
 create mode 100644 drivers/usb/host/ehci-lpm.c

diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index ef3e88f..e6f4344 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -100,6 +100,11 @@ static int ignore_oc = 0;
 module_param (ignore_oc, bool, S_IRUGO);
 MODULE_PARM_DESC (ignore_oc, "ignore bogus hardware overcurrent indications");
 
+/* for link power management(LPM) feature */
+static unsigned int hird;
+module_param(hird, int, S_IRUGO);
+MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us\n");
+
 #define	INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT)
 
 /*-------------------------------------------------------------------------*/
@@ -304,6 +309,7 @@ static void end_unlink_async(struct ehci_hcd *ehci);
 static void ehci_work(struct ehci_hcd *ehci);
 
 #include "ehci-hub.c"
+#include "ehci-lpm.c"
 #include "ehci-mem.c"
 #include "ehci-q.c"
 #include "ehci-sched.c"
@@ -603,6 +609,17 @@ static int ehci_init(struct usb_hcd *hcd)
 		default:	BUG();
 		}
 	}
+	if (HCC_LPM(hcc_params)) {
+		/* support link power management EHCI 1.1 addendum */
+		ehci_dbg(ehci, "support lpm\n");
+		ehci->has_lpm = 1;
+		if (hird > 0xf) {
+			ehci_dbg(ehci, "hird %d invalid, use default 0",
+			hird);
+			hird = 0;
+		}
+		temp |= hird << 24;
+	}
 	ehci->command = temp;
 
 	/* Accept arbitrarily long scatter-gather lists */
@@ -840,6 +857,7 @@ static int ehci_urb_enqueue (
 ) {
 	struct ehci_hcd		*ehci = hcd_to_ehci (hcd);
 	struct list_head	qtd_list;
+	int status;
 
 	INIT_LIST_HEAD (&qtd_list);
 
@@ -855,7 +873,16 @@ static int ehci_urb_enqueue (
 	default:
 		if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags))
 			return -ENOMEM;
-		return submit_async(ehci, urb, &qtd_list, mem_flags);
+		status = submit_async(ehci, urb, &qtd_list, mem_flags);
+
+		/* check device LPM cap after set address */
+		if (usb_pipecontrol(urb->pipe)) {
+			if (((struct usb_ctrlrequest *)urb->setup_packet)
+				->bRequest == USB_REQ_SET_ADDRESS &&
+				ehci->has_lpm)
+				ehci_lpm_check(ehci, urb->dev->portnum);
+		}
+		return status;
 
 	case PIPE_INTERRUPT:
 		if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags))
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index e7d3d8d..8a28dae 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -790,6 +790,11 @@ static int ehci_hub_control (
 					  status_reg);
 			break;
 		case USB_PORT_FEAT_C_CONNECTION:
+			if (ehci->has_lpm) {
+				/* clear PORTSC bits on disconnect */
+				temp &= ~PORT_LPM;
+				temp &= ~PORT_DEV_ADDR;
+			}
 			ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_CSC,
 					status_reg);
 			break;
diff --git a/drivers/usb/host/ehci-lpm.c b/drivers/usb/host/ehci-lpm.c
new file mode 100644
index 0000000..93a53a3
--- /dev/null
+++ b/drivers/usb/host/ehci-lpm.c
@@ -0,0 +1,87 @@
+/* ehci-lpm.c EHCI HCD LPM support code
+ * Copyright (c) 2008 - 2010,  Intel Corporation.
+ * Author: Jacob Pan <jacob.jun.pan@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/* this file is part of ehci-hcd.c */
+static int ehci_lpm_set_da(struct ehci_hcd *ehci, int dev_addr, int port_num)
+{
+	u32 __iomem portsc;
+
+	ehci_dbg(ehci, "set dev address %d for port %d\n", dev_addr, port_num);
+	if (port_num > HCS_N_PORTS(ehci->hcs_params)) {
+		ehci_dbg(ehci, "invalid port number %d\n", port_num);
+		return -ENODEV;
+	}
+	portsc = ehci_readl(ehci, &ehci->regs->port_status[port_num-1]);
+	portsc &= ~PORT_DEV_ADDR;
+	portsc |= dev_addr<<25;
+	ehci_writel(ehci, portsc, &ehci->regs->port_status[port_num-1]);
+	return 0;
+}
+
+/*
+ * this function is called to put a link into L1 state. the steps are:
+ * - verify HC supports LPM
+ * - make sure all pipe idle on the link
+ * - shutdown all qh on the pipe
+ * - send LPM packet
+ * - confirm device ack
+ */
+static unsigned ehci_lpm_check(struct ehci_hcd *ehci, int port)
+{
+	u32 __iomem	*portsc ;
+	u32 val32;
+	int retval;
+
+	portsc = &ehci->regs->port_status[port-1];
+	val32 = ehci_readl(ehci, portsc);
+	if (!(val32 & PORT_DEV_ADDR)) {
+		ehci_dbg(ehci, "LPM: no device attached\n");
+		return -ENODEV;
+	}
+	val32 |= PORT_LPM;
+	ehci_writel(ehci, val32, portsc);
+	msleep(5);
+	val32 |= PORT_SUSPEND;
+	ehci_dbg(ehci, "Sending LPM 0x%08x to port %d\n", val32, port);
+	ehci_writel(ehci, val32, portsc);
+	/* wait for ACK */
+	msleep(10);
+	retval = handshake(ehci, &ehci->regs->port_status[port-1], PORT_SSTS,
+			PORTSC_SUSPEND_STS_ACK, 125);
+	dbg_port(ehci, "LPM", port, val32);
+	if (retval != -ETIMEDOUT) {
+		ehci_dbg(ehci, "LPM: device ACK for LPM\n");
+		val32 |= PORT_LPM;
+		/*
+		 * now device should be in L1 sleep, let's wake up the device
+		 * so that we can complete enumeration.
+		 */
+		ehci_writel(ehci, val32, portsc);
+		msleep(10);
+		val32 |= PORT_RESUME;
+		ehci_writel(ehci, val32, portsc);
+	} else {
+		ehci_dbg(ehci, "LPM: device does not ACK, disable LPM %d\n",
+			retval);
+		val32 &= ~PORT_LPM;
+		retval = -ETIMEDOUT;
+		ehci_writel(ehci, val32, portsc);
+	}
+
+	return retval;
+}
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index 11a79c4..0c39f49 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -643,6 +643,16 @@ qh_urb_transaction (
 				sizeof (struct usb_ctrlrequest),
 				token | (2 /* "setup" */ << 8), 8);
 
+		if (((struct usb_ctrlrequest *)urb->setup_packet)->bRequest
+			== USB_REQ_SET_ADDRESS) {
+			/* for LPM capable HC, set up device address*/
+			int dev_address = ((struct usb_ctrlrequest *)
+						(urb->setup_packet))->wValue;
+			if (ehci->has_lpm)
+				ehci_lpm_set_da(ehci, dev_address,
+						urb->dev->portnum);
+		}
+
 		/* ... and always at least one more pid */
 		token ^= QTD_TOGGLE;
 		qtd_prev = qtd;
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index bfaac16..84dad07 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -140,6 +140,7 @@ struct ehci_hcd {			/* one per controller */
 	#define OHCI_HCCTRL_LEN         0x4
 	__hc32			*ohci_hcctrl_reg;
 	unsigned		has_hostpc:1;
+	unsigned		has_lpm:1;  /* support link power management */
 
 	u8			sbrn;		/* packed release number */
 
@@ -723,6 +724,7 @@ static inline u32 hc32_to_cpup (const struct ehci_hcd *ehci, const __hc32 *x)
 
 #endif
 
+static unsigned ehci_lpm_check(struct ehci_hcd *ehci, int port);
 /*-------------------------------------------------------------------------*/
 
 #ifndef DEBUG
-- 
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