Skip to content

Commit a558ccd

Browse files
matnymanSarah Sharp
authored andcommitted
usb: xhci: add USB2 Link power management BESL support
usb 2.0 devices with link power managment (LPM) can describe their idle link timeouts either in BESL or HIRD format, so far xHCI has only supported HIRD but later xHCI errata add BESL support as well BESL timeouts need to inform exit latency changes with an evaluate context command the same way USB 3.0 link PM code does. The same xhci_change_max_exit_latency() function is used as with USB3 but code is pulled out from #ifdef CONFIG_PM as USB2.0 BESL LPM funcionality does not depend on CONFIG_PM. Signed-off-by: Mathias Nyman <[email protected]> Signed-off-by: Sarah Sharp <[email protected]>
1 parent b6e7637 commit a558ccd

File tree

4 files changed

+164
-64
lines changed

4 files changed

+164
-64
lines changed

drivers/usb/host/xhci-ext-caps.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171

7272
/* USB 2.0 xHCI 1.0 hardware LMP capability - section 7.2.2.1.3.2 */
7373
#define XHCI_HLC (1 << 19)
74+
#define XHCI_BLC (1 << 19)
7475

7576
/* command register values to disable interrupts and halt the HC */
7677
/* start/stop HC execution - do not write unless HC is halted*/

drivers/usb/host/xhci.c

Lines changed: 140 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3815,6 +3815,56 @@ int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1)
38153815
return raw_port;
38163816
}
38173817

3818+
/*
3819+
* Issue an Evaluate Context command to change the Maximum Exit Latency in the
3820+
* slot context. If that succeeds, store the new MEL in the xhci_virt_device.
3821+
*/
3822+
static int xhci_change_max_exit_latency(struct xhci_hcd *xhci,
3823+
struct usb_device *udev, u16 max_exit_latency)
3824+
{
3825+
struct xhci_virt_device *virt_dev;
3826+
struct xhci_command *command;
3827+
struct xhci_input_control_ctx *ctrl_ctx;
3828+
struct xhci_slot_ctx *slot_ctx;
3829+
unsigned long flags;
3830+
int ret;
3831+
3832+
spin_lock_irqsave(&xhci->lock, flags);
3833+
if (max_exit_latency == xhci->devs[udev->slot_id]->current_mel) {
3834+
spin_unlock_irqrestore(&xhci->lock, flags);
3835+
return 0;
3836+
}
3837+
3838+
/* Attempt to issue an Evaluate Context command to change the MEL. */
3839+
virt_dev = xhci->devs[udev->slot_id];
3840+
command = xhci->lpm_command;
3841+
xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx);
3842+
spin_unlock_irqrestore(&xhci->lock, flags);
3843+
3844+
ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx);
3845+
ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG);
3846+
slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx);
3847+
slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT));
3848+
slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency);
3849+
3850+
xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n");
3851+
xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id);
3852+
xhci_dbg_ctx(xhci, command->in_ctx, 0);
3853+
3854+
/* Issue and wait for the evaluate context command. */
3855+
ret = xhci_configure_endpoint(xhci, udev, command,
3856+
true, true);
3857+
xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id);
3858+
xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0);
3859+
3860+
if (!ret) {
3861+
spin_lock_irqsave(&xhci->lock, flags);
3862+
virt_dev->current_mel = max_exit_latency;
3863+
spin_unlock_irqrestore(&xhci->lock, flags);
3864+
}
3865+
return ret;
3866+
}
3867+
38183868
#ifdef CONFIG_PM_RUNTIME
38193869

38203870
/* BESL to HIRD Encoding array for USB2 LPM */
@@ -3856,6 +3906,28 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci,
38563906
return besl;
38573907
}
38583908

