From d5e4cbfe2049fca375cb19c4bc0cf676e8b4a88a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 27 Nov 2011 13:11:36 +0100 Subject: [PATCH 01/18] PM / Domains: Make it possible to use per-device domain callbacks The current generic PM domains code requires that the same .stop(), .start() and .active_wakeup() device callback routines be used for all devices in the given domain, which is inflexible and may not cover some specific use cases. For this reason, make it possible to use device specific .start()/.stop() and .active_wakeup() callback routines by adding corresponding callback pointers to struct generic_pm_domain_data. Add a new helper routine, pm_genpd_register_callbacks(), that can be used to populate the new per-device callback pointers. Modify the shmobile's power domains code to allow drivers to add their own code to be run during the device stop and start operations with the help of the new callback pointers. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm --- arch/arm/mach-shmobile/pm-sh7372.c | 40 +++++++- drivers/base/power/domain.c | 152 ++++++++++++++++++++++------- include/linux/pm_domain.h | 27 ++++- 3 files changed, 175 insertions(+), 44 deletions(-) diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 34bbcbfb1706..6777bb1be059 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -156,7 +156,10 @@ static void sh7372_a4r_suspend(void) static bool pd_active_wakeup(struct device *dev) { - return true; + bool (*active_wakeup)(struct device *dev); + + active_wakeup = dev_gpd_data(dev)->ops.active_wakeup; + return active_wakeup ? active_wakeup(dev) : true; } static bool sh7372_power_down_forbidden(struct dev_pm_domain *domain) @@ -168,15 +171,44 @@ struct dev_power_governor sh7372_always_on_gov = { .power_down_ok = sh7372_power_down_forbidden, }; +static int sh7372_stop_dev(struct device *dev) +{ + int (*stop)(struct device *dev); + + stop = dev_gpd_data(dev)->ops.stop; + if (stop) { + int ret = stop(dev); + if (ret) + return ret; + } + return pm_clk_suspend(dev); +} + +static int sh7372_start_dev(struct device *dev) +{ + int (*start)(struct device *dev); + int ret; + + ret = pm_clk_resume(dev); + if (ret) + return ret; + + start = dev_gpd_data(dev)->ops.start; + if (start) + ret = start(dev); + + return ret; +} + void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd) { struct generic_pm_domain *genpd = &sh7372_pd->genpd; pm_genpd_init(genpd, sh7372_pd->gov, false); - genpd->stop_device = pm_clk_suspend; - genpd->start_device = pm_clk_resume; + genpd->dev_ops.stop = sh7372_stop_dev; + genpd->dev_ops.start = sh7372_start_dev; + genpd->dev_ops.active_wakeup = pd_active_wakeup; genpd->dev_irq_safe = true; - genpd->active_wakeup = pd_active_wakeup; genpd->power_off = pd_power_down; genpd->power_on = pd_power_up; __pd_power_up(sh7372_pd, false); diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 6790cf7eba5a..94afaa2686a6 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -15,6 +15,23 @@ #include #include #include +#include + +#define GENPD_DEV_CALLBACK(genpd, type, callback, dev) \ +({ \ + type (*__routine)(struct device *__d); \ + type __ret = (type)0; \ + \ + __routine = genpd->dev_ops.callback; \ + if (__routine) { \ + __ret = __routine(dev); \ + } else { \ + __routine = dev_gpd_data(dev)->ops.callback; \ + if (__routine) \ + __ret = __routine(dev); \ + } \ + __ret; \ +}) static LIST_HEAD(gpd_list); static DEFINE_MUTEX(gpd_list_lock); @@ -29,6 +46,16 @@ static struct generic_pm_domain *dev_to_genpd(struct device *dev) return pd_to_genpd(dev->pm_domain); } +static int genpd_stop_dev(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, stop, dev); +} + +static int genpd_start_dev(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, start, dev); +} + static bool genpd_sd_counter_dec(struct generic_pm_domain *genpd) { bool ret = false; @@ -199,13 +226,9 @@ static int __pm_genpd_save_device(struct pm_domain_data *pdd, mutex_unlock(&genpd->lock); if (drv && drv->pm && drv->pm->runtime_suspend) { - if (genpd->start_device) - genpd->start_device(dev); - + genpd_start_dev(genpd, dev); ret = drv->pm->runtime_suspend(dev); - - if (genpd->stop_device) - genpd->stop_device(dev); + genpd_stop_dev(genpd, dev); } mutex_lock(&genpd->lock); @@ -235,13 +258,9 @@ static void __pm_genpd_restore_device(struct pm_domain_data *pdd, mutex_unlock(&genpd->lock); if (drv && drv->pm && drv->pm->runtime_resume) { - if (genpd->start_device) - genpd->start_device(dev); - + genpd_start_dev(genpd, dev); drv->pm->runtime_resume(dev); - - if (genpd->stop_device) - genpd->stop_device(dev); + genpd_stop_dev(genpd, dev); } mutex_lock(&genpd->lock); @@ -413,6 +432,7 @@ static void genpd_power_off_work_fn(struct work_struct *work) static int pm_genpd_runtime_suspend(struct device *dev) { struct generic_pm_domain *genpd; + int ret; dev_dbg(dev, "%s()\n", __func__); @@ -422,11 +442,9 @@ static int pm_genpd_runtime_suspend(struct device *dev) might_sleep_if(!genpd->dev_irq_safe); - if (genpd->stop_device) { - int ret = genpd->stop_device(dev); - if (ret) - return ret; - } + ret = genpd_stop_dev(genpd, dev); + if (ret) + return ret; /* * If power.irq_safe is set, this routine will be run with interrupts @@ -502,8 +520,7 @@ static int pm_genpd_runtime_resume(struct device *dev) mutex_unlock(&genpd->lock); out: - if (genpd->start_device) - genpd->start_device(dev); + genpd_start_dev(genpd, dev); return 0; } @@ -534,6 +551,12 @@ static inline void genpd_power_off_work_fn(struct work_struct *work) {} #ifdef CONFIG_PM_SLEEP +static bool genpd_dev_active_wakeup(struct generic_pm_domain *genpd, + struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, bool, active_wakeup, dev); +} + /** * pm_genpd_sync_poweroff - Synchronously power off a PM domain and its masters. * @genpd: PM domain to power off, if possible. @@ -590,7 +613,7 @@ static bool resume_needed(struct device *dev, struct generic_pm_domain *genpd) if (!device_can_wakeup(dev)) return false; - active_wakeup = genpd->active_wakeup && genpd->active_wakeup(dev); + active_wakeup = genpd_dev_active_wakeup(genpd, dev); return device_may_wakeup(dev) ? active_wakeup : !active_wakeup; } @@ -646,7 +669,7 @@ static int pm_genpd_prepare(struct device *dev) /* * The PM domain must be in the GPD_STATE_ACTIVE state at this point, * so pm_genpd_poweron() will return immediately, but if the device - * is suspended (e.g. it's been stopped by .stop_device()), we need + * is suspended (e.g. it's been stopped by genpd_stop_dev()), we need * to make it operational. */ pm_runtime_resume(dev); @@ -714,12 +737,10 @@ static int pm_genpd_suspend_noirq(struct device *dev) if (ret) return ret; - if (dev->power.wakeup_path - && genpd->active_wakeup && genpd->active_wakeup(dev)) + if (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev)) return 0; - if (genpd->stop_device) - genpd->stop_device(dev); + genpd_stop_dev(genpd, dev); /* * Since all of the "noirq" callbacks are executed sequentially, it is @@ -761,8 +782,7 @@ static int pm_genpd_resume_noirq(struct device *dev) */ pm_genpd_poweron(genpd); genpd->suspended_count--; - if (genpd->start_device) - genpd->start_device(dev); + genpd_start_dev(genpd, dev); return pm_generic_resume_noirq(dev); } @@ -836,8 +856,7 @@ static int pm_genpd_freeze_noirq(struct device *dev) if (ret) return ret; - if (genpd->stop_device) - genpd->stop_device(dev); + genpd_stop_dev(genpd, dev); return 0; } @@ -864,8 +883,7 @@ static int pm_genpd_thaw_noirq(struct device *dev) if (genpd->suspend_power_off) return 0; - if (genpd->start_device) - genpd->start_device(dev); + genpd_start_dev(genpd, dev); return pm_generic_thaw_noirq(dev); } @@ -938,12 +956,10 @@ static int pm_genpd_dev_poweroff_noirq(struct device *dev) if (ret) return ret; - if (dev->power.wakeup_path - && genpd->active_wakeup && genpd->active_wakeup(dev)) + if (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev)) return 0; - if (genpd->stop_device) - genpd->stop_device(dev); + genpd_stop_dev(genpd, dev); /* * Since all of the "noirq" callbacks are executed sequentially, it is @@ -993,8 +1009,7 @@ static int pm_genpd_restore_noirq(struct device *dev) pm_genpd_poweron(genpd); genpd->suspended_count--; - if (genpd->start_device) - genpd->start_device(dev); + genpd_start_dev(genpd, dev); return pm_generic_restore_noirq(dev); } @@ -1279,6 +1294,69 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, return ret; } +/** + * pm_genpd_add_callbacks - Add PM domain callbacks to a given device. + * @dev: Device to add the callbacks to. + * @ops: Set of callbacks to add. + */ +int pm_genpd_add_callbacks(struct device *dev, struct gpd_dev_ops *ops) +{ + struct pm_domain_data *pdd; + int ret = 0; + + if (!(dev && dev->power.subsys_data && ops)) + return -EINVAL; + + pm_runtime_disable(dev); + device_pm_lock(); + + pdd = dev->power.subsys_data->domain_data; + if (pdd) { + struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd); + + gpd_data->ops = *ops; + } else { + ret = -EINVAL; + } + + device_pm_unlock(); + pm_runtime_enable(dev); + + return ret; +} +EXPORT_SYMBOL_GPL(pm_genpd_add_callbacks); + +/** + * pm_genpd_remove_callbacks - Remove PM domain callbacks from a given device. + * @dev: Device to remove the callbacks from. + */ +int pm_genpd_remove_callbacks(struct device *dev) +{ + struct pm_domain_data *pdd; + int ret = 0; + + if (!(dev && dev->power.subsys_data)) + return -EINVAL; + + pm_runtime_disable(dev); + device_pm_lock(); + + pdd = dev->power.subsys_data->domain_data; + if (pdd) { + struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd); + + gpd_data->ops = (struct gpd_dev_ops){ 0 }; + } else { + ret = -EINVAL; + } + + device_pm_unlock(); + pm_runtime_enable(dev); + + return ret; +} +EXPORT_SYMBOL_GPL(pm_genpd_remove_callbacks); + /** * pm_genpd_init - Initialize a generic I/O PM domain object. * @genpd: PM domain object to initialize. diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 65633e5a2bc0..8949d2d202ae 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -23,6 +23,12 @@ struct dev_power_governor { bool (*power_down_ok)(struct dev_pm_domain *domain); }; +struct gpd_dev_ops { + int (*start)(struct device *dev); + int (*stop)(struct device *dev); + bool (*active_wakeup)(struct device *dev); +}; + struct generic_pm_domain { struct dev_pm_domain domain; /* PM domain operations */ struct list_head gpd_list_node; /* Node in the global PM domains list */ @@ -45,9 +51,7 @@ struct generic_pm_domain { bool dev_irq_safe; /* Device callbacks are IRQ-safe */ int (*power_off)(struct generic_pm_domain *domain); int (*power_on)(struct generic_pm_domain *domain); - int (*start_device)(struct device *dev); - int (*stop_device)(struct device *dev); - bool (*active_wakeup)(struct device *dev); + struct gpd_dev_ops dev_ops; }; static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd) @@ -64,6 +68,7 @@ struct gpd_link { struct generic_pm_domain_data { struct pm_domain_data base; + struct gpd_dev_ops ops; bool need_restore; }; @@ -73,6 +78,11 @@ static inline struct generic_pm_domain_data *to_gpd_data(struct pm_domain_data * } #ifdef CONFIG_PM_GENERIC_DOMAINS +static inline struct generic_pm_domain_data *dev_gpd_data(struct device *dev) +{ + return to_gpd_data(dev->power.subsys_data->domain_data); +} + extern int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev); extern int pm_genpd_remove_device(struct generic_pm_domain *genpd, @@ -81,6 +91,8 @@ extern int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *new_subdomain); extern int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *target); +extern int pm_genpd_add_callbacks(struct device *dev, struct gpd_dev_ops *ops); +extern int pm_genpd_remove_callbacks(struct device *dev); extern void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off); extern int pm_genpd_poweron(struct generic_pm_domain *genpd); @@ -105,6 +117,15 @@ static inline int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, { return -ENOSYS; } +static inline int pm_genpd_add_callbacks(struct device *dev, + struct gpd_dev_ops *ops) +{ + return -ENOSYS; +} +static inline int pm_genpd_remove_callbacks(struct device *dev) +{ + return -ENOSYS; +} static inline void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off) {} static inline int pm_genpd_poweron(struct generic_pm_domain *genpd) From ecf00475f229fcf06362412ad2d15a3267e354a1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 27 Nov 2011 13:11:44 +0100 Subject: [PATCH 02/18] PM / Domains: Introduce "save/restore state" device callbacks The current PM domains code uses device drivers' .runtime_suspend() and .runtime_resume() callbacks as the "save device state" and "restore device state" operations, which may not be appropriate in general, because it forces drivers to assume that they always will be used with generic PM domains. However, in theory, the same hardware may be used in devices that don't belong to any PM domain, in which case it would be necessary to add "fake" PM domains to satisfy the above assumption. It also may be located in a PM domain that's not handled with the help of the generic code. To allow device drivers that may be used along with the generic PM domains code of more flexibility, introduce new device callbacks, .save_state() and .restore_state(), that can be supplied by the drivers in addition to their "standard" runtime PM callbacks. This will allow the drivers to be designed to work with generic PM domains as well as without them. For backwards compatibility, introduce default .save_state() and .restore_state() callback routines for PM domains that will execute a device driver's .runtime_suspend() and .runtime_resume() callbacks, respectively, for the given device if the driver doesn't provide its own implementations of .save_state() and .restore_state(). Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 68 ++++++++++++++++++++++++++++++------- include/linux/pm_domain.h | 2 ++ 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 94afaa2686a6..3c9451b10427 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -56,6 +56,16 @@ static int genpd_start_dev(struct generic_pm_domain *genpd, struct device *dev) return GENPD_DEV_CALLBACK(genpd, int, start, dev); } +static int genpd_save_dev(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, save_state, dev); +} + +static int genpd_restore_dev(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, restore_state, dev); +} + static bool genpd_sd_counter_dec(struct generic_pm_domain *genpd) { bool ret = false; @@ -217,7 +227,6 @@ static int __pm_genpd_save_device(struct pm_domain_data *pdd, { struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd); struct device *dev = pdd->dev; - struct device_driver *drv = dev->driver; int ret = 0; if (gpd_data->need_restore) @@ -225,11 +234,9 @@ static int __pm_genpd_save_device(struct pm_domain_data *pdd, mutex_unlock(&genpd->lock); - if (drv && drv->pm && drv->pm->runtime_suspend) { - genpd_start_dev(genpd, dev); - ret = drv->pm->runtime_suspend(dev); - genpd_stop_dev(genpd, dev); - } + genpd_start_dev(genpd, dev); + ret = genpd_save_dev(genpd, dev); + genpd_stop_dev(genpd, dev); mutex_lock(&genpd->lock); @@ -250,18 +257,15 @@ static void __pm_genpd_restore_device(struct pm_domain_data *pdd, { struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd); struct device *dev = pdd->dev; - struct device_driver *drv = dev->driver; if (!gpd_data->need_restore) return; mutex_unlock(&genpd->lock); - if (drv && drv->pm && drv->pm->runtime_resume) { - genpd_start_dev(genpd, dev); - drv->pm->runtime_resume(dev); - genpd_stop_dev(genpd, dev); - } + genpd_start_dev(genpd, dev); + genpd_restore_dev(genpd, dev); + genpd_stop_dev(genpd, dev); mutex_lock(&genpd->lock); @@ -1357,6 +1361,44 @@ int pm_genpd_remove_callbacks(struct device *dev) } EXPORT_SYMBOL_GPL(pm_genpd_remove_callbacks); +/** + * pm_genpd_default_save_state - Default "save device state" for PM domians. + * @dev: Device to handle. + */ +static int pm_genpd_default_save_state(struct device *dev) +{ + int (*cb)(struct device *__dev); + struct device_driver *drv = dev->driver; + + cb = dev_gpd_data(dev)->ops.save_state; + if (cb) + return cb(dev); + + if (drv && drv->pm && drv->pm->runtime_suspend) + return drv->pm->runtime_suspend(dev); + + return 0; +} + +/** + * pm_genpd_default_restore_state - Default PM domians "restore device state". + * @dev: Device to handle. + */ +static int pm_genpd_default_restore_state(struct device *dev) +{ + int (*cb)(struct device *__dev); + struct device_driver *drv = dev->driver; + + cb = dev_gpd_data(dev)->ops.restore_state; + if (cb) + return cb(dev); + + if (drv && drv->pm && drv->pm->runtime_resume) + return drv->pm->runtime_resume(dev); + + return 0; +} + /** * pm_genpd_init - Initialize a generic I/O PM domain object. * @genpd: PM domain object to initialize. @@ -1400,6 +1442,8 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq; genpd->domain.ops.restore = pm_genpd_restore; genpd->domain.ops.complete = pm_genpd_complete; + genpd->dev_ops.save_state = pm_genpd_default_save_state; + genpd->dev_ops.restore_state = pm_genpd_default_restore_state; mutex_lock(&gpd_list_lock); list_add(&genpd->gpd_list_node, &gpd_list); mutex_unlock(&gpd_list_lock); diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 8949d2d202ae..731080dad250 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -26,6 +26,8 @@ struct dev_power_governor { struct gpd_dev_ops { int (*start)(struct device *dev); int (*stop)(struct device *dev); + int (*save_state)(struct device *dev); + int (*restore_state)(struct device *dev); bool (*active_wakeup)(struct device *dev); }; From d23b9b00cdde5c93b914a172cecd57d5625fcd04 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 27 Nov 2011 13:11:51 +0100 Subject: [PATCH 03/18] PM / Domains: Rework system suspend callback routines (v2) The current generic PM domains code attempts to use the generic system suspend operations along with the domains' device stop/start routines, which requires device drivers to assume that their system suspend/resume (and hibernation/restore) callbacks will always be used with generic PM domains. However, in theory, the same hardware may be used in devices that don't belong to any PM domain, in which case it would be necessary to add "fake" PM domains to satisfy the above assumption. Also, the domain the hardware belongs to may not be handled with the help of the generic code. To allow device drivers that may be used along with the generic PM domains code of more flexibility, add new device callbacks, .suspend(), .suspend_late(), .resume_early(), .resume(), .freeze(), .freeze_late(), .thaw_early(), and .thaw(), that can be supplied by the drivers in addition to their "standard" system suspend and hibernation callbacks. These new callbacks, if defined, will be used by the generic PM domains code for the handling of system suspend and hibernation instead of the "standard" ones. This will allow drivers to be designed to work with generic PM domains as well as without them. For backwards compatibility, introduce default implementations of the new callbacks for PM domains that will execute pm_generic_suspend(), pm_generic_suspend_noirq(), pm_generic_resume_noirq(), pm_generic_resume(), pm_generic_freeze(), pm_generic_freeze_noirq(), pm_generic_thaw_noirq(), and pm_generic_thaw(), respectively, for the given device if its driver doesn't define those callbacks. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 249 ++++++++++++++++++++++-------------- include/linux/pm_domain.h | 8 ++ 2 files changed, 158 insertions(+), 99 deletions(-) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 3c9451b10427..9a77080cb799 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -561,6 +561,46 @@ static bool genpd_dev_active_wakeup(struct generic_pm_domain *genpd, return GENPD_DEV_CALLBACK(genpd, bool, active_wakeup, dev); } +static int genpd_suspend_dev(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, suspend, dev); +} + +static int genpd_suspend_late(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, suspend_late, dev); +} + +static int genpd_resume_early(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, resume_early, dev); +} + +static int genpd_resume_dev(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, resume, dev); +} + +static int genpd_freeze_dev(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, freeze, dev); +} + +static int genpd_freeze_late(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, freeze_late, dev); +} + +static int genpd_thaw_early(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, thaw_early, dev); +} + +static int genpd_thaw_dev(struct generic_pm_domain *genpd, struct device *dev) +{ + return GENPD_DEV_CALLBACK(genpd, int, thaw, dev); +} + /** * pm_genpd_sync_poweroff - Synchronously power off a PM domain and its masters. * @genpd: PM domain to power off, if possible. @@ -712,7 +752,7 @@ static int pm_genpd_suspend(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - return genpd->suspend_power_off ? 0 : pm_generic_suspend(dev); + return genpd->suspend_power_off ? 0 : genpd_suspend_dev(genpd, dev); } /** @@ -737,7 +777,7 @@ static int pm_genpd_suspend_noirq(struct device *dev) if (genpd->suspend_power_off) return 0; - ret = pm_generic_suspend_noirq(dev); + ret = genpd_suspend_late(genpd, dev); if (ret) return ret; @@ -788,7 +828,7 @@ static int pm_genpd_resume_noirq(struct device *dev) genpd->suspended_count--; genpd_start_dev(genpd, dev); - return pm_generic_resume_noirq(dev); + return genpd_resume_early(genpd, dev); } /** @@ -809,7 +849,7 @@ static int pm_genpd_resume(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - return genpd->suspend_power_off ? 0 : pm_generic_resume(dev); + return genpd->suspend_power_off ? 0 : genpd_resume_dev(genpd, dev); } /** @@ -830,7 +870,7 @@ static int pm_genpd_freeze(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - return genpd->suspend_power_off ? 0 : pm_generic_freeze(dev); + return genpd->suspend_power_off ? 0 : genpd_freeze_dev(genpd, dev); } /** @@ -856,7 +896,7 @@ static int pm_genpd_freeze_noirq(struct device *dev) if (genpd->suspend_power_off) return 0; - ret = pm_generic_freeze_noirq(dev); + ret = genpd_freeze_late(genpd, dev); if (ret) return ret; @@ -889,7 +929,7 @@ static int pm_genpd_thaw_noirq(struct device *dev) genpd_start_dev(genpd, dev); - return pm_generic_thaw_noirq(dev); + return genpd_thaw_early(genpd, dev); } /** @@ -910,70 +950,7 @@ static int pm_genpd_thaw(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - return genpd->suspend_power_off ? 0 : pm_generic_thaw(dev); -} - -/** - * pm_genpd_dev_poweroff - Power off a device belonging to an I/O PM domain. - * @dev: Device to suspend. - * - * Power off a device under the assumption that its pm_domain field points to - * the domain member of an object of type struct generic_pm_domain representing - * a PM domain consisting of I/O devices. - */ -static int pm_genpd_dev_poweroff(struct device *dev) -{ - struct generic_pm_domain *genpd; - - dev_dbg(dev, "%s()\n", __func__); - - genpd = dev_to_genpd(dev); - if (IS_ERR(genpd)) - return -EINVAL; - - return genpd->suspend_power_off ? 0 : pm_generic_poweroff(dev); -} - -/** - * pm_genpd_dev_poweroff_noirq - Late power off of a device from a PM domain. - * @dev: Device to suspend. - * - * Carry out a late powering off of a device under the assumption that its - * pm_domain field points to the domain member of an object of type - * struct generic_pm_domain representing a PM domain consisting of I/O devices. - */ -static int pm_genpd_dev_poweroff_noirq(struct device *dev) -{ - struct generic_pm_domain *genpd; - int ret; - - dev_dbg(dev, "%s()\n", __func__); - - genpd = dev_to_genpd(dev); - if (IS_ERR(genpd)) - return -EINVAL; - - if (genpd->suspend_power_off) - return 0; - - ret = pm_generic_poweroff_noirq(dev); - if (ret) - return ret; - - if (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev)) - return 0; - - genpd_stop_dev(genpd, dev); - - /* - * Since all of the "noirq" callbacks are executed sequentially, it is - * guaranteed that this function will never run twice in parallel for - * the same PM domain, so it is not necessary to use locking here. - */ - genpd->suspended_count++; - pm_genpd_sync_poweroff(genpd); - - return 0; + return genpd->suspend_power_off ? 0 : genpd_thaw_dev(genpd, dev); } /** @@ -1015,28 +992,7 @@ static int pm_genpd_restore_noirq(struct device *dev) genpd->suspended_count--; genpd_start_dev(genpd, dev); - return pm_generic_restore_noirq(dev); -} - -/** - * pm_genpd_restore - Restore a device belonging to an I/O power domain. - * @dev: Device to resume. - * - * Restore a device under the assumption that its pm_domain field points to the - * domain member of an object of type struct generic_pm_domain representing - * a power domain consisting of I/O devices. - */ -static int pm_genpd_restore(struct device *dev) -{ - struct generic_pm_domain *genpd; - - dev_dbg(dev, "%s()\n", __func__); - - genpd = dev_to_genpd(dev); - if (IS_ERR(genpd)) - return -EINVAL; - - return genpd->suspend_power_off ? 0 : pm_generic_restore(dev); + return genpd_resume_early(genpd, dev); } /** @@ -1086,10 +1042,7 @@ static void pm_genpd_complete(struct device *dev) #define pm_genpd_freeze_noirq NULL #define pm_genpd_thaw_noirq NULL #define pm_genpd_thaw NULL -#define pm_genpd_dev_poweroff_noirq NULL -#define pm_genpd_dev_poweroff NULL #define pm_genpd_restore_noirq NULL -#define pm_genpd_restore NULL #define pm_genpd_complete NULL #endif /* CONFIG_PM_SLEEP */ @@ -1361,6 +1314,8 @@ int pm_genpd_remove_callbacks(struct device *dev) } EXPORT_SYMBOL_GPL(pm_genpd_remove_callbacks); +/* Default device callbacks for generic PM domains. */ + /** * pm_genpd_default_save_state - Default "save device state" for PM domians. * @dev: Device to handle. @@ -1399,6 +1354,94 @@ static int pm_genpd_default_restore_state(struct device *dev) return 0; } +/** + * pm_genpd_default_suspend - Default "device suspend" for PM domians. + * @dev: Device to handle. + */ +static int pm_genpd_default_suspend(struct device *dev) +{ + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.freeze; + + return cb ? cb(dev) : pm_generic_suspend(dev); +} + +/** + * pm_genpd_default_suspend_late - Default "late device suspend" for PM domians. + * @dev: Device to handle. + */ +static int pm_genpd_default_suspend_late(struct device *dev) +{ + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.freeze_late; + + return cb ? cb(dev) : pm_generic_suspend_noirq(dev); +} + +/** + * pm_genpd_default_resume_early - Default "early device resume" for PM domians. + * @dev: Device to handle. + */ +static int pm_genpd_default_resume_early(struct device *dev) +{ + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.thaw_early; + + return cb ? cb(dev) : pm_generic_resume_noirq(dev); +} + +/** + * pm_genpd_default_resume - Default "device resume" for PM domians. + * @dev: Device to handle. + */ +static int pm_genpd_default_resume(struct device *dev) +{ + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.thaw; + + return cb ? cb(dev) : pm_generic_resume(dev); +} + +/** + * pm_genpd_default_freeze - Default "device freeze" for PM domians. + * @dev: Device to handle. + */ +static int pm_genpd_default_freeze(struct device *dev) +{ + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.freeze; + + return cb ? cb(dev) : pm_generic_freeze(dev); +} + +/** + * pm_genpd_default_freeze_late - Default "late device freeze" for PM domians. + * @dev: Device to handle. + */ +static int pm_genpd_default_freeze_late(struct device *dev) +{ + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.freeze_late; + + return cb ? cb(dev) : pm_generic_freeze_noirq(dev); +} + +/** + * pm_genpd_default_thaw_early - Default "early device thaw" for PM domians. + * @dev: Device to handle. + */ +static int pm_genpd_default_thaw_early(struct device *dev) +{ + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.thaw_early; + + return cb ? cb(dev) : pm_generic_thaw_noirq(dev); +} + +/** + * pm_genpd_default_thaw - Default "device thaw" for PM domians. + * @dev: Device to handle. + */ +static int pm_genpd_default_thaw(struct device *dev) +{ + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.thaw; + + return cb ? cb(dev) : pm_generic_thaw(dev); +} + /** * pm_genpd_init - Initialize a generic I/O PM domain object. * @genpd: PM domain object to initialize. @@ -1437,13 +1480,21 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->domain.ops.freeze_noirq = pm_genpd_freeze_noirq; genpd->domain.ops.thaw_noirq = pm_genpd_thaw_noirq; genpd->domain.ops.thaw = pm_genpd_thaw; - genpd->domain.ops.poweroff = pm_genpd_dev_poweroff; - genpd->domain.ops.poweroff_noirq = pm_genpd_dev_poweroff_noirq; + genpd->domain.ops.poweroff = pm_genpd_suspend; + genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq; genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq; - genpd->domain.ops.restore = pm_genpd_restore; + genpd->domain.ops.restore = pm_genpd_resume; genpd->domain.ops.complete = pm_genpd_complete; genpd->dev_ops.save_state = pm_genpd_default_save_state; genpd->dev_ops.restore_state = pm_genpd_default_restore_state; + genpd->dev_ops.freeze = pm_genpd_default_suspend; + genpd->dev_ops.freeze_late = pm_genpd_default_suspend_late; + genpd->dev_ops.thaw_early = pm_genpd_default_resume_early; + genpd->dev_ops.thaw = pm_genpd_default_resume; + genpd->dev_ops.freeze = pm_genpd_default_freeze; + genpd->dev_ops.freeze_late = pm_genpd_default_freeze_late; + genpd->dev_ops.thaw_early = pm_genpd_default_thaw_early; + genpd->dev_ops.thaw = pm_genpd_default_thaw; mutex_lock(&gpd_list_lock); list_add(&genpd->gpd_list_node, &gpd_list); mutex_unlock(&gpd_list_lock); diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 731080dad250..10a197dce07e 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -28,6 +28,14 @@ struct gpd_dev_ops { int (*stop)(struct device *dev); int (*save_state)(struct device *dev); int (*restore_state)(struct device *dev); + int (*suspend)(struct device *dev); + int (*suspend_late)(struct device *dev); + int (*resume_early)(struct device *dev); + int (*resume)(struct device *dev); + int (*freeze)(struct device *dev); + int (*freeze_late)(struct device *dev); + int (*thaw_early)(struct device *dev); + int (*thaw)(struct device *dev); bool (*active_wakeup)(struct device *dev); }; From b02c999ac325e977585abeb4caf6e0a2ee21e30b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 1 Dec 2011 00:02:05 +0100 Subject: [PATCH 04/18] PM / Domains: Add device stop governor function (v4) Add a function deciding whether or not devices should be stopped in pm_genpd_runtime_suspend() depending on their PM QoS constraints and stop/start timing values. Make it possible to add information used by this function to device objects. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm --- arch/arm/mach-shmobile/pm-sh7372.c | 4 +- drivers/base/power/Makefile | 2 +- drivers/base/power/domain.c | 33 +++++++++++---- drivers/base/power/domain_governor.c | 33 +++++++++++++++ include/linux/pm_domain.h | 63 ++++++++++++++++++++++++---- 5 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 drivers/base/power/domain_governor.c diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 6777bb1be059..adf1765e69c6 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -169,6 +169,7 @@ static bool sh7372_power_down_forbidden(struct dev_pm_domain *domain) struct dev_power_governor sh7372_always_on_gov = { .power_down_ok = sh7372_power_down_forbidden, + .stop_ok = default_stop_ok, }; static int sh7372_stop_dev(struct device *dev) @@ -203,8 +204,9 @@ static int sh7372_start_dev(struct device *dev) void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd) { struct generic_pm_domain *genpd = &sh7372_pd->genpd; + struct dev_power_governor *gov = sh7372_pd->gov; - pm_genpd_init(genpd, sh7372_pd->gov, false); + pm_genpd_init(genpd, gov ? : &simple_qos_governor, false); genpd->dev_ops.stop = sh7372_stop_dev; genpd->dev_ops.start = sh7372_start_dev; genpd->dev_ops.active_wakeup = pd_active_wakeup; diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index 81676dd17900..2e58ebb1f6c0 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -3,7 +3,7 @@ obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o obj-$(CONFIG_PM_RUNTIME) += runtime.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o obj-$(CONFIG_PM_OPP) += opp.o -obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o +obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o obj-$(CONFIG_HAVE_CLK) += clock_ops.o ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 9a77080cb799..3af9f5a71ad5 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -38,7 +38,7 @@ static DEFINE_MUTEX(gpd_list_lock); #ifdef CONFIG_PM -static struct generic_pm_domain *dev_to_genpd(struct device *dev) +struct generic_pm_domain *dev_to_genpd(struct device *dev) { if (IS_ERR_OR_NULL(dev->pm_domain)) return ERR_PTR(-EINVAL); @@ -436,6 +436,7 @@ static void genpd_power_off_work_fn(struct work_struct *work) static int pm_genpd_runtime_suspend(struct device *dev) { struct generic_pm_domain *genpd; + bool (*stop_ok)(struct device *__dev); int ret; dev_dbg(dev, "%s()\n", __func__); @@ -446,10 +447,17 @@ static int pm_genpd_runtime_suspend(struct device *dev) might_sleep_if(!genpd->dev_irq_safe); + stop_ok = genpd->gov ? genpd->gov->stop_ok : NULL; + if (stop_ok && !stop_ok(dev)) + return -EBUSY; + ret = genpd_stop_dev(genpd, dev); if (ret) return ret; + pm_runtime_update_max_time_suspended(dev, + dev_gpd_data(dev)->td.start_latency_ns); + /* * If power.irq_safe is set, this routine will be run with interrupts * off, so it can't use mutexes. @@ -1048,11 +1056,13 @@ static void pm_genpd_complete(struct device *dev) #endif /* CONFIG_PM_SLEEP */ /** - * pm_genpd_add_device - Add a device to an I/O PM domain. + * __pm_genpd_add_device - Add a device to an I/O PM domain. * @genpd: PM domain to add the device to. * @dev: Device to be added. + * @td: Set of PM QoS timing parameters to attach to the device. */ -int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) +int __pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, + struct gpd_timing_data *td) { struct generic_pm_domain_data *gpd_data; struct pm_domain_data *pdd; @@ -1095,6 +1105,8 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) gpd_data->base.dev = dev; gpd_data->need_restore = false; list_add_tail(&gpd_data->base.list_node, &genpd->dev_list); + if (td) + gpd_data->td = *td; out: genpd_release_lock(genpd); @@ -1255,8 +1267,10 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, * pm_genpd_add_callbacks - Add PM domain callbacks to a given device. * @dev: Device to add the callbacks to. * @ops: Set of callbacks to add. + * @td: Timing data to add to the device along with the callbacks (optional). */ -int pm_genpd_add_callbacks(struct device *dev, struct gpd_dev_ops *ops) +int pm_genpd_add_callbacks(struct device *dev, struct gpd_dev_ops *ops, + struct gpd_timing_data *td) { struct pm_domain_data *pdd; int ret = 0; @@ -1272,6 +1286,8 @@ int pm_genpd_add_callbacks(struct device *dev, struct gpd_dev_ops *ops) struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd); gpd_data->ops = *ops; + if (td) + gpd_data->td = *td; } else { ret = -EINVAL; } @@ -1284,10 +1300,11 @@ int pm_genpd_add_callbacks(struct device *dev, struct gpd_dev_ops *ops) EXPORT_SYMBOL_GPL(pm_genpd_add_callbacks); /** - * pm_genpd_remove_callbacks - Remove PM domain callbacks from a given device. + * __pm_genpd_remove_callbacks - Remove PM domain callbacks from a given device. * @dev: Device to remove the callbacks from. + * @clear_td: If set, clear the device's timing data too. */ -int pm_genpd_remove_callbacks(struct device *dev) +int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td) { struct pm_domain_data *pdd; int ret = 0; @@ -1303,6 +1320,8 @@ int pm_genpd_remove_callbacks(struct device *dev) struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd); gpd_data->ops = (struct gpd_dev_ops){ 0 }; + if (clear_td) + gpd_data->td = (struct gpd_timing_data){ 0 }; } else { ret = -EINVAL; } @@ -1312,7 +1331,7 @@ int pm_genpd_remove_callbacks(struct device *dev) return ret; } -EXPORT_SYMBOL_GPL(pm_genpd_remove_callbacks); +EXPORT_SYMBOL_GPL(__pm_genpd_remove_callbacks); /* Default device callbacks for generic PM domains. */ diff --git a/drivers/base/power/domain_governor.c b/drivers/base/power/domain_governor.c new file mode 100644 index 000000000000..97b21c10c496 --- /dev/null +++ b/drivers/base/power/domain_governor.c @@ -0,0 +1,33 @@ +/* + * drivers/base/power/domain_governor.c - Governors for device PM domains. + * + * Copyright (C) 2011 Rafael J. Wysocki , Renesas Electronics Corp. + * + * This file is released under the GPLv2. + */ + +#include +#include +#include +#include + +/** + * default_stop_ok - Default PM domain governor routine for stopping devices. + * @dev: Device to check. + */ +bool default_stop_ok(struct device *dev) +{ + struct gpd_timing_data *td = &dev_gpd_data(dev)->td; + + dev_dbg(dev, "%s()\n", __func__); + + if (dev->power.max_time_suspended_ns < 0 || td->break_even_ns == 0) + return true; + + return td->stop_latency_ns + td->start_latency_ns < td->break_even_ns + && td->break_even_ns < dev->power.max_time_suspended_ns; +} + +struct dev_power_governor simple_qos_governor = { + .stop_ok = default_stop_ok, +}; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 10a197dce07e..f6745c213a57 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -21,6 +21,7 @@ enum gpd_status { struct dev_power_governor { bool (*power_down_ok)(struct dev_pm_domain *domain); + bool (*stop_ok)(struct device *dev); }; struct gpd_dev_ops { @@ -76,9 +77,16 @@ struct gpd_link { struct list_head slave_node; }; +struct gpd_timing_data { + s64 stop_latency_ns; + s64 start_latency_ns; + s64 break_even_ns; +}; + struct generic_pm_domain_data { struct pm_domain_data base; struct gpd_dev_ops ops; + struct gpd_timing_data td; bool need_restore; }; @@ -93,20 +101,48 @@ static inline struct generic_pm_domain_data *dev_gpd_data(struct device *dev) return to_gpd_data(dev->power.subsys_data->domain_data); } -extern int pm_genpd_add_device(struct generic_pm_domain *genpd, - struct device *dev); +extern struct dev_power_governor simple_qos_governor; + +extern struct generic_pm_domain *dev_to_genpd(struct device *dev); +extern int __pm_genpd_add_device(struct generic_pm_domain *genpd, + struct device *dev, + struct gpd_timing_data *td); + +static inline int pm_genpd_add_device(struct generic_pm_domain *genpd, + struct device *dev) +{ + return __pm_genpd_add_device(genpd, dev, NULL); +} + extern int pm_genpd_remove_device(struct generic_pm_domain *genpd, struct device *dev); extern int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *new_subdomain); extern int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *target); -extern int pm_genpd_add_callbacks(struct device *dev, struct gpd_dev_ops *ops); -extern int pm_genpd_remove_callbacks(struct device *dev); +extern int pm_genpd_add_callbacks(struct device *dev, + struct gpd_dev_ops *ops, + struct gpd_timing_data *td); +extern int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td); extern void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off); + extern int pm_genpd_poweron(struct generic_pm_domain *genpd); + +extern bool default_stop_ok(struct device *dev); + #else + +static inline struct generic_pm_domain *dev_to_genpd(struct device *dev) +{ + return ERR_PTR(-ENOSYS); +} +static inline int __pm_genpd_add_device(struct generic_pm_domain *genpd, + struct device *dev, + struct gpd_timing_data *td) +{ + return -ENOSYS; +} static inline int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) { @@ -128,22 +164,33 @@ static inline int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, return -ENOSYS; } static inline int pm_genpd_add_callbacks(struct device *dev, - struct gpd_dev_ops *ops) + struct gpd_dev_ops *ops, + struct gpd_timing_data *td) { return -ENOSYS; } -static inline int pm_genpd_remove_callbacks(struct device *dev) +static inline int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td) { return -ENOSYS; } -static inline void pm_genpd_init(struct generic_pm_domain *genpd, - struct dev_power_governor *gov, bool is_off) {} +static inline void pm_genpd_init(struct generic_pm_domain *genpd, bool is_off) +{ +} static inline int pm_genpd_poweron(struct generic_pm_domain *genpd) { return -ENOSYS; } +static inline bool default_stop_ok(struct device *dev) +{ + return false; +} #endif +static inline int pm_genpd_remove_callbacks(struct device *dev) +{ + return __pm_genpd_remove_callbacks(dev, true); +} + #ifdef CONFIG_PM_GENERIC_DOMAINS_RUNTIME extern void genpd_queue_power_off_work(struct generic_pm_domain *genpd); extern void pm_genpd_poweroff_unused(void); From 221e9b58380abdd6c05e11b4538597e2586ee141 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 1 Dec 2011 00:02:10 +0100 Subject: [PATCH 05/18] PM / Domains: Add default power off governor function (v4) Add a function deciding whether or not a given PM domain should be powered off on the basis of the PM QoS constraints of devices belonging to it and their PM QoS timing data. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 12 +++ drivers/base/power/domain_governor.c | 110 +++++++++++++++++++++++++++ include/linux/pm_domain.h | 7 ++ 3 files changed, 129 insertions(+) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 3af9f5a71ad5..91896194e76b 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -398,6 +398,17 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) } genpd->status = GPD_STATE_POWER_OFF; + genpd->power_off_time = ktime_get(); + + /* Update PM QoS information for devices in the domain. */ + list_for_each_entry_reverse(pdd, &genpd->dev_list, list_node) { + struct gpd_timing_data *td = &to_gpd_data(pdd)->td; + + pm_runtime_update_max_time_suspended(pdd->dev, + td->start_latency_ns + + td->restore_state_latency_ns + + genpd->power_on_latency_ns); + } list_for_each_entry(link, &genpd->slave_links, slave_node) { genpd_sd_counter_dec(link->master); @@ -1487,6 +1498,7 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->resume_count = 0; genpd->device_count = 0; genpd->suspended_count = 0; + genpd->max_off_time_ns = -1; genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume; genpd->domain.ops.runtime_idle = pm_generic_runtime_idle; diff --git a/drivers/base/power/domain_governor.c b/drivers/base/power/domain_governor.c index 97b21c10c496..da78540e9b40 100644 --- a/drivers/base/power/domain_governor.c +++ b/drivers/base/power/domain_governor.c @@ -10,6 +10,7 @@ #include #include #include +#include /** * default_stop_ok - Default PM domain governor routine for stopping devices. @@ -28,6 +29,115 @@ bool default_stop_ok(struct device *dev) && td->break_even_ns < dev->power.max_time_suspended_ns; } +/** + * default_power_down_ok - Default generic PM domain power off governor routine. + * @pd: PM domain to check. + * + * This routine must be executed under the PM domain's lock. + */ +static bool default_power_down_ok(struct dev_pm_domain *pd) +{ + struct generic_pm_domain *genpd = pd_to_genpd(pd); + struct gpd_link *link; + struct pm_domain_data *pdd; + s64 min_dev_off_time_ns; + s64 off_on_time_ns; + ktime_t time_now = ktime_get(); + + off_on_time_ns = genpd->power_off_latency_ns + + genpd->power_on_latency_ns; + /* + * It doesn't make sense to remove power from the domain if saving + * the state of all devices in it and the power off/power on operations + * take too much time. + * + * All devices in this domain have been stopped already at this point. + */ + list_for_each_entry(pdd, &genpd->dev_list, list_node) { + if (pdd->dev->driver) + off_on_time_ns += + to_gpd_data(pdd)->td.save_state_latency_ns; + } + + /* + * Check if subdomains can be off for enough time. + * + * All subdomains have been powered off already at this point. + */ + list_for_each_entry(link, &genpd->master_links, master_node) { + struct generic_pm_domain *sd = link->slave; + s64 sd_max_off_ns = sd->max_off_time_ns; + + if (sd_max_off_ns < 0) + continue; + + sd_max_off_ns -= ktime_to_ns(ktime_sub(time_now, + sd->power_off_time)); + /* + * Check if the subdomain is allowed to be off long enough for + * the current domain to turn off and on (that's how much time + * it will have to wait worst case). + */ + if (sd_max_off_ns <= off_on_time_ns) + return false; + } + + /* + * Check if the devices in the domain can be off enough time. + */ + min_dev_off_time_ns = -1; + list_for_each_entry(pdd, &genpd->dev_list, list_node) { + struct gpd_timing_data *td; + struct device *dev = pdd->dev; + s64 dev_off_time_ns; + + if (!dev->driver || dev->power.max_time_suspended_ns < 0) + continue; + + td = &to_gpd_data(pdd)->td; + dev_off_time_ns = dev->power.max_time_suspended_ns - + (td->start_latency_ns + td->restore_state_latency_ns + + ktime_to_ns(ktime_sub(time_now, + dev->power.suspend_time))); + if (dev_off_time_ns <= off_on_time_ns) + return false; + + if (min_dev_off_time_ns > dev_off_time_ns + || min_dev_off_time_ns < 0) + min_dev_off_time_ns = dev_off_time_ns; + } + + if (min_dev_off_time_ns < 0) { + /* + * There are no latency constraints, so the domain can spend + * arbitrary time in the "off" state. + */ + genpd->max_off_time_ns = -1; + return true; + } + + /* + * The difference between the computed minimum delta and the time needed + * to turn the domain on is the maximum theoretical time this domain can + * spend in the "off" state. + */ + min_dev_off_time_ns -= genpd->power_on_latency_ns; + + /* + * If the difference between the computed minimum delta and the time + * needed to turn the domain off and back on on is smaller than the + * domain's power break even time, removing power from the domain is not + * worth it. + */ + if (genpd->break_even_ns > + min_dev_off_time_ns - genpd->power_off_latency_ns) + return false; + + genpd->max_off_time_ns = min_dev_off_time_ns; + return true; +} + struct dev_power_governor simple_qos_governor = { .stop_ok = default_stop_ok, + .power_down_ok = default_power_down_ok, }; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index f6745c213a57..cc1a2450ff7b 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -61,8 +61,13 @@ struct generic_pm_domain { bool suspend_power_off; /* Power status before system suspend */ bool dev_irq_safe; /* Device callbacks are IRQ-safe */ int (*power_off)(struct generic_pm_domain *domain); + s64 power_off_latency_ns; int (*power_on)(struct generic_pm_domain *domain); + s64 power_on_latency_ns; struct gpd_dev_ops dev_ops; + s64 break_even_ns; /* Power break even for the entire domain. */ + s64 max_off_time_ns; /* Maximum allowed "suspended" time. */ + ktime_t power_off_time; }; static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd) @@ -80,6 +85,8 @@ struct gpd_link { struct gpd_timing_data { s64 stop_latency_ns; s64 start_latency_ns; + s64 save_state_latency_ns; + s64 restore_state_latency_ns; s64 break_even_ns; }; From 0140d8bd47f798d55c3720f7fcade9e50929a5e5 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 1 Dec 2011 00:02:17 +0100 Subject: [PATCH 06/18] PM / Domains: Automatically update overoptimistic latency information Measure the time of execution of the .stop(), .start(), .save_state() and .restore_state() PM domain device callbacks and if the result is greater than the corresponding latency value stored in the device's struct generic_pm_domain_data object, replace the inaccurate value with the measured time. Do analogously for the PM domains' .power_off() and .power_off() callbacks. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 43 +++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 91896194e76b..5a8d67d51f0e 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -33,6 +33,20 @@ __ret; \ }) +#define GENPD_DEV_TIMED_CALLBACK(genpd, type, callback, dev, field, name) \ +({ \ + ktime_t __start = ktime_get(); \ + type __retval = GENPD_DEV_CALLBACK(genpd, type, callback, dev); \ + s64 __elapsed = ktime_to_ns(ktime_sub(ktime_get(), __start)); \ + struct generic_pm_domain_data *__gpd_data = dev_gpd_data(dev); \ + if (__elapsed > __gpd_data->td.field) { \ + __gpd_data->td.field = __elapsed; \ + dev_warn(dev, name " latency exceeded, new value %lld ns\n", \ + __elapsed); \ + } \ + __retval; \ +}) + static LIST_HEAD(gpd_list); static DEFINE_MUTEX(gpd_list_lock); @@ -48,22 +62,27 @@ struct generic_pm_domain *dev_to_genpd(struct device *dev) static int genpd_stop_dev(struct generic_pm_domain *genpd, struct device *dev) { - return GENPD_DEV_CALLBACK(genpd, int, stop, dev); + return GENPD_DEV_TIMED_CALLBACK(genpd, int, stop, dev, + stop_latency_ns, "stop"); } static int genpd_start_dev(struct generic_pm_domain *genpd, struct device *dev) { - return GENPD_DEV_CALLBACK(genpd, int, start, dev); + return GENPD_DEV_TIMED_CALLBACK(genpd, int, start, dev, + start_latency_ns, "start"); } static int genpd_save_dev(struct generic_pm_domain *genpd, struct device *dev) { - return GENPD_DEV_CALLBACK(genpd, int, save_state, dev); + return GENPD_DEV_TIMED_CALLBACK(genpd, int, save_state, dev, + save_state_latency_ns, "state save"); } static int genpd_restore_dev(struct generic_pm_domain *genpd, struct device *dev) { - return GENPD_DEV_CALLBACK(genpd, int, restore_state, dev); + return GENPD_DEV_TIMED_CALLBACK(genpd, int, restore_state, dev, + restore_state_latency_ns, + "state restore"); } static bool genpd_sd_counter_dec(struct generic_pm_domain *genpd) @@ -182,9 +201,16 @@ int __pm_genpd_poweron(struct generic_pm_domain *genpd) } if (genpd->power_on) { + ktime_t time_start = ktime_get(); + s64 elapsed_ns; + ret = genpd->power_on(genpd); if (ret) goto err; + + elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); + if (elapsed_ns > genpd->power_on_latency_ns) + genpd->power_on_latency_ns = elapsed_ns; } genpd_set_active(genpd); @@ -377,11 +403,16 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) } if (genpd->power_off) { + ktime_t time_start; + s64 elapsed_ns; + if (atomic_read(&genpd->sd_count) > 0) { ret = -EBUSY; goto out; } + time_start = ktime_get(); + /* * If sd_count > 0 at this point, one of the subdomains hasn't * managed to call pm_genpd_poweron() for the master yet after @@ -395,6 +426,10 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) genpd_set_active(genpd); goto out; } + + elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); + if (elapsed_ns > genpd->power_off_latency_ns) + genpd->power_off_latency_ns = elapsed_ns; } genpd->status = GPD_STATE_POWER_OFF; From 4f042cdad40e1566a53b7ae85e72b6945a4b0fde Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 1 Dec 2011 00:05:31 +0100 Subject: [PATCH 07/18] PM / Domains: fix compilation failure for CONFIG_PM_GENERIC_DOMAINS unset Fix the following compalitaion breakage: In file included from linux/drivers/sh/pm_runtime.c:15: linux/include/linux/pm_domain.h: In function 'dev_to_genpd': linux/include/linux/pm_domain.h:142: error: implicit declaration of function 'ERR_PTR' linux/include/linux/pm_domain.h:142: warning: return makes pointer from integer without a cast In file included from linux/include/linux/sh_clk.h:10, from linux/drivers/sh/pm_runtime.c:19: linux/include/linux/err.h: At top level: linux/include/linux/err.h:22: error: conflicting types for 'ERR_PTR' linux/include/linux/pm_domain.h:142: note: previous implicit declaration of 'ERR_PTR' was here make[3]: *** [drivers/sh/pm_runtime.o] Error 1 Reported-by: Nobuhiro Iwamatsu Signed-off-by: Guennadi Liakhovetski Signed-off-by: Rafael J. Wysocki --- include/linux/pm_domain.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index cc1a2450ff7b..fbb81bc5065a 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -10,6 +10,7 @@ #define _LINUX_PM_DOMAIN_H #include +#include enum gpd_status { GPD_STATE_ACTIVE = 0, /* PM domain is active */ From e84b2c202771bbd538866207efcb1f7dbab8045b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 6 Dec 2011 22:19:54 +0100 Subject: [PATCH 08/18] PM / Domains: Make it possible to assign names to generic PM domains Add a name member pointer to struct generic_pm_domain and use it in diagnostic messages regarding the domain power-off and power-on latencies. Update the ARM shmobile SH7372 code to assign names to the PM domains used by it. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm --- arch/arm/mach-shmobile/pm-sh7372.c | 16 ++++++++++++---- drivers/base/power/domain.c | 14 ++++++++++++-- include/linux/pm_domain.h | 1 + 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index adf1765e69c6..8d9ea8924ed3 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -101,8 +101,8 @@ static int pd_power_down(struct generic_pm_domain *genpd) } if (!sh7372_pd->no_debug) - pr_debug("sh7372 power domain down 0x%08x -> PSTR = 0x%08x\n", - mask, __raw_readl(PSTR)); + pr_debug("%s: Power off, 0x%08x -> PSTR = 0x%08x\n", + genpd->name, mask, __raw_readl(PSTR)); return 0; } @@ -133,8 +133,8 @@ static int __pd_power_up(struct sh7372_pm_domain *sh7372_pd, bool do_resume) ret = -EIO; if (!sh7372_pd->no_debug) - pr_debug("sh7372 power domain up 0x%08x -> PSTR = 0x%08x\n", - mask, __raw_readl(PSTR)); + pr_debug("%s: Power on, 0x%08x -> PSTR = 0x%08x\n", + sh7372_pd->genpd.name, mask, __raw_readl(PSTR)); out: if (ret == 0 && sh7372_pd->resume && do_resume) @@ -233,18 +233,22 @@ void sh7372_pm_add_subdomain(struct sh7372_pm_domain *sh7372_pd, } struct sh7372_pm_domain sh7372_a4lc = { + .genpd.name = "A4LC", .bit_shift = 1, }; struct sh7372_pm_domain sh7372_a4mp = { + .genpd.name = "A4MP", .bit_shift = 2, }; struct sh7372_pm_domain sh7372_d4 = { + .genpd.name = "D4", .bit_shift = 3, }; struct sh7372_pm_domain sh7372_a4r = { + .genpd.name = "A4R", .bit_shift = 5, .gov = &sh7372_always_on_gov, .suspend = sh7372_a4r_suspend, @@ -253,14 +257,17 @@ struct sh7372_pm_domain sh7372_a4r = { }; struct sh7372_pm_domain sh7372_a3rv = { + .genpd.name = "A3RV", .bit_shift = 6, }; struct sh7372_pm_domain sh7372_a3ri = { + .genpd.name = "A3RI", .bit_shift = 8, }; struct sh7372_pm_domain sh7372_a3sp = { + .genpd.name = "A3SP", .bit_shift = 11, .gov = &sh7372_always_on_gov, .no_debug = true, @@ -275,6 +282,7 @@ static void sh7372_a3sp_init(void) } struct sh7372_pm_domain sh7372_a3sg = { + .genpd.name = "A3SG", .bit_shift = 13, }; diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 5a8d67d51f0e..ad6ba2e04677 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -209,8 +209,13 @@ int __pm_genpd_poweron(struct generic_pm_domain *genpd) goto err; elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); - if (elapsed_ns > genpd->power_on_latency_ns) + if (elapsed_ns > genpd->power_on_latency_ns) { genpd->power_on_latency_ns = elapsed_ns; + if (genpd->name) + pr_warning("%s: Power-on latency exceeded, " + "new value %lld ns\n", genpd->name, + elapsed_ns); + } } genpd_set_active(genpd); @@ -428,8 +433,13 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) } elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); - if (elapsed_ns > genpd->power_off_latency_ns) + if (elapsed_ns > genpd->power_off_latency_ns) { genpd->power_off_latency_ns = elapsed_ns; + if (genpd->name) + pr_warning("%s: Power-off latency exceeded, " + "new value %lld ns\n", genpd->name, + elapsed_ns); + } } genpd->status = GPD_STATE_POWER_OFF; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index fbb81bc5065a..fb809b904891 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -50,6 +50,7 @@ struct generic_pm_domain { struct mutex lock; struct dev_power_governor *gov; struct work_struct power_off_work; + char *name; unsigned int in_progress; /* Number of devices being suspended now */ atomic_t sd_count; /* Number of subdomains with power "on" */ enum gpd_status status; /* Current state of the domain */ From c9914854b4ca339e511d052ce3a1a441ef15b928 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 6 Dec 2011 23:16:47 +0100 Subject: [PATCH 09/18] PM / Domains: Fix default system suspend/resume operations Commit d23b9b00cdde5c93b914a172cecd57d5625fcd04 (PM / Domains: Rework system suspend callback routines (v2)) broke the system suspend and resume handling by devices belonging to generic PM domains, because it used freeze/thaw callbacks instead of suspend/resume ones and didn't initialize device callbacks for system suspend/resume properly at all. Fix those problems. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index ad6ba2e04677..92e6a9048065 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1435,7 +1435,7 @@ static int pm_genpd_default_restore_state(struct device *dev) */ static int pm_genpd_default_suspend(struct device *dev) { - int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.freeze; + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.suspend; return cb ? cb(dev) : pm_generic_suspend(dev); } @@ -1446,7 +1446,7 @@ static int pm_genpd_default_suspend(struct device *dev) */ static int pm_genpd_default_suspend_late(struct device *dev) { - int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.freeze_late; + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.suspend_late; return cb ? cb(dev) : pm_generic_suspend_noirq(dev); } @@ -1457,7 +1457,7 @@ static int pm_genpd_default_suspend_late(struct device *dev) */ static int pm_genpd_default_resume_early(struct device *dev) { - int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.thaw_early; + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.resume_early; return cb ? cb(dev) : pm_generic_resume_noirq(dev); } @@ -1468,7 +1468,7 @@ static int pm_genpd_default_resume_early(struct device *dev) */ static int pm_genpd_default_resume(struct device *dev) { - int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.thaw; + int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.resume; return cb ? cb(dev) : pm_generic_resume(dev); } @@ -1563,10 +1563,10 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->domain.ops.complete = pm_genpd_complete; genpd->dev_ops.save_state = pm_genpd_default_save_state; genpd->dev_ops.restore_state = pm_genpd_default_restore_state; - genpd->dev_ops.freeze = pm_genpd_default_suspend; - genpd->dev_ops.freeze_late = pm_genpd_default_suspend_late; - genpd->dev_ops.thaw_early = pm_genpd_default_resume_early; - genpd->dev_ops.thaw = pm_genpd_default_resume; + genpd->dev_ops.suspend = pm_genpd_default_suspend; + genpd->dev_ops.suspend_late = pm_genpd_default_suspend_late; + genpd->dev_ops.resume_early = pm_genpd_default_resume_early; + genpd->dev_ops.resume = pm_genpd_default_resume; genpd->dev_ops.freeze = pm_genpd_default_freeze; genpd->dev_ops.freeze_late = pm_genpd_default_freeze_late; genpd->dev_ops.thaw_early = pm_genpd_default_thaw_early; From 925b44a273aa8c4c23c006c1228aacd538eead09 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 8 Dec 2011 23:27:28 +0100 Subject: [PATCH 10/18] PM / Domains: Provide an always on power domain governor Since systems are likely to have power domains that can't be turned off for various reasons at least temporarily while implementing power domain support provide a default governor which will always refuse to power off the domain, saving platforms having to implement their own. Since the code is so tiny don't bother with a Kconfig symbol for it. Signed-off-by: Mark Brown Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain_governor.c | 13 +++++++++++++ include/linux/pm_domain.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/drivers/base/power/domain_governor.c b/drivers/base/power/domain_governor.c index da78540e9b40..51527ee92d10 100644 --- a/drivers/base/power/domain_governor.c +++ b/drivers/base/power/domain_governor.c @@ -141,3 +141,16 @@ struct dev_power_governor simple_qos_governor = { .stop_ok = default_stop_ok, .power_down_ok = default_power_down_ok, }; + +static bool always_on_power_down_ok(struct dev_pm_domain *domain) +{ + return false; +} + +/** + * pm_genpd_gov_always_on - A governor implementing an always-on policy + */ +struct dev_power_governor pm_domain_always_on_gov = { + .power_down_ok = always_on_power_down_ok, + .stop_ok = default_stop_ok, +}; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index fb809b904891..a03a0ad998b8 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -140,6 +140,7 @@ extern int pm_genpd_poweron(struct generic_pm_domain *genpd); extern bool default_stop_ok(struct device *dev); +extern struct dev_power_governor pm_domain_always_on_gov; #else static inline struct generic_pm_domain *dev_to_genpd(struct device *dev) @@ -193,6 +194,7 @@ static inline bool default_stop_ok(struct device *dev) { return false; } +#define pm_domain_always_on_gov NULL #endif static inline int pm_genpd_remove_callbacks(struct device *dev) From a87dc8fdc250f6416b522a3eb302c8cf95c2317c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 8 Dec 2011 23:27:40 +0100 Subject: [PATCH 11/18] PM / shmobile: Use common always on power domain governor Saves a tiny amount of code. Signed-off-by: Mark Brown Acked-by: Magnus Damm Signed-off-by: Rafael J. Wysocki --- arch/arm/mach-shmobile/pm-sh7372.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 8d9ea8924ed3..f742c61c8902 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -162,16 +162,6 @@ static bool pd_active_wakeup(struct device *dev) return active_wakeup ? active_wakeup(dev) : true; } -static bool sh7372_power_down_forbidden(struct dev_pm_domain *domain) -{ - return false; -} - -struct dev_power_governor sh7372_always_on_gov = { - .power_down_ok = sh7372_power_down_forbidden, - .stop_ok = default_stop_ok, -}; - static int sh7372_stop_dev(struct device *dev) { int (*stop)(struct device *dev); @@ -250,7 +240,7 @@ struct sh7372_pm_domain sh7372_d4 = { struct sh7372_pm_domain sh7372_a4r = { .genpd.name = "A4R", .bit_shift = 5, - .gov = &sh7372_always_on_gov, + .gov = &pm_domain_always_on_gov, .suspend = sh7372_a4r_suspend, .resume = sh7372_intcs_resume, .stay_on = true, @@ -269,7 +259,7 @@ struct sh7372_pm_domain sh7372_a3ri = { struct sh7372_pm_domain sh7372_a3sp = { .genpd.name = "A3SP", .bit_shift = 11, - .gov = &sh7372_always_on_gov, + .gov = &pm_domain_always_on_gov, .no_debug = true, }; From c656c30668b9408c46d6bfbd1eea472f39a3155d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 8 Dec 2011 23:27:48 +0100 Subject: [PATCH 12/18] ARM: S3C64XX: Implement basic power domain support The S3C64xx SoCs contain a set of gateable power domains which can be enabled and disabled at runtime in order to save power. Use the generic power domain code to implement support for these in software, enabling runtime control of most domains: - ETM (not supported in mainline). - Domain G: 3D acceleration (no mainline support). - Domain V: MFC (no mainline support). - Domain I: JPEG and camera interface (no mainline support). - Domain P: 2D acceleration, TV encoder and scaler (no mainline support) - Domain S: Security (no mainline support). - Domain F: LCD (driver already uses runtime PM), post processing and rotation (no mainline support). The IROM domain is marked as always enabled as we should arrange for it to be enabled when we suspend which will need a bit more work. Due to all the conditional device registration that the platform does wrap s3c_pm_init() with s3c64xx_pm_init() which actually puts the device into the power domain after the machines have registered, looking for platform data to tell if the device was registered. Since currently only Cragganmore actually sets up PM that is the only machine updated. Signed-off-by: Mark Brown Acked-by: Kukjin Kim Signed-off-by: Rafael J. Wysocki --- arch/arm/mach-s3c64xx/Kconfig | 1 + arch/arm/mach-s3c64xx/mach-crag6410.c | 2 +- arch/arm/mach-s3c64xx/pm.c | 176 +++++++++++++++++++++++- arch/arm/plat-samsung/include/plat/pm.h | 6 + 4 files changed, 182 insertions(+), 3 deletions(-) diff --git a/arch/arm/mach-s3c64xx/Kconfig b/arch/arm/mach-s3c64xx/Kconfig index 5552e048c2be..381586c7b1b2 100644 --- a/arch/arm/mach-s3c64xx/Kconfig +++ b/arch/arm/mach-s3c64xx/Kconfig @@ -8,6 +8,7 @@ config PLAT_S3C64XX bool depends on ARCH_S3C64XX select SAMSUNG_WAKEMASK + select PM_GENERIC_DOMAINS default y help Base platform code for any Samsung S3C64XX device diff --git a/arch/arm/mach-s3c64xx/mach-crag6410.c b/arch/arm/mach-s3c64xx/mach-crag6410.c index d04b65448510..707b9b22f9fd 100644 --- a/arch/arm/mach-s3c64xx/mach-crag6410.c +++ b/arch/arm/mach-s3c64xx/mach-crag6410.c @@ -704,7 +704,7 @@ static void __init crag6410_machine_init(void) regulator_has_full_constraints(); - s3c_pm_init(); + s3c64xx_pm_init(); } MACHINE_START(WLF_CRAGG_6410, "Wolfson Cragganmore 6410") diff --git a/arch/arm/mach-s3c64xx/pm.c b/arch/arm/mach-s3c64xx/pm.c index b375cd5c47cb..7d3e81b9dd06 100644 --- a/arch/arm/mach-s3c64xx/pm.c +++ b/arch/arm/mach-s3c64xx/pm.c @@ -17,10 +17,12 @@ #include #include #include +#include #include #include +#include #include #include @@ -31,6 +33,148 @@ #include #include +struct s3c64xx_pm_domain { + char *const name; + u32 ena; + u32 pwr_stat; + struct generic_pm_domain pd; +}; + +static int s3c64xx_pd_off(struct generic_pm_domain *domain) +{ + struct s3c64xx_pm_domain *pd; + u32 val; + + pd = container_of(domain, struct s3c64xx_pm_domain, pd); + + val = __raw_readl(S3C64XX_NORMAL_CFG); + val &= ~(pd->ena); + __raw_writel(val, S3C64XX_NORMAL_CFG); + + return 0; +} + +static int s3c64xx_pd_on(struct generic_pm_domain *domain) +{ + struct s3c64xx_pm_domain *pd; + u32 val; + long retry = 1000000L; + + pd = container_of(domain, struct s3c64xx_pm_domain, pd); + + val = __raw_readl(S3C64XX_NORMAL_CFG); + val |= pd->ena; + __raw_writel(val, S3C64XX_NORMAL_CFG); + + /* Not all domains provide power status readback */ + if (pd->pwr_stat) { + do { + cpu_relax(); + if (__raw_readl(S3C64XX_BLK_PWR_STAT) & pd->pwr_stat) + break; + } while (retry--); + + if (!retry) { + pr_err("Failed to start domain %s\n", pd->name); + return -EBUSY; + } + } + + return 0; +} + +static struct s3c64xx_pm_domain s3c64xx_pm_irom = { + .name = "IROM", + .ena = S3C64XX_NORMALCFG_IROM_ON, + .pd = { + .power_off = s3c64xx_pd_off, + .power_on = s3c64xx_pd_on, + }, +}; + +static struct s3c64xx_pm_domain s3c64xx_pm_etm = { + .name = "ETM", + .ena = S3C64XX_NORMALCFG_DOMAIN_ETM_ON, + .pwr_stat = S3C64XX_BLKPWRSTAT_ETM, + .pd = { + .power_off = s3c64xx_pd_off, + .power_on = s3c64xx_pd_on, + }, +}; + +static struct s3c64xx_pm_domain s3c64xx_pm_s = { + .name = "S", + .ena = S3C64XX_NORMALCFG_DOMAIN_S_ON, + .pwr_stat = S3C64XX_BLKPWRSTAT_S, + .pd = { + .power_off = s3c64xx_pd_off, + .power_on = s3c64xx_pd_on, + }, +}; + +static struct s3c64xx_pm_domain s3c64xx_pm_f = { + .name = "F", + .ena = S3C64XX_NORMALCFG_DOMAIN_F_ON, + .pwr_stat = S3C64XX_BLKPWRSTAT_F, + .pd = { + .power_off = s3c64xx_pd_off, + .power_on = s3c64xx_pd_on, + }, +}; + +static struct s3c64xx_pm_domain s3c64xx_pm_p = { + .name = "P", + .ena = S3C64XX_NORMALCFG_DOMAIN_P_ON, + .pwr_stat = S3C64XX_BLKPWRSTAT_P, + .pd = { + .power_off = s3c64xx_pd_off, + .power_on = s3c64xx_pd_on, + }, +}; + +static struct s3c64xx_pm_domain s3c64xx_pm_i = { + .name = "I", + .ena = S3C64XX_NORMALCFG_DOMAIN_I_ON, + .pwr_stat = S3C64XX_BLKPWRSTAT_I, + .pd = { + .power_off = s3c64xx_pd_off, + .power_on = s3c64xx_pd_on, + }, +}; + +static struct s3c64xx_pm_domain s3c64xx_pm_g = { + .name = "G", + .ena = S3C64XX_NORMALCFG_DOMAIN_G_ON, + .pd = { + .power_off = s3c64xx_pd_off, + .power_on = s3c64xx_pd_on, + }, +}; + +static struct s3c64xx_pm_domain s3c64xx_pm_v = { + .name = "V", + .ena = S3C64XX_NORMALCFG_DOMAIN_V_ON, + .pwr_stat = S3C64XX_BLKPWRSTAT_V, + .pd = { + .power_off = s3c64xx_pd_off, + .power_on = s3c64xx_pd_on, + }, +}; + +static struct s3c64xx_pm_domain *s3c64xx_always_on_pm_domains[] = { + &s3c64xx_pm_irom, +}; + +static struct s3c64xx_pm_domain *s3c64xx_pm_domains[] = { + &s3c64xx_pm_etm, + &s3c64xx_pm_g, + &s3c64xx_pm_v, + &s3c64xx_pm_i, + &s3c64xx_pm_p, + &s3c64xx_pm_s, + &s3c64xx_pm_f, +}; + #ifdef CONFIG_S3C_PM_DEBUG_LED_SMDK void s3c_pm_debug_smdkled(u32 set, u32 clear) { @@ -89,6 +233,8 @@ static struct sleep_save misc_save[] = { SAVE_ITEM(S3C64XX_SDMA_SEL), SAVE_ITEM(S3C64XX_MODEM_MIFPCON), + + SAVE_ITEM(S3C64XX_NORMAL_CFG), }; void s3c_pm_configure_extint(void) @@ -179,7 +325,26 @@ static void s3c64xx_pm_prepare(void) __raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT), S3C64XX_WAKEUP_STAT); } -static int s3c64xx_pm_init(void) +int __init s3c64xx_pm_init(void) +{ + int i; + + s3c_pm_init(); + + for (i = 0; i < ARRAY_SIZE(s3c64xx_always_on_pm_domains); i++) + pm_genpd_init(&s3c64xx_always_on_pm_domains[i]->pd, + &pm_domain_always_on_gov, false); + + for (i = 0; i < ARRAY_SIZE(s3c64xx_pm_domains); i++) + pm_genpd_init(&s3c64xx_pm_domains[i]->pd, NULL, false); + + if (dev_get_platdata(&s3c_device_fb.dev)) + pm_genpd_add_device(&s3c64xx_pm_f.pd, &s3c_device_fb.dev); + + return 0; +} + +static __init int s3c64xx_pm_initcall(void) { pm_cpu_prep = s3c64xx_pm_prepare; pm_cpu_sleep = s3c64xx_cpu_suspend; @@ -198,5 +363,12 @@ static int s3c64xx_pm_init(void) return 0; } +arch_initcall(s3c64xx_pm_initcall); -arch_initcall(s3c64xx_pm_init); +static __init int s3c64xx_pm_late_initcall(void) +{ + pm_genpd_poweroff_unused(); + + return 0; +} +late_initcall(s3c64xx_pm_late_initcall); diff --git a/arch/arm/plat-samsung/include/plat/pm.h b/arch/arm/plat-samsung/include/plat/pm.h index dcf68709f9cf..a6bdee204f2a 100644 --- a/arch/arm/plat-samsung/include/plat/pm.h +++ b/arch/arm/plat-samsung/include/plat/pm.h @@ -22,6 +22,7 @@ struct sys_device; #ifdef CONFIG_PM extern __init int s3c_pm_init(void); +extern __init int s3c64xx_pm_init(void); #else @@ -29,6 +30,11 @@ static inline int s3c_pm_init(void) { return 0; } + +static inline int s3c64xx_pm_init(void) +{ + return 0; +} #endif /* configuration for the IRQ mask over sleep */ From f7dadb37931a6ffa2aa6b443188299166dc5e638 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Fri, 23 Dec 2011 01:23:07 +0100 Subject: [PATCH 13/18] PM / shmobile: Add support for the sh7372 A4S power domain / sleep mode The sh7372 contains a power domain named A4S which in turn contains power domains for both I/O Devices and CPU cores. At this point only System wide Suspend-to-RAM is supported, but the the hardware can also support CPUIdle. With more efforts in the future CPUIdle can work with bot A4S and A3SM. Tested on the sh7372 Mackerel board. [rjw: Rebased on top of the current linux-pm tree.] Signed-off-by: Magnus Damm Signed-off-by: Rafael J. Wysocki --- arch/arm/mach-shmobile/include/mach/common.h | 4 +- arch/arm/mach-shmobile/include/mach/sh7372.h | 3 + arch/arm/mach-shmobile/intc-sh7372.c | 49 +++++++++++++++ arch/arm/mach-shmobile/pm-sh7372.c | 64 ++++++++++++++------ arch/arm/mach-shmobile/setup-sh7372.c | 6 +- arch/arm/mach-shmobile/sleep-sh7372.S | 21 ++++--- 6 files changed, 117 insertions(+), 30 deletions(-) diff --git a/arch/arm/mach-shmobile/include/mach/common.h b/arch/arm/mach-shmobile/include/mach/common.h index 834bd6cd508f..4807623fb71c 100644 --- a/arch/arm/mach-shmobile/include/mach/common.h +++ b/arch/arm/mach-shmobile/include/mach/common.h @@ -35,8 +35,8 @@ extern void sh7372_add_standard_devices(void); extern void sh7372_clock_init(void); extern void sh7372_pinmux_init(void); extern void sh7372_pm_init(void); -extern void sh7372_resume_core_standby_a3sm(void); -extern int sh7372_do_idle_a3sm(unsigned long unused); +extern void sh7372_resume_core_standby_sysc(void); +extern int sh7372_do_idle_sysc(unsigned long sleep_mode); extern struct clk sh7372_extal1_clk; extern struct clk sh7372_extal2_clk; diff --git a/arch/arm/mach-shmobile/include/mach/sh7372.h b/arch/arm/mach-shmobile/include/mach/sh7372.h index 84532f9629b2..d0731d973f1f 100644 --- a/arch/arm/mach-shmobile/include/mach/sh7372.h +++ b/arch/arm/mach-shmobile/include/mach/sh7372.h @@ -499,6 +499,7 @@ extern struct sh7372_pm_domain sh7372_d4; extern struct sh7372_pm_domain sh7372_a4r; extern struct sh7372_pm_domain sh7372_a3rv; extern struct sh7372_pm_domain sh7372_a3ri; +extern struct sh7372_pm_domain sh7372_a4s; extern struct sh7372_pm_domain sh7372_a3sp; extern struct sh7372_pm_domain sh7372_a3sg; @@ -515,5 +516,7 @@ extern void sh7372_pm_add_subdomain(struct sh7372_pm_domain *sh7372_pd, extern void sh7372_intcs_suspend(void); extern void sh7372_intcs_resume(void); +extern void sh7372_intca_suspend(void); +extern void sh7372_intca_resume(void); #endif /* __ASM_SH7372_H__ */ diff --git a/arch/arm/mach-shmobile/intc-sh7372.c b/arch/arm/mach-shmobile/intc-sh7372.c index 2d8856df80e2..d087b31b5d12 100644 --- a/arch/arm/mach-shmobile/intc-sh7372.c +++ b/arch/arm/mach-shmobile/intc-sh7372.c @@ -611,3 +611,52 @@ void sh7372_intcs_resume(void) for (k = 0x80; k <= 0x9c; k += 4) __raw_writeb(ffd5[k], intcs_ffd5 + k); } + +static unsigned short e694[0x200]; +static unsigned short e695[0x200]; + +void sh7372_intca_suspend(void) +{ + int k; + + for (k = 0x00; k <= 0x38; k += 4) + e694[k] = __raw_readw(0xe6940000 + k); + + for (k = 0x80; k <= 0xb4; k += 4) + e694[k] = __raw_readb(0xe6940000 + k); + + for (k = 0x180; k <= 0x1b4; k += 4) + e694[k] = __raw_readb(0xe6940000 + k); + + for (k = 0x00; k <= 0x50; k += 4) + e695[k] = __raw_readw(0xe6950000 + k); + + for (k = 0x80; k <= 0xa8; k += 4) + e695[k] = __raw_readb(0xe6950000 + k); + + for (k = 0x180; k <= 0x1a8; k += 4) + e695[k] = __raw_readb(0xe6950000 + k); +} + +void sh7372_intca_resume(void) +{ + int k; + + for (k = 0x00; k <= 0x38; k += 4) + __raw_writew(e694[k], 0xe6940000 + k); + + for (k = 0x80; k <= 0xb4; k += 4) + __raw_writeb(e694[k], 0xe6940000 + k); + + for (k = 0x180; k <= 0x1b4; k += 4) + __raw_writeb(e694[k], 0xe6940000 + k); + + for (k = 0x00; k <= 0x50; k += 4) + __raw_writew(e695[k], 0xe6950000 + k); + + for (k = 0x80; k <= 0xa8; k += 4) + __raw_writeb(e695[k], 0xe6950000 + k); + + for (k = 0x180; k <= 0x1a8; k += 4) + __raw_writeb(e695[k], 0xe6950000 + k); +} diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index f742c61c8902..1e659d7360d1 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -256,6 +256,14 @@ struct sh7372_pm_domain sh7372_a3ri = { .bit_shift = 8, }; +struct sh7372_pm_domain sh7372_a4s = { + .genpd.name = "A4S", + .bit_shift = 10, + .gov = &pm_domain_always_on_gov, + .no_debug = true, + .stay_on = true, +}; + struct sh7372_pm_domain sh7372_a3sp = { .genpd.name = "A3SP", .bit_shift = 11, @@ -289,11 +297,16 @@ static int sh7372_do_idle_core_standby(unsigned long unused) return 0; } -static void sh7372_enter_core_standby(void) +static void sh7372_set_reset_vector(unsigned long address) { /* set reset vector, translate 4k */ - __raw_writel(__pa(sh7372_resume_core_standby_a3sm), SBAR); + __raw_writel(address, SBAR); __raw_writel(0, APARMBAREA); +} + +static void sh7372_enter_core_standby(void) +{ + sh7372_set_reset_vector(__pa(sh7372_resume_core_standby_sysc)); /* enter sleep mode with SYSTBCR to 0x10 */ __raw_writel(0x10, SYSTBCR); @@ -306,27 +319,22 @@ static void sh7372_enter_core_standby(void) #endif #ifdef CONFIG_SUSPEND -static void sh7372_enter_a3sm_common(int pllc0_on) +static void sh7372_enter_sysc(int pllc0_on, unsigned long sleep_mode) { - /* set reset vector, translate 4k */ - __raw_writel(__pa(sh7372_resume_core_standby_a3sm), SBAR); - __raw_writel(0, APARMBAREA); - if (pllc0_on) __raw_writel(0, PLLC01STPCR); else __raw_writel(1 << 28, PLLC01STPCR); - __raw_writel(0, PDNSEL); /* power-down A3SM only, not A4S */ __raw_readl(WUPSFAC); /* read wakeup int. factor before sleep */ - cpu_suspend(0, sh7372_do_idle_a3sm); + cpu_suspend(sleep_mode, sh7372_do_idle_sysc); __raw_readl(WUPSFAC); /* read wakeup int. factor after wakeup */ /* disable reset vector translation */ __raw_writel(0, SBAR); } -static int sh7372_a3sm_valid(unsigned long *mskp, unsigned long *msk2p) +static int sh7372_sysc_valid(unsigned long *mskp, unsigned long *msk2p) { unsigned long mstpsr0, mstpsr1, mstpsr2, mstpsr3, mstpsr4; unsigned long msk, msk2; @@ -414,7 +422,7 @@ static void sh7372_icr_to_irqcr(unsigned long icr, u16 *irqcr1p, u16 *irqcr2p) *irqcr2p = irqcr2; } -static void sh7372_setup_a3sm(unsigned long msk, unsigned long msk2) +static void sh7372_setup_sysc(unsigned long msk, unsigned long msk2) { u16 irqcrx_low, irqcrx_high, irqcry_low, irqcry_high; unsigned long tmp; @@ -447,6 +455,22 @@ static void sh7372_setup_a3sm(unsigned long msk, unsigned long msk2) __raw_writel((irqcrx_high << 16) | irqcrx_low, IRQCR3); __raw_writel((irqcry_high << 16) | irqcry_low, IRQCR4); } + +static void sh7372_enter_a3sm_common(int pllc0_on) +{ + sh7372_set_reset_vector(__pa(sh7372_resume_core_standby_sysc)); + sh7372_enter_sysc(pllc0_on, 1 << 12); +} + +static void sh7372_enter_a4s_common(int pllc0_on) +{ + sh7372_intca_suspend(); + memcpy((void *)SMFRAM, sh7372_resume_core_standby_sysc, 0x100); + sh7372_set_reset_vector(SMFRAM); + sh7372_enter_sysc(pllc0_on, 1 << 10); + sh7372_intca_resume(); +} + #endif #ifdef CONFIG_CPU_IDLE @@ -480,14 +504,20 @@ static int sh7372_enter_suspend(suspend_state_t suspend_state) unsigned long msk, msk2; /* check active clocks to determine potential wakeup sources */ - if (sh7372_a3sm_valid(&msk, &msk2)) { - + if (sh7372_sysc_valid(&msk, &msk2)) { /* convert INTC mask and sense to SYSC mask and sense */ - sh7372_setup_a3sm(msk, msk2); + sh7372_setup_sysc(msk, msk2); - /* enter A3SM sleep with PLLC0 off */ - pr_debug("entering A3SM\n"); - sh7372_enter_a3sm_common(0); + if (!sh7372_a3sp.stay_on && + sh7372_a4s.genpd.status == GPD_STATE_POWER_OFF) { + /* enter A4S sleep with PLLC0 off */ + pr_debug("entering A4S\n"); + sh7372_enter_a4s_common(0); + } else { + /* enter A3SM sleep with PLLC0 off */ + pr_debug("entering A3SM\n"); + sh7372_enter_a3sm_common(0); + } } else { /* default to Core Standby that supports all wakeup sources */ pr_debug("entering Core Standby\n"); diff --git a/arch/arm/mach-shmobile/setup-sh7372.c b/arch/arm/mach-shmobile/setup-sh7372.c index 2380389e6ac5..c197f9d29d04 100644 --- a/arch/arm/mach-shmobile/setup-sh7372.c +++ b/arch/arm/mach-shmobile/setup-sh7372.c @@ -994,12 +994,16 @@ void __init sh7372_add_standard_devices(void) sh7372_init_pm_domain(&sh7372_a4r); sh7372_init_pm_domain(&sh7372_a3rv); sh7372_init_pm_domain(&sh7372_a3ri); - sh7372_init_pm_domain(&sh7372_a3sg); + sh7372_init_pm_domain(&sh7372_a4s); sh7372_init_pm_domain(&sh7372_a3sp); + sh7372_init_pm_domain(&sh7372_a3sg); sh7372_pm_add_subdomain(&sh7372_a4lc, &sh7372_a3rv); sh7372_pm_add_subdomain(&sh7372_a4r, &sh7372_a4lc); + sh7372_pm_add_subdomain(&sh7372_a4s, &sh7372_a3sg); + sh7372_pm_add_subdomain(&sh7372_a4s, &sh7372_a3sp); + platform_add_devices(sh7372_early_devices, ARRAY_SIZE(sh7372_early_devices)); diff --git a/arch/arm/mach-shmobile/sleep-sh7372.S b/arch/arm/mach-shmobile/sleep-sh7372.S index f3ab3c5810ea..1d564674451d 100644 --- a/arch/arm/mach-shmobile/sleep-sh7372.S +++ b/arch/arm/mach-shmobile/sleep-sh7372.S @@ -37,13 +37,18 @@ #if defined(CONFIG_SUSPEND) || defined(CONFIG_CPU_IDLE) .align 12 .text - .global sh7372_resume_core_standby_a3sm -sh7372_resume_core_standby_a3sm: + .global sh7372_resume_core_standby_sysc +sh7372_resume_core_standby_sysc: ldr pc, 1f 1: .long cpu_resume - PAGE_OFFSET + PLAT_PHYS_OFFSET - .global sh7372_do_idle_a3sm -sh7372_do_idle_a3sm: +#define SPDCR 0xe6180008 + + /* A3SM & A4S power down */ + .global sh7372_do_idle_sysc +sh7372_do_idle_sysc: + mov r8, r0 /* sleep mode passed in r0 */ + /* * Clear the SCTLR.C bit to prevent further data cache * allocation. Clearing SCTLR.C would make all the data accesses @@ -80,13 +85,9 @@ sh7372_do_idle_a3sm: dsb dmb -#define SPDCR 0xe6180008 -#define A3SM (1 << 12) - - /* A3SM power down */ + /* SYSC power down */ ldr r0, =SPDCR - ldr r1, =A3SM - str r1, [r0] + str r8, [r0] 1: b 1b From 0f966d74cf77a9140a025464a287e1d2fee8a1fc Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 23 Dec 2011 01:23:30 +0100 Subject: [PATCH 14/18] PM / shmobile: Don't include SH7372's INTCS in syscore suspend/resume Since the SH7372's INTCS in included into syscore suspend/resume, which causes the chip to be accessed when PM domains have been turned off during system suspend, the A4R domain containing the INTCS has to stay on during system sleep, which is suboptimal from the power consumption point of view. For this reason, add a new INTC flag, skip_syscore_suspend, to mark the INTCS for intc_suspend() and intc_resume(), so that they don't touch it. This allows the A4R domain to be turned off during system suspend and the INTCS state is resrored during system resume by the A4R's "power on" code. Suggested-by: Magnus Damm Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm --- arch/arm/mach-shmobile/intc-sh7372.c | 1 + drivers/sh/intc/core.c | 8 ++++++++ drivers/sh/intc/internals.h | 1 + include/linux/sh_intc.h | 1 + 4 files changed, 11 insertions(+) diff --git a/arch/arm/mach-shmobile/intc-sh7372.c b/arch/arm/mach-shmobile/intc-sh7372.c index d087b31b5d12..89afcaba99a1 100644 --- a/arch/arm/mach-shmobile/intc-sh7372.c +++ b/arch/arm/mach-shmobile/intc-sh7372.c @@ -535,6 +535,7 @@ static struct resource intcs_resources[] __initdata = { static struct intc_desc intcs_desc __initdata = { .name = "sh7372-intcs", .force_enable = ENABLED_INTCS, + .skip_syscore_suspend = true, .resource = intcs_resources, .num_resources = ARRAY_SIZE(intcs_resources), .hw = INTC_HW_DESC(intcs_vectors, intcs_groups, intcs_mask_registers, diff --git a/drivers/sh/intc/core.c b/drivers/sh/intc/core.c index 8b7a141ff35e..be5a025eeca3 100644 --- a/drivers/sh/intc/core.c +++ b/drivers/sh/intc/core.c @@ -354,6 +354,8 @@ int __init register_intc_controller(struct intc_desc *desc) if (desc->force_enable) intc_enable_disable_enum(desc, d, desc->force_enable, 1); + d->skip_suspend = desc->skip_syscore_suspend; + nr_intc_controllers++; return 0; @@ -386,6 +388,9 @@ static int intc_suspend(void) list_for_each_entry(d, &intc_list, list) { int irq; + if (d->skip_suspend) + continue; + /* enable wakeup irqs belonging to this intc controller */ for_each_active_irq(irq) { struct irq_data *data; @@ -409,6 +414,9 @@ static void intc_resume(void) list_for_each_entry(d, &intc_list, list) { int irq; + if (d->skip_suspend) + continue; + for_each_active_irq(irq) { struct irq_data *data; struct irq_chip *chip; diff --git a/drivers/sh/intc/internals.h b/drivers/sh/intc/internals.h index 5b934851efa8..b3fe1cf25a28 100644 --- a/drivers/sh/intc/internals.h +++ b/drivers/sh/intc/internals.h @@ -67,6 +67,7 @@ struct intc_desc_int { struct intc_window *window; unsigned int nr_windows; struct irq_chip chip; + bool skip_suspend; }; diff --git a/include/linux/sh_intc.h b/include/linux/sh_intc.h index 5812fefbcedf..b160645f5599 100644 --- a/include/linux/sh_intc.h +++ b/include/linux/sh_intc.h @@ -95,6 +95,7 @@ struct intc_desc { unsigned int num_resources; intc_enum force_enable; intc_enum force_disable; + bool skip_syscore_suspend; struct intc_hw_desc hw; }; From 767c0f3aed74be56f268709f5347e6c86d52b408 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 23 Dec 2011 01:23:39 +0100 Subject: [PATCH 15/18] PM / shmobile: Remove the stay_on flag from SH7372's PM domains SH7372 uses two independent mechanisms for ensuring that power domains will never be turned off: the stay_on flag and the "always on" domain governor. Moreover, the "always on" governor is only taken into accout by runtime PM code paths, while the stay_on flag affects all attempts to turn the given domain off. Thus setting the stay_on flag causes the "always on" governor to be unnecessary, which is quite confusing. However, the stay_on flag is currently only set for two domains: A3SP and A4S. Moreover, it only is set for the A3SP domain if console_suspend_enabled is set, so stay_on won't be necessary for that domain any more if console_suspend_enabled is checked directly in its .suspend() routine. [This requires domain .suspend() to return a result, but that is a minor modification.] Analogously, stay_on won't be necessary for the A4S domain if it's .suspend() routine always returns an error code. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm --- arch/arm/mach-shmobile/include/mach/sh7372.h | 3 +- arch/arm/mach-shmobile/pm-sh7372.c | 49 +++++++++++--------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/arch/arm/mach-shmobile/include/mach/sh7372.h b/arch/arm/mach-shmobile/include/mach/sh7372.h index d0731d973f1f..8254ab86f6cd 100644 --- a/arch/arm/mach-shmobile/include/mach/sh7372.h +++ b/arch/arm/mach-shmobile/include/mach/sh7372.h @@ -480,11 +480,10 @@ struct platform_device; struct sh7372_pm_domain { struct generic_pm_domain genpd; struct dev_power_governor *gov; - void (*suspend)(void); + int (*suspend)(void); void (*resume)(void); unsigned int bit_shift; bool no_debug; - bool stay_on; }; static inline struct sh7372_pm_domain *to_sh7372_pd(struct generic_pm_domain *d) diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 1e659d7360d1..7fda2301c9b2 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -82,11 +82,12 @@ static int pd_power_down(struct generic_pm_domain *genpd) struct sh7372_pm_domain *sh7372_pd = to_sh7372_pd(genpd); unsigned int mask = 1 << sh7372_pd->bit_shift; - if (sh7372_pd->suspend) - sh7372_pd->suspend(); + if (sh7372_pd->suspend) { + int ret = sh7372_pd->suspend(); - if (sh7372_pd->stay_on) - return 0; + if (ret) + return ret; + } if (__raw_readl(PSTR) & mask) { unsigned int retry_count; @@ -113,9 +114,6 @@ static int __pd_power_up(struct sh7372_pm_domain *sh7372_pd, bool do_resume) unsigned int retry_count; int ret = 0; - if (sh7372_pd->stay_on) - goto out; - if (__raw_readl(PSTR) & mask) goto out; @@ -148,10 +146,11 @@ static int pd_power_up(struct generic_pm_domain *genpd) return __pd_power_up(to_sh7372_pd(genpd), true); } -static void sh7372_a4r_suspend(void) +static int sh7372_a4r_suspend(void) { sh7372_intcs_suspend(); __raw_writel(0x300fffff, WUPRMSK); /* avoid wakeup */ + return 0; } static bool pd_active_wakeup(struct device *dev) @@ -243,7 +242,6 @@ struct sh7372_pm_domain sh7372_a4r = { .gov = &pm_domain_always_on_gov, .suspend = sh7372_a4r_suspend, .resume = sh7372_intcs_resume, - .stay_on = true, }; struct sh7372_pm_domain sh7372_a3rv = { @@ -256,29 +254,40 @@ struct sh7372_pm_domain sh7372_a3ri = { .bit_shift = 8, }; +static int sh7372_a4s_suspend(void) +{ + /* + * The A4S domain contains the CPU core and therefore it should + * only be turned off if the CPU is in use. + */ + return -EBUSY; +} + struct sh7372_pm_domain sh7372_a4s = { .genpd.name = "A4S", .bit_shift = 10, .gov = &pm_domain_always_on_gov, .no_debug = true, - .stay_on = true, + .suspend = sh7372_a4s_suspend, }; +static int sh7372_a3sp_suspend(void) +{ + /* + * Serial consoles make use of SCIF hardware located in A3SP, + * keep such power domain on if "no_console_suspend" is set. + */ + return console_suspend_enabled ? -EBUSY : 0; +} + struct sh7372_pm_domain sh7372_a3sp = { .genpd.name = "A3SP", .bit_shift = 11, .gov = &pm_domain_always_on_gov, .no_debug = true, + .suspend = sh7372_a3sp_suspend, }; -static void sh7372_a3sp_init(void) -{ - /* serial consoles make use of SCIF hardware located in A3SP, - * keep such power domain on if "no_console_suspend" is set. - */ - sh7372_a3sp.stay_on = !console_suspend_enabled; -} - struct sh7372_pm_domain sh7372_a3sg = { .genpd.name = "A3SG", .bit_shift = 13, @@ -508,7 +517,7 @@ static int sh7372_enter_suspend(suspend_state_t suspend_state) /* convert INTC mask and sense to SYSC mask and sense */ sh7372_setup_sysc(msk, msk2); - if (!sh7372_a3sp.stay_on && + if (!console_suspend_enabled && sh7372_a4s.genpd.status == GPD_STATE_POWER_OFF) { /* enter A4S sleep with PLLC0 off */ pr_debug("entering A4S\n"); @@ -544,8 +553,6 @@ void __init sh7372_pm_init(void) /* do not convert A3SM, A3SP, A3SG, A4R power down into A4S */ __raw_writel(0, PDNSEL); - sh7372_a3sp_init(); - sh7372_suspend_init(); sh7372_cpuidle_init(); } From 40a5f8be2f482783de0f1f0fe856660e489734a8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 23 Dec 2011 01:23:52 +0100 Subject: [PATCH 16/18] PM / QoS: Introduce dev_pm_qos_add_ancestor_request() Some devices, like the I2C controller on SH7372, are not necessary for providing power to their children or forwarding wakeup signals (and generally interrupts) from them. They are only needed by their children when there's some data to transfer, so they may be suspended for the majority of time and resumed on demand, when the children have data to send or receive. For this purpose, however, their power.ignore_children flags have to be set, or the PM core wouldn't allow them to be suspended while their children were active. Unfortunately, in some situations it may take too much time to resume such devices so that they can assist their children in transferring data. For example, if such a device belongs to a PM domain which goes to the "power off" state when that device is suspended, it may take too much time to restore power to the domain in response to the request from one of the device's children. In that case, if the parent's resume time is critical, the domain should stay in the "power on" state, although it still may be desirable to power manage the parent itself (e.g. by manipulating its clock). In general, device PM QoS may be used to address this problem. Namely, if the device's children added PM QoS latency constraints for it, they would be able to prevent it from being put into an overly deep low-power state. However, in some cases the devices needing to be serviced are not the immediate children of a "children-ignoring" device, but its grandchildren or even less direct descendants. In those cases, the entity wanting to add a PM QoS request for a given device's ancestor that ignores its children will have to find it in the first place, so introduce a new helper function that may be used to achieve that. This function, dev_pm_qos_add_ancestor_request(), will search for the first ancestor of the given device whose power.ignore_children flag is set and will add a device PM QoS latency request for that ancestor on behalf of the caller. The request added this way may be removed with the help of dev_pm_qos_remove_request() in the future, like any other device PM QoS latency request. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/qos.c | 25 +++++++++++++++++++++++++ include/linux/pm_qos.h | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c index 86de6c50fc41..edf7687615e8 100644 --- a/drivers/base/power/qos.c +++ b/drivers/base/power/qos.c @@ -412,3 +412,28 @@ int dev_pm_qos_remove_global_notifier(struct notifier_block *notifier) return blocking_notifier_chain_unregister(&dev_pm_notifiers, notifier); } EXPORT_SYMBOL_GPL(dev_pm_qos_remove_global_notifier); + +/** + * dev_pm_qos_add_ancestor_request - Add PM QoS request for device's ancestor. + * @dev: Device whose ancestor to add the request for. + * @req: Pointer to the preallocated handle. + * @value: Constraint latency value. + */ +int dev_pm_qos_add_ancestor_request(struct device *dev, + struct dev_pm_qos_request *req, s32 value) +{ + struct device *ancestor = dev->parent; + int error = -ENODEV; + + while (ancestor && !ancestor->power.ignore_children) + ancestor = ancestor->parent; + + if (ancestor) + error = dev_pm_qos_add_request(ancestor, req, value); + + if (error) + req->dev = NULL; + + return error; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request); diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h index 83b0ea302a80..fe247b33652d 100644 --- a/include/linux/pm_qos.h +++ b/include/linux/pm_qos.h @@ -91,6 +91,8 @@ int dev_pm_qos_add_global_notifier(struct notifier_block *notifier); int dev_pm_qos_remove_global_notifier(struct notifier_block *notifier); void dev_pm_qos_constraints_init(struct device *dev); void dev_pm_qos_constraints_destroy(struct device *dev); +int dev_pm_qos_add_ancestor_request(struct device *dev, + struct dev_pm_qos_request *req, s32 value); #else static inline int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node, @@ -150,6 +152,9 @@ static inline void dev_pm_qos_constraints_destroy(struct device *dev) { dev->power.power_state = PMSG_INVALID; } +static inline int dev_pm_qos_add_ancestor_request(struct device *dev, + struct dev_pm_qos_request *req, s32 value) + { return 0; } #endif #endif From 9ee27ffbe303ce18e7336115f1d443e9911eba53 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 23 Dec 2011 01:24:25 +0100 Subject: [PATCH 17/18] PM / input / touchscreen: Make st1232 use device PM QoS constraints Make the st1232 driver use dev_pm_qos_add_ancestor_request() to add a device PM QoS latency constraint for the controller it depends on, so that the controller won't go into an overly deep low-power state when the touchscreen has to be particularly responsive (e.g. when the user moves his or her finger on it). This change is based on a prototype patch from Guennadi Liakhovetski. Signed-off-by: Rafael J. Wysocki --- drivers/input/touchscreen/st1232.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/input/touchscreen/st1232.c b/drivers/input/touchscreen/st1232.c index 4ab371358b33..8825fe37d433 100644 --- a/drivers/input/touchscreen/st1232.c +++ b/drivers/input/touchscreen/st1232.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,7 @@ struct st1232_ts_data { struct i2c_client *client; struct input_dev *input_dev; struct st1232_ts_finger finger[MAX_FINGERS]; + struct dev_pm_qos_request low_latency_req; }; static int st1232_ts_read_data(struct st1232_ts_data *ts) @@ -118,8 +120,17 @@ static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id) } /* SYN_MT_REPORT only if no contact */ - if (!count) + if (!count) { input_mt_sync(input_dev); + if (ts->low_latency_req.dev) { + dev_pm_qos_remove_request(&ts->low_latency_req); + ts->low_latency_req.dev = NULL; + } + } else if (!ts->low_latency_req.dev) { + /* First contact, request 100 us latency. */ + dev_pm_qos_add_ancestor_request(&ts->client->dev, + &ts->low_latency_req, 100); + } /* SYN_REPORT */ input_sync(input_dev); From a8cf27bee7adc40d91956cf1b9e44d7001f93aba Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 23 Dec 2011 01:24:34 +0100 Subject: [PATCH 18/18] PM / shmobile: Allow the A4R domain to be turned off at run time After adding PM QoS constraints for the I2C controller in the A4R domain, that domain can be allowed to be turned off and on by runtime PM, so remove the "always on" governor from it. However, the A4R domain has to be "on" when suspend_device_irqs() and resume_device_irqs() are executed during system suspend and resume, respectively, so that those functions don't crash while accessing the INTCS. For this reason, add a PM notifier to the SH7372 PM code and make it restore power to A4R before system suspend and remove power from all unused PM domains after system resume. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm --- arch/arm/mach-shmobile/pm-sh7372.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 7fda2301c9b2..77b8fc12fc2f 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -239,7 +239,6 @@ struct sh7372_pm_domain sh7372_d4 = { struct sh7372_pm_domain sh7372_a4r = { .genpd.name = "A4R", .bit_shift = 5, - .gov = &pm_domain_always_on_gov, .suspend = sh7372_a4r_suspend, .resume = sh7372_intcs_resume, }; @@ -535,9 +534,37 @@ static int sh7372_enter_suspend(suspend_state_t suspend_state) return 0; } +/** + * sh7372_pm_notifier_fn - SH7372 PM notifier routine. + * @notifier: Unused. + * @pm_event: Event being handled. + * @unused: Unused. + */ +static int sh7372_pm_notifier_fn(struct notifier_block *notifier, + unsigned long pm_event, void *unused) +{ + switch (pm_event) { + case PM_SUSPEND_PREPARE: + /* + * This is necessary, because the A4R domain has to be "on" + * when suspend_device_irqs() and resume_device_irqs() are + * executed during system suspend and resume, respectively, so + * that those functions don't crash while accessing the INTCS. + */ + pm_genpd_poweron(&sh7372_a4r.genpd); + break; + case PM_POST_SUSPEND: + pm_genpd_poweroff_unused(); + break; + } + + return NOTIFY_DONE; +} + static void sh7372_suspend_init(void) { shmobile_suspend_ops.enter = sh7372_enter_suspend; + pm_notifier(sh7372_pm_notifier_fn, 0); } #else static void sh7372_suspend_init(void) {}