diff mbox series

[v2,3/3] PM / Domains: Add support for PM domain on/off notifiers for genpd

Message ID 20200924110449.329523-4-ulf.hansson@linaro.org
State New
Headers show
Series PM / Domains: Add power on/off notifiers for genpd | expand

Commit Message

Ulf Hansson Sept. 24, 2020, 11:04 a.m. UTC
A device may have specific HW constraints that must be obeyed to, before
its corresponding PM domain (genpd) can be powered off - and vice verse at
power on. These constraints can't be managed through the regular runtime PM
based deployment for a device, because the access pattern for it, isn't
always request based. In other words, using the runtime PM callbacks to
deal with the constraints doesn't work for these cases.

For these reasons, let's instead add a PM domain power on/off notification
mechanism to genpd. To add/remove a notifier for a device, the device must
already have been attached to the genpd, which also means that it needs to
be a part of the PM domain topology.

To add/remove a notifier, let's introduce two genpd specific functions:
 - dev_pm_genpd_add|remove_notifier()

Note that, to further clarify when genpd power on/off notifiers may be
used, one 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 them, but that requires additional genpd based platform support
for the current users.

Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>

---

Changes in v2:
	- Improved error handling from fired notifications, according to
	suggestions by Lina Iyer.

---
 drivers/base/power/domain.c | 142 ++++++++++++++++++++++++++++++++++--
 include/linux/pm_domain.h   |  15 ++++
 2 files changed, 152 insertions(+), 5 deletions(-)

-- 
2.25.1

Comments

Peng Fan Sept. 25, 2020, 6:08 a.m. UTC | #1
Hi Ulf,

> Subject: [PATCH v2 3/3] PM / Domains: Add support for PM domain on/off

> notifiers for genpd

> 

> A device may have specific HW constraints that must be obeyed to, before its

> corresponding PM domain (genpd) can be powered off - and vice verse at

> power on. These constraints can't be managed through the regular runtime

> PM based deployment for a device, because the access pattern for it, isn't

> always request based. In other words, using the runtime PM callbacks to deal

> with the constraints doesn't work for these cases.


Could the notification be added before/after power on, and before/after power
off? not just after power on and before power off?

Our SoC has a requirement that before power on/off the specific module,
the corresponding clk needs to be on to make sure the hardware async
bridge could finish handshake.

So we need clk_prepare_on/off to guard power on and power off as below:

clk_prepare_on
power on
clk_prepare_off

clk_prepare_on
power off
clk_prepare_off

Thanks,
Peng.

> 

> For these reasons, let's instead add a PM domain power on/off notification

> mechanism to genpd. To add/remove a notifier for a device, the device must

> already have been attached to the genpd, which also means that it needs to

> be a part of the PM domain topology.

> 

> To add/remove a notifier, let's introduce two genpd specific functions:

>  - dev_pm_genpd_add|remove_notifier()

> 

> Note that, to further clarify when genpd power on/off notifiers may be used,

> one 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

> them, but that requires additional genpd based platform support for the

> current users.

> 

> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>

> ---

> 

> Changes in v2:

> 	- Improved error handling from fired notifications, according to

> 	suggestions by Lina Iyer.

> 

> ---

>  drivers/base/power/domain.c | 142

> ++++++++++++++++++++++++++++++++++--

>  include/linux/pm_domain.h   |  15 ++++

>  2 files changed, 152 insertions(+), 5 deletions(-)

> 

> diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c

> index 0198af358503..f001ac6326fb 100644

> --- a/drivers/base/power/domain.c

> +++ b/drivers/base/power/domain.c

