On Tuesday, November 13, 2012 04:00:03 PM Lan Tianyu wrote: > 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. > > Signed-off-by: Lan Tianyu <tianyu.lan@xxxxxxxxx> Again, from the PM side: Acked-by: Rafael J. Wysocki <rafael.j.wysocki@xxxxxxxxx> although as far as the USB internals are concerned, I'm far from being an expert. :-) Thanks, Rafael > --- > drivers/usb/core/hub.c | 76 +++++++++++++++++++++++++++++++++++++++++++++-- > drivers/usb/core/port.c | 1 + > include/linux/usb.h | 2 ++ > 3 files changed, 76 insertions(+), 3 deletions(-) > > diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c > index 2cb414e..267e9d7 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> > @@ -159,11 +160,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) > { > @@ -855,7 +859,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); > @@ -2852,6 +2860,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; > > @@ -2946,6 +2955,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; > } > @@ -3029,6 +3051,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 > @@ -3066,10 +3107,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) { > + 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)) > @@ -3077,8 +3144,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, > @@ -4256,6 +4321,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) > usb_disconnect(&hub->ports[port1 - 1]->child); > diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c > index 1cda766..b7388fd 100644 > --- a/drivers/usb/core/port.c > +++ b/drivers/usb/core/port.c > @@ -99,6 +99,7 @@ int usb_hub_create_port_device(struct device *intfdev, > { > int retval; > > + port_dev->power_on = true; > port_dev->portnum = port1; > port_dev->dev.parent = intfdev; > port_dev->dev.groups = port_dev_group; > diff --git a/include/linux/usb.h b/include/linux/usb.h > index 71510bf..8002640 100644 > --- a/include/linux/usb.h > +++ b/include/linux/usb.h > @@ -579,6 +579,7 @@ struct usb_device { > * @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; > @@ -586,6 +587,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) \ > container_of(_dev, struct usb_port, dev) > -- I speak only for myself. Rafael J. Wysocki, Intel Open Source Technology Center. -- 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