Merge branch 'pm-qos'
Rafael J. Wysocki [Fri, 16 Mar 2012 20:49:24 +0000 (21:49 +0100)]
* pm-qos:
  sh_mmcif / PM: Use PM QoS latency constraint
  tmio_mmc / PM: Use PM QoS latency constraint
  PM / QoS: Make it possible to expose PM QoS latency constraints

Documentation/ABI/testing/sysfs-devices-power
drivers/base/power/power.h
drivers/base/power/qos.c
drivers/base/power/sysfs.c
drivers/mmc/host/sh_mmcif.c
drivers/mmc/host/tmio_mmc_pio.c
include/linux/pm.h
include/linux/pm_qos.h

index 8ffbc25..840f7d6 100644 (file)
@@ -165,3 +165,21 @@ Description:
 
                Not all drivers support this attribute.  If it isn't supported,
                attempts to read or write it will yield I/O errors.
+
+What:          /sys/devices/.../power/pm_qos_latency_us
+Date:          March 2012
+Contact:       Rafael J. Wysocki <rjw@sisk.pl>
+Description:
+               The /sys/devices/.../power/pm_qos_resume_latency_us attribute
+               contains the PM QoS resume latency limit for the given device,
+               which is the maximum allowed time it can take to resume the
+               device, after it has been suspended at run time, from a resume
+               request to the moment the device will be ready to process I/O,
+               in microseconds.  If it is equal to 0, however, this means that
+               the PM QoS resume latency may be arbitrary.
+
+               Not all drivers support this attribute.  If it isn't supported,
+               it is not present.
+
+               This attribute has no effect on system-wide suspend/resume and
+               hibernation.
index 9bf6232..eeb4bff 100644 (file)
@@ -71,6 +71,8 @@ extern void dpm_sysfs_remove(struct device *dev);
 extern void rpm_sysfs_remove(struct device *dev);
 extern int wakeup_sysfs_add(struct device *dev);
 extern void wakeup_sysfs_remove(struct device *dev);
+extern int pm_qos_sysfs_add(struct device *dev);
+extern void pm_qos_sysfs_remove(struct device *dev);
 
 #else /* CONFIG_PM */
 
@@ -79,5 +81,7 @@ static inline void dpm_sysfs_remove(struct device *dev) {}
 static inline void rpm_sysfs_remove(struct device *dev) {}
 static inline int wakeup_sysfs_add(struct device *dev) { return 0; }
 static inline void wakeup_sysfs_remove(struct device *dev) {}
+static inline int pm_qos_sysfs_add(struct device *dev) { return 0; }
+static inline void pm_qos_sysfs_remove(struct device *dev) {}
 
 #endif
index c5d3588..7185557 100644 (file)
@@ -41,6 +41,7 @@
 #include <linux/mutex.h>
 #include <linux/export.h>
 
+#include "power.h"
 
 static DEFINE_MUTEX(dev_pm_qos_mtx);
 
@@ -166,6 +167,12 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
        struct dev_pm_qos_request *req, *tmp;
        struct pm_qos_constraints *c;
 
+       /*
+        * If the device's PM QoS resume latency limit has been exposed to user
+        * space, it has to be hidden at this point.
+        */
+       dev_pm_qos_hide_latency_limit(dev);
+
        mutex_lock(&dev_pm_qos_mtx);
 
        dev->power.power_state = PMSG_INVALID;
@@ -445,3 +452,57 @@ int dev_pm_qos_add_ancestor_request(struct device *dev,
        return error;
 }
 EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request);
+
+#ifdef CONFIG_PM_RUNTIME
+static void __dev_pm_qos_drop_user_request(struct device *dev)
+{
+       dev_pm_qos_remove_request(dev->power.pq_req);
+       dev->power.pq_req = 0;
+}
+
+/**
+ * dev_pm_qos_expose_latency_limit - Expose PM QoS latency limit to user space.
+ * @dev: Device whose PM QoS latency limit is to be exposed to user space.
+ * @value: Initial value of the latency limit.
+ */
+int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value)
+{
+       struct dev_pm_qos_request *req;
+       int ret;
+
+       if (!device_is_registered(dev) || value < 0)
+               return -EINVAL;
+
+       if (dev->power.pq_req)
+               return -EEXIST;
+
+       req = kzalloc(sizeof(*req), GFP_KERNEL);
+       if (!req)
+               return -ENOMEM;
+
+       ret = dev_pm_qos_add_request(dev, req, value);
+       if (ret < 0)
+               return ret;
+
+       dev->power.pq_req = req;
+       ret = pm_qos_sysfs_add(dev);
+       if (ret)
+               __dev_pm_qos_drop_user_request(dev);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit);
+
+/**
+ * dev_pm_qos_hide_latency_limit - Hide PM QoS latency limit from user space.
+ * @dev: Device whose PM QoS latency limit is to be hidden from user space.
+ */
+void dev_pm_qos_hide_latency_limit(struct device *dev)
+{
+       if (dev->power.pq_req) {
+               pm_qos_sysfs_remove(dev);
+               __dev_pm_qos_drop_user_request(dev);
+       }
+}
+EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit);
+#endif /* CONFIG_PM_RUNTIME */
index adf41be..95c12f6 100644 (file)
@@ -5,6 +5,7 @@
 #include <linux/device.h>
 #include <linux/string.h>
 #include <linux/export.h>
+#include <linux/pm_qos.h>
 #include <linux/pm_runtime.h>
 #include <linux/atomic.h>
 #include <linux/jiffies.h>
