Re: [RFC PATCH v2 4/6] usb: add usb port auto power off mechanism

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tuesday, November 13, 2012 04:00:03 PM Lan Tianyu wrote:
> This patch is to add usb port auto power off mechanism.
> When usb device is suspending, usb core will suspend usb port and
> usb port runtime pm callback will clear PORT_POWER feature to
> power off port if all conditions were met.These conditions are
> remote wakeup disable, pm qos NO_POWER_OFF flag clear and persist
> enable. When device is suspended and power off, usb core
> will ignore port's connect and enable change event to keep the device
> not being disconnected. When it resumes, power on port again.
> 
> Signed-off-by: Lan Tianyu <tianyu.lan@xxxxxxxxx>

Again, from the PM side:

Acked-by: Rafael J. Wysocki <rafael.j.wysocki@xxxxxxxxx>

although as far as the USB internals are concerned, I'm far from being an expert. :-)

Thanks,
Rafael


> ---
>  drivers/usb/core/hub.c  |   76 +++++++++++++++++++++++++++++++++++++++++++++--
>  drivers/usb/core/port.c |    1 +
>  include/linux/usb.h     |    2 ++
>  3 files changed, 76 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 2cb414e..267e9d7 100644
> --- a/drivers/usb/core/hub.c
> +++ b/drivers/usb/core/hub.c
> @@ -26,6 +26,7 @@
>  #include <linux/mutex.h>
>  #include <linux/freezer.h>
>  #include <linux/random.h>
> +#include <linux/pm_qos.h>
>  
>  #include <asm/uaccess.h>
>  #include <asm/byteorder.h>
> @@ -159,11 +160,14 @@ MODULE_PARM_DESC(use_both_schemes,
>  DECLARE_RWSEM(ehci_cf_port_reset_rwsem);
>  EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem);
>  
> +#define HUB_PORT_RECONNECT_TRIES	20
> +
>  #define HUB_DEBOUNCE_TIMEOUT	1500
>  #define HUB_DEBOUNCE_STEP	  25
>  #define HUB_DEBOUNCE_STABLE	 100
>  
>  static int usb_reset_and_verify_device(struct usb_device *udev);
> +static int hub_port_debounce(struct usb_hub *hub, int port1);
>  
>  static inline char *portspeed(struct usb_hub *hub, int portstatus)
>  {
> @@ -855,7 +859,11 @@ static unsigned hub_power_on(struct usb_hub *hub, bool do_delay)
>  		dev_dbg(hub->intfdev, "trying to enable port power on "
>  				"non-switchable hub\n");
>  	for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++)
> -		set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER);
> +		if (hub->ports[port1 - 1]->power_on)
> +			set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER);
> +		else
> +			clear_port_feature(hub->hdev, port1,
> +						USB_PORT_FEAT_POWER);
>  
>  	/* Wait at least 100 msec for power to become stable */
>  	delay = max(pgood_delay, (unsigned) 100);
> @@ -2852,6 +2860,7 @@ EXPORT_SYMBOL_GPL(usb_enable_ltm);
>  int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
>  {
>  	struct usb_hub	*hub = hdev_to_hub(udev->parent);
> +	struct usb_port *port_dev = hub->ports[udev->portnum - 1];
>  	int		port1 = udev->portnum;
>  	int		status;
>  
> @@ -2946,6 +2955,19 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
>  		udev->port_is_suspended = 1;
>  		msleep(10);
>  	}
> +
> +	/*
> +	 * Check whether current status is meeting requirement of
> +	 * usb port power off mechanism
> +	 */
> +	if (!udev->do_remote_wakeup
> +			&& (dev_pm_qos_flags(&port_dev->dev,
> +			PM_QOS_FLAG_NO_POWER_OFF) != PM_QOS_FLAGS_ALL)
> +			&& udev->persist_enabled
> +			&& !status)
> +		if (!pm_runtime_put_sync(&port_dev->dev))
> +			port_dev->power_on = false;
> +
>  	usb_mark_last_busy(hub->hdev);
>  	return status;
>  }
> @@ -3029,6 +3051,25 @@ static int finish_port_resume(struct usb_device *udev)
>  	return status;
>  }
>  
> +static int usb_port_wait_for_connected(struct usb_hub *hub, int port1)
> +{
> +	int status, i;
> +
> +	for (i = 0; i < HUB_PORT_RECONNECT_TRIES; i++) {
> +		status = hub_port_debounce(hub, port1);
> +		if (status & USB_PORT_STAT_CONNECTION) {
> +			/*
> +			 * just clear enable-change feature since debounce
> +			 *  has cleared connect-change feature.
> +			 */
> +			clear_port_feature(hub->hdev, port1,
> +					USB_PORT_FEAT_C_ENABLE);
> +			return 0;
> +		}
> +	}
> +	return -ETIMEDOUT;
> +}
> +
>  /*
>   * usb_port_resume - re-activate a suspended usb device's upstream port
>   * @udev: device to re-activate, not a root hub
> @@ -3066,10 +3107,36 @@ static int finish_port_resume(struct usb_device *udev)
>  int usb_port_resume(struct usb_device *udev, pm_message_t msg)
>  {
>  	struct usb_hub	*hub = hdev_to_hub(udev->parent);
> +	struct usb_port *port_dev = hub->ports[udev->portnum  - 1];
>  	int		port1 = udev->portnum;
>  	int		status;
>  	u16		portchange, portstatus;
>  
> +
> +	set_bit(port1, hub->busy_bits);
> +
> +	if (!port_dev->power_on) {
> +		status = pm_runtime_get_sync(&port_dev->dev);
> +		if (status) {
> +			dev_dbg(&udev->dev, "can't resume usb port, status %d\n",
> +					status);
> +			return status;
> +		}
> +
> +		port_dev->power_on = true;
> +
> +		/*
> +		 * Wait for usb hub port to be reconnected in order to make
> +		 * the resume procedure successful.
> +		 */
> +		status = usb_port_wait_for_connected(hub, port1);
> +		if (status < 0) {
> +			dev_dbg(&udev->dev, "can't get reconnection after setting port  power on, status %d\n",
> +					status);
> +			return status;
> +		}
> +	}
> +
>  	/* Skip the initial Clear-Suspend step for a remote wakeup */
>  	status = hub_port_status(hub, port1, &portstatus, &portchange);
>  	if (status == 0 && !port_is_suspended(hub, portstatus))
> @@ -3077,8 +3144,6 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
>  
>  	// dev_dbg(hub->intfdev, "resume port %d\n", port1);
>  
> -	set_bit(port1, hub->busy_bits);
> -
>  	/* see 7.1.7.7; affects power usage, but not budgeting */
>  	if (hub_is_superspeed(hub->hdev))
>  		status = set_port_feature(hub->hdev,
> @@ -4256,6 +4321,11 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
>  		}
>  	}
>  
> +	if (!hub->ports[port1 - 1]->power_on) {
> +		clear_bit(port1, hub->change_bits);
> +		return;
> +	}
> +
>  	/* Disconnect any existing devices under this port */
>  	if (udev)
>  		usb_disconnect(&hub->ports[port1 - 1]->child);
> diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
> index 1cda766..b7388fd 100644
> --- a/drivers/usb/core/port.c
> +++ b/drivers/usb/core/port.c
> @@ -99,6 +99,7 @@ int usb_hub_create_port_device(struct device *intfdev,
>  {
>  	int retval;
>  
> +	port_dev->power_on = true;
>  	port_dev->portnum = port1;
>  	port_dev->dev.parent = intfdev;
>  	port_dev->dev.groups = port_dev_group;
> diff --git a/include/linux/usb.h b/include/linux/usb.h
> index 71510bf..8002640 100644
> --- a/include/linux/usb.h
> +++ b/include/linux/usb.h
> @@ -579,6 +579,7 @@ struct usb_device {
>   * @port_owner: port's owner
>   * @connect_type: port's connect type
>   * @portnum: port index num based one
> + * @power_on: port's power state
>   */
>  struct usb_port {
>  	struct usb_device *child;
> @@ -586,6 +587,7 @@ struct usb_port {
>  	struct dev_state *port_owner;
>  	enum usb_port_connect_type connect_type;
>  	u8 portnum;
> +	bool power_on;
>  };
>  #define to_usb_port(_dev) \
>  	container_of(_dev, struct usb_port, dev)
> 
-- 
I speak only for myself.
Rafael J. Wysocki, Intel Open Source Technology Center.
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux