[Quantal][SRU][PATCH 2/2] usb: xhci: add USB2 Link power management BESL support
Tim Gardner
tim.gardner at canonical.com
Wed Sep 25 14:52:15 UTC 2013
On 09/25/2013 07:27 AM, Gavin Guo wrote:
> From: Mathias Nyman <mathias.nyman at linux.intel.com>
>
> BugLink: https://bugs.launchpad.net/bugs/1229576
>
> 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 <mathias.nyman at linux.intel.com>
> Signed-off-by: Sarah Sharp <sarah.a.sharp at linux.intel.com>
> (backported from commit a558ccdcc71c7770c5e80c926a31cfe8a3892a09)
>
> Signed-off-by: Shawn Wang <shawn.wang at canonical.com>
> Signed-off-by: Gavin Guo <gavin.guo at canonical.com>
>
> Conflicts:
> drivers/usb/host/xhci.c
> ---
> drivers/usb/host/xhci.c | 201 ++++++++++++++++++++++++++++++++---------------
> drivers/usb/host/xhci.h | 21 +++++
> 2 files changed, 158 insertions(+), 64 deletions(-)
>
> diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
> index c3b0f2d..fb2aaaf 100644
> --- a/drivers/usb/host/xhci.c
> +++ b/drivers/usb/host/xhci.c
> @@ -3783,6 +3783,56 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev)
> return 0;
> }
>
> +/*
> + * Issue an Evaluate Context command to change the Maximum Exit Latency in the
> + * slot context. If that succeeds, store the new MEL in the xhci_virt_device.
> + */
> +static int xhci_change_max_exit_latency(struct xhci_hcd *xhci,
> + struct usb_device *udev, u16 max_exit_latency)
> +{
> + struct xhci_virt_device *virt_dev;
> + struct xhci_command *command;
> + struct xhci_input_control_ctx *ctrl_ctx;
> + struct xhci_slot_ctx *slot_ctx;
> + unsigned long flags;
> + int ret;
> +
> + spin_lock_irqsave(&xhci->lock, flags);
> + if (max_exit_latency == xhci->devs[udev->slot_id]->current_mel) {
> + spin_unlock_irqrestore(&xhci->lock, flags);
> + return 0;
> + }
> +
> + /* Attempt to issue an Evaluate Context command to change the MEL. */
> + virt_dev = xhci->devs[udev->slot_id];
> + command = xhci->lpm_command;
> + xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx);
> + spin_unlock_irqrestore(&xhci->lock, flags);
> +
> + ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx);
> + ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG);
> + slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx);
> + slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT));
> + slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency);
> +
> + xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n");
> + xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id);
> + xhci_dbg_ctx(xhci, command->in_ctx, 0);
> +
> + /* Issue and wait for the evaluate context command. */
> + ret = xhci_configure_endpoint(xhci, udev, command,
> + true, true);
> + xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id);
> + xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0);
> +
> + if (!ret) {
> + spin_lock_irqsave(&xhci->lock, flags);
> + virt_dev->current_mel = max_exit_latency;
> + spin_unlock_irqrestore(&xhci->lock, flags);
> + }
> + return ret;
> +}
> +
> #ifdef CONFIG_USB_SUSPEND
>
> /* BESL to HIRD Encoding array for USB2 LPM */
> @@ -3824,6 +3874,28 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci,
> return besl;
> }
>
> +/* Calculate BESLD, L1 timeout and HIRDM for USB2 PORTHLPMC */
> +static int xhci_calculate_usb2_hw_lpm_params(struct usb_device *udev)
> +{
> + u32 field;
> + int l1;
> + int besld = 0;
> + int hirdm = 0;
> +
> + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes);
> +
> + /* xHCI l1 is set in steps of 256us, xHCI 1.0 section 5.4.11.2 */
> + l1 = XHCI_L1_TIMEOUT / 256;
> +
> + /* device has preferred BESLD */
> + if (field & USB_BESL_DEEP_VALID) {
> + besld = USB_GET_BESL_DEEP(field);
> + hirdm = 1;
> + }
> +
> + return PORT_BESLD(besld) | PORT_L1_TIMEOUT(l1) | PORT_HIRDM(hirdm);
> +}
> +
> static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd,
> struct usb_device *udev)
> {
> @@ -3955,11 +4027,12 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
> {
> struct xhci_hcd *xhci = hcd_to_xhci(hcd);
> __le32 __iomem **port_array;
> - __le32 __iomem *pm_addr;
> - u32 temp;
> + __le32 __iomem *pm_addr, *hlpm_addr;
> + u32 pm_val, hlpm_val, field;
> unsigned int port_num;
> unsigned long flags;
> - int hird;
> + int hird, exit_latency;
> + int ret;
>
> if (hcd->speed == HCD_USB3 || !xhci->hw_lpm_support ||
> !udev->lpm_capable)
> @@ -3977,23 +4050,73 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
> port_array = xhci->usb2_ports;
> port_num = udev->portnum - 1;
> pm_addr = port_array[port_num] + PORTPMSC;
> - temp = xhci_readl(xhci, pm_addr);
> + pm_val = xhci_readl(xhci, pm_addr);
> + hlpm_addr = port_array[port_num] + PORTHLPMC;
> + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes);
>
> xhci_dbg(xhci, "%s port %d USB2 hardware LPM\n",
> enable ? "enable" : "disable", port_num);
>
> - hird = xhci_calculate_hird_besl(xhci, udev);
> -
> if (enable) {
> - temp &= ~PORT_HIRD_MASK;
> - temp |= PORT_HIRD(hird) | PORT_RWE;
> - xhci_writel(xhci, temp, pm_addr);
> - temp = xhci_readl(xhci, pm_addr);
> - temp |= PORT_HLE;
> - xhci_writel(xhci, temp, pm_addr);
> + /* Host supports BESL timeout instead of HIRD */
> + if (udev->usb2_hw_lpm_capable) {
> + /* if device doesn't have a preferred BESL value use a
> + * default one which works with mixed HIRD and BESL
> + * systems. See XHCI_DEFAULT_BESL definition in xhci.h
> + */
> + if ((field & USB_BESL_SUPPORT) &&
> + (field & USB_BESL_BASELINE_VALID))
> + hird = USB_GET_BESL_BASELINE(field);
> + else
> + hird = XHCI_DEFAULT_BESL;
> +
> + exit_latency = xhci_besl_encoding[hird];
> + spin_unlock_irqrestore(&xhci->lock, flags);
> +
> + /* USB 3.0 code dedicate one xhci->lpm_command->in_ctx
> + * input context for link powermanagement evaluate
> + * context commands. It is protected by hcd->bandwidth
> + * mutex and is shared by all devices. We need to set
> + * the max ext latency in USB 2 BESL LPM as well, so
> + * use the same mutex and xhci_change_max_exit_latency()
> + */
> + mutex_lock(hcd->bandwidth_mutex);
> + ret = xhci_change_max_exit_latency(xhci, udev,
> + exit_latency);
> + mutex_unlock(hcd->bandwidth_mutex);
> +
> + if (ret < 0)
> + return ret;
> + spin_lock_irqsave(&xhci->lock, flags);
> +
> + hlpm_val = xhci_calculate_usb2_hw_lpm_params(udev);
> + xhci_writel(xhci, hlpm_val, hlpm_addr);
> + /* flush write */
> + xhci_readl(xhci, hlpm_addr);
> + } else {
> + hird = xhci_calculate_hird_besl(xhci, udev);
> + }
> +
> + pm_val &= ~PORT_HIRD_MASK;
> + pm_val |= PORT_HIRD(hird) | PORT_RWE;
> + xhci_writel(xhci, pm_val, pm_addr);
> + pm_val = xhci_readl(xhci, pm_addr);
> + pm_val |= PORT_HLE;
> + xhci_writel(xhci, pm_val, pm_addr);
> + /* flush write */
> + xhci_readl(xhci, pm_addr);
> } else {
> - temp &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK);
> - xhci_writel(xhci, temp, pm_addr);
> + pm_val &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK);
> + xhci_writel(xhci, pm_val, pm_addr);
> + /* flush write */
> + xhci_readl(xhci, pm_addr);
> + if (udev->usb2_hw_lpm_capable) {
> + spin_unlock_irqrestore(&xhci->lock, flags);
> + mutex_lock(hcd->bandwidth_mutex);
> + xhci_change_max_exit_latency(xhci, udev, 0);
> + mutex_unlock(hcd->bandwidth_mutex);
> + return 0;
> + }
> }
>
> spin_unlock_irqrestore(&xhci->lock, flags);
> @@ -4340,56 +4463,6 @@ static u16 xhci_calculate_lpm_timeout(struct usb_hcd *hcd,
> return timeout;
> }
>
> -/*
> - * Issue an Evaluate Context command to change the Maximum Exit Latency in the
> - * slot context. If that succeeds, store the new MEL in the xhci_virt_device.
> - */
> -static int xhci_change_max_exit_latency(struct xhci_hcd *xhci,
> - struct usb_device *udev, u16 max_exit_latency)
> -{
> - struct xhci_virt_device *virt_dev;
> - struct xhci_command *command;
> - struct xhci_input_control_ctx *ctrl_ctx;
> - struct xhci_slot_ctx *slot_ctx;
> - unsigned long flags;
> - int ret;
> -
> - spin_lock_irqsave(&xhci->lock, flags);
> - if (max_exit_latency == xhci->devs[udev->slot_id]->current_mel) {
> - spin_unlock_irqrestore(&xhci->lock, flags);
> - return 0;
> - }
> -
> - /* Attempt to issue an Evaluate Context command to change the MEL. */
> - virt_dev = xhci->devs[udev->slot_id];
> - command = xhci->lpm_command;
> - xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx);
> - spin_unlock_irqrestore(&xhci->lock, flags);
> -
> - ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx);
> - ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG);
> - slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx);
> - slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT));
> - slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency);
> -
> - xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n");
> - xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id);
> - xhci_dbg_ctx(xhci, command->in_ctx, 0);
> -
> - /* Issue and wait for the evaluate context command. */
> - ret = xhci_configure_endpoint(xhci, udev, command,
> - true, true);
> - xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id);
> - xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0);
> -
> - if (!ret) {
> - spin_lock_irqsave(&xhci->lock, flags);
> - virt_dev->current_mel = max_exit_latency;
> - spin_unlock_irqrestore(&xhci->lock, flags);
> - }
> - return ret;
> -}
> -
> static int calculate_max_exit_latency(struct usb_device *udev,
> enum usb3_link_state state_changed,
> u16 hub_encoded_timeout)
> diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
> index 79c324a..5897c13 100644
> --- a/drivers/usb/host/xhci.h
> +++ b/drivers/usb/host/xhci.h
> @@ -386,6 +386,27 @@ struct xhci_op_regs {
> #define PORT_L1DS(p) (((p) & 0xff) << 8)
> #define PORT_HLE (1 << 16)
>
> +
> +/* USB2 Protocol PORTHLPMC */
> +#define PORT_HIRDM(p)((p) & 3)
> +#define PORT_L1_TIMEOUT(p)(((p) & 0xff) << 2)
> +#define PORT_BESLD(p)(((p) & 0xf) << 10)
> +
> +/* use 512 microseconds as USB2 LPM L1 default timeout. */
> +#define XHCI_L1_TIMEOUT 512
> +
> +/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency.
> + * Safe to use with mixed HIRD and BESL systems (host and device) and is used
> + * by other operating systems.
> + *
> + * XHCI 1.0 errata 8/14/12 Table 13 notes:
> + * "Software should choose xHC BESL/BESLD field values that do not violate a
> + * device's resume latency requirements,
> + * e.g. not program values > '4' if BLC = '1' and a HIRD device is attached,
> + * or not program values < '4' if BLC = '0' and a BESL device is attached.
> + */
> +#define XHCI_DEFAULT_BESL 4
> +
> /**
> * struct xhci_intr_reg - Interrupt Register Set
> * @irq_pending: IMAN - Interrupt Management Register. Used to enable
>
Hmm, might you also need dcf06a036848b4e8e6c8220f2e00b9adf6f84918 ?
--
Tim Gardner tim.gardner at canonical.com
More information about the kernel-team
mailing list