[PATCH/RFC 7/9] pmdomain: arm: scmi: Add clock domain support

Geert Uytterhoeven <[email protected]>
Newsgroups org.kernel.vger.arm-scmi,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-clk,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pm,org.kernel.vger.linux-renesas-soc
Message-ID <e99b203025504306cd858563d438df32ea47605e.1781171706.git.geert+renesas@glider.be>
PM domain consumer devices may also part of a clock domain.

Add support for managing power management clocks in a clock domain
through Runtime PM.

Signed-off-by: Geert Uytterhoeven <[email protected]>
---
 drivers/pmdomain/arm/Kconfig          |  1 +
 drivers/pmdomain/arm/scmi_pm_domain.c | 81 ++++++++++++++++++++++++++-
 2 files changed, 81 insertions(+), 1 deletion(-)

diff --git a/drivers/pmdomain/arm/Kconfig b/drivers/pmdomain/arm/Kconfig
index afed10d382ad7f66..11c4db47c1eadab0 100644
--- a/drivers/pmdomain/arm/Kconfig
+++ b/drivers/pmdomain/arm/Kconfig
@@ -14,6 +14,7 @@ config ARM_SCMI_PERF_DOMAIN
 config ARM_SCMI_POWER_DOMAIN
 	tristate "SCMI power domain driver"
 	depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
+	depends on COMMON_CLK_SCMI || !COMMON_CLK_SCMI
 	default ARM_SCMI_PROTOCOL
 	select PM_GENERIC_DOMAINS if PM
 	help
diff --git a/drivers/pmdomain/arm/scmi_pm_domain.c b/drivers/pmdomain/arm/scmi_pm_domain.c
index 8e67f971c707e121..838917d3b236e3aa 100644
--- a/drivers/pmdomain/arm/scmi_pm_domain.c
+++ b/drivers/pmdomain/arm/scmi_pm_domain.c
@@ -5,9 +5,12 @@
  * Copyright (C) 2018-2021 ARM Ltd.
  */
 
+#include <linux/clk.h>
+#include <linux/clk/scmi.h>
 #include <linux/err.h>
 #include <linux/io.h>
 #include <linux/module.h>
+#include <linux/pm_clock.h>
 #include <linux/pm_domain.h>
 #include <linux/scmi_protocol.h>
 
@@ -16,6 +19,7 @@ static const struct scmi_power_proto_ops *power_ops;
 struct scmi_pm_domain {
 	struct generic_pm_domain genpd;
 	const struct scmi_protocol_handle *ph;
+	struct device_node *clock_domain;
 	const char *name;
 	u32 domain;
 };
@@ -39,6 +43,67 @@ static int scmi_pd_power_off(struct generic_pm_domain *domain)
 	return scmi_pd_power(domain, SCMI_POWER_STATE_GENERIC_OFF);
 }
 
+static int scmi_pd_attach_dev(struct generic_pm_domain *domain,
+			      struct device *dev)
+{
+	struct scmi_pm_domain *pd = to_scmi_pd(domain);
+	struct device_node *np = dev->of_node;
+	struct of_phandle_args clkspec;
+	bool once = true;
+	struct clk *clk;
+	int ret;
+
+	for (int i = 0;
+	     !of_parse_phandle_with_args(np, "clocks", "#clock-cells", i, &clkspec);
+	     i++) {
+		if (clkspec.np != pd->clock_domain || clkspec.args_count != 1) {
+			of_node_put(clkspec.np);
+			continue;
+		}
+
+		clk = of_clk_get_from_provider(&clkspec);
+		of_node_put(clkspec.np);
+		if (!clk)
+			continue;
+
+		if (IS_ERR(clk)) {
+			ret = PTR_ERR(clk);
+			clk = NULL;
+			goto fail;
+		}
+
+		if (!scmi_clk_is_pm_clk(clk)) {
+			clk_put(clk);
+			continue;
+		}
+
+		if (once) {
+			once = false;
+			ret = pm_clk_create(dev);
+			if (ret)
+				goto fail;
+		}
+
+		ret = pm_clk_add_clk(dev, clk);
+		if (ret)
+			goto fail;
+	}
+
+	return 0;
+
+fail:
+	pm_clk_destroy(dev);
+	clk_put(clk);
+	return ret;
+}
+
+static void scmi_pd_detach_dev(struct generic_pm_domain *domain,
+			       struct device *dev)
+{
+	if (!pm_clk_no_clocks(dev))
+		pm_clk_destroy(dev);
+}
+
 static int scmi_pm_domain_probe(struct scmi_device *sdev)
 {
 	int num_domains, i, ret;
@@ -48,6 +113,7 @@ static int scmi_pm_domain_probe(struct scmi_device *sdev)
 	struct genpd_onecell_data *scmi_pd_data;
 	struct generic_pm_domain **domains;
 	const struct scmi_handle *handle = sdev->handle;
+	struct device_node *clock_domain;
 	struct scmi_protocol_handle *ph;
 
 	if (!handle)
@@ -75,6 +141,8 @@ static int scmi_pm_domain_probe(struct scmi_device *sdev)
 	if (!domains)
 		return -ENOMEM;
 
+	clock_domain = of_parse_phandle(np, "arm,clock-domain", 0);
+
 	for (i = 0; i < num_domains; i++, scmi_pd++) {
 		const struct scmi_power_domain_info *info;
 		u32 state;
@@ -106,6 +174,12 @@ static int scmi_pm_domain_probe(struct scmi_device *sdev)
 		scmi_pd->genpd.power_on = scmi_pd_power_on;
 		scmi_pd->genpd.flags = GENPD_FLAG_ACTIVE_WAKEUP |
 				       info->genpd_flags;
+		if (clock_domain) {
+			scmi_pd->clock_domain = of_node_get(clock_domain);
+			scmi_pd->genpd.attach_dev = scmi_pd_attach_dev;
+			scmi_pd->genpd.detach_dev = scmi_pd_detach_dev;
+			scmi_pd->genpd.flags |= GENPD_FLAG_PM_CLK;
+		}
 
 		pm_genpd_init(&scmi_pd->genpd, NULL,
 			      state == SCMI_POWER_STATE_GENERIC_OFF);
@@ -113,6 +187,8 @@ static int scmi_pm_domain_probe(struct scmi_device *sdev)
 		domains[i] = &scmi_pd->genpd;
 	}
 
+	of_node_put(clock_domain);
+
 	scmi_pd_data->domains = domains;
 	scmi_pd_data->num_domains = num_domains;
 
@@ -134,8 +210,10 @@ static int scmi_pm_domain_probe(struct scmi_device *sdev)
 
 	return 0;
 err_rm_genpds:
-	for (i = num_domains - 1; i >= 0; i--)
+	for (i = num_domains - 1; i >= 0; i--) {
 		pm_genpd_remove(domains[i]);
+		of_node_put(to_scmi_pd(domains[i])->clock_domain);
+	}
 
 	return ret;
 }
@@ -158,6 +236,7 @@ static void scmi_pm_domain_remove(struct scmi_device *sdev)
 		if (!scmi_pd_data->domains[i])
 			continue;
 		pm_genpd_remove(scmi_pd_data->domains[i]);
+		of_node_put(to_scmi_pd(scmi_pd_data->domains[i])->clock_domain);
 	}
 }
 
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.