3909+
/* Calculate BESLD, L1 timeout and HIRDM for USB2 PORTHLPMC */
3910+
static int xhci_calculate_usb2_hw_lpm_params(struct usb_device *udev)
3911+
{
3912+
u32 field;
3913+
int l1;
3914+
int besld = 0;
3915+
int hirdm = 0;
3916+
3917+
field = le32_to_cpu(udev->bos->ext_cap->bmAttributes);
3918+
3919+
/* xHCI l1 is set in steps of 256us, xHCI 1.0 section 5.4.11.2 */
3920+
l1 = XHCI_L1_TIMEOUT / 256;
3921+
3922+
/* device has preferred BESLD */
3923+
if (field & USB_BESL_DEEP_VALID) {
3924+
besld = USB_GET_BESL_DEEP(field);
3925+
hirdm = 1;
3926+
}
3927+
3928+
return PORT_BESLD(besld) | PORT_L1_TIMEOUT(l1) | PORT_HIRDM(hirdm);
3929+
}
3930+
38593931
static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd,
38603932
struct usb_device *udev)
38613933
{
@@ -3988,11 +4060,12 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
39884060
{
39894061
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
39904062
__le32 __iomem **port_array;
3991-
__le32 __iomem *pm_addr;
3992-
u32 temp;
4063+
__le32 __iomem *pm_addr, *hlpm_addr;
4064+
u32 pm_val, hlpm_val, field;
39934065
unsigned int port_num;
39944066
unsigned long flags;
3995-
int hird;
4067+
int hird, exit_latency;
4068+
int ret;
39964069

39974070
if (hcd->speed == HCD_USB3 || !xhci->hw_lpm_support ||
39984071
!udev->lpm_capable)
@@ -4010,23 +4083,73 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
40104083
port_array = xhci->usb2_ports;
40114084
port_num = udev->portnum - 1;
40124085
pm_addr = port_array[port_num] + PORTPMSC;
4013-
temp = xhci_readl(xhci, pm_addr);
4086+
pm_val = xhci_readl(xhci, pm_addr);
4087+
hlpm_addr = port_array[port_num] + PORTHLPMC;
4088+
field = le32_to_cpu(udev->bos->ext_cap->bmAttributes);
40144089

40154090
xhci_dbg(xhci, "%s port %d USB2 hardware LPM\n",
40164091
enable ? "enable" : "disable", port_num);
40174092

4018-
hird = xhci_calculate_hird_besl(xhci, udev);
4019-
40204093
if (enable) {
4021-
temp &= ~PORT_HIRD_MASK;
4022-
temp |= PORT_HIRD(hird) | PORT_RWE;
4023-
xhci_writel(xhci, temp, pm_addr);
4024-
temp = xhci_readl(xhci, pm_addr);
4025-
temp |= PORT_HLE;
4026-
xhci_writel(xhci, temp, pm_addr);
4094+
/* Host supports BESL timeout instead of HIRD */
4095+
if (udev->usb2_hw_lpm_besl_capable) {
4096+
/* if device doesn't have a preferred BESL value use a
4097+
* default one which works with mixed HIRD and BESL
4098+
* systems. See XHCI_DEFAULT_BESL definition in xhci.h
4099+
*/
4100+
if ((field & USB_BESL_SUPPORT) &&
4101+
(field & USB_BESL_BASELINE_VALID))
4102+
hird = USB_GET_BESL_BASELINE(field);
4103+
else
4104+
hird = XHCI_DEFAULT_BESL;
4105+
4106+
exit_latency = xhci_besl_encoding[hird];
4107+
spin_unlock_irqrestore(&xhci->lock, flags);
4108+
4109+
/* USB 3.0 code dedicate one xhci->lpm_command->in_ctx
4110+
* input context for link powermanagement evaluate
4111+
* context commands. It is protected by hcd->bandwidth
4112+
* mutex and is shared by all devices. We need to set
4113+
* the max ext latency in USB 2 BESL LPM as well, so
4114+
* use the same mutex and xhci_change_max_exit_latency()
4115+
*/
4116+
mutex_lock(hcd->bandwidth_mutex);
4117+
ret = xhci_change_max_exit_latency(xhci, udev,
4118+
exit_latency);
4119+
mutex_unlock(hcd->bandwidth_mutex);
4120+
4121+
if (ret < 0)
4122+
return ret;
4123+
spin_lock_irqsave(&xhci->lock, flags);
4124+
4125+
hlpm_val = xhci_calculate_usb2_hw_lpm_params(udev);
4126+
xhci_writel(xhci, hlpm_val, hlpm_addr);
4127+
/* flush write */
4128+
xhci_readl(xhci, hlpm_addr);
4129+
} else {
4130+
hird = xhci_calculate_hird_besl(xhci, udev);
4131+
}
4132+
4133+
pm_val &= ~PORT_HIRD_MASK;
4134+
pm_val |= PORT_HIRD(hird) | PORT_RWE;
4135+
xhci_writel(xhci, pm_val, pm_addr);
4136+
pm_val = xhci_readl(xhci, pm_addr);
4137+
pm_val |= PORT_HLE;
4138+
xhci_writel(xhci, pm_val, pm_addr);
4139+
/* flush write */
4140+
xhci_readl(xhci, pm_addr);
40274141
} else {
4028-
temp &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK);
4029-
xhci_writel(xhci, temp, pm_addr);
4142+
pm_val &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK);
4143+
xhci_writel(xhci, pm_val, pm_addr);
4144+
/* flush write */
4145+
xhci_readl(xhci, pm_addr);
4146+
if (udev->usb2_hw_lpm_besl_capable) {
4147+
spin_unlock_irqrestore(&xhci->lock, flags);
4148+
mutex_lock(hcd->bandwidth_mutex);
4149+
xhci_change_max_exit_latency(xhci, udev, 0);
4150+
mutex_unlock(hcd->bandwidth_mutex);
4151+
return 0;
4152+
}
40304153
}
40314154

