Commit aa8e54b5 authored by Tomeu Vizoso's avatar Tomeu Vizoso Committed by Rafael J. Wysocki

PM / sleep: Go direct_complete if driver has no callbacks

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: default avatarTomeu Vizoso <tomeu.vizoso@collabora.com>
Reviewed-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
parent 989561de
...@@ -250,6 +250,8 @@ static void driver_bound(struct device *dev) ...@@ -250,6 +250,8 @@ static void driver_bound(struct device *dev)
klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices); 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 * Make sure the device is no longer in one of the deferred lists and
* kick off retrying all pending devices * kick off retrying all pending devices
...@@ -766,6 +768,7 @@ static void __device_release_driver(struct device *dev) ...@@ -766,6 +768,7 @@ static void __device_release_driver(struct device *dev)
pm_runtime_reinit(dev); pm_runtime_reinit(dev);
klist_remove(&dev->p->knode_driver); klist_remove(&dev->p->knode_driver);
device_pm_check_callbacks(dev);
if (dev->bus) if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier, blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_UNBOUND_DRIVER, BUS_NOTIFY_UNBOUND_DRIVER,
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
#include <linux/acpi.h> #include <linux/acpi.h>
#include <linux/pm_domain.h> #include <linux/pm_domain.h>
#include "power.h"
/** /**
* dev_pm_get_subsys_data - Create or refcount power.subsys_data for device. * dev_pm_get_subsys_data - Create or refcount power.subsys_data for device.
* @dev: Device to handle. * @dev: Device to handle.
...@@ -147,5 +149,6 @@ void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd) ...@@ -147,5 +149,6 @@ void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd)
WARN(device_is_bound(dev), WARN(device_is_bound(dev),
"PM domains can only be changed for unbound devices\n"); "PM domains can only be changed for unbound devices\n");
dev->pm_domain = pd; dev->pm_domain = pd;
device_pm_check_callbacks(dev);
} }
EXPORT_SYMBOL_GPL(dev_pm_domain_set); EXPORT_SYMBOL_GPL(dev_pm_domain_set);
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
#include <linux/suspend.h> #include <linux/suspend.h>
#include <linux/export.h> #include <linux/export.h>
#include "power.h"
#define GENPD_RETRY_MAX_MS 250 /* Approximate */ #define GENPD_RETRY_MAX_MS 250 /* Approximate */
#define GENPD_DEV_CALLBACK(genpd, type, callback, dev) \ #define GENPD_DEV_CALLBACK(genpd, type, callback, dev) \
......
...@@ -125,6 +125,7 @@ void device_pm_add(struct device *dev) ...@@ -125,6 +125,7 @@ void device_pm_add(struct device *dev)
{ {
pr_debug("PM: Adding info for %s:%s\n", pr_debug("PM: Adding info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus", dev_name(dev)); dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
device_pm_check_callbacks(dev);
mutex_lock(&dpm_list_mtx); mutex_lock(&dpm_list_mtx);
if (dev->parent && dev->parent->power.is_prepared) if (dev->parent && dev->parent->power.is_prepared)
dev_warn(dev, "parent %s should not be sleeping\n", dev_warn(dev, "parent %s should not be sleeping\n",
...@@ -147,6 +148,7 @@ void device_pm_remove(struct device *dev) ...@@ -147,6 +148,7 @@ void device_pm_remove(struct device *dev)
mutex_unlock(&dpm_list_mtx); mutex_unlock(&dpm_list_mtx);
device_wakeup_disable(dev); device_wakeup_disable(dev);
pm_runtime_remove(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) ...@@ -1572,6 +1574,11 @@ static int device_prepare(struct device *dev, pm_message_t state)
dev->power.wakeup_path = device_may_wakeup(dev); 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) { if (dev->pm_domain) {
info = "preparing power domain "; info = "preparing power domain ";
callback = dev->pm_domain->ops.prepare; callback = dev->pm_domain->ops.prepare;
...@@ -1594,6 +1601,7 @@ static int device_prepare(struct device *dev, pm_message_t state) ...@@ -1594,6 +1601,7 @@ static int device_prepare(struct device *dev, pm_message_t state)
if (callback) if (callback)
ret = callback(dev); ret = callback(dev);
unlock:
device_unlock(dev); device_unlock(dev);
if (ret < 0) { if (ret < 0) {
...@@ -1736,3 +1744,30 @@ void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *)) ...@@ -1736,3 +1744,30 @@ void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *))
device_pm_unlock(); device_pm_unlock();
} }
EXPORT_SYMBOL_GPL(dpm_for_each_dev); 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);
}
...@@ -125,6 +125,7 @@ extern void device_pm_remove(struct device *); ...@@ -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_before(struct device *, struct device *);
extern void device_pm_move_after(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_move_last(struct device *);
extern void device_pm_check_callbacks(struct device *dev);
#else /* !CONFIG_PM_SLEEP */ #else /* !CONFIG_PM_SLEEP */
...@@ -143,6 +144,8 @@ static inline void device_pm_move_after(struct device *deva, ...@@ -143,6 +144,8 @@ static inline void device_pm_move_after(struct device *deva,
struct device *devb) {} struct device *devb) {}
static inline void device_pm_move_last(struct device *dev) {} static inline void device_pm_move_last(struct device *dev) {}
static inline void device_pm_check_callbacks(struct device *dev) {}
#endif /* !CONFIG_PM_SLEEP */ #endif /* !CONFIG_PM_SLEEP */
static inline void device_pm_init(struct device *dev) static inline void device_pm_init(struct device *dev)
......
...@@ -573,6 +573,7 @@ struct dev_pm_info { ...@@ -573,6 +573,7 @@ struct dev_pm_info {
struct wakeup_source *wakeup; struct wakeup_source *wakeup;
bool wakeup_path:1; bool wakeup_path:1;
bool syscore:1; bool syscore:1;
bool no_pm_callbacks:1; /* Owned by the PM core */
#else #else
unsigned int should_wakeup:1; unsigned int should_wakeup:1;
#endif #endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment