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

xHCI: set USB2 hardware LPM

If the device pass the USB2 software LPM and the host supports hardware
LPM, enable hardware LPM for the device to let the host decide when to
put the link into lower power state.

If hardware LPM is enabled for a port and driver wants to put it into
suspend, it must first disable hardware LPM, resume the port into U0,
and then suspend the port.

Signed-off-by: Andiry Xu <andiry.xu@amd.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

authored by

Andiry Xu and committed by
Greg Kroah-Hartman
65580b43 9574323c

+120 -1
+14
drivers/usb/core/driver.c
··· 1700 1700 return 0; 1701 1701 } 1702 1702 1703 + int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) 1704 + { 1705 + struct usb_hcd *hcd = bus_to_hcd(udev->bus); 1706 + int ret = -EPERM; 1707 + 1708 + if (hcd->driver->set_usb2_hw_lpm) { 1709 + ret = hcd->driver->set_usb2_hw_lpm(hcd, udev, enable); 1710 + if (!ret) 1711 + udev->usb2_hw_lpm_enabled = enable; 1712 + } 1713 + 1714 + return ret; 1715 + } 1716 + 1703 1717 #endif /* CONFIG_USB_SUSPEND */ 1704 1718 1705 1719 struct bus_type usb_bus_type = {
+9
drivers/usb/core/hub.c
··· 2392 2392 } 2393 2393 } 2394 2394 2395 + /* disable USB2 hardware LPM */ 2396 + if (udev->usb2_hw_lpm_enabled == 1) 2397 + usb_set_usb2_hardware_lpm(udev, 0); 2398 + 2395 2399 /* see 7.1.7.6 */ 2396 2400 if (hub_is_superspeed(hub->hdev)) 2397 2401 status = set_port_feature(hub->hdev, ··· 2607 2603 if (status < 0) { 2608 2604 dev_dbg(&udev->dev, "can't resume, status %d\n", status); 2609 2605 hub_port_logical_disconnect(hub, port1); 2606 + } else { 2607 + /* Try to enable USB2 hardware LPM */ 2608 + if (udev->usb2_hw_lpm_capable == 1) 2609 + usb_set_usb2_hardware_lpm(udev, 1); 2610 2610 } 2611 + 2611 2612 return status; 2612 2613 } 2613 2614
+5
drivers/usb/core/usb.h
··· 82 82 extern int usb_runtime_suspend(struct device *dev); 83 83 extern int usb_runtime_resume(struct device *dev); 84 84 extern int usb_runtime_idle(struct device *dev); 85 + extern int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable); 85 86 86 87 #else 87 88 ··· 97 96 return 0; 98 97 } 99 98 99 + static inline int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) 100 + { 101 + return 0; 102 + } 100 103 #endif 101 104 102 105 extern struct bus_type usb_bus_type;
+9
drivers/usb/host/xhci-hub.c
··· 574 574 switch (wValue) { 575 575 case USB_PORT_FEAT_SUSPEND: 576 576 temp = xhci_readl(xhci, port_array[wIndex]); 577 + if ((temp & PORT_PLS_MASK) != XDEV_U0) { 578 + /* Resume the port to U0 first */ 579 + xhci_set_link_state(xhci, port_array, wIndex, 580 + XDEV_U0); 581 + spin_unlock_irqrestore(&xhci->lock, flags); 582 + msleep(10); 583 + spin_lock_irqsave(&xhci->lock, flags); 584 + } 577 585 /* In spec software should not attempt to suspend 578 586 * a port unless the port reports that it is in the 579 587 * enabled (PED = ‘1’,PLS < ‘3’) state. 580 588 */ 589 + temp = xhci_readl(xhci, port_array[wIndex]); 581 590 if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) 582 591 || (temp & PORT_PLS_MASK) >= XDEV_U3) { 583 592 xhci_warn(xhci, "USB core suspending device "
+1
drivers/usb/host/xhci-pci.c
··· 349 349 * call back when device connected and addressed 350 350 */ 351 351 .update_device = xhci_update_device, 352 + .set_usb2_hw_lpm = xhci_set_usb2_hardware_lpm, 352 353 }; 353 354 354 355 /*-------------------------------------------------------------------------*/
+73 -1
drivers/usb/host/xhci.c
··· 3286 3286 del_timer_sync(&virt_dev->eps[i].stop_cmd_timer); 3287 3287 } 3288 3288 3289 + if (udev->usb2_hw_lpm_enabled) { 3290 + xhci_set_usb2_hardware_lpm(hcd, udev, 0); 3291 + udev->usb2_hw_lpm_enabled = 0; 3292 + } 3293 + 3289 3294 spin_lock_irqsave(&xhci->lock, flags); 3290 3295 /* Don't disable the slot if the host controller is dead. */ 3291 3296 state = xhci_readl(xhci, &xhci->op_regs->status); ··· 3704 3699 return ret; 3705 3700 } 3706 3701 3702 + int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, 3703 + struct usb_device *udev, int enable) 3704 + { 3705 + struct xhci_hcd *xhci = hcd_to_xhci(hcd); 3706 + __le32 __iomem **port_array; 3707 + __le32 __iomem *pm_addr; 3708 + u32 temp; 3709 + unsigned int port_num; 3710 + unsigned long flags; 3711 + int u2del, hird; 3712 + 3713 + if (hcd->speed == HCD_USB3 || !xhci->hw_lpm_support || 3714 + !udev->lpm_capable) 3715 + return -EPERM; 3716 + 3717 + if (!udev->parent || udev->parent->parent || 3718 + udev->descriptor.bDeviceClass == USB_CLASS_HUB) 3719 + return -EPERM; 3720 + 3721 + if (udev->usb2_hw_lpm_capable != 1) 3722 + return -EPERM; 3723 + 3724 + spin_lock_irqsave(&xhci->lock, flags); 3725 + 3726 + port_array = xhci->usb2_ports; 3727 + port_num = udev->portnum - 1; 3728 + pm_addr = port_array[port_num] + 1; 3729 + temp = xhci_readl(xhci, pm_addr); 3730 + 3731 + xhci_dbg(xhci, "%s port %d USB2 hardware LPM\n", 3732 + enable ? "enable" : "disable", port_num); 3733 + 3734 + u2del = HCS_U2_LATENCY(xhci->hcs_params3); 3735 + if (le32_to_cpu(udev->bos->ext_cap->bmAttributes) & (1 << 2)) 3736 + hird = xhci_calculate_hird_besl(u2del, 1); 3737 + else 3738 + hird = xhci_calculate_hird_besl(u2del, 0); 3739 + 3740 + if (enable) { 3741 + temp &= ~PORT_HIRD_MASK; 3742 + temp |= PORT_HIRD(hird) | PORT_RWE; 3743 + xhci_writel(xhci, temp, pm_addr); 3744 + temp = xhci_readl(xhci, pm_addr); 3745 + temp |= PORT_HLE; 3746 + xhci_writel(xhci, temp, pm_addr); 3747 + } else { 3748 + temp &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK); 3749 + xhci_writel(xhci, temp, pm_addr); 3750 + } 3751 + 3752 + spin_unlock_irqrestore(&xhci->lock, flags); 3753 + return 0; 3754 + } 3755 + 3707 3756 int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) 3708 3757 { 3709 3758 struct xhci_hcd *xhci = hcd_to_xhci(hcd); 3710 3759 int ret; 3711 3760 3712 3761 ret = xhci_usb2_software_lpm_test(hcd, udev); 3713 - if (!ret) 3762 + if (!ret) { 3714 3763 xhci_dbg(xhci, "software LPM test succeed\n"); 3764 + if (xhci->hw_lpm_support == 1) { 3765 + udev->usb2_hw_lpm_capable = 1; 3766 + ret = xhci_set_usb2_hardware_lpm(hcd, udev, 1); 3767 + if (!ret) 3768 + udev->usb2_hw_lpm_enabled = 1; 3769 + } 3770 + } 3715 3771 3716 3772 return 0; 3717 3773 } 3718 3774 3719 3775 #else 3776 + 3777 + int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, 3778 + struct usb_device *udev, int enable) 3779 + { 3780 + return 0; 3781 + } 3720 3782 3721 3783 int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) 3722 3784 {
+4
drivers/usb/host/xhci.h
··· 367 367 #define PORT_L1S_SUCCESS 1 368 368 #define PORT_RWE (1 << 3) 369 369 #define PORT_HIRD(p) (((p) & 0xf) << 4) 370 + #define PORT_HIRD_MASK (0xf << 4) 370 371 #define PORT_L1DS(p) (((p) & 0xff) << 8) 372 + #define PORT_HLE (1 << 16) 371 373 372 374 /** 373 375 * struct xhci_intr_reg - Interrupt Register Set ··· 1679 1677 gfp_t mem_flags); 1680 1678 int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev); 1681 1679 int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev); 1680 + int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, 1681 + struct usb_device *udev, int enable); 1682 1682 int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, 1683 1683 struct usb_tt *tt, gfp_t mem_flags); 1684 1684 int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags);
+4
include/linux/usb.h
··· 411 411 * @authenticated: Crypto authentication passed 412 412 * @wusb: device is Wireless USB 413 413 * @lpm_capable: device supports LPM 414 + * @usb2_hw_lpm_capable: device can perform USB2 hardware LPM 415 + * @usb2_hw_lpm_enabled: USB2 hardware LPM enabled 414 416 * @string_langid: language ID for strings 415 417 * @product: iProduct string, if present (static) 416 418 * @manufacturer: iManufacturer string, if present (static) ··· 476 474 unsigned authenticated:1; 477 475 unsigned wusb:1; 478 476 unsigned lpm_capable:1; 477 + unsigned usb2_hw_lpm_capable:1; 478 + unsigned usb2_hw_lpm_enabled:1; 479 479 int string_langid; 480 480 481 481 /* static strings from the device */
+1
include/linux/usb/hcd.h
··· 343 343 * address is set 344 344 */ 345 345 int (*update_device)(struct usb_hcd *, struct usb_device *); 346 + int (*set_usb2_hw_lpm)(struct usb_hcd *, struct usb_device *, int); 346 347 }; 347 348 348 349 extern int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb);