mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Joshua Yeong <joshua.yeong@starfivetech.com>
To: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
	pjw@kernel.org, palmer@dabbelt.com, aou@eecs.berkeley.edu,
	rafael@kernel.org, viresh.kumar@linaro.org, ulfh@kernel.org,
	rahul@summations.net, anup@brainfault.org, lftan.linux@gmail.com
Cc: alex@ghiti.fr, joshua.yeong@starfivetech.com,
	linux-riscv@lists.infradead.org, devicetree@vger.kernel.org,
	linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH v2 6/7] pmdomain: riscv: Add RPMI performance domains as power domains
Date: Thu,  8 Oct 2026 17:10:30 +0800	[thread overview]
Message-ID: <20261008091032.2832333-7-joshua.yeong@starfivetech.com> (raw)
In-Reply-To: <20261008091032.2832333-1-joshua.yeong@starfivetech.com>

A device that is not a CPU has no generic way to drive an RPMI
performance domain: "performance-domains" is only resolved by cpufreq,
which would leave a device driver to call into RPMI directly, with
nothing to arbitrate between devices that share a domain.

A device names its domain through "power-domains", is handed an
operating point per level when it attaches, and drives the domain
through the OPP library, while genpd settles on the highest state any
device in the domain asks for.

Like cpufreq, this is a front-end over the performance service group
core, which creates the device it binds to when the node carries
"#power-domain-cells".

Signed-off-by: Joshua Yeong <joshua.yeong@starfivetech.com>
---
 .../firmware/riscv/riscv-rpmi-performance.c   |  26 ++
 drivers/pmdomain/riscv/Kconfig                |  16 ++
 drivers/pmdomain/riscv/Makefile               |   1 +
 .../pmdomain/riscv/riscv-rpmi-perf-domain.c   | 265 ++++++++++++++++++
 4 files changed, 308 insertions(+)
 create mode 100644 drivers/pmdomain/riscv/riscv-rpmi-perf-domain.c

diff --git a/drivers/firmware/riscv/riscv-rpmi-performance.c b/drivers/firmware/riscv/riscv-rpmi-performance.c
index f91f2d861dc0..d532d41ecbba 100644
--- a/drivers/firmware/riscv/riscv-rpmi-performance.c
+++ b/drivers/firmware/riscv/riscv-rpmi-performance.c
@@ -1046,6 +1046,28 @@ static int rpmi_perf_cpufreq_register(struct device *dev, struct rpmi_perf *perf
 	return devm_add_action_or_reset(dev, rpmi_perf_frontend_unregister, pdev);
 }
 
+/*
+ * A device that is not a CPU names its domain through "power-domains", which
+ * the power domain front-end serves on this node. Create the device it binds
+ * to, but only when the node is a power domain provider at all.
+ */
+static int rpmi_perf_genpd_register(struct device *dev, struct rpmi_perf *perf)
+{
+	struct platform_device *pdev;
+
+	if (!IS_ENABLED(CONFIG_RISCV_RPMI_PERF_DOMAIN) ||
+	    !of_property_present(dev_of_node(dev), "#power-domain-cells"))
+		return 0;
+
+	pdev = platform_device_register_data(dev, "riscv-rpmi-perf-domain",
+					     PLATFORM_DEVID_AUTO, &perf,
+					     sizeof(perf));
+	if (IS_ERR(pdev))
+		return PTR_ERR(pdev);
+
+	return devm_add_action_or_reset(dev, rpmi_perf_frontend_unregister, pdev);
+}
+
 static int rpmi_perf_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -1133,6 +1155,10 @@ static int rpmi_perf_probe(struct platform_device *pdev)
 	if (ret)
 		return dev_err_probe(dev, ret, "failed to register cpufreq device\n");
 
+	ret = rpmi_perf_genpd_register(dev, mpxy_perf);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register power domain device\n");
+
 	dev_info(dev, "%d MPXY performance domains registered\n", num_domains);
 
 	return 0;
diff --git a/drivers/pmdomain/riscv/Kconfig b/drivers/pmdomain/riscv/Kconfig
index 97232edc294a..6f3006d3220a 100644
--- a/drivers/pmdomain/riscv/Kconfig
+++ b/drivers/pmdomain/riscv/Kconfig
@@ -13,3 +13,19 @@ config RISCV_RPMI_DEVICE_POWER
 	  specification. The power domains advertised by the platform
 	  microcontroller are registered as generic power domains, so that
 	  devices can reference them through the "power-domains" property.
