[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