usbcore: add sysfs support to xHCI usb2 hardware LPM
Andiry Xu [Fri, 23 Sep 2011 21:19:53 +0000 (14:19 -0700)]
This patch adds sysfs support to xHCI usb2 hardware LPM, so developer can
enable and disable usb2 hardware LPM manually for test purpose.

Signed-off-by: Andiry Xu <andiry.xu@amd.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

Documentation/ABI/testing/sysfs-bus-usb
Documentation/usb/power-management.txt
drivers/usb/core/sysfs.c

index 294aa86..e647378 100644 (file)
@@ -142,3 +142,18 @@ Description:
                such devices.
 Users:
                usb_modeswitch
+
+What:          /sys/bus/usb/devices/.../power/usb2_hardware_lpm
+Date:          September 2011
+Contact:       Andiry Xu <andiry.xu@amd.com>
+Description:
+               If CONFIG_USB_SUSPEND is set and a USB 2.0 lpm-capable device
+               is plugged in to a xHCI host which support link PM, it will
+               perform a LPM test; if the test is passed and host supports
+               USB2 hardware LPM (xHCI 1.0 feature), USB2 hardware LPM will
+               be enabled for the device and the USB device directory will
+               contain a file named power/usb2_hardware_lpm.  The file holds
+               a string value (enable or disable) indicating whether or not
+               USB2 hardware LPM is enabled for the device. Developer can
+               write y/Y/1 or n/N/0 to the file to enable/disable the
+               feature.
index c9ffa9c..9d85d96 100644 (file)
@@ -487,3 +487,29 @@ succeed, it may still remain active and thus cause the system to
 resume as soon as the system suspend is complete.  Or the remote
 wakeup may fail and get lost.  Which outcome occurs depends on timing
 and on the hardware and firmware design.
+
+
+       xHCI hardware link PM
+       ---------------------
+
+xHCI host controller provides hardware link power management to usb2.0
+(xHCI 1.0 feature) and usb3.0 devices which support link PM. By
+enabling hardware LPM, the host can automatically put the device into
+lower power state(L1 for usb2.0 devices, or U1/U2 for usb3.0 devices),
+which state device can enter and resume very quickly.
+
+The user interface for controlling USB2 hardware LPM is located in the
+power/ subdirectory of each USB device's sysfs directory, that is, in
+/sys/bus/usb/devices/.../power/ where "..." is the device's ID. The
+relevant attribute files is usb2_hardware_lpm.
+
+       power/usb2_hardware_lpm
+
+               When a USB2 device which support LPM is plugged to a
+               xHCI host root hub which support software LPM, the
+               host will run a software LPM test for it; if the device
+               enters L1 state and resume successfully and the host
+               supports USB2 hardware LPM, this file will show up and
+               driver will enable hardware LPM for the device. You
+               can write y/Y/1 or n/N/0 to the file to enable/disable
+               USB2 hardware LPM manually. This is for test purpose mainly.
index cf05b97..662c0cf 100644 (file)
@@ -412,6 +412,56 @@ set_level(struct device *dev, struct device_attribute *attr,
 
 static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level);
 
+static ssize_t
+show_usb2_hardware_lpm(struct device *dev, struct device_attribute *attr,
+                               char *buf)
+{
+       struct usb_device *udev = to_usb_device(dev);
+       const char *p;
+
+       if (udev->usb2_hw_lpm_enabled == 1)
+               p = "enabled";
+       else
+               p = "disabled";
+
+       return sprintf(buf, "%s\n", p);
+}
+
+static ssize_t
+set_usb2_hardware_lpm(struct device *dev, struct device_attribute *attr,
+               const char *buf, size_t count)
+{
+       struct usb_device *udev = to_usb_device(dev);
+       bool value;
+       int ret;
+
+       usb_lock_device(udev);
+
+       ret = strtobool(buf, &value);
+
+       if (!ret)
+               ret = usb_set_usb2_hardware_lpm(udev, value);
+
+       usb_unlock_device(udev);
+
+       if (!ret)
+               return count;
+
+       return ret;
+}
+
+static DEVICE_ATTR(usb2_hardware_lpm, S_IRUGO | S_IWUSR, show_usb2_hardware_lpm,
+                       set_usb2_hardware_lpm);
+
+static struct attribute *usb2_hardware_lpm_attr[] = {
+       &dev_attr_usb2_hardware_lpm.attr,
+       NULL,
+};
+static struct attribute_group usb2_hardware_lpm_attr_group = {
+       .name   = power_group_name,
+       .attrs  = usb2_hardware_lpm_attr,
+};
+
 static struct attribute *power_attrs[] = {
        &dev_attr_autosuspend.attr,
        &dev_attr_level.attr,
@@ -428,13 +478,20 @@ static int add_power_attributes(struct device *dev)
 {
        int rc = 0;
 
-       if (is_usb_device(dev))
+       if (is_usb_device(dev)) {
+               struct usb_device *udev = to_usb_device(dev);
                rc = sysfs_merge_group(&dev->kobj, &power_attr_group);
+               if (udev->usb2_hw_lpm_capable == 1)
+                       rc = sysfs_merge_group(&dev->kobj,
+                                       &usb2_hardware_lpm_attr_group);
+       }
+
        return rc;
 }
 
 static void remove_power_attributes(struct device *dev)
 {
+       sysfs_unmerge_group(&dev->kobj, &usb2_hardware_lpm_attr_group);
        sysfs_unmerge_group(&dev->kobj, &power_attr_group);
 }