[PATCH v3 0/2] Introduce multi PM domains helpers
From: Daniel Baluta daniel.baluta@nxp.com
i.MX8QXP/i.MX8QM has IPs that need multiple power domains to be up in order to work. In order to help drivers, we introduce multi PM domains helpers that are able to activate/deactivate multi PM domains.
First patch introduces the helpers and second patch demonstrates how a driver can use them instead of hardcoding the PM domains handling.
Changes since v2: » - fix kernel test robot reported issues (missing static inline for newly introduced functions in headers and arguments swapped for devm_kzalloc).
Changes since v1: (addressed Ranjani's comments) » - enhanced description for dev_multi_pm_attach return value » - renamed exit_unroll_pm label to exit_detach_pm
Ideally would be to have patch 1/2 merged via power tree and then I will submit again patch 2/2 on sound tree.
Daniel Baluta (2): PM / domains: Introduce multi PM domains helpers ASoC: SOF: Use multi PM domains helpers
drivers/base/power/common.c | 93 +++++++++++++++++++++++++++++++++++++ include/linux/pm_domain.h | 19 ++++++++ sound/soc/sof/imx/imx8.c | 60 ++++-------------------- 3 files changed, 121 insertions(+), 51 deletions(-)
From: Daniel Baluta daniel.baluta@nxp.com
This patch introduces helpers support for multi PM domains.
API consists of:
1) dev_multi_pm_attach - powers up all PM domains associated with a given device. Because we can attach one PM domain per device, we create virtual devices (children of initial device) and associate PM domains one per virtual device.
2) dev_multi_pm_detach - detaches all virtual devices from PM domains attached with.
Signed-off-by: Daniel Baluta daniel.baluta@nxp.com --- drivers/base/power/common.c | 93 +++++++++++++++++++++++++++++++++++++ include/linux/pm_domain.h | 19 ++++++++ 2 files changed, 112 insertions(+)
diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index bbddb267c2e6..b0a4d0109810 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -228,3 +228,96 @@ void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd) device_pm_check_callbacks(dev); } EXPORT_SYMBOL_GPL(dev_pm_domain_set); + +/** + * dev_multi_pm_attach - power up device associated power domains + * @dev: The device used to lookup the PM domains + * + * Parse device's OF node to find all PM domains specifiers. For each power + * domain found, create a virtual device and associate it with the + * current power domain. + * + * This function should typically be invoked by a driver during the + * probe phase, in the case its device requires power management through + * multiple PM domains. + * + * Returns a pointer to @dev_multi_pm_domain_data if successfully attached PM + * domains, NULL when the device doesn't need a PM domain or when single + * power-domains exists for it, else an ERR_PTR() in case of + * failures. + */ +struct dev_multi_pm_domain_data *dev_multi_pm_attach(struct device *dev) +{ + struct dev_multi_pm_domain_data *mpd, *retp; + int num_domains; + int i; + + num_domains = of_count_phandle_with_args(dev->of_node, "power-domains", + "#power-domain-cells"); + if (num_domains < 2) + return NULL; + + mpd = devm_kzalloc(dev, sizeof(*mpd), GFP_KERNEL); + if (!mpd) + return ERR_PTR(-ENOMEM); + + mpd->dev = dev; + mpd->num_domains = num_domains; + + mpd->virt_devs = devm_kmalloc_array(dev, mpd->num_domains, + sizeof(*mpd->virt_devs), + GFP_KERNEL); + if (!mpd->virt_devs) + return ERR_PTR(-ENOMEM); + + mpd->links = devm_kmalloc_array(dev, mpd->num_domains, + sizeof(*mpd->links), GFP_KERNEL); + if (!mpd->links) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < mpd->num_domains; i++) { + mpd->virt_devs[i] = dev_pm_domain_attach_by_id(dev, i); + if (IS_ERR(mpd->virt_devs[i])) { + retp = (struct dev_multi_pm_domain_data *) + mpd->virt_devs[i]; + goto exit_unroll_pm; + } + mpd->links[i] = device_link_add(dev, mpd->virt_devs[i], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!mpd->links[i]) { + retp = ERR_PTR(-ENOMEM); + dev_pm_domain_detach(mpd->virt_devs[i], false); + goto exit_unroll_pm; + } + } + return mpd; + +exit_unroll_pm: + while (--i >= 0) { + device_link_del(mpd->links[i]); + dev_pm_domain_detach(mpd->virt_devs[i], false); + } + + return retp; +} +EXPORT_SYMBOL(dev_multi_pm_attach); + +/** + * dev_multi_pm_detach - Detach a device from its PM domains. + * Each multi power domain is attached to a virtual children device + * + * @mpd: multi power domains data, contains the association between + * virtul device and PM domain + */ +void dev_multi_pm_detach(struct dev_multi_pm_domain_data *mpd) +{ + int i; + + for (i = 0; i < mpd->num_domains; i++) { + device_link_del(mpd->links[i]); + dev_pm_domain_detach(mpd->virt_devs[i], false); + } +} +EXPORT_SYMBOL(dev_multi_pm_detach); diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 9ec78ee53652..af1107731044 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -183,6 +183,13 @@ struct generic_pm_domain_data { void *data; };
+struct dev_multi_pm_domain_data { + struct device *dev; /* parent device */ + struct device **virt_devs; /* virtual children links */ + struct device_link **links; /* links parent <-> virtual children */ + int num_domains; +}; + #ifdef CONFIG_PM_GENERIC_DOMAINS static inline struct generic_pm_domain_data *to_gpd_data(struct pm_domain_data *pdd) { @@ -369,18 +376,27 @@ struct generic_pm_domain *of_genpd_remove_last(struct device_node *np)
#ifdef CONFIG_PM int dev_pm_domain_attach(struct device *dev, bool power_on); +struct dev_multi_pm_domain_data *dev_multi_pm_attach(struct device *dev); struct device *dev_pm_domain_attach_by_id(struct device *dev, unsigned int index); struct device *dev_pm_domain_attach_by_name(struct device *dev, const char *name); void dev_pm_domain_detach(struct device *dev, bool power_off); int dev_pm_domain_start(struct device *dev); +void dev_multi_pm_detach(struct dev_multi_pm_domain_data *mpd); void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd); + #else static inline int dev_pm_domain_attach(struct device *dev, bool power_on) { return 0; } + +static inline struct dev_multi_pm_domain_data *dev_multi_pm_attach(struct device *dev) +{ + return NULL; +} + static inline struct device *dev_pm_domain_attach_by_id(struct device *dev, unsigned int index) { @@ -396,6 +412,9 @@ static inline int dev_pm_domain_start(struct device *dev) { return 0; } + +static inline void dev_multi_pm_detach(struct dev_multi_pm_domain_data *mpd) {} + static inline void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd) {} #endif
On Wed, 24 Jun 2020 at 12:33, Daniel Baluta daniel.baluta@oss.nxp.com wrote:
From: Daniel Baluta daniel.baluta@nxp.com
This patch introduces helpers support for multi PM domains.
API consists of:
- dev_multi_pm_attach - powers up all PM domains associated with a given
device. Because we can attach one PM domain per device, we create virtual devices (children of initial device) and associate PM domains one per virtual device.
- dev_multi_pm_detach - detaches all virtual devices from PM domains
attached with.
Nit pick: I suggest to rename the helpers into dev_pm_domain_attach|detach_multi(), to be more consistent with existing function names. It's a bit long I admit that, but I prefer the consistency.
Signed-off-by: Daniel Baluta daniel.baluta@nxp.com
drivers/base/power/common.c | 93 +++++++++++++++++++++++++++++++++++++ include/linux/pm_domain.h | 19 ++++++++ 2 files changed, 112 insertions(+)
diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index bbddb267c2e6..b0a4d0109810 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -228,3 +228,96 @@ void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd) device_pm_check_callbacks(dev); } EXPORT_SYMBOL_GPL(dev_pm_domain_set);
+/**
- dev_multi_pm_attach - power up device associated power domains
- @dev: The device used to lookup the PM domains
- Parse device's OF node to find all PM domains specifiers. For each power
- domain found, create a virtual device and associate it with the
- current power domain.
- This function should typically be invoked by a driver during the
- probe phase, in the case its device requires power management through
- multiple PM domains.
- Returns a pointer to @dev_multi_pm_domain_data if successfully attached PM
- domains, NULL when the device doesn't need a PM domain or when single
- power-domains exists for it, else an ERR_PTR() in case of
- failures.
- */
+struct dev_multi_pm_domain_data *dev_multi_pm_attach(struct device *dev) +{
struct dev_multi_pm_domain_data *mpd, *retp;
int num_domains;
int i;
num_domains = of_count_phandle_with_args(dev->of_node, "power-domains",
"#power-domain-cells");
if (num_domains < 2)
return NULL;
dev_pm_domain_attach_* is typically wrapper functions, allowing different types of PM domains to be supported. For example, dev_pm_domain_attach() calls acpi_dev_pm_attach() and genpd_dev_pm_attach(). While dev_pm_domain_attach_by_id() only calls genpd_dev_pm_attach_by_id(), as there's no corresponding interface for the acpi PM domain.
The above said, I don't think another layer should be needed here, but there is something missing that makes this consistent with the behaviour of the above mentioned functions.
How about adding a genpd OF helper ("of_genpd_num_domains(struct device_node *)"), that deals with the above parsing and returns the number of domains for the device? In this way, if of_genpd_num_domains() returns an error code or zero, it's easier to continue to try with other PM domain providers (if/when that is supported).
mpd = devm_kzalloc(dev, sizeof(*mpd), GFP_KERNEL);
if (!mpd)
return ERR_PTR(-ENOMEM);
mpd->dev = dev;
mpd->num_domains = num_domains;
mpd->virt_devs = devm_kmalloc_array(dev, mpd->num_domains,
sizeof(*mpd->virt_devs),
GFP_KERNEL);
if (!mpd->virt_devs)
return ERR_PTR(-ENOMEM);
mpd->links = devm_kmalloc_array(dev, mpd->num_domains,
sizeof(*mpd->links), GFP_KERNEL);
if (!mpd->links)
return ERR_PTR(-ENOMEM);
for (i = 0; i < mpd->num_domains; i++) {
mpd->virt_devs[i] = dev_pm_domain_attach_by_id(dev, i);
if (IS_ERR(mpd->virt_devs[i])) {
retp = (struct dev_multi_pm_domain_data *)
mpd->virt_devs[i];
goto exit_unroll_pm;
}
mpd->links[i] = device_link_add(dev, mpd->virt_devs[i],
DL_FLAG_STATELESS |
DL_FLAG_PM_RUNTIME |
DL_FLAG_RPM_ACTIVE);
As a suggestion to be a little bit more flexible, perhaps these bits should be given as an in-parameter instead. Potentially we could then also treat the in-parameter being zero, as that no device link should be added.
Although, it's kind of hard to know as the users of this interface aren't really widely known yet.
if (!mpd->links[i]) {
retp = ERR_PTR(-ENOMEM);
dev_pm_domain_detach(mpd->virt_devs[i], false);
goto exit_unroll_pm;
}
}
return mpd;
+exit_unroll_pm:
while (--i >= 0) {
device_link_del(mpd->links[i]);
dev_pm_domain_detach(mpd->virt_devs[i], false);
}
return retp;
+} +EXPORT_SYMBOL(dev_multi_pm_attach);
[...]
Kind regards Uffe
From: Daniel Baluta daniel.baluta@nxp.com
Use dev_multi_pm_attach / dev_multi_pm_detach instead of the hardcoded version.
Signed-off-by: Daniel Baluta daniel.baluta@nxp.com --- sound/soc/sof/imx/imx8.c | 60 ++++++---------------------------------- 1 file changed, 9 insertions(+), 51 deletions(-)
diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index 68b2edccd791..7229993c5af8 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -51,10 +51,7 @@ struct imx8_priv { struct imx_sc_ipc *sc_ipc;
/* Power domain handling */ - int num_domains; - struct device **pd_dev; - struct device_link **link; - + struct dev_multi_pm_domain_data *mpd; };
static void imx8_get_reply(struct snd_sof_dev *sdev) @@ -207,7 +204,6 @@ static int imx8_probe(struct snd_sof_dev *sdev) struct resource res; u32 base, size; int ret = 0; - int i;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -218,45 +214,15 @@ static int imx8_probe(struct snd_sof_dev *sdev) priv->sdev = sdev;
/* power up device associated power domains */ - priv->num_domains = of_count_phandle_with_args(np, "power-domains", - "#power-domain-cells"); - if (priv->num_domains < 0) { - dev_err(sdev->dev, "no power-domains property in %pOF\n", np); - return priv->num_domains; - } - - priv->pd_dev = devm_kmalloc_array(&pdev->dev, priv->num_domains, - sizeof(*priv->pd_dev), GFP_KERNEL); - if (!priv->pd_dev) - return -ENOMEM; - - priv->link = devm_kmalloc_array(&pdev->dev, priv->num_domains, - sizeof(*priv->link), GFP_KERNEL); - if (!priv->link) - return -ENOMEM; - - for (i = 0; i < priv->num_domains; i++) { - priv->pd_dev[i] = dev_pm_domain_attach_by_id(&pdev->dev, i); - if (IS_ERR(priv->pd_dev[i])) { - ret = PTR_ERR(priv->pd_dev[i]); - goto exit_unroll_pm; - } - priv->link[i] = device_link_add(&pdev->dev, priv->pd_dev[i], - DL_FLAG_STATELESS | - DL_FLAG_PM_RUNTIME | - DL_FLAG_RPM_ACTIVE); - if (!priv->link[i]) { - ret = -ENOMEM; - dev_pm_domain_detach(priv->pd_dev[i], false); - goto exit_unroll_pm; - } - } + priv->mpd = dev_multi_pm_attach(&pdev->dev); + if (IS_ERR(priv->mpd)) + return PTR_ERR(priv->mpd);
ret = imx_scu_get_handle(&priv->sc_ipc); if (ret) { dev_err(sdev->dev, "Cannot obtain SCU handle (err = %d)\n", ret); - goto exit_unroll_pm; + goto exit_detach_pm; }
priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp", @@ -264,7 +230,7 @@ static int imx8_probe(struct snd_sof_dev *sdev) pdev, sizeof(*pdev)); if (IS_ERR(priv->ipc_dev)) { ret = PTR_ERR(priv->ipc_dev); - goto exit_unroll_pm; + goto exit_detach_pm; }
priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); @@ -328,26 +294,18 @@ static int imx8_probe(struct snd_sof_dev *sdev)
exit_pdev_unregister: platform_device_unregister(priv->ipc_dev); -exit_unroll_pm: - while (--i >= 0) { - device_link_del(priv->link[i]); - dev_pm_domain_detach(priv->pd_dev[i], false); - } - +exit_detach_pm: + dev_multi_pm_detach(priv->mpd); return ret; }
static int imx8_remove(struct snd_sof_dev *sdev) { struct imx8_priv *priv = (struct imx8_priv *)sdev->private; - int i;
platform_device_unregister(priv->ipc_dev);
- for (i = 0; i < priv->num_domains; i++) { - device_link_del(priv->link[i]); - dev_pm_domain_detach(priv->pd_dev[i], false); - } + dev_multi_pm_detach(priv->mpd);
return 0; }
participants (2)
-
Daniel Baluta
-
Ulf Hansson