[PATCH 08/10] usb: add usb port auto power off mechanism

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

 



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.

Acked-by: Rafael J. Wysocki <rafael.j.wysocki@xxxxxxxxx>
Signed-off-by: Lan Tianyu <tianyu.lan@xxxxxxxxx>
---
 drivers/usb/core/hub.c  |   75 +++++++++++++++++++++++++++++++++++++++++++++--
 drivers/usb/core/hub.h  |    2 ++
 drivers/usb/core/port.c |   14 ++++++++-
 3 files changed, 87 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 3e69d66..8eeee62 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -88,11 +88,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)
 {
@@ -784,7 +787,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);
@@ -2767,6 +2774,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;
 
@@ -2861,6 +2869,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;
 }
@@ -2944,6 +2965,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
@@ -2981,10 +3021,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 < 0) {
+			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))
@@ -2992,8 +3058,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,
@@ -4174,6 +4238,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) {
 		if (hcd->phy && !hdev->parent &&
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 1b574a2..ddad018 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -100,6 +100,7 @@ struct usb_hub {
  * @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;
@@ -107,6 +108,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) \
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index d44d8d3..8b66aeb 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -72,6 +72,9 @@ static int usb_port_runtime_resume(struct device *dev)
 	struct usb_device *hdev =
 		to_usb_device(dev->parent->parent);
 
+	if (port_dev->power_on)
+		return 0;
+
 	return usb_hub_set_port_power(hdev, port_dev->portnum,
 			true);
 }
@@ -81,13 +84,21 @@ static int usb_port_runtime_suspend(struct device *dev)
 	struct usb_port *port_dev = to_usb_port(dev);
 	struct usb_device *hdev =
 		to_usb_device(dev->parent->parent);
+	int retval;
 
 	if (dev_pm_qos_flags(&port_dev->dev, PM_QOS_FLAG_NO_POWER_OFF)
 			== PM_QOS_FLAGS_ALL)
 		return -EAGAIN;
 
-	return usb_hub_set_port_power(hdev, port_dev->portnum,
+	if (!port_dev->power_on)
+		return 0;
+
+	retval = usb_hub_set_port_power(hdev, port_dev->portnum,
 			false);
+	if (!retval)
+		port_dev->power_on = false;
+
+	return retval;
 }
 
 static int usb_port_runtime_idle(struct device *dev)
@@ -124,6 +135,7 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
 
 	hub->ports[port1 - 1] = port_dev;
 	port_dev->portnum = port1;
+	port_dev->power_on = true;
 	port_dev->dev.parent = hub->intfdev;
 	port_dev->dev.groups = port_dev_group;
 	port_dev->dev.type = &usb_port_device_type;
-- 
1.7.9.5

--
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