usbcore: add sysfs support to xHCI usb2 hardware LPM
authorAndiry Xu <andiry.xu@amd.com>
Fri, 23 Sep 2011 21:19:53 +0000 (14:19 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Mon, 26 Sep 2011 22:51:10 +0000 (15:51 -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 294aa864a60a1d6056d2b5cf76536648f6d49567..e647378e9e88eaba8256af7b609cebc28d9506aa 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 c9ffa9ced7eec964e47c2513584f9041a6fb9c7e..9d85d96ec6e0517a236c22d0c3a20d71af0b8c4d 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 cf05b97693ea8ff7a0534a76fcc226f10bb57e68..662c0cf3a3e139b6ba9bccfcf18232e3b8e7fa8a 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);
 }