Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

USB: 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@intel.com>
Signed-off-by: Alek Du <alek.du@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

authored by

Alek Du and committed by
Greg Kroah-Hartman
48f24970 aa4d8342

+134 -2
+3 -1
drivers/usb/core/hub.c
··· 2880 2880 } 2881 2881 2882 2882 retval = 0; 2883 - 2883 + /* notify HCD that we have a device connected and addressed */ 2884 + if (hcd->driver->update_device) 2885 + hcd->driver->update_device(hcd, udev); 2884 2886 fail: 2885 2887 if (retval) { 2886 2888 hub_port_disable(hub, port1, 0);
+17
drivers/usb/host/ehci-hcd.c
··· 101 101 module_param (ignore_oc, bool, S_IRUGO); 102 102 MODULE_PARM_DESC (ignore_oc, "ignore bogus hardware overcurrent indications"); 103 103 104 + /* for link power management(LPM) feature */ 105 + static unsigned int hird; 106 + module_param(hird, int, S_IRUGO); 107 + MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us\n"); 108 + 104 109 #define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) 105 110 106 111 /*-------------------------------------------------------------------------*/ ··· 310 305 static void ehci_work(struct ehci_hcd *ehci); 311 306 312 307 #include "ehci-hub.c" 308 + #include "ehci-lpm.c" 313 309 #include "ehci-mem.c" 314 310 #include "ehci-q.c" 315 311 #include "ehci-sched.c" ··· 609 603 case 2: ehci->periodic_size = 256; break; 610 604 default: BUG(); 611 605 } 606 + } 607 + if (HCC_LPM(hcc_params)) { 608 + /* support link power management EHCI 1.1 addendum */ 609 + ehci_dbg(ehci, "support lpm\n"); 610 + ehci->has_lpm = 1; 611 + if (hird > 0xf) { 612 + ehci_dbg(ehci, "hird %d invalid, use default 0", 613 + hird); 614 + hird = 0; 615 + } 616 + temp |= hird << 24; 612 617 } 613 618 ehci->command = temp; 614 619
+5
drivers/usb/host/ehci-hub.c
··· 790 790 status_reg); 791 791 break; 792 792 case USB_PORT_FEAT_C_CONNECTION: 793 + if (ehci->has_lpm) { 794 + /* clear PORTSC bits on disconnect */ 795 + temp &= ~PORT_LPM; 796 + temp &= ~PORT_DEV_ADDR; 797 + } 793 798 ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_CSC, 794 799 status_reg); 795 800 break;
+83
drivers/usb/host/ehci-lpm.c
··· 1 + /* ehci-lpm.c EHCI HCD LPM support code 2 + * Copyright (c) 2008 - 2010, Intel Corporation. 3 + * Author: Jacob Pan <jacob.jun.pan@intel.com> 4 + * 5 + * This program is free software; you can redistribute it and/or modify 6 + * it under the terms of the GNU General Public License version 2 as 7 + * published by the Free Software Foundation. 8 + * 9 + * This program is distributed in the hope that it will be useful, 10 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 + * GNU General Public License for more details. 13 + * 14 + * You should have received a copy of the GNU General Public License 15 + * along with this program; if not, write to the Free Software 16 + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 + */ 18 + 19 + /* this file is part of ehci-hcd.c */ 20 + static int ehci_lpm_set_da(struct ehci_hcd *ehci, int dev_addr, int port_num) 21 + { 22 + u32 __iomem portsc; 23 + 24 + ehci_dbg(ehci, "set dev address %d for port %d\n", dev_addr, port_num); 25 + if (port_num > HCS_N_PORTS(ehci->hcs_params)) { 26 + ehci_dbg(ehci, "invalid port number %d\n", port_num); 27 + return -ENODEV; 28 + } 29 + portsc = ehci_readl(ehci, &ehci->regs->port_status[port_num-1]); 30 + portsc &= ~PORT_DEV_ADDR; 31 + portsc |= dev_addr<<25; 32 + ehci_writel(ehci, portsc, &ehci->regs->port_status[port_num-1]); 33 + return 0; 34 + } 35 + 36 + /* 37 + * this function is used to check if the device support LPM 38 + * if yes, mark the PORTSC register with PORT_LPM bit 39 + */ 40 + static int ehci_lpm_check(struct ehci_hcd *ehci, int port) 41 + { 42 + u32 __iomem *portsc ; 43 + u32 val32; 44 + int retval; 45 + 46 + portsc = &ehci->regs->port_status[port-1]; 47 + val32 = ehci_readl(ehci, portsc); 48 + if (!(val32 & PORT_DEV_ADDR)) { 49 + ehci_dbg(ehci, "LPM: no device attached\n"); 50 + return -ENODEV; 51 + } 52 + val32 |= PORT_LPM; 53 + ehci_writel(ehci, val32, portsc); 54 + msleep(5); 55 + val32 |= PORT_SUSPEND; 56 + ehci_dbg(ehci, "Sending LPM 0x%08x to port %d\n", val32, port); 57 + ehci_writel(ehci, val32, portsc); 58 + /* wait for ACK */ 59 + msleep(10); 60 + retval = handshake(ehci, &ehci->regs->port_status[port-1], PORT_SSTS, 61 + PORTSC_SUSPEND_STS_ACK, 125); 62 + dbg_port(ehci, "LPM", port, val32); 63 + if (retval != -ETIMEDOUT) { 64 + ehci_dbg(ehci, "LPM: device ACK for LPM\n"); 65 + val32 |= PORT_LPM; 66 + /* 67 + * now device should be in L1 sleep, let's wake up the device 68 + * so that we can complete enumeration. 69 + */ 70 + ehci_writel(ehci, val32, portsc); 71 + msleep(10); 72 + val32 |= PORT_RESUME; 73 + ehci_writel(ehci, val32, portsc); 74 + } else { 75 + ehci_dbg(ehci, "LPM: device does not ACK, disable LPM %d\n", 76 + retval); 77 + val32 &= ~PORT_LPM; 78 + retval = -ETIMEDOUT; 79 + ehci_writel(ehci, val32, portsc); 80 + } 81 + 82 + return retval; 83 + }
+21
drivers/usb/host/ehci-pci.c
··· 361 361 } 362 362 #endif 363 363 364 + static int ehci_update_device(struct usb_hcd *hcd, struct usb_device *udev) 365 + { 366 + struct ehci_hcd *ehci = hcd_to_ehci(hcd); 367 + int rc = 0; 368 + 369 + if (!udev->parent) /* udev is root hub itself, impossible */ 370 + rc = -1; 371 + /* we only support lpm device connected to root hub yet */ 372 + if (ehci->has_lpm && !udev->parent->parent) { 373 + rc = ehci_lpm_set_da(ehci, udev->devnum, udev->portnum); 374 + if (!rc) 375 + rc = ehci_lpm_check(ehci, udev->portnum); 376 + } 377 + return rc; 378 + } 379 + 364 380 static const struct hc_driver ehci_pci_hc_driver = { 365 381 .description = hcd_name, 366 382 .product_desc = "EHCI Host Controller", ··· 422 406 .bus_resume = ehci_bus_resume, 423 407 .relinquish_port = ehci_relinquish_port, 424 408 .port_handed_over = ehci_port_handed_over, 409 + 410 + /* 411 + * call back when device connected and addressed 412 + */ 413 + .update_device = ehci_update_device, 425 414 426 415 .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, 427 416 };
+1 -1
drivers/usb/host/ehci.h
··· 140 140 #define OHCI_HCCTRL_LEN 0x4 141 141 __hc32 *ohci_hcctrl_reg; 142 142 unsigned has_hostpc:1; 143 - 143 + unsigned has_lpm:1; /* support link power management */ 144 144 u8 sbrn; /* packed release number */ 145 145 146 146 /* irq statistics */
+4
include/linux/usb/hcd.h
··· 300 300 int (*update_hub_device)(struct usb_hcd *, struct usb_device *hdev, 301 301 struct usb_tt *tt, gfp_t mem_flags); 302 302 int (*reset_device)(struct usb_hcd *, struct usb_device *); 303 + /* Notifies the HCD after a device is connected and its 304 + * address is set 305 + */ 306 + int (*update_device)(struct usb_hcd *, struct usb_device *); 303 307 }; 304 308 305 309 extern int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb);