summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Hansson <ulf.hansson@linaro.org>2020-06-16 14:02:21 +0200
committerUlf Hansson <ulf.hansson@linaro.org>2020-06-18 13:11:08 +0200
commit8947a515b47903bec7f37a1c66a9022ff5618be0 (patch)
tree82eaea44d5914d3e714e3ba49a2e180eb06b9d37
parentba384df2898841b29d92249c4a55f7927a4b9987 (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.c133
-rw-r--r--include/linux/pm_domain.h20
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