@@ -217,6 +218,31 @@ static ssize_t autosuspend_delay_ms_store(struct device *dev,
 static DEVICE_ATTR(autosuspend_delay_ms, 0644, autosuspend_delay_ms_show,
                autosuspend_delay_ms_store);
 
+static ssize_t pm_qos_latency_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d\n", dev->power.pq_req->node.prio);
+}
+
+static ssize_t pm_qos_latency_store(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t n)
+{
+       s32 value;
+       int ret;
+
+       if (kstrtos32(buf, 0, &value))
+               return -EINVAL;
+
+       if (value < 0)
+               return -EINVAL;
+
+       ret = dev_pm_qos_update_request(dev->power.pq_req, value);
+       return ret < 0 ? ret : n;
+}
+
+static DEVICE_ATTR(pm_qos_resume_latency_us, 0644,
+                  pm_qos_latency_show, pm_qos_latency_store);
 #endif /* CONFIG_PM_RUNTIME */
 
 #ifdef CONFIG_PM_SLEEP
@@ -490,6 +516,17 @@ static struct attribute_group pm_runtime_attr_group = {
        .attrs  = runtime_attrs,
 };
 
+static struct attribute *pm_qos_attrs[] = {
+#ifdef CONFIG_PM_RUNTIME
+       &dev_attr_pm_qos_resume_latency_us.attr,
+#endif /* CONFIG_PM_RUNTIME */
+       NULL,
+};
+static struct attribute_group pm_qos_attr_group = {
+       .name   = power_group_name,
+       .attrs  = pm_qos_attrs,
+};
+
 int dpm_sysfs_add(struct device *dev)
 {
        int rc;
@@ -530,6 +567,16 @@ void wakeup_sysfs_remove(struct device *dev)
        sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group);
 }
 
+int pm_qos_sysfs_add(struct device *dev)
+{
+       return sysfs_merge_group(&dev->kobj, &pm_qos_attr_group);
+}
+
+void pm_qos_sysfs_remove(struct device *dev)
+{
+       sysfs_unmerge_group(&dev->kobj, &pm_qos_attr_group);
+}
+
 void rpm_sysfs_remove(struct device *dev)
 {
        sysfs_unmerge_group(&dev->kobj, &pm_runtime_attr_group);
index 352d479..75a4854 100644 (file)
@@ -56,6 +56,7 @@
 #include <linux/mmc/sh_mmcif.h>
 #include <linux/pagemap.h>
 #include <linux/platform_device.h>
+#include <linux/pm_qos.h>
 #include <linux/pm_runtime.h>
 #include <linux/spinlock.h>
 #include <linux/module.h>
@@ -1346,6 +1347,8 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)
        if (ret < 0)
                goto clean_up5;
 
+       dev_pm_qos_expose_latency_limit(&pdev->dev, 100);
+
        dev_info(&pdev->dev, "driver version %s\n", DRIVER_VERSION);
        dev_dbg(&pdev->dev, "chip ver H'%04x\n",
                sh_mmcif_readl(host->addr, MMCIF_CE_VERSION) & 0x0000ffff);
@@ -1376,6 +1379,8 @@ static int __devexit sh_mmcif_remove(struct platform_device *pdev)
        host->dying = true;
        pm_runtime_get_sync(&pdev->dev);
 
+       dev_pm_qos_hide_latency_limit(&pdev->dev);
+
        mmc_remove_host(host->mmc);
        sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
 
index 5f9ad74..e219889 100644 (file)
@@ -39,6 +39,7 @@
 #include <linux/module.h>
 #include <linux/pagemap.h>
 #include <linux/platform_device.h>
+#include <linux/pm_qos.h>
 #include <linux/pm_runtime.h>
 #include <linux/scatterlist.h>
 #include <linux/spinlock.h>
@@ -955,6 +956,8 @@ int __devinit tmio_mmc_host_probe(struct tmio_mmc_host **host,
 
        mmc_add_host(mmc);
 
+       dev_pm_qos_expose_latency_limit(&pdev->dev, 100);
+
        /* Unmask the IRQs we want to know about */
        if (!_host->chan_rx)
                irq_mask |= TMIO_MASK_READOP;
@@ -993,6 +996,8 @@ void tmio_mmc_host_remove(struct tmio_mmc_host *host)
                || host->mmc->caps & MMC_CAP_NONREMOVABLE)
                pm_runtime_get_sync(&pdev->dev);
 
+       dev_pm_qos_hide_latency_limit(&pdev->dev);
+
        mmc_remove_host(host->mmc);
        cancel_work_sync(&host->done);
        cancel_delayed_work_sync(&host->delayed_reset_work);
index d6dd6f6..715305e 100644 (file)
@@ -546,6 +546,7 @@ struct dev_pm_info {
        unsigned long           accounting_timestamp;
        ktime_t                 suspend_time;
        s64                     max_time_suspended_ns;
+       struct dev_pm_qos_request *pq_req;
 #endif
        struct pm_subsys_data   *subsys_data;  /* Owned by the subsystem. */
        struct pm_qos_constraints *constraints;
index c8a541e..2e9191a 100644 (file)
@@ -137,4 +137,13 @@ static inline int dev_pm_qos_add_ancestor_request(struct device *dev,
                        { return 0; }
 #endif
 
+#ifdef CONFIG_PM_RUNTIME
+int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value);
+void dev_pm_qos_hide_latency_limit(struct device *dev);
+#else
+static inline int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value)
+                       { return 0; }
+static inline void dev_pm_qos_hide_latency_limit(struct device *dev) {}
+#endif
+
 #endif