40324155
spin_unlock_irqrestore(&xhci->lock, flags);
@@ -4068,6 +4191,9 @@ int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev)
40684191
if (xhci->hw_lpm_support == 1 &&
40694192
xhci_check_usb2_port_capability(xhci, portnum, XHCI_HLC)) {
40704193
udev->usb2_hw_lpm_capable = 1;
4194+
if (xhci_check_usb2_port_capability(xhci, portnum,
4195+
XHCI_BLC))
4196+
udev->usb2_hw_lpm_besl_capable = 1;
40714197
ret = xhci_set_usb2_hardware_lpm(hcd, udev, 1);
40724198
if (!ret)
40734199
udev->usb2_hw_lpm_enabled = 1;
@@ -4398,56 +4524,6 @@ static u16 xhci_calculate_lpm_timeout(struct usb_hcd *hcd,
43984524
return timeout;
43994525
}
44004526

4401-
/*
4402-
* Issue an Evaluate Context command to change the Maximum Exit Latency in the
4403-
* slot context. If that succeeds, store the new MEL in the xhci_virt_device.
4404-
*/
4405-
static int xhci_change_max_exit_latency(struct xhci_hcd *xhci,
4406-
struct usb_device *udev, u16 max_exit_latency)
4407-
{
4408-
struct xhci_virt_device *virt_dev;
4409-
struct xhci_command *command;
4410-
struct xhci_input_control_ctx *ctrl_ctx;
4411-
struct xhci_slot_ctx *slot_ctx;
4412-
unsigned long flags;
4413-
int ret;
4414-
4415-
spin_lock_irqsave(&xhci->lock, flags);
4416-
if (max_exit_latency == xhci->devs[udev->slot_id]->current_mel) {
4417-
spin_unlock_irqrestore(&xhci->lock, flags);
4418-
return 0;
4419-
}
4420-
4421-
/* Attempt to issue an Evaluate Context command to change the MEL. */
4422-
virt_dev = xhci->devs[udev->slot_id];
4423-
command = xhci->lpm_command;
4424-
xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx);
4425-
spin_unlock_irqrestore(&xhci->lock, flags);
4426-
4427-
ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx);
4428-
ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG);
4429-
slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx);
4430-
slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT));
4431-
slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency);
4432-
4433-
xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n");
4434-
xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id);
4435-
xhci_dbg_ctx(xhci, command->in_ctx, 0);
4436-
4437-
/* Issue and wait for the evaluate context command. */
4438-
ret = xhci_configure_endpoint(xhci, udev, command,
4439-
true, true);
4440-
xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id);
4441-
xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0);
4442-
4443-
if (!ret) {
4444-
spin_lock_irqsave(&xhci->lock, flags);
4445-
virt_dev->current_mel = max_exit_latency;
4446-
spin_unlock_irqrestore(&xhci->lock, flags);
4447-
}
4448-
return ret;
4449-
}
4450-
44514527
static int calculate_max_exit_latency(struct usb_device *udev,
44524528
enum usb3_link_state state_changed,
44534529
u16 hub_encoded_timeout)

drivers/usb/host/xhci.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,27 @@ struct xhci_op_regs {
386386
#define PORT_L1DS(p) (((p) & 0xff) << 8)
387387
#define PORT_HLE (1 << 16)
388388

389+
390+
/* USB2 Protocol PORTHLPMC */
391+
#define PORT_HIRDM(p)((p) & 3)
392+
#define PORT_L1_TIMEOUT(p)(((p) & 0xff) << 2)
393+
#define PORT_BESLD(p)(((p) & 0xf) << 10)
394+
395+
/* use 512 microseconds as USB2 LPM L1 default timeout. */
396+
#define XHCI_L1_TIMEOUT 512
397+
398+
/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency.
399+
* Safe to use with mixed HIRD and BESL systems (host and device) and is used
400+
* by other operating systems.
401+
*
402+
* XHCI 1.0 errata 8/14/12 Table 13 notes:
403+
* "Software should choose xHC BESL/BESLD field values that do not violate a
404+
* device's resume latency requirements,
405+
* e.g. not program values > '4' if BLC = '1' and a HIRD device is attached,
406+
* or not program values < '4' if BLC = '0' and a BESL device is attached.
407+
*/
408+
#define XHCI_DEFAULT_BESL 4
409+
389410
/**
390411
* struct xhci_intr_reg - Interrupt Register Set
391412
* @irq_pending: IMAN - Interrupt Management Register. Used to enable

include/linux/usb.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ struct usb3_lpm_parameters {
468468
* @wusb: device is Wireless USB
469469
* @lpm_capable: device supports LPM
470470
* @usb2_hw_lpm_capable: device can perform USB2 hardware LPM
471+
* @usb2_hw_lpm_besl_capable: device can perform USB2 hardware BESL LPM
471472
* @usb2_hw_lpm_enabled: USB2 hardware LPM enabled
472473
* @usb3_lpm_enabled: USB3 hardware LPM enabled
473474
* @string_langid: language ID for strings
@@ -538,6 +539,7 @@ struct usb_device {
538539
unsigned wusb:1;
539540
unsigned lpm_capable:1;
540541
unsigned usb2_hw_lpm_capable:1;
542+
unsigned usb2_hw_lpm_besl_capable:1;
541543
unsigned usb2_hw_lpm_enabled:1;
542544
unsigned usb3_lpm_enabled:1;
543545
int string_langid;

0 commit comments

Comments
 (0)