[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 cbe5649b517e5e9136b0fece244f4b33bfa9e59d Mon Sep 17 00:00:00 2001
From: Alek Du <alek.du@xxxxxxxxx>
Date: Mon, 10 May 2010 15:40:05 +0800
Subject: [PATCH 2/3] EHCI: EHCI 1.1 addendum: Basic LPM feature support

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 |   90 +++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/ehci-q.c   |   10 +++++
 drivers/usb/host/ehci.h     |    2 +
 5 files changed, 135 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 13ead00..e509288 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -101,6 +101,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)
 
 /*-------------------------------------------------------------------------*/
@@ -305,6 +310,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"
@@ -604,6 +610,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 */
@@ -841,6 +858,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);
 
@@ -856,7 +874,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 be8ef39..2c8e71f 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -787,6 +787,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..11e5f86
--- /dev/null
+++ b/drivers/usb/host/ehci-lpm.c
@@ -0,0 +1,90 @@
+/*
+ *
+ * Author: Jacob Pan <jacob.jun.pan@xxxxxxxxx>
+ *
+ * Copyright 2009- Intel Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * 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 8952177..3d63b0b 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 2f0d007..18fa7f9 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 */
 
@@ -713,6 +714,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