PM / sleep: Go direct_complete if driver has no callbacks
authorTomeu Vizoso <tomeu.vizoso@collabora.com>
Thu, 7 Jan 2016 15:46:14 +0000 (16:46 +0100)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Fri, 8 Jan 2016 00:12:06 +0000 (01:12 +0100)
If a suitable prepare callback cannot be found for a given device and
its driver has no PM callbacks at all, assume that it can go direct to
complete when the system goes to sleep.

The reason for this is that there's lots of devices in a system that do
no PM at all and there's no reason for them to prevent their ancestors
to do direct_complete if they can support it.

Signed-off-by: Tomeu Vizoso <tomeu.vizoso@collabora.com>
Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/base/dd.c
drivers/base/power/common.c
drivers/base/power/domain.c
drivers/base/power/main.c
drivers/base/power/power.h
include/linux/pm.h

index 13a0d66e57824b9fe01dc0dd5536eb77aa58148a..049942176b00abf76780bdc8bbe20fbc28de8ec8 100644 (file)
@@ -250,6 +250,8 @@ static void driver_bound(struct device *dev)
 
        klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
 
+       device_pm_check_callbacks(dev);
+
        /*
         * Make sure the device is no longer in one of the deferred lists and
         * kick off retrying all pending devices
@@ -766,6 +768,7 @@ static void __device_release_driver(struct device *dev)
                pm_runtime_reinit(dev);
 
                klist_remove(&dev->p->knode_driver);
+               device_pm_check_callbacks(dev);
                if (dev->bus)
                        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                                                     BUS_NOTIFY_UNBOUND_DRIVER,
index 02812bcabcac7985be4ed3421d2b4eb2c65846f6..93ed14cc22524ccdd04301dbe460e0958bb69acf 100644 (file)
@@ -14,6 +14,8 @@
 #include <linux/acpi.h>
 #include <linux/pm_domain.h>
 
+#include "power.h"
+
 /**
  * dev_pm_get_subsys_data - Create or refcount power.subsys_data for device.
  * @dev: Device to handle.
@@ -147,5 +149,6 @@ void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd)
        WARN(device_is_bound(dev),
             "PM domains can only be changed for unbound devices\n");
        dev->pm_domain = pd;
+       device_pm_check_callbacks(dev);
 }
 EXPORT_SYMBOL_GPL(dev_pm_domain_set);
index abbac6fe8fd50dadedac2f6aa63137b61188c8ef..33a5f4b752ed7f69344f55060b69f9a507e7d41b 100644 (file)
@@ -20,6 +20,8 @@
 #include <linux/suspend.h>
 #include <linux/export.h>
 
+#include "power.h"
+
 #define GENPD_RETRY_MAX_MS     250             /* Approximate */
 
 #define GENPD_DEV_CALLBACK(genpd, type, callback, dev)         \
index 9d626ac08d9c05d6cd5ee97559950bedf89976f8..6e7c3ccea24bbd3d2c10e199fe4b2018477a8a0c 100644 (file)
@@ -125,6 +125,7 @@ void device_pm_add(struct device *dev)
 {
        pr_debug("PM: Adding info for %s:%s\n",
                 dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
+       device_pm_check_callbacks(dev);
        mutex_lock(&dpm_list_mtx);
        if (dev->parent && dev->parent->power.is_prepared)
                dev_warn(dev, "parent %s should not be sleeping\n",
@@ -147,6 +148,7 @@ void device_pm_remove(struct device *dev)
        mutex_unlock(&dpm_list_mtx);
        device_wakeup_disable(dev);
        pm_runtime_remove(dev);
+       device_pm_check_callbacks(dev);
 }
 
 /**
@@ -1572,6 +1574,11 @@ static int device_prepare(struct device *dev, pm_message_t state)
 
        dev->power.wakeup_path = device_may_wakeup(dev);
 
+       if (dev->power.no_pm_callbacks) {
+               ret = 1;        /* Let device go direct_complete */
+               goto unlock;
+       }
+
        if (dev->pm_domain) {
                info = "preparing power domain ";
                callback = dev->pm_domain->ops.prepare;
@@ -1594,6 +1601,7 @@ static int device_prepare(struct device *dev, pm_message_t state)
        if (callback)
                ret = callback(dev);
 
+unlock:
        device_unlock(dev);
 
        if (ret < 0) {
@@ -1736,3 +1744,30 @@ void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *))
        device_pm_unlock();
 }
 EXPORT_SYMBOL_GPL(dpm_for_each_dev);
+
+static bool pm_ops_is_empty(const struct dev_pm_ops *ops)
+{
+       if (!ops)
+               return true;
+
+       return !ops->prepare &&
+              !ops->suspend &&
+              !ops->suspend_late &&
+              !ops->suspend_noirq &&
+              !ops->resume_noirq &&
+              !ops->resume_early &&
+              !ops->resume &&
+              !ops->complete;
+}
+
+void device_pm_check_callbacks(struct device *dev)
+{
+       spin_lock_irq(&dev->power.lock);
+       dev->power.no_pm_callbacks =
+               (!dev->bus || pm_ops_is_empty(dev->bus->pm)) &&
+               (!dev->class || pm_ops_is_empty(dev->class->pm)) &&
+               (!dev->type || pm_ops_is_empty(dev->type->pm)) &&
+               (!dev->pm_domain || pm_ops_is_empty(&dev->pm_domain->ops)) &&
+               (!dev->driver || pm_ops_is_empty(dev->driver->pm));
+       spin_unlock_irq(&dev->power.lock);
+}
index 8b06193d4a5e9f1aa42cfae570a5499be86ee4a5..50e30e7b059d18a034c80b2a71fd846a098189a1 100644 (file)
@@ -125,6 +125,7 @@ extern void device_pm_remove(struct device *);
 extern void device_pm_move_before(struct device *, struct device *);
 extern void device_pm_move_after(struct device *, struct device *);
 extern void device_pm_move_last(struct device *);
+extern void device_pm_check_callbacks(struct device *dev);
 
 #else /* !CONFIG_PM_SLEEP */
 
@@ -143,6 +144,8 @@ static inline void device_pm_move_after(struct device *deva,
                                        struct device *devb) {}
 static inline void device_pm_move_last(struct device *dev) {}
 
+static inline void device_pm_check_callbacks(struct device *dev) {}
+
 #endif /* !CONFIG_PM_SLEEP */
 
 static inline void device_pm_init(struct device *dev)
index 528be6787796b52a405fbc0af8bf707c31d55192..6a5d654f444726abc824d9d07377ad66e86c6a44 100644 (file)
@@ -573,6 +573,7 @@ struct dev_pm_info {
        struct wakeup_source    *wakeup;
        bool                    wakeup_path:1;
        bool                    syscore:1;
+       bool                    no_pm_callbacks:1;      /* Owned by the PM core */
 #else
        unsigned int            should_wakeup:1;
 #endif