Skip to content

Commit 65580b4

Browse files
Andiry Xugregkh
authored andcommitted
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 <[email protected]> Signed-off-by: Sarah Sharp <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 9574323 commit 65580b4

File tree

9 files changed

+120
-1
lines changed

9 files changed

+120
-1
lines changed

drivers/usb/core/driver.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,20 @@ int usb_runtime_idle(struct device *dev)
17001700
return 0;
17011701
}
17021702

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+
17031717
#endif /* CONFIG_USB_SUSPEND */
17041718

17051719
struct bus_type usb_bus_type = {

drivers/usb/core/hub.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2392,6 +2392,10 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
23922392
}
23932393
}
23942394

2395+
/* disable USB2 hardware LPM */
2396+
if (udev->usb2_hw_lpm_enabled == 1)
2397+
usb_set_usb2_hardware_lpm(udev, 0);
2398+
23952399
/* see 7.1.7.6 */
23962400
if (hub_is_superspeed(hub->hdev))
23972401
status = set_port_feature(hub->hdev,
@@ -2603,7 +2607,12 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
26032607
if (status < 0) {
26042608
dev_dbg(&udev->dev, "can't resume, status %d\n", status);
26052609
hub_port_logical_disconnect(hub, port1);
2610+
} else {
2611+
/* Try to enable USB2 hardware LPM */
2612+
if (udev->usb2_hw_lpm_capable == 1)
2613+
usb_set_usb2_hardware_lpm(udev, 1);
26062614
}
2615+
26072616
return status;
26082617
}
26092618

drivers/usb/core/usb.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ extern int usb_remote_wakeup(struct usb_device *dev);
8282
extern int usb_runtime_suspend(struct device *dev);
8383
extern int usb_runtime_resume(struct device *dev);
8484
extern int usb_runtime_idle(struct device *dev);
85+
extern int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable);
8586

8687
#else
8788

@@ -96,6 +97,10 @@ static inline int usb_remote_wakeup(struct usb_device *udev)
9697
return 0;
9798
}
9899

100+
static inline int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable)
101+
{
102+
return 0;
103+
}
99104
#endif
100105

101106
extern struct bus_type usb_bus_type;

drivers/usb/host/xhci-hub.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,10 +574,19 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
574574
switch (wValue) {
575575
case USB_PORT_FEAT_SUSPEND:
576576
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+
}
577585
/* In spec software should not attempt to suspend
578586
* a port unless the port reports that it is in the
579587
* enabled (PED = ‘1’,PLS < ‘3’) state.
580588
*/
589+
temp = xhci_readl(xhci, port_array[wIndex]);
581590
if ((temp & PORT_PE) == 0 || (temp & PORT_RESET)
582591
|| (temp & PORT_PLS_MASK) >= XDEV_U3) {
583592
xhci_warn(xhci, "USB core suspending device "

drivers/usb/host/xhci-pci.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ static const struct hc_driver xhci_pci_hc_driver = {
349349
* call back when device connected and addressed
350350
*/
351351
.update_device = xhci_update_device,
352+
.set_usb2_hw_lpm = xhci_set_usb2_hardware_lpm,
352353
};
353354

354355
/*-------------------------------------------------------------------------*/

drivers/usb/host/xhci.c

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3286,6 +3286,11 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev)
32863286
del_timer_sync(&virt_dev->eps[i].stop_cmd_timer);
32873287
}
32883288

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+
32893294
spin_lock_irqsave(&xhci->lock, flags);
32903295
/* Don't disable the slot if the host controller is dead. */
32913296
state = xhci_readl(xhci, &xhci->op_regs->status);
@@ -3699,20 +3704,87 @@ static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd,
36993704
return ret;
37003705
}
37013706