+
+config RISCV_RPMI_PERF_DOMAIN
+	tristate "RISC-V RPMI performance domains as power domains"
+	depends on RISCV_RPMI_PERFORMANCE
+	depends on PM
+	default RISCV
+	select PM_GENERIC_DOMAINS
+	help
+	  Register the performance domains of the RISC-V platform management
+	  interface (RPMI) performance service group as generic power domains,
+	  with a performance state for each level the platform microcontroller
+	  advertises. A device that is not a CPU references its domain through
+	  the "power-domains" property and drives it through the OPP library.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called riscv-rpmi-perf-domain.
diff --git a/drivers/pmdomain/riscv/Makefile b/drivers/pmdomain/riscv/Makefile
index 2cb0ee8ad413..32fd12ebb9cd 100644
--- a/drivers/pmdomain/riscv/Makefile
+++ b/drivers/pmdomain/riscv/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
 obj-$(CONFIG_RISCV_RPMI_DEVICE_POWER)	+= riscv-rpmi-device-power.o
+obj-$(CONFIG_RISCV_RPMI_PERF_DOMAIN)	+= riscv-rpmi-perf-domain.o
diff --git a/drivers/pmdomain/riscv/riscv-rpmi-perf-domain.c b/drivers/pmdomain/riscv/riscv-rpmi-perf-domain.c
new file mode 100644
index 000000000000..fc5c512fe069
--- /dev/null
+++ b/drivers/pmdomain/riscv/riscv-rpmi-perf-domain.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RISC-V RPMI performance domains as generic power domains
+ *
+ * Copyright (C) 2026 Shanghai StarFive Technology Co., Ltd.
+ *
+ * A device that is not a CPU names its RPMI performance domain through
+ * "power-domains". Each domain is registered here as a generic power domain
+ * whose performance states are the levels the platform microcontroller
+ * advertises, so the device's driver drives it through the OPP library like
+ * any other power domain with performance states, and genpd settles on the
+ * highest state any device in the domain asks for.
+ *
+ * Performance state 0 means that no device in the domain asks for anything,
+ * while 0 is a valid RPMI level index. State N is therefore level index
+ * N - 1, and state 0 leaves the domain at whatever level it is running.
+ *
+ * genpd runs the domain at the highest state asked for, which is the highest
+ * level index. That is the level to run at: the specification makes the
+ * level index the platform's performance scale, which its limit services
+ * also order levels by, so a higher index is a higher level whatever the
+ * indices in between.
+ *
+ * A domain that CPUs name through "performance-domains" belongs to cpufreq,
+ * which sets its level directly, so no device is attached to it as well.
+ *
+ * The RPMI protocol and the domain enumeration live in the performance
+ * service group core, which owns the mailbox channel and creates the device
+ * this driver binds to.
+ */
+
+#include <linux/firmware/riscv/riscv-rpmi-performance.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/units.h>
+
+struct rpmi_perf_genpd {
+	struct generic_pm_domain genpd;
+	struct rpmi_perf_domain *domain;
+	bool cpu_domain;
+};
+
+struct rpmi_perf_genpd_provider {
+	struct genpd_onecell_data data;
+	struct rpmi_perf_genpd *pds;
+};
+
+#define to_rpmi_perf_genpd(gpd) container_of(gpd, struct rpmi_perf_genpd, genpd)
+
+static int rpmi_perf_genpd_set_performance_state(struct generic_pm_domain *genpd,
+						 unsigned int state)
+{
+	struct rpmi_perf_genpd *pd = to_rpmi_perf_genpd(genpd);
+	int ret;
+
+	/* No device in the domain asks for a level, so leave it where it is. */
+	if (!state)
+		return 0;
+
+	ret = rpmi_perf_domain_set_level(pd->domain, state - 1);
+	if (ret)
+		dev_warn(&genpd->dev, "failed to set level %u: %d\n",
+			 state - 1, ret);
+
+	return ret;
+}
+
+/*
+ * The levels come from the platform microcontroller, so there is no
+ * operating-points-v2 table in the device tree. Hand each device that
+ * attaches an operating point per level instead, carrying the level's
+ * frequency and its performance state.
+ */
+static int rpmi_perf_genpd_attach_dev(struct generic_pm_domain *genpd,
+				      struct device *dev)
+{
+	struct rpmi_perf_genpd *pd = to_rpmi_perf_genpd(genpd);
+	struct dev_pm_opp_data data = {};
+	struct rpmi_perf_level level;
+	u32 idx;
+	int ret;
+
+	/* cpufreq and genpd would each set the level behind the other's back. */
+	if (pd->cpu_domain) {
+		dev_err(dev, "%s is the performance domain of a CPU\n", genpd->name);
+		return -EBUSY;
+	}
+
+	/* A domain whose level cannot be set has no state to offer. */
+	if (!rpmi_perf_domain_can_set_level(pd->domain))
+		return 0;
+
+	for (idx = 0; idx < rpmi_perf_domain_level_count(pd->domain); idx++) {
+		ret = rpmi_perf_domain_level_info(pd->domain, idx, &level);
+		if (ret)
+			goto err;
+
+		/* Frequency from RPMI is in kHz */
+		data.freq = (unsigned long)level.clock_freq * HZ_PER_KHZ;
+		data.level = level.index + 1;
+
+		ret = dev_pm_opp_add_dynamic(dev, &data);
+		if (ret)
+			goto err;
+	}
+
+	return 0;
+
+err:
+	dev_warn(dev, "failed to add operating points for %s: %d\n",
+		 genpd->name, ret);
+	dev_pm_opp_remove_all_dynamic(dev);
+
+	return ret;
+}
+
+static void rpmi_perf_genpd_detach_dev(struct generic_pm_domain *genpd,
+				       struct device *dev)
+{
+	struct rpmi_perf_genpd *pd = to_rpmi_perf_genpd(genpd);
+
+	if (rpmi_perf_domain_can_set_level(pd->domain))
+		dev_pm_opp_remove_all_dynamic(dev);
+}
+
+/* Mark the domains that CPUs name through "performance-domains". */
+static void rpmi_perf_genpd_mark_cpu_domains(struct device_node *np,
+					     struct rpmi_perf_genpd *pds,
+					     u32 num_domains)
+{
+	struct of_phandle_args args;
+	struct device_node *cpu_np;
+
+	for_each_of_cpu_node(cpu_np) {
+		if (of_parse_phandle_with_args(cpu_np, "performance-domains",
+					       "#performance-domain-cells", 0,
+					       &args))
+			continue;
+
+		if (args.np == np && args.args_count >= 1 &&
+		    args.args[0] < num_domains)
+			pds[args.args[0]].cpu_domain = true;
+
+		of_node_put(args.np);
+	}
+}
+
+static int rpmi_perf_genpd_probe(struct platform_device *pdev)
+{
+	struct rpmi_perf **mpxy_perf = dev_get_platdata(&pdev->dev);
+	struct device *dev = &pdev->dev;
+	struct rpmi_perf_genpd_provider *provider;
+	struct generic_pm_domain **domains;
+	u32 i, num_domains, registered = 0;
+	int ret;
+
+	if (!mpxy_perf || !*mpxy_perf)
+		return -EINVAL;
+
+	num_domains = rpmi_perf_num_domains(*mpxy_perf);
+
+	provider = devm_kzalloc(dev, sizeof(*provider), GFP_KERNEL);
+	if (!provider)
+		return -ENOMEM;
+
+	provider->pds = devm_kcalloc(dev, num_domains, sizeof(*provider->pds),
+				     GFP_KERNEL);
+	if (!provider->pds)
+		return -ENOMEM;
+
+	domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
+	if (!domains)
+		return -ENOMEM;
+
+	rpmi_perf_genpd_mark_cpu_domains(dev_of_node(dev->parent), provider->pds,
+					 num_domains);
+
+	for (i = 0; i < num_domains; i++) {
+		struct rpmi_perf_genpd *pd = &provider->pds[i];
+
+		/*
+		 * A domain that could not be enumerated is left out, the way
+		 * the device power provider leaves out a domain it cannot use.
+		 * A device that names it then never gets its power domain.
+		 */
+		pd->domain = rpmi_perf_domain_by_id(*mpxy_perf, i);
+		if (!pd->domain)
+			continue;
+
+		pd->genpd.name = rpmi_perf_domain_name(pd->domain);
+		/*
+		 * A performance domain has no off state, its operating points
+		 * come from the platform microcontroller rather than from the
+		 * device tree, and its name is not guaranteed to be unique.
+		 */
+		pd->genpd.flags = GENPD_FLAG_ALWAYS_ON |
+				  GENPD_FLAG_OPP_TABLE_FW |
+				  GENPD_FLAG_DEV_NAME_FW;
+		pd->genpd.set_performance_state = rpmi_perf_genpd_set_performance_state;
+		pd->genpd.attach_dev = rpmi_perf_genpd_attach_dev;
+		pd->genpd.detach_dev = rpmi_perf_genpd_detach_dev;
+
+		ret = pm_genpd_init(&pd->genpd, NULL, false);
+		if (ret)
+			goto err;
+
+		domains[i] = &pd->genpd;
+		registered++;
+	}
+
+	provider->data.domains = domains;
+	provider->data.num_domains = num_domains;
+
+	/* The domains belong to the node the core was probed from. */
+	ret = of_genpd_add_provider_onecell(dev_of_node(dev->parent),
+					    &provider->data);
+	if (ret)
+		goto err;
+
+	platform_set_drvdata(pdev, provider);
+
+	dev_info(dev, "%u MPXY performance domains registered as power domains\n",
+		 registered);
+
+	return 0;
+
+err:
+	while (i--) {
+		if (domains[i])
+			pm_genpd_remove(domains[i]);
+	}
+
+	return dev_err_probe(dev, ret, "failed to register power domains\n");
+}
+
+static void rpmi_perf_genpd_remove(struct platform_device *pdev)
+{
+	struct rpmi_perf_genpd_provider *provider = platform_get_drvdata(pdev);
+	u32 i;
+
+	of_genpd_del_provider(dev_of_node(pdev->dev.parent));
+
+	for (i = 0; i < provider->data.num_domains; i++) {
+		if (provider->data.domains[i])
+			pm_genpd_remove(provider->data.domains[i]);
+	}
+}
+
+static struct platform_driver rpmi_perf_genpd_platdrv = {
+	.driver = {
+		.name = "riscv-rpmi-perf-domain",
+	},
+	.probe = rpmi_perf_genpd_probe,
+	.remove = rpmi_perf_genpd_remove,
+};
+
+module_platform_driver(rpmi_perf_genpd_platdrv);
+
+MODULE_ALIAS("platform:riscv-rpmi-perf-domain");
+MODULE_AUTHOR("Joshua Yeong <joshua.yeong@starfivetech.com>");
+MODULE_DESCRIPTION("Power domains for the RISC-V RPMI performance service");
+MODULE_LICENSE("GPL");
-- 
2.43.0


  parent reply	other threads:[~2026-10-08  9:12 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-08  9:10 [PATCH v2 0/7] Add RISC-V RPMI performance service support Joshua Yeong
2026-10-08  9:10 ` [PATCH v2 1/7] dt-bindings: dvfs: Add RPMI performance service message proxy bindings Joshua Yeong
2026-10-08  9:10 ` [PATCH v2 2/7] dt-bindings: dvfs: Add RPMI performance service bindings Joshua Yeong
2026-10-08 10:41   ` Conor Dooley
2026-10-08  9:10 ` [PATCH v2 3/7] dt-bindings: riscv: cpus: document performance-domains property Joshua Yeong
2026-10-08  9:10 ` [PATCH v2 4/7] firmware: riscv: Add RPMI performance service Joshua Yeong
2026-10-08  9:10 ` [PATCH v2 5/7] cpufreq: Add RISC-V RPMI cpufreq driver Joshua Yeong
2026-10-08  9:10 ` Joshua Yeong [this message]
2026-10-08  9:10 ` [PATCH v2 7/7] MAINTAINERS: Add RISC-V RPMI performance driver Joshua Yeong

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261008091032.2832333-7-joshua.yeong@starfivetech.com \
    --to=joshua.yeong@starfivetech.com \
    --cc=alex@ghiti.fr \
    --cc=anup@brainfault.org \
    --cc=aou@eecs.berkeley.edu \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=lftan.linux@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=linux-riscv@lists.infradead.org \
    --cc=palmer@dabbelt.com \
    --cc=pjw@kernel.org \
    --cc=rafael@kernel.org \
    --cc=rahul@summations.net \
    --cc=robh@kernel.org \
    --cc=ulfh@kernel.org \
    --cc=viresh.kumar@linaro.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®