> @@ -497,7 +497,7 @@ static int genpd_power_off(struct

> generic_pm_domain *genpd, bool one_dev_on,

>  	struct pm_domain_data *pdd;

>  	struct gpd_link *link;

>  	unsigned int not_suspended = 0;

> -	int ret;

> +	int ret, nr_calls = 0;

> 

>  	/*

>  	 * Do not try to power off the domain in the following situations:

> @@ -545,13 +545,22 @@ 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 = __raw_notifier_call_chain(&genpd->power_notifiers,

> +					GENPD_STATE_OFF, NULL, -1, &nr_calls);

> +	ret = notifier_to_errno(ret);

> +	if (ret)

> +		goto busy;

> +

>  	/* Don't power off, if a child domain is waiting to 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 = GENPD_STATE_OFF;

>  	genpd_update_accounting(genpd);

> @@ -564,6 +573,12 @@ static int genpd_power_off(struct

> generic_pm_domain *genpd, bool one_dev_on,

>  	}

> 

>  	return 0;

> +busy:

> +	if (nr_calls)

> +		__raw_notifier_call_chain(&genpd->power_notifiers,

> +					  GENPD_STATE_ON, NULL,

> +					  nr_calls - 1, NULL);

> +	return ret;

>  }

> 

>  /**

> @@ -609,6 +624,9 @@ static int genpd_power_on(struct

> generic_pm_domain *genpd, unsigned int depth)

>  	genpd->status = GENPD_STATE_ON;

>  	genpd_update_accounting(genpd);

> 

> +	/* Inform consumers that we have powered on. */

> +	raw_notifier_call_chain(&genpd->power_notifiers, GENPD_STATE_ON,

> +NULL);

> +

>  	return 0;

> 

>   err:

> @@ -938,6 +956,7 @@ static void genpd_sync_power_off(struct

> generic_pm_domain *genpd, bool use_lock,

>  				 unsigned int depth)

>  {

>  	struct gpd_link *link;

> +	int err, nr_calls = 0;

> 

>  	if (!genpd_status_on(genpd) || genpd_is_always_on(genpd))

>  		return;

> @@ -948,8 +967,15 @@ 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;

> +

> +	/* Notify consumers that we are about to power off. */

> +	err = __raw_notifier_call_chain(&genpd->power_notifiers,

> +					GENPD_STATE_OFF, NULL, -1, &nr_calls);

> +	if (notifier_to_errno(err))

> +		goto err;

> +

>  	if (_genpd_power_off(genpd, false))

> -		return;

> +		goto err;

> 

>  	genpd->status = GENPD_STATE_OFF;

> 

> @@ -964,6 +990,13 @@ static void genpd_sync_power_off(struct

> generic_pm_domain *genpd, bool use_lock,

>  		if (use_lock)

>  			genpd_unlock(link->parent);

>  	}

> +

> +	return;

> +err:

> +	if (nr_calls)

> +		__raw_notifier_call_chain(&genpd->power_notifiers,

> +					  GENPD_STATE_ON, NULL,

> +					  nr_calls - 1, NULL);

>  }

> 

>  /**

> @@ -998,6 +1031,9 @@ static void genpd_sync_power_on(struct

> generic_pm_domain *genpd, bool use_lock,

> 

>  	_genpd_power_on(genpd, false);

>  	genpd->status = GENPD_STATE_ON;

> +

> +	/* Inform consumers that we have powered on. */

> +	raw_notifier_call_chain(&genpd->power_notifiers, GENPD_STATE_ON,

> +NULL);

>  }

> 

