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