| title: | PATCH 2 3 EHCI EHCI 1 1 addendum Basic LP |
|
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
");
+
#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
");
+ 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
", dev_addr, port_num);
+ if (port_num HCS_N_PORTS(ehci- hcs_params)) {
+ ehci_dbg(ehci, "invalid port number %d
", 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
");
+ 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
", 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
");
+ val32 |= PORT_LPM;
+ /*
+ * now device should be in L1 sleep, lets 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
",
+ 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 rel="nofollow" vger.kernel.org/majordomo-info.html vger.kernel.org/majordomo-info.html
|