Skip to content

Commit

Permalink
usb: add runtime pm support for usb port device
Browse files Browse the repository at this point in the history
This patch is to add runtime pm callback for usb port device.
Set/clear PORT_POWER feature in the resume/suspend callback.
Add portnum for struct usb_port to record port number. Do
pm_rumtime_get_sync/put(portdev) when a device is plugged/unplugged
to prevent it from being powered off when it is active.

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>
  • Loading branch information
Lan Tianyu authored and Greg Kroah-Hartman committed Jan 25, 2013
1 parent 6802771 commit 971fcd4
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 0 deletions.
27 changes: 27 additions & 0 deletions drivers/usb/core/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,27 @@ static void hub_tt_work(struct work_struct *work)
spin_unlock_irqrestore (&hub->tt.lock, flags);
}

/**
* usb_hub_set_port_power - control hub port's power state
* @hdev: target hub
* @port1: port index
* @set: expected status
*
* call this function to control port's power via setting or
* clearing the port's PORT_POWER feature.
*/
int usb_hub_set_port_power(struct usb_device *hdev, int port1,
bool set)
{
int ret;

if (set)
ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
else
ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
return ret;
}

/**
* usb_hub_clear_tt_buffer - clear control/bulk TT state in high speed hub
* @urb: an URB associated with the failed or incomplete split transaction
Expand Down Expand Up @@ -1569,6 +1590,7 @@ static void hub_disconnect(struct usb_interface *intf)
kfree(hub->status);
kfree(hub->buffer);

pm_suspend_ignore_children(&intf->dev, false);
kref_put(&hub->kref, hub_release);
}

Expand Down Expand Up @@ -1671,6 +1693,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)

usb_set_intfdata (intf, hub);
intf->needs_remote_wakeup = 1;
pm_suspend_ignore_children(&intf->dev, true);

if (hdev->speed == USB_SPEED_HIGH)
highspeed_hubs++;
Expand Down Expand Up @@ -1997,6 +2020,8 @@ void usb_disconnect(struct usb_device **pdev)

sysfs_remove_link(&udev->dev.kobj, "port");
sysfs_remove_link(&port_dev->dev.kobj, "device");

pm_runtime_put(&port_dev->dev);
}

usb_remove_ep_devs(&udev->ep0);
Expand Down Expand Up @@ -2307,6 +2332,8 @@ int usb_new_device(struct usb_device *udev)
sysfs_remove_link(&udev->dev.kobj, "port");
goto fail;
}

pm_runtime_get_sync(&port_dev->dev);
}

(void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
Expand Down
4 changes: 4 additions & 0 deletions drivers/usb/core/hub.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,14 @@ struct usb_hub {
* @dev: generic device interface
* @port_owner: port's owner
* @connect_type: port's connect type
* @portnum: port index num based one
*/
struct usb_port {
struct usb_device *child;
struct device dev;
struct dev_state *port_owner;
enum usb_port_connect_type connect_type;
u8 portnum;
};

#define to_usb_port(_dev) \
Expand All @@ -94,4 +96,6 @@ extern int usb_hub_create_port_device(struct usb_hub *hub,
int port1);
extern void usb_hub_remove_port_device(struct usb_hub *hub,
int port1);
extern int usb_hub_set_port_power(struct usb_device *hdev,
int port1, bool set);

46 changes: 46 additions & 0 deletions drivers/usb/core/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

#include <linux/slab.h>
#include <linux/pm_qos.h>

#include "hub.h"

Expand Down Expand Up @@ -70,9 +71,50 @@ static void usb_port_device_release(struct device *dev)
kfree(port_dev);
}

#ifdef CONFIG_USB_SUSPEND
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);
int retval;

usb_autopm_get_interface(intf);
retval = usb_hub_set_port_power(hdev, port_dev->portnum, true);
usb_autopm_put_interface(intf);
return retval;
}

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);
int retval;

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);
usb_autopm_put_interface(intf);
return retval;
}
#endif

static const struct dev_pm_ops usb_port_pm_ops = {
#ifdef CONFIG_USB_SUSPEND
.runtime_suspend = usb_port_runtime_suspend,
.runtime_resume = usb_port_runtime_resume,
.runtime_idle = pm_generic_runtime_idle,
#endif
};

struct device_type usb_port_device_type = {
.name = "usb_port",
.release = usb_port_device_release,
.pm = &usb_port_pm_ops,
};

int usb_hub_create_port_device(struct usb_hub *hub, int port1)
Expand All @@ -87,6 +129,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->dev.parent = hub->intfdev;
port_dev->dev.groups = port_dev_group;
port_dev->dev.type = &usb_port_device_type;
Expand All @@ -96,6 +139,9 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
if (retval)
goto error_register;

pm_runtime_set_active(&port_dev->dev);
pm_runtime_enable(&port_dev->dev);

retval = usb_acpi_register_power_resources(&port_dev->dev);
if (retval && retval != -ENODEV)
dev_warn(&port_dev->dev, "the port can't register its ACPI power resource.\n");
Expand Down

0 comments on commit 971fcd4

Please sign in to comment.