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