3707+
int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
3708+
struct usb_device *udev, int enable)
3709+
{
3710+
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
3711+
__le32 __iomem **port_array;
3712+
__le32 __iomem *pm_addr;
3713+
u32 temp;
3714+
unsigned int port_num;
3715+
unsigned long flags;
3716+
int u2del, hird;
3717+
3718+
if (hcd->speed == HCD_USB3 || !xhci->hw_lpm_support ||
3719+
!udev->lpm_capable)
3720+
return -EPERM;
3721+
3722+
if (!udev->parent || udev->parent->parent ||
3723+
udev->descriptor.bDeviceClass == USB_CLASS_HUB)
3724+
return -EPERM;
3725+
3726+
if (udev->usb2_hw_lpm_capable != 1)
3727+
return -EPERM;
3728+
3729+
spin_lock_irqsave(&xhci->lock, flags);
3730+
3731+
port_array = xhci->usb2_ports;
3732+
port_num = udev->portnum - 1;
3733+
pm_addr = port_array[port_num] + 1;
3734+
temp = xhci_readl(xhci, pm_addr);
3735+
3736+
xhci_dbg(xhci, "%s port %d USB2 hardware LPM\n",
3737+
enable ? "enable" : "disable", port_num);
3738+
3739+
u2del = HCS_U2_LATENCY(xhci->hcs_params3);
3740+
if (le32_to_cpu(udev->bos->ext_cap->bmAttributes) & (1 << 2))
3741+
hird = xhci_calculate_hird_besl(u2del, 1);
3742+
else
3743+
hird = xhci_calculate_hird_besl(u2del, 0);
3744+
3745+
if (enable) {
3746+
temp &= ~PORT_HIRD_MASK;
3747+
temp |= PORT_HIRD(hird) | PORT_RWE;
3748+
xhci_writel(xhci, temp, pm_addr);
3749+
temp = xhci_readl(xhci, pm_addr);
3750+
temp |= PORT_HLE;
3751+
xhci_writel(xhci, temp, pm_addr);
3752+
} else {
3753+
temp &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK);
3754+
xhci_writel(xhci, temp, pm_addr);
3755+
}
3756+
3757+
spin_unlock_irqrestore(&xhci->lock, flags);
3758+
return 0;
3759+
}
3760+
37023761
int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev)
37033762
{
37043763
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
37053764
int ret;
37063765

37073766
ret = xhci_usb2_software_lpm_test(hcd, udev);
3708-
if (!ret)
3767+
if (!ret) {
37093768
xhci_dbg(xhci, "software LPM test succeed\n");
3769+
if (xhci->hw_lpm_support == 1) {
3770+
udev->usb2_hw_lpm_capable = 1;
3771+
ret = xhci_set_usb2_hardware_lpm(hcd, udev, 1);
3772+
if (!ret)
3773+
udev->usb2_hw_lpm_enabled = 1;
3774+
}
3775+
}
37103776

37113777
return 0;
37123778
}
37133779

37143780
#else
37153781

3782+
int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
3783+
struct usb_device *udev, int enable)
3784+
{
3785+
return 0;
3786+
}
3787+
37163788
int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev)
37173789
{
37183790
return 0;

drivers/usb/host/xhci.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,9 @@ struct xhci_op_regs {
367367
#define PORT_L1S_SUCCESS 1
368368
#define PORT_RWE (1 << 3)
369369
#define PORT_HIRD(p) (((p) & 0xf) << 4)
370+
#define PORT_HIRD_MASK (0xf << 4)
370371
#define PORT_L1DS(p) (((p) & 0xff) << 8)
372+
#define PORT_HLE (1 << 16)
371373

372374
/**
373375
* struct xhci_intr_reg - Interrupt Register Set
@@ -1677,6 +1679,8 @@ int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev,
16771679
gfp_t mem_flags);
16781680
int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev);
16791681
int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev);
1682+
int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
1683+
struct usb_device *udev, int enable);
16801684
int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev,
16811685
struct usb_tt *tt, gfp_t mem_flags);
16821686
int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags);

include/linux/usb.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,8 @@ struct usb_tt;
411411
* @authenticated: Crypto authentication passed
412412
* @wusb: device is Wireless USB
413413
* @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
414416
* @string_langid: language ID for strings
415417
* @product: iProduct string, if present (static)
416418
* @manufacturer: iManufacturer string, if present (static)
@@ -474,6 +476,8 @@ struct usb_device {
474476
unsigned authenticated:1;
475477
unsigned wusb:1;
476478
unsigned lpm_capable:1;
479+
unsigned usb2_hw_lpm_capable:1;
480+
unsigned usb2_hw_lpm_enabled:1;
477481
int string_langid;
478482

479483
/* static strings from the device */

include/linux/usb/hcd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ struct hc_driver {
343343
* address is set
344344
*/
345345
int (*update_device)(struct usb_hcd *, struct usb_device *);
346+
int (*set_usb2_hw_lpm)(struct usb_hcd *, struct usb_device *, int);
346347
};
347348

348349
extern int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb);

0 commit comments

Comments
 (0)