This patch is to add usb port auto power off mechanism.
When usb device is suspending, usb core will send clear usb port's
POWER feature requst to power off port if all conditions were met.
These conditions are remote wakeup enable, pm qos NO_POWER_OFF flags
and persist feature. 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....@intel.com>
---
 drivers/usb/core/hub.c |   78 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 75 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index d0e1f08..1e9a395 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>
@@ -47,6 +48,7 @@ struct usb_port {
        struct device dev;
        struct dev_state *port_owner;
        enum usb_port_connect_type connect_type;
+       unsigned                power_state:1;
 };
 
 struct usb_hub {
@@ -166,6 +168,11 @@ MODULE_PARM_DESC(use_both_schemes,
 DECLARE_RWSEM(ehci_cf_port_reset_rwsem);
 EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem);
 
+#define USB_PORT_POWER_STATE_ON                0
+#define USB_PORT_POWER_STATE_OFF       1
+
+#define HUB_PORT_RECONNECT_TRIES       20
+
 #define HUB_DEBOUNCE_TIMEOUT   1500
 #define HUB_DEBOUNCE_STEP        25
 #define HUB_DEBOUNCE_STABLE     100
@@ -174,6 +181,7 @@ EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem);
        container_of(_dev, struct usb_port, dev)
 
 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)
 {
@@ -851,7 +859,12 @@ 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_state
+                               == USB_PORT_POWER_STATE_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);
@@ -2876,6 +2889,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;
 
@@ -2970,6 +2984,23 @@ 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) {
+               port_dev->power_state = USB_PORT_POWER_STATE_OFF;
+               if (clear_port_feature(udev->parent, port1,
+                               USB_PORT_FEAT_POWER)) {
+                       port_dev->power_state = USB_PORT_POWER_STATE_ON;
+               }
+       }
+
        usb_mark_last_busy(hub->hdev);
        return status;
 }
@@ -3053,6 +3084,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
@@ -3094,6 +3144,25 @@ int usb_port_resume(struct usb_device *udev, 
pm_message_t msg)
        int             status;
        u16             portchange, portstatus;
 
+
+       set_bit(port1, hub->busy_bits);
+
+       if (hub->ports[port1 - 1]->power_state == USB_PORT_POWER_STATE_OFF) {
+               set_port_feature(udev->parent, port1, USB_PORT_FEAT_POWER);
+
+               /*
+                * 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;
+               }
+               hub->ports[port1 - 1]->power_state = USB_PORT_POWER_STATE_ON;
+       }
+
        /* 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))
@@ -3101,8 +3170,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,
@@ -4280,6 +4347,11 @@ static void hub_port_connect_change(struct usb_hub *hub, 
int port1,
                }
        }
 
+       if (hub->ports[port1 - 1]->power_state == USB_PORT_POWER_STATE_OFF) {
+               clear_bit(port1, hub->change_bits);
+               return;
+       }
+
        /* Disconnect any existing devices under this port */
        if (udev)
                usb_disconnect(&hub->ports[port1 - 1]->child);
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to