diff options
author | Ulf Hansson <ulf.hansson@linaro.org> | 2020-06-16 14:02:21 +0200 |
---|---|---|
committer | Ulf Hansson <ulf.hansson@linaro.org> | 2020-06-18 13:11:08 +0200 |
commit | 8947a515b47903bec7f37a1c66a9022ff5618be0 (patch) | |
tree | 82eaea44d5914d3e714e3ba49a2e180eb06b9d37 | |
parent | ba384df2898841b29d92249c4a55f7927a4b9987 (diff) |
PM / Domains: Add support for genpd on/off notifiers for specific devicescpuidle_psci_to_driver+genpd_onoff_notify
A device may have specific HW constraints that must be obeyed to, before
its corresponding genpd can be powered off - and vice verse for power on.
However, these constraints can't be managed through the regular runtime PM
based deployment for a device, because the access pattern for it, isn't
solely request based.
For these reasons, let's add dev_pm_genpd_add|remove_notifier(), which
should be called with a device that is already being attached to a genpd,
to add/remove a power on/off notifier for it.
Note that, to further clarify when genpd power on/off notifiers should be
used, we can compare with the existing CPU_CLUSTER_PM_ENTER|EXIT notifiers.
In the long run, the genpd power on/off notifiers should be able to replace
these, even if that also requires additional genpd based platform support
for the current users.
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
-rw-r--r-- | drivers/base/power/domain.c | 133 | ||||
-rw-r--r-- | include/linux/pm_domain.h | 20 |
2 files changed, 149 insertions, 4 deletions
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index b1634228def2..454729b5cb12 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -545,13 +545,21 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, if (!genpd->gov) genpd->state_idx = 0; + /* Notify consumers that we are about to power off. */ + ret = blocking_notifier_call_chain(&genpd->power_notifiers, + GENPD_POWER_OFF, NULL); + if (ret) + return ret; + /* Bail out and don't power off, if a subdomain wants power on. */ - if (atomic_read(&genpd->sd_count) > 0) - return -EBUSY; + if (atomic_read(&genpd->sd_count) > 0) { + ret = -EBUSY; + goto busy; + } ret = _genpd_power_off(genpd, true); if (ret) - return ret; + goto busy; genpd->status = GPD_STATE_POWER_OFF; genpd_update_accounting(genpd); @@ -564,6 +572,10 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, } return 0; +busy: + blocking_notifier_call_chain(&genpd->power_notifiers, + GENPD_POWER_ON, NULL); + return ret; } /** @@ -606,6 +618,10 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) if (ret) goto err; + /* Inform consumers that we have powered on. */ + blocking_notifier_call_chain(&genpd->power_notifiers, + GENPD_POWER_ON, NULL); + genpd->status = GPD_STATE_ACTIVE; genpd_update_accounting(genpd); @@ -948,9 +964,18 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock, /* Choose the deepest state when suspending */ genpd->state_idx = genpd->state_count - 1; - if (_genpd_power_off(genpd, false)) + + /* Notify consumers that we are about to power off. */ + if (blocking_notifier_call_chain(&genpd->power_notifiers, + GENPD_POWER_OFF, NULL)) return; + if (_genpd_power_off(genpd, false)) { + blocking_notifier_call_chain(&genpd->power_notifiers, + GENPD_POWER_ON, NULL); + return; + } + genpd->status = GPD_STATE_POWER_OFF; list_for_each_entry(link, &genpd->slave_links, slave_node) { @@ -998,6 +1023,10 @@ static void genpd_sync_power_on(struct generic_pm_domain *genpd, bool use_lock, _genpd_power_on(genpd, false); + /* Inform consumers that we have powered on. */ + blocking_notifier_call_chain(&genpd->power_notifiers, + GENPD_POWER_ON, NULL); + genpd->status = GPD_STATE_ACTIVE; } @@ -1593,6 +1622,101 @@ int pm_genpd_remove_device(struct device *dev) } EXPORT_SYMBOL_GPL(pm_genpd_remove_device); +/** + * dev_pm_genpd_add_notifier - Add a genpd power on/off notifier for @dev + * + * @dev: Device that should be associated with the notifier + * @nb: The notifier block to register + * + * Users may call this function to add a genpd power on/off notifier for an + * attached @dev. Only one notifier per device is allowed. The notifier is + * sent when genpd is powering on/off the PM domain. + * + * It is assumed that the user guarantee that the genpd wouldn't be detached + * while this routine is getting called. + * + * Returns 0 on success and negative error values on failures. + */ +int dev_pm_genpd_add_notifier(struct device *dev, struct notifier_block *nb) +{ + struct generic_pm_domain *genpd; + struct generic_pm_domain_data *gpd_data; + int ret; + + genpd = dev_to_genpd_safe(dev); + if (!genpd) + return -ENODEV; + + if (WARN_ON(!dev->power.subsys_data || + !dev->power.subsys_data->domain_data)) + return -EINVAL; + + gpd_data = to_gpd_data(dev->power.subsys_data->domain_data); + if (gpd_data->power_nb) + return -EEXIST; + + genpd_lock(genpd); + ret = blocking_notifier_chain_register(&genpd->power_notifiers, nb); + genpd_unlock(genpd); + + if (ret) { + dev_warn(dev, "failed to add notifier for PM domain %s\n", + genpd->name); + return ret; + } + + gpd_data->power_nb = nb; + return 0; +} +EXPORT_SYMBOL_GPL(dev_pm_genpd_add_notifier); + +/** + * dev_pm_genpd_remove_notifier - Remove a genpd power on/off notifier for @dev + * + * @dev: Device that is associated with the notifier + * + * Users may call this function to remove a genpd power on/off notifier for an + * attached @dev. + * + * It is assumed that the user guarantee that the genpd wouldn't be detached + * while this routine is getting called. + * + * Returns 0 on success and negative error values on failures. + */ +int dev_pm_genpd_remove_notifier(struct device *dev) +{ + struct generic_pm_domain *genpd; + struct generic_pm_domain_data *gpd_data; + int ret; + + genpd = dev_to_genpd_safe(dev); + if (!genpd) + return -ENODEV; + + if (WARN_ON(!dev->power.subsys_data || + !dev->power.subsys_data->domain_data)) + return -EINVAL; + + gpd_data = to_gpd_data(dev->power.subsys_data->domain_data); + if (!gpd_data->power_nb) + return -ENODEV; + + genpd_lock(genpd); + ret = blocking_notifier_chain_unregister(&genpd->power_notifiers, + gpd_data->power_nb); + genpd_unlock(genpd); + + if (ret) { + dev_warn(dev, "failed to remove notifier for PM domain %s\n", + genpd->name); + return ret; + } + + gpd_data->power_nb = NULL; + return 0; +} +EXPORT_SYMBOL_GPL(dev_pm_genpd_remove_notifier); + static int genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *subdomain) { @@ -1763,6 +1887,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd, INIT_LIST_HEAD(&genpd->master_links); INIT_LIST_HEAD(&genpd->slave_links); INIT_LIST_HEAD(&genpd->dev_list); + BLOCKING_INIT_NOTIFIER_HEAD(&genpd->power_notifiers); genpd_lock_init(genpd); genpd->gov = gov; INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn); diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 9ec78ee53652..b4c547a2da97 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -68,6 +68,11 @@ enum gpd_status { GPD_STATE_POWER_OFF, /* PM domain is off */ }; +enum genpd_notification_state { + GENPD_POWER_ON, + GENPD_POWER_OFF +}; + struct dev_power_governor { bool (*power_down_ok)(struct dev_pm_domain *domain); bool (*suspend_ok)(struct device *dev); @@ -112,6 +117,7 @@ struct generic_pm_domain { cpumask_var_t cpus; /* A cpumask of the attached CPUs */ int (*power_off)(struct generic_pm_domain *domain); int (*power_on)(struct generic_pm_domain *domain); + struct blocking_notifier_head power_notifiers; /* Power on/off notifiers */ struct opp_table *opp_table; /* OPP table of the genpd */ unsigned int (*opp_to_performance_state)(struct generic_pm_domain *genpd, struct dev_pm_opp *opp); @@ -178,6 +184,7 @@ struct generic_pm_domain_data { struct pm_domain_data base; struct gpd_timing_data td; struct notifier_block nb; + struct notifier_block *power_nb; int cpu; unsigned int performance_state; void *data; @@ -204,6 +211,8 @@ int pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off); int pm_genpd_remove(struct generic_pm_domain *genpd); int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state); +int dev_pm_genpd_add_notifier(struct device *dev, struct notifier_block *nb); +int dev_pm_genpd_remove_notifier(struct device *dev); extern struct dev_power_governor simple_qos_governor; extern struct dev_power_governor pm_domain_always_on_gov; @@ -251,6 +260,17 @@ static inline int dev_pm_genpd_set_performance_state(struct device *dev, return -ENOTSUPP; } +static inline int dev_pm_genpd_add_notifier(struct device *dev, + struct notifier_block *nb) +{ + return -ENOTSUPP; +} + +static inline int dev_pm_genpd_remove_notifier(struct device *dev) +{ + return -ENOTSUPP; +} + #define simple_qos_governor (*(struct dev_power_governor *)(NULL)) #define pm_domain_always_on_gov (*(struct dev_power_governor *)(NULL)) #endif |