>  /**

> @@ -1592,6 +1628,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 = raw_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 = raw_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)  { @@

> -1762,6 +1893,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd,

>  	INIT_LIST_HEAD(&genpd->parent_links);

>  	INIT_LIST_HEAD(&genpd->child_links);

>  	INIT_LIST_HEAD(&genpd->dev_list);

> +	RAW_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

> 66f3c5d64d81..3b2b561ce846 100644

> --- a/include/linux/pm_domain.h

> +++ b/include/linux/pm_domain.h

> @@ -112,6 +112,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 raw_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 +179,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 +206,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 +255,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

> --

> 2.25.1
Ulf Hansson Sept. 25, 2020, 11:48 a.m. UTC | #2
On Fri, 25 Sep 2020 at 08:08, Peng Fan <peng.fan@nxp.com> wrote:
>

> Hi Ulf,

>

> > Subject: [PATCH v2 3/3] PM / Domains: Add support for PM domain on/off

> > notifiers for genpd

> >

> > A device may have specific HW constraints that must be obeyed to, before its

> > corresponding PM domain (genpd) can be powered off - and vice verse at

> > power on. These constraints can't be managed through the regular runtime

> > PM based deployment for a device, because the access pattern for it, isn't

> > always request based. In other words, using the runtime PM callbacks to deal

> > with the constraints doesn't work for these cases.

>

> Could the notification be added before/after power on, and before/after power

> off? not just after power on and before power off?

>

> Our SoC has a requirement that before power on/off the specific module,

> the corresponding clk needs to be on to make sure the hardware async

> bridge could finish handshake.


Thanks for your comments!

May I ask, to be sure - does the clock correspond to the genpd
provider or is it a clock for the genpd consumer device?

If the former, couldn't the clock be managed from the ->power_on|off()
callbacks for the genpd provider?

>

> So we need clk_prepare_on/off to guard power on and power off as below:

>

> clk_prepare_on

> power on

> clk_prepare_off

>

> clk_prepare_on

> power off

> clk_prepare_off

>

> Thanks,

> Peng.

>


[...]

Kind regards
Uffe
Peng Fan Sept. 25, 2020, 2:30 p.m. UTC | #3
> Subject: Re: [PATCH v2 3/3] PM / Domains: Add support for PM domain on/off

> notifiers for genpd

> 

> On Fri, 25 Sep 2020 at 08:08, Peng Fan <peng.fan@nxp.com> wrote:

> >

> > Hi Ulf,

> >

> > > Subject: [PATCH v2 3/3] PM / Domains: Add support for PM domain

> > > on/off notifiers for genpd

> > >

> > > A device may have specific HW constraints that must be obeyed to,

> > > before its corresponding PM domain (genpd) can be powered off - and

> > > vice verse at power on. These constraints can't be managed through

> > > the regular runtime PM based deployment for a device, because the

> > > access pattern for it, isn't always request based. In other words,

> > > using the runtime PM callbacks to deal with the constraints doesn't work

> for these cases.

> >

> > Could the notification be added before/after power on, and

> > before/after power off? not just after power on and before power off?

> >

> > Our SoC has a requirement that before power on/off the specific

> > module, the corresponding clk needs to be on to make sure the hardware

> > async bridge could finish handshake.

> 

> Thanks for your comments!

> 

> May I ask, to be sure - does the clock correspond to the genpd provider or is it

> a clock for the genpd consumer device?


It is the clock for the genpd consumer device.

> 

> If the former, couldn't the clock be managed from the ->power_on|off()

> callbacks for the genpd provider?


Sadly not former.

Our current solution is to add a clock property to the power domain node(NXP ARM SIP
based power domain driver), and when power_on/off, we enable/disable the clocks.

But we are moving to use SCMI power domain, and leave the clock in Linux,
Scmi power domain driver is a generic driver, and we are not able to mix clock
parts in the driver which is bad.

Your patch gives me a light that if we could use notification to let consumer
device driver do the clock enable/disable for each power on and power off.
That would be great.


Thanks,
Peng.
> 

> >

> > So we need clk_prepare_on/off to guard power on and power off as below:

> >

> > clk_prepare_on

> > power on

> > clk_prepare_off

> >

> > clk_prepare_on

> > power off

> > clk_prepare_off

> >

> > Thanks,

> > Peng.

> >

> 

> [...]

> 

> Kind regards

> Uffe
diff mbox series

Patch

diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
index 0198af358503..f001ac6326fb 100644
--- a/drivers/base/power/domain.c
+++ b/drivers/base/power/domain.c
@@ -497,7 +497,7 @@  static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
 	struct pm_domain_data *pdd;
 	struct gpd_link *link;
 	unsigned int not_suspended = 0;
-	int ret;
+	int ret, nr_calls = 0;
 
 	/*
 	 * Do not try to power off the domain in the following situations:
@@ -545,13 +545,22 @@  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 = __raw_notifier_call_chain(&genpd->power_notifiers,
+					GENPD_STATE_OFF, NULL, -1, &nr_calls);
+	ret = notifier_to_errno(ret);
+	if (ret)
+		goto busy;
+
 	/* Don't power off, if a child domain is waiting to 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 = GENPD_STATE_OFF;
 	genpd_update_accounting(genpd);
@@ -564,6 +573,12 @@  static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
 	}
 
 	return 0;
+busy:
+	if (nr_calls)
+		__raw_notifier_call_chain(&genpd->power_notifiers,
+					  GENPD_STATE_ON, NULL,
+					  nr_calls - 1, NULL);
+	return ret;
 }
 
 /**
@@ -609,6 +624,9 @@  static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth)
 	genpd->status = GENPD_STATE_ON;
 	genpd_update_accounting(genpd);
 
+	/* Inform consumers that we have powered on. */
+	raw_notifier_call_chain(&genpd->power_notifiers, GENPD_STATE_ON, NULL);
+
 	return 0;
 
  err:
@@ -938,6 +956,7 @@  static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock,
 				 unsigned int depth)
 {
 	struct gpd_link *link;
+	int err, nr_calls = 0;
 
 	if (!genpd_status_on(genpd) || genpd_is_always_on(genpd))
 		return;
@@ -948,8 +967,15 @@  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;
+
+	/* Notify consumers that we are about to power off. */
+	err = __raw_notifier_call_chain(&genpd->power_notifiers,
+					GENPD_STATE_OFF, NULL, -1, &nr_calls);
+	if (notifier_to_errno(err))
+		goto err;
+
 	if (_genpd_power_off(genpd, false))
-		return;
+		goto err;
 
 	genpd->status = GENPD_STATE_OFF;
 
@@ -964,6 +990,13 @@  static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock,
 		if (use_lock)
 			genpd_unlock(link->parent);
 	}
+
+	return;
+err:
+	if (nr_calls)
+		__raw_notifier_call_chain(&genpd->power_notifiers,
+					  GENPD_STATE_ON, NULL,
+					  nr_calls - 1, NULL);
 }
 
 /**
@@ -998,6 +1031,9 @@  static void genpd_sync_power_on(struct generic_pm_domain *genpd, bool use_lock,
 
 	_genpd_power_on(genpd, false);
 	genpd->status = GENPD_STATE_ON;
+
+	/* Inform consumers that we have powered on. */
+	raw_notifier_call_chain(&genpd->power_notifiers, GENPD_STATE_ON, NULL);
 }
 
 /**
@@ -1592,6 +1628,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 = raw_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 = raw_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)
 {
@@ -1762,6 +1893,7 @@  int pm_genpd_init(struct generic_pm_domain *genpd,
 	INIT_LIST_HEAD(&genpd->parent_links);
 	INIT_LIST_HEAD(&genpd->child_links);
 	INIT_LIST_HEAD(&genpd->dev_list);
+	RAW_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 66f3c5d64d81..3b2b561ce846 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -112,6 +112,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 raw_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 +179,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 +206,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 +255,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