diff options
author | Lan Tianyu <tianyu.lan@intel.com> | 2013-01-23 04:26:30 +0800 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-01-25 10:14:20 -0800 |
commit | ad493e5e580546e6c3024b76a41535476da1546a (patch) | |
tree | 256edf0ac3bc23573c0daa0b66a985970c489f61 /drivers/usb/core/port.c | |
parent | 971fcd492cebf544714f12d94549d2f0d2002645 (diff) |
usb: add usb port auto power off mechanism
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 it resumes, power on port again.
Add did_runtime_put in the struct usb_port to ensure
pm_runtime_get/put(portdev) to be called pairedly. Set did_runtime_put
to true when call pm_runtime_put(portdev) during suspending. The
pm_runtime_get(portdev) only will be called when did_runtime_put
is set to true during resuming. Set did_runtime_put to false after
calling pm_runtime_get(portdev).
Make clear_port_feature() and hdev_to_hub() as global symbol.
Rename clear_port_feature() to usb_clear_port_feature() and
hdev_to_hub() to usb_hub_to_struct_hub().
Extend hub_port_debounce() with the fuction of debouncing to
be connected. Add two wraps: hub_port_debounce_be_connected()
and hub_port_debouce_be_stable().
Increase HUB_DEBOUNCE_TIMEOUT to 2000 because some usb ssds
needs around 1.5 or more to make the hub port status to be
connected steadily after being powered off and powered on.
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Signed-off-by: Lan Tianyu <tianyu.lan@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/core/port.c')
-rw-r--r-- | drivers/usb/core/port.c | 40 |
1 files changed, 38 insertions, 2 deletions
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index d288dfed6cc..280433d8088 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -77,10 +77,36 @@ static int usb_port_runtime_resume(struct device *dev) struct usb_port *port_dev = to_usb_port(dev); struct usb_device *hdev = to_usb_device(dev->parent->parent); struct usb_interface *intf = to_usb_interface(dev->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + int port1 = port_dev->portnum; int retval; + if (!hub) + return -EINVAL; + usb_autopm_get_interface(intf); - retval = usb_hub_set_port_power(hdev, port_dev->portnum, true); + set_bit(port1, hub->busy_bits); + + retval = usb_hub_set_port_power(hdev, port1, true); + if (port_dev->child && !retval) { + /* + * Wait for usb hub port to be reconnected in order to make + * the resume procedure successful. + */ + retval = hub_port_debounce_be_connected(hub, port1); + if (retval < 0) { + dev_dbg(&port_dev->dev, "can't get reconnection after setting port power on, status %d\n", + retval); + goto out; + } + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); + + /* Set return value to 0 if debounce successful */ + retval = 0; + } + +out: + clear_bit(port1, hub->busy_bits); usb_autopm_put_interface(intf); return retval; } @@ -90,14 +116,23 @@ 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); struct usb_interface *intf = to_usb_interface(dev->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + int port1 = port_dev->portnum; int retval; + if (!hub) + return -EINVAL; + if (dev_pm_qos_flags(&port_dev->dev, PM_QOS_FLAG_NO_POWER_OFF) == PM_QOS_FLAGS_ALL) return -EAGAIN; usb_autopm_get_interface(intf); - retval = usb_hub_set_port_power(hdev, port_dev->portnum, false); + set_bit(port1, hub->busy_bits); + retval = usb_hub_set_port_power(hdev, port1, false); + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); + clear_bit(port1, hub->busy_bits); usb_autopm_put_interface(intf); return retval; } @@ -130,6 +165,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_is_on = true; port_dev->dev.parent = hub->intfdev; port_dev->dev.groups = port_dev_group; port_dev->dev.type = &usb_port_device_type; |