[PATCH v2 05/14] USB: allow hubs to runtime suspend after all ports are powered off

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

 



Ports are kept powered on while they have a wakeup enabled child.  If
all ports are off it means that wakeup has been disabled on all child
devices.  As a consequence we can suspend the hub once all ports are
powered off.

Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---
 drivers/usb/core/hub.c  |   24 +++++++++++++++++++++++-
 drivers/usb/core/hub.h  |    3 +++
 drivers/usb/core/port.c |    6 +++---
 3 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index d4c2c3a3fb5e..683f528f5ea7 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -5063,6 +5063,28 @@ static int descriptors_changed(struct usb_device *udev,
 	return changed;
 }
 
+void usb_assign_port_poweroff(struct usb_hub *hub, int port1, int val)
+{
+	/* usb_port_runtime_suspend may run concurrently with
+	 * usb_reset_and_verify_device or usb_port_runtime_resume
+	 */
+	static DEFINE_SPINLOCK(lock);
+	struct usb_interface *intf = to_usb_interface(hub->intfdev);
+
+	spin_lock(&lock);
+	if (val) {
+		struct usb_device *hdev = hub->hdev;
+
+		set_bit(port1, hub->poweroff_bits);
+		if (hweight32(hub->poweroff_bits[0]) == hdev->maxchild)
+			intf->needs_remote_wakeup = 0;
+	} else {
+		intf->needs_remote_wakeup = 1;
+		clear_bit(port1, hub->poweroff_bits);
+	}
+	spin_unlock(&lock);
+}
+
 /**
  * usb_reset_and_verify_device - perform a USB port reset to reinitialize a device
  * @udev: device to reset (not in SUSPENDED or NOTATTACHED state)
@@ -5154,7 +5176,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
 		if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)
 			break;
 	}
-	clear_bit(port1, parent_hub->poweroff_bits);
+	usb_clear_port_poweroff(parent_hub, port1);
 	clear_bit(port1, parent_hub->busy_bits);
 
 	if (ret < 0)
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index cd0c40d959c0..44508d41c095 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -107,3 +107,6 @@ extern int usb_clear_port_feature(struct usb_device *hdev,
 		int port1, int feature);
 extern int usb_set_port_feature(struct usb_device *hdev,
 		int port1, int feature);
+extern void usb_assign_port_poweroff(struct usb_hub *hub, int port1, int val);
+#define usb_set_port_poweroff(h, p) usb_assign_port_poweroff(h, p, 1)
+#define usb_clear_port_poweroff(h, p) usb_assign_port_poweroff(h, p, 0)
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 3dbd4d653b15..094a9d0e31c7 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -88,7 +88,7 @@ static int usb_port_runtime_resume(struct device *dev)
 
 	/* no child? we're done recovering this port */
 	if (!port_dev->child)
-		clear_bit(port1, hub->poweroff_bits);
+		usb_clear_port_poweroff(hub, port1);
 
 	return retval;
 }
@@ -109,11 +109,11 @@ static int usb_port_runtime_suspend(struct device *dev)
 			== PM_QOS_FLAGS_ALL)
 		return -EAGAIN;
 
-	set_bit(port1, hub->poweroff_bits);
 	usb_autopm_get_interface(intf);
+	usb_set_port_poweroff(hub, port1);
 	retval = usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
 	if (retval)
-		clear_bit(port1, hub->poweroff_bits);
+		usb_clear_port_poweroff(hub, port1);
 	usb_autopm_put_interface(intf);
 
 	return retval;

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