* [PATCH v2 1/7] dt-bindings: dvfs: Add RPMI performance service message proxy bindings
2026-10-08 9:10 [PATCH v2 0/7] Add RISC-V RPMI performance service support Joshua Yeong
@ 2026-10-08 9:10 ` Joshua Yeong
2026-10-08 9:10 ` [PATCH v2 2/7] dt-bindings: dvfs: Add RPMI performance service bindings Joshua Yeong
` (5 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Joshua Yeong @ 2026-10-08 9:10 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, pjw, palmer, aou, rafael, viresh.kumar,
ulfh, rahul, anup, lftan.linux
Cc: alex, joshua.yeong, linux-riscv, devicetree, linux-pm, linux-kernel
Add device tree bindings for the RPMI performance service group based
message proxy implemented by the SBI implementation (machine mode
firmware or hypervisor).
The RPMI performance service group is defined by the RISC-V Platform
Management Interface (RPMI) specification.
Signed-off-by: Joshua Yeong <joshua.yeong@starfivetech.com>
---
.../dvfs/riscv,rpmi-mpxy-performance.yaml | 65 +++++++++++++++++++
1 file changed, 65 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dvfs/riscv,rpmi-mpxy-performance.yaml
diff --git a/Documentation/devicetree/bindings/dvfs/riscv,rpmi-mpxy-performance.yaml b/Documentation/devicetree/bindings/dvfs/riscv,rpmi-mpxy-performance.yaml
new file mode 100644
index 000000000000..4a05c48c1e12
--- /dev/null
+++ b/Documentation/devicetree/bindings/dvfs/riscv,rpmi-mpxy-performance.yaml
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dvfs/riscv,rpmi-mpxy-performance.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: RISC-V RPMI performance service group based message proxy
+
+maintainers:
+ - Joshua Yeong <joshua.yeong@starfivetech.com>
+
+description: |
+ The RISC-V Platform Management Interface (RPMI) [1] defines a modular and
+ extensible messaging protocol for platform management functions. The supervisor
+ software can send and receive RPMI messages via SBI MPXY extension [2]
+ or via a dedicated supervisor-mode RPMI transport.
+
+ The RPMI specification [1] defines performance service group (performance
+ domain) for accessing and controlling platform-managed performance-related
+ resources implemented by a platform microcontroller. The SBI implementation
+ (machine mode firmware or hypervisor) can implement an SBI MPXY channel
+ to allow supervisor software to access the RPMI performance service group.
+
+ ===========================================
+ References
+ ===========================================
+
+ [1] RISC-V Platform Management Interface (RPMI) v1.0 (or higher)
+ https://github.com/riscv-non-isa/riscv-rpmi/releases
+
+ [2] RISC-V Supervisor Binary Interface (SBI) v3.0 (or higher)
+ https://github.com/riscv-non-isa/riscv-sbi-doc/releases
+
+properties:
+ compatible:
+ description:
+ Intended for use by the SBI implementation.
+ const: riscv,rpmi-mpxy-performance
+
+ mboxes:
+ maxItems: 1
+ description:
+ Mailbox channel of the underlying RPMI transport.
+
+ riscv,sbi-mpxy-channel-id:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ The SBI MPXY channel id to be used for providing RPMI access to
+ the supervisor software.
+
+required:
+ - compatible
+ - mboxes
+ - riscv,sbi-mpxy-channel-id
+
+additionalProperties: false
+
+examples:
+ - |
+ performance {
+ compatible = "riscv,rpmi-mpxy-performance";
+ mboxes = <&rpmi_shmem_mbox 0x0a>;
+ riscv,sbi-mpxy-channel-id = <0x1003>;
+ };
+...
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v2 2/7] dt-bindings: dvfs: Add RPMI performance service bindings
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 ` 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
` (4 subsequent siblings)
6 siblings, 1 reply; 9+ messages in thread
From: Joshua Yeong @ 2026-10-08 9:10 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, pjw, palmer, aou, rafael, viresh.kumar,
ulfh, rahul, anup, lftan.linux
Cc: alex, joshua.yeong, linux-riscv, devicetree, linux-pm, linux-kernel
Add device tree bindings for the RPMI performance service group based
controller for the supervisor software.
A CPU names its performance domain through "performance-domains". Any
other device names it through "power-domains": the controller is also a
power domain provider, with one power domain per performance domain and
the levels the domain advertises as the performance states of that
power domain.
The RPMI performance service group is defined by the RISC-V Platform
Management Interface (RPMI) specification.
Signed-off-by: Joshua Yeong <joshua.yeong@starfivetech.com>
---
.../bindings/dvfs/riscv,rpmi-performance.yaml | 88 +++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dvfs/riscv,rpmi-performance.yaml
diff --git a/Documentation/devicetree/bindings/dvfs/riscv,rpmi-performance.yaml b/Documentation/devicetree/bindings/dvfs/riscv,rpmi-performance.yaml
new file mode 100644
index 000000000000..ec7856cb7257
--- /dev/null
+++ b/Documentation/devicetree/bindings/dvfs/riscv,rpmi-performance.yaml
@@ -0,0 +1,88 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dvfs/riscv,rpmi-performance.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: RISC-V RPMI performance service group
+
+maintainers:
+ - Joshua Yeong <joshua.yeong@starfivetech.com>
+
+description: |
+ The RISC-V Platform Management Interface (RPMI) [1] defines a modular and
+ extensible messaging protocol for platform management functions. The supervisor
+ software can send and receive RPMI messages via SBI MPXY extension [2]
+ or via a dedicated supervisor-mode RPMI transport.
+
+ The RPMI specification [1] defines performance service group (performance
+ domain) for accessing and controlling platform-managed performance-related
+ resources, as implemented by a platform microcontroller. Supervisor software
+ can interact with the RPMI performance service group through an SBI MPXY
+ channel or through a dedicated supervisor-mode RPMI transport.
+
+ The node is a performance domain provider. A CPU references its domain
+ through the generic "performance-domains" property described in
+ dvfs/performance-domain.yaml.
+
+ The node can also be a power domain provider, with one power domain for each
+ performance domain. Any other device that runs in one of the domains
+ references it through "power-domains", and each level the domain advertises
+ is a performance state of that power domain.
+
+ For example, with the provider labelled "performance", a CPU that runs in
+ domain 0 and a device that runs in domain 1 reference their domains as:
+
+ cpu@0 {
+ ...
+ performance-domains = <&performance 0>;
+ };
+
+ gpu@40000000 {
+ ...
+ power-domains = <&performance 1>;
+ };
+
+ ===========================================
+ References
+ ===========================================
+
+ [1] RISC-V Platform Management Interface (RPMI) v1.0 (or higher)
+ https://github.com/riscv-non-isa/riscv-rpmi/releases
+
+ [2] RISC-V Supervisor Binary Interface (SBI) v3.0 (or higher)
+ https://github.com/riscv-non-isa/riscv-sbi-doc/releases
+
+properties:
+ compatible:
+ description:
+ Intended for use by the supervisor software.
+ const: riscv,rpmi-performance
+
+ mboxes:
+ maxItems: 1
+ description:
+ Mailbox channel of the underlying RPMI transport or SBI message proxy channel.
+
+ "#performance-domain-cells":
+ const: 1
+
+ "#power-domain-cells":
+ const: 1
+
+required:
+ - compatible
+ - mboxes
+ - "#performance-domain-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ rpmi-performance {
+ compatible = "riscv,rpmi-performance";
+ mboxes = <&mpxy_mbox 0x1003 0x0>;
+ #performance-domain-cells = <0x01>;
+ #power-domain-cells = <0x01>;
+ };
+...
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v2 2/7] dt-bindings: dvfs: Add RPMI performance service bindings
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
0 siblings, 0 replies; 9+ messages in thread
From: Conor Dooley @ 2026-10-08 10:41 UTC (permalink / raw)
To: Joshua Yeong
Cc: robh, krzk+dt, conor+dt, pjw, palmer, aou, rafael, viresh.kumar,
ulfh, rahul, anup, lftan.linux, alex, linux-riscv, devicetree,
linux-pm, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 4763 bytes --]
On Thu, Oct 08, 2026 at 05:10:26PM +0800, Joshua Yeong wrote:
> Add device tree bindings for the RPMI performance service group based
> controller for the supervisor software.
>
> A CPU names its performance domain through "performance-domains". Any
> other device names it through "power-domains": the controller is also a
> power domain provider, with one power domain per performance domain and
> the levels the domain advertises as the performance states of that
> power domain.
>
> The RPMI performance service group is defined by the RISC-V Platform
> Management Interface (RPMI) specification.
>
> Signed-off-by: Joshua Yeong <joshua.yeong@starfivetech.com>
> ---
> .../bindings/dvfs/riscv,rpmi-performance.yaml | 88 +++++++++++++++++++
> 1 file changed, 88 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/dvfs/riscv,rpmi-performance.yaml
>
> diff --git a/Documentation/devicetree/bindings/dvfs/riscv,rpmi-performance.yaml b/Documentation/devicetree/bindings/dvfs/riscv,rpmi-performance.yaml
> new file mode 100644
> index 000000000000..ec7856cb7257
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dvfs/riscv,rpmi-performance.yaml
> @@ -0,0 +1,88 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/dvfs/riscv,rpmi-performance.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: RISC-V RPMI performance service group
> +
> +maintainers:
> + - Joshua Yeong <joshua.yeong@starfivetech.com>
> +
> +description: |
> + The RISC-V Platform Management Interface (RPMI) [1] defines a modular and
> + extensible messaging protocol for platform management functions. The supervisor
> + software can send and receive RPMI messages via SBI MPXY extension [2]
> + or via a dedicated supervisor-mode RPMI transport.
> +
> + The RPMI specification [1] defines performance service group (performance
> + domain) for accessing and controlling platform-managed performance-related
> + resources, as implemented by a platform microcontroller. Supervisor software
> + can interact with the RPMI performance service group through an SBI MPXY
> + channel or through a dedicated supervisor-mode RPMI transport.
> +
> + The node is a performance domain provider. A CPU references its domain
> + through the generic "performance-domains" property described in
> + dvfs/performance-domain.yaml.
> +
> + The node can also be a power domain provider, with one power domain for each
> + performance domain. Any other device that runs in one of the domains
> + references it through "power-domains", and each level the domain advertises
> + is a performance state of that power domain.
> +
> + For example, with the provider labelled "performance", a CPU that runs in
> + domain 0 and a device that runs in domain 1 reference their domains as:
> +
> + cpu@0 {
> + ...
> + performance-domains = <&performance 0>;
> + };
> +
> + gpu@40000000 {
> + ...
> + power-domains = <&performance 1>;
> + };
> +
> + ===========================================
> + References
> + ===========================================
> +
> + [1] RISC-V Platform Management Interface (RPMI) v1.0 (or higher)
> + https://github.com/riscv-non-isa/riscv-rpmi/releases
> +
> + [2] RISC-V Supervisor Binary Interface (SBI) v3.0 (or higher)
> + https://github.com/riscv-non-isa/riscv-sbi-doc/releases
> +
> +properties:
> + compatible:
> + description:
> + Intended for use by the supervisor software.
> + const: riscv,rpmi-performance
> +
> + mboxes:
> + maxItems: 1
> + description:
> + Mailbox channel of the underlying RPMI transport or SBI message proxy channel.
> +
> + "#performance-domain-cells":
> + const: 1
> +
> + "#power-domain-cells":
> + const: 1
> +
> +required:
> + - compatible
> + - mboxes
> + - "#performance-domain-cells"
Why is performance-domain-cells mandatory? Is it not possible for a
domain to only control non-cpu devices? If that's the case, this should
be relaxed to require that either cells property is present I think.
Also, all other rpmi/mpxy series have been written such that there's
both M and S mode bindings in a single file. Why has that approach not
been taken here?
Thanks,
Conor.
-
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + rpmi-performance {
> + compatible = "riscv,rpmi-performance";
> + mboxes = <&mpxy_mbox 0x1003 0x0>;
> + #performance-domain-cells = <0x01>;
> + #power-domain-cells = <0x01>;
> + };
> +...
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 3/7] dt-bindings: riscv: cpus: document performance-domains property
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 9:10 ` Joshua Yeong
2026-10-08 9:10 ` [PATCH v2 4/7] firmware: riscv: Add RPMI performance service Joshua Yeong
` (3 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Joshua Yeong @ 2026-10-08 9:10 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, pjw, palmer, aou, rafael, viresh.kumar,
ulfh, rahul, anup, lftan.linux
Cc: alex, joshua.yeong, linux-riscv, devicetree, linux-pm, linux-kernel
A CPU names the performance domain it runs in through the generic
"performance-domains" property of dvfs/performance-domain.yaml, which
gives the property its type and serves any performance domain provider.
The RISC-V CPU binding only allows the properties it lists, so list it
there, with one domain per CPU as the Arm CPU binding does.
Signed-off-by: Joshua Yeong <joshua.yeong@starfivetech.com>
---
Documentation/devicetree/bindings/riscv/cpus.yaml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Documentation/devicetree/bindings/riscv/cpus.yaml b/Documentation/devicetree/bindings/riscv/cpus.yaml
index 0da219ae6769..f21f003575b6 100644
--- a/Documentation/devicetree/bindings/riscv/cpus.yaml
+++ b/Documentation/devicetree/bindings/riscv/cpus.yaml
@@ -144,6 +144,9 @@ properties:
DMIPS/MHz, relative to highest capacity-dmips-mhz
in the system.
+ performance-domains:
+ maxItems: 1
+
anyOf:
- required:
- riscv,isa
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v2 4/7] firmware: riscv: Add RPMI performance service
2026-10-08 9:10 [PATCH v2 0/7] Add RISC-V RPMI performance service support Joshua Yeong
` (2 preceding siblings ...)
2026-10-08 9:10 ` [PATCH v2 3/7] dt-bindings: riscv: cpus: document performance-domains property Joshua Yeong
@ 2026-10-08 9:10 ` Joshua Yeong
2026-10-08 9:10 ` [PATCH v2 5/7] cpufreq: Add RISC-V RPMI cpufreq driver Joshua Yeong
` (2 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Joshua Yeong @ 2026-10-08 9:10 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, pjw, palmer, aou, rafael, viresh.kumar,
ulfh, rahul, anup, lftan.linux
Cc: alex, joshua.yeong, linux-riscv, devicetree, linux-pm, linux-kernel
The RPMI specification defines a performance service group which can be
accessed via SBI MPXY extension or dedicated S-mode RPMI transport.
Add a mailbox client based core for the RISC-V RPMI performance service
group. It owns the mailbox channel, which cannot be shared, enumerates
the performance domains advertised by the platform microcontroller, and
exports an interface to read and set their levels and limits, through
the fast channel where a domain has one. The front-ends that drive the
domains build on it and are added separately.
Signed-off-by: Joshua Yeong <joshua.yeong@starfivetech.com>
---
drivers/firmware/Kconfig | 1 +
drivers/firmware/Makefile | 1 +
drivers/firmware/riscv/Kconfig | 17 +
drivers/firmware/riscv/Makefile | 3 +
.../firmware/riscv/riscv-rpmi-performance.c | 1105 +++++++++++++++++
.../firmware/riscv/riscv-rpmi-performance.h | 148 +++
include/linux/mailbox/riscv-rpmi-message.h | 16 +
7 files changed, 1291 insertions(+)
create mode 100644 drivers/firmware/riscv/Kconfig
create mode 100644 drivers/firmware/riscv/Makefile
create mode 100644 drivers/firmware/riscv/riscv-rpmi-performance.c
create mode 100644 include/linux/firmware/riscv/riscv-rpmi-performance.h
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index b7cc11e4fbfa..cf0212576dfd 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -306,6 +306,7 @@ source "drivers/firmware/meson/Kconfig"
source "drivers/firmware/microchip/Kconfig"
source "drivers/firmware/psci/Kconfig"
source "drivers/firmware/qcom/Kconfig"
+source "drivers/firmware/riscv/Kconfig"
source "drivers/firmware/samsung/Kconfig"
source "drivers/firmware/smccc/Kconfig"
source "drivers/firmware/tegra/Kconfig"
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index be46f1e1dc77..879953c316dc 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -35,6 +35,7 @@ obj-y += efi/
obj-y += imx/
obj-y += psci/
obj-y += qcom/
+obj-y += riscv/
obj-y += samsung/
obj-y += smccc/
obj-y += tegra/
diff --git a/drivers/firmware/riscv/Kconfig b/drivers/firmware/riscv/Kconfig
new file mode 100644
index 000000000000..309f43bdb6d1
--- /dev/null
+++ b/drivers/firmware/riscv/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "RISC-V Firmware Drivers"
+ depends on RISCV || COMPILE_TEST
+
+config RISCV_RPMI_PERFORMANCE
+ tristate "RISC-V RPMI performance service group"
+ depends on RISCV || COMPILE_TEST
+ depends on MAILBOX
+ default RISCV
+ select PM_OPP
+ help
+ Support for the performance service group defined by the RISC-V
+ platform management interface (RPMI) specification. The performance
+ domains advertised by the platform microcontroller are enumerated
+ here and made available to the front-end drivers that control them.
+
+endmenu
diff --git a/drivers/firmware/riscv/Makefile b/drivers/firmware/riscv/Makefile
new file mode 100644
index 000000000000..4020f828704a
--- /dev/null
+++ b/drivers/firmware/riscv/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_RISCV_RPMI_PERFORMANCE) += riscv-rpmi-performance.o
diff --git a/drivers/firmware/riscv/riscv-rpmi-performance.c b/drivers/firmware/riscv/riscv-rpmi-performance.c
new file mode 100644
index 000000000000..d44802f07ab0
--- /dev/null
+++ b/drivers/firmware/riscv/riscv-rpmi-performance.c
@@ -0,0 +1,1105 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RISC-V RPMI performance service group core
+ *
+ * Copyright (C) 2026 Shanghai StarFive Technology Co., Ltd.
+ *
+ * Owns the MPXY mailbox channel of the RPMI performance service group and the
+ * enumeration of the performance domains behind it, and hands both to the
+ * front-ends that drive a domain. The channel cannot be shared, so it has a
+ * single owner here and the front-ends sit on top of it.
+ */
+
+#define pr_fmt(fmt) "riscv-rpmi-performance: " fmt
+
+#include <linux/bitfield.h>
+#include <linux/bsearch.h>
+#include <linux/firmware/riscv/riscv-rpmi-performance.h>
+#include <linux/io.h>
+#include <linux/mailbox/riscv-rpmi-message.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+
+#define RPMI_PERF_DOMAIN_NAME_LEN 16
+
+struct rpmi_fc_db_info {
+ int width;
+ u32 set;
+ void __iomem *addr;
+};
+
+struct rpmi_fc_info {
+ void __iomem *set_addr;
+ void __iomem *get_addr;
+ struct rpmi_fc_db_info *set_db;
+};
+
+struct rpmi_ctx {
+ struct mbox_chan *chan;
+ struct mbox_client client;
+ /* serialises RPMI transactions on @chan */
+ struct mutex lock;
+ u32 max_msg_size;
+};
+
+struct rpmi_fast_channel {
+ u64 addr;
+ u64 size;
+};
+
+struct rpmi_perf_domain {
+ u32 id;
+ bool enumerated;
+ bool set_limit;
+ bool set_level;
+ bool has_fastchannels;
+ u32 level_count;
+ u32 rate_limit_us;
+ char name[RPMI_PERF_DOMAIN_NAME_LEN];
+ struct rpmi_perf_level *level;
+ struct rpmi_fc_info *fc_info;
+ struct rpmi_ctx *mpxy_ctx;
+ struct device *dev;
+ struct rpmi_perf *perf;
+};
+
+struct rpmi_perf {
+ u32 num_domains;
+ struct rpmi_fast_channel fast_channel;
+ struct device *dev;
+ struct rpmi_perf_domain *domain;
+};
+
+enum {
+ RPMI_PERF_FC_LEVEL,
+ RPMI_PERF_FC_MAX,
+};
+
+/* Service: ENABLE_NOTIFICATION */
+struct rpmi_perf_enable_notification_tx {
+ __le32 event_id;
+};
+
+struct rpmi_perf_enable_notification_rx {
+ __le32 status;
+};
+
+/* Service: GET_NUM_DOMAINS */
+struct rpmi_perf_get_num_domain_rx {
+ __le32 status;
+ __le32 num_domains;
+};
+
+/* Service: GET_ATTRS */
+struct rpmi_perf_get_attrs_tx {
+ __le32 domain_id;
+};
+
+struct rpmi_perf_get_attrs_rx {
+ __le32 status;
+ __le32 flags;
+#define PERF_LIMIT_SETTING(f) (FIELD_GET(BIT(2), (f)))
+#define PERF_LEVEL_SETTING(f) (FIELD_GET(BIT(1), (f)))
+#define FAST_CHANNEL_SUPPORT(f) (FIELD_GET(BIT(0), (f)))
+ __le32 num_levels;
+ __le32 trans_latency_us;
+ char name[RPMI_PERF_DOMAIN_NAME_LEN];
+};
+
+/* Service: GET_SUPPORTED_LEVELS */
+struct rpmi_perf_get_supported_level_tx {
+ __le32 domain_id;
+ __le32 level_index;
+};
+
+struct rpmi_perf_supported_level {
+ __le32 index;
+ __le32 clock_freq;
+ __le32 power_cost;
+ __le32 trans_latency_us;
+};
+
+struct rpmi_perf_get_supported_level_rx {
+ __le32 status;
+ __le32 flags;
+ __le32 remaining;
+ __le32 returned;
+ struct rpmi_perf_supported_level level[];
+};
+
+/* Service: GET_LEVEL */
+struct rpmi_perf_get_level_tx {
+ __le32 domain_id;
+};
+
+struct rpmi_perf_get_level_rx {
+ __le32 status;
+ __le32 level_index;
+};
+
+/* Service: SET_LEVEL */
+struct rpmi_perf_set_level_tx {
+ __le32 domain_id;
+ __le32 level_index;
+};
+
+struct rpmi_perf_set_level_rx {
+ __le32 status;
+};
+
+/* Service: GET_LIMIT */
+struct rpmi_perf_get_limit_tx {
+ __le32 domain_id;
+};
+
+struct rpmi_perf_get_limit_rx {
+ __le32 status;
+ __le32 max_level;
+ __le32 min_level;
+};
+
+/* Service: SET_LIMIT */
+struct rpmi_perf_set_limit_tx {
+ __le32 domain_id;
+ __le32 max_level;
+ __le32 min_level;
+};
+
+struct rpmi_perf_set_limit_rx {
+ __le32 status;
+};
+
+/* Service: GET_FAST_CHANNEL_REGION */
+struct rpmi_perf_get_fast_channel_region_rx {
+ __le32 status;
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 size_low;
+ __le32 size_high;
+};
+
+/* Service: GET_FAST_CHANNEL_ATTRIBUTES */
+struct rpmi_perf_get_fast_channel_attributes_tx {
+ __le32 domain_id;
+ __le32 service_id;
+};
+
+struct rpmi_perf_get_fast_channel_attributes_rx {
+ __le32 status;
+ __le32 flags;
+#define DOORBELL_REG_WIDTH(f) (FIELD_GET(GENMASK(2, 1), (f)))
+#define DOORBELL_REG_WIDTH_RESERVED 0x3
+#define SUPPORTS_DOORBELL(f) (FIELD_GET(BIT(0), (f)))
+ __le32 offset_low;
+ __le32 offset_high;
+ __le32 size;
+ __le32 db_addr_low;
+ __le32 db_addr_high;
+ __le32 db_id;
+};
+
+static void rpmi_perf_fastchannel_db_ring(struct rpmi_fc_db_info *db)
+{
+ if (!db || !db->addr)
+ return;
+
+ switch (db->width) {
+ case 8:
+ iowrite8((u8)db->set, db->addr);
+ break;
+ case 16:
+ iowrite16((u16)db->set, db->addr);
+ break;
+ case 32:
+ iowrite32(db->set, db->addr);
+ break;
+ }
+}
+
+static int rpmi_perf_send_message(struct rpmi_ctx *mpxy_ctx,
+ struct rpmi_mbox_message *msg)
+{
+ guard(mutex)(&mpxy_ctx->lock);
+
+ return rpmi_mbox_send_message(mpxy_ctx->chan, msg);
+}
+
+static int rpmi_perf_get_num_domains(struct rpmi_ctx *mpxy_ctx, u32 *domain)
+{
+ struct rpmi_perf_get_num_domain_rx rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_PERF_SRV_GET_NUM_DOMAINS,
+ NULL, 0, &rx, sizeof(rx));
+ ret = rpmi_perf_send_message(mpxy_ctx, &msg);
+ if (ret)
+ return ret;
+
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ if (msg.data.out_response_len < sizeof(rx))
+ return -EPROTO;
+
+ *domain = le32_to_cpu(rx.num_domains);
+
+ return 0;
+}
+
+static int rpmi_perf_get_attrs(struct rpmi_perf *mpxy_perf, struct rpmi_perf_domain *perf_domain)
+{
+ struct rpmi_perf_get_attrs_tx tx;
+ struct rpmi_perf_get_attrs_rx rx = { };
+ struct rpmi_mbox_message msg;
+ u32 flags;
+ int ret;
+
+ tx.domain_id = cpu_to_le32(perf_domain->id);
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_PERF_SRV_GET_ATTRIBUTES,
+ &tx, sizeof(tx), &rx, sizeof(rx));
+ ret = rpmi_perf_send_message(perf_domain->mpxy_ctx, &msg);
+ if (ret)
+ return ret;
+
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ if (msg.data.out_response_len < sizeof(rx))
+ return -EPROTO;
+
+ flags = le32_to_cpu(rx.flags);
+
+ perf_domain->set_limit = PERF_LIMIT_SETTING(flags);
+ perf_domain->set_level = PERF_LEVEL_SETTING(flags);
+ perf_domain->has_fastchannels = mpxy_perf->fast_channel.size &&
+ (perf_domain->set_limit || perf_domain->set_level) ?
+ FAST_CHANNEL_SUPPORT(flags) : false;
+ perf_domain->level_count = le32_to_cpu(rx.num_levels);
+ perf_domain->rate_limit_us = le32_to_cpu(rx.trans_latency_us);
+ strscpy(perf_domain->name, rx.name, RPMI_PERF_DOMAIN_NAME_LEN);
+
+ if (!perf_domain->level_count)
+ return -EINVAL;
+
+ perf_domain->level = devm_kcalloc(mpxy_perf->dev, perf_domain->level_count,
+ sizeof(struct rpmi_perf_level), GFP_KERNEL);
+ if (!perf_domain->level)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int rpmi_perf_get_supported_levels(struct rpmi_perf_domain *perf_domain)
+{
+ struct rpmi_perf_get_supported_level_tx tx;
+ struct rpmi_perf_get_supported_level_rx *rx;
+ struct rpmi_mbox_message msg;
+ struct rpmi_perf_level *level;
+ u32 index = 0, remaining, returned;
+ int ret = 0;
+
+ /*
+ * A reply carries as many levels as fit in one message, so size it
+ * for the largest message the channel carries. max_msg_size is in
+ * bytes, and was checked to hold at least one level.
+ */
+ rx = kzalloc(perf_domain->mpxy_ctx->max_msg_size, GFP_KERNEL);
+ if (!rx)
+ return -ENOMEM;
+
+ do {
+ tx.domain_id = cpu_to_le32(perf_domain->id);
+ tx.level_index = cpu_to_le32(index);
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_PERF_SRV_GET_SUPPORTED_LEVELS,
+ &tx, sizeof(tx), rx,
+ perf_domain->mpxy_ctx->max_msg_size);
+ ret = rpmi_perf_send_message(perf_domain->mpxy_ctx, &msg);
+ if (ret)
+ goto exit;
+
+ if (rx->status) {
+ ret = rpmi_to_linux_error(le32_to_cpu(rx->status));
+ goto exit;
+ }
+
+ if (msg.data.out_response_len < sizeof(*rx)) {
+ ret = -EPROTO;
+ goto exit;
+ }
+
+ remaining = le32_to_cpu(rx->remaining);
+ returned = le32_to_cpu(rx->returned);
+
+ if (!returned || returned > perf_domain->level_count - index ||
+ returned > (msg.data.out_response_len - sizeof(*rx)) /
+ sizeof(rx->level[0]) ||
+ remaining != perf_domain->level_count - index - returned) {
+ ret = -EPROTO;
+ goto exit;
+ }
+
+ for (u32 i = 0; i < returned; i++) {
+ level = &perf_domain->level[index + i];
+ level->index = le32_to_cpu(rx->level[i].index);
+ level->clock_freq = le32_to_cpu(rx->level[i].clock_freq);
+ level->power_cost = le32_to_cpu(rx->level[i].power_cost);
+ level->trans_latency_us = le32_to_cpu(rx->level[i].trans_latency_us);
+ }
+
+ index += returned;
+
+ } while (remaining);
+
+exit:
+ kfree(rx);
+
+ return ret;
+}
+
+static int rpmi_perf_get_fast_channel_regions(struct rpmi_ctx *mpxy_ctx, struct rpmi_perf *perf)
+{
+ struct rpmi_perf_get_fast_channel_region_rx rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_PERF_SRV_GET_FAST_CHANNEL_REGION,
+ NULL, 0, &rx, sizeof(rx));
+ ret = rpmi_perf_send_message(mpxy_ctx, &msg);
+ if (ret)
+ return ret;
+
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ if (msg.data.out_response_len < sizeof(rx))
+ return -EPROTO;
+
+ perf->fast_channel.addr = le32_to_cpu(rx.addr_low) |
+ ((u64)le32_to_cpu(rx.addr_high) << 32);
+ perf->fast_channel.size = le32_to_cpu(rx.size_low) |
+ ((u64)le32_to_cpu(rx.size_high) << 32);
+
+ /* Every fast channel is checked against this region, so it must not wrap. */
+ if (perf->fast_channel.addr + perf->fast_channel.size < perf->fast_channel.addr) {
+ perf->fast_channel.size = 0;
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+static int rpmi_perf_extract_attributes(struct rpmi_perf_get_fast_channel_attributes_rx *rx,
+ struct rpmi_perf_domain *domain,
+ struct rpmi_fc_info *fc, u32 service_id)
+{
+ u64 region_size = domain->perf->fast_channel.size;
+ u64 phys_offset, phys_addr, db_phys_addr;
+ struct rpmi_fc_db_info *db = NULL;
+ void __iomem *addr, *db_addr;
+ u32 flags, size;
+ u8 db_addr_width;
+ int ret;
+
+ flags = le32_to_cpu(rx->flags);
+ phys_offset = le32_to_cpu(rx->offset_low);
+ phys_offset |= (u64)le32_to_cpu(rx->offset_high) << 32;
+ size = le32_to_cpu(rx->size);
+
+ /*
+ * The fast channels all lie in the region PERF_GET_FAST_CHANNEL_REGION
+ * describes, at an offset from its base, so refuse one that does not
+ * fit in it or is too small for its payload, a 32-bit level.
+ */
+ if (size < sizeof(u32) || phys_offset > region_size ||
+ size > region_size - phys_offset)
+ return -EPROTO;
+
+ phys_addr = domain->perf->fast_channel.addr + phys_offset;
+
+ addr = devm_ioremap(domain->dev, phys_addr, size);
+ if (!addr) {
+ dev_err(domain->dev,
+ "failed to get fastchannel virtual addr in domain: %d\n",
+ domain->id);
+ return -EADDRNOTAVAIL;
+ }
+
+ switch (service_id) {
+ case RPMI_PERF_SRV_GET_LEVEL:
+ fc->get_addr = addr;
+ break;
+ case RPMI_PERF_SRV_SET_LEVEL:
+ if (SUPPORTS_DOORBELL(flags)) {
+ db = devm_kzalloc(domain->dev,
+ sizeof(struct rpmi_fc_db_info),
+ GFP_KERNEL);
+ if (!db) {
+ ret = -ENOMEM;
+ goto err_unmap;
+ }
+
+ db_addr_width = 1 << (DOORBELL_REG_WIDTH(flags) + 3);
+ db_phys_addr = le32_to_cpu(rx->db_addr_low);
+ db_phys_addr |= (u64)le32_to_cpu(rx->db_addr_high) << 32;
+
+ /* The doorbell can be any register; map only its width. */
+ db_addr = devm_ioremap(domain->dev, db_phys_addr,
+ db_addr_width / BITS_PER_BYTE);
+ if (!db_addr) {
+ devm_kfree(domain->dev, db);
+ ret = -EADDRNOTAVAIL;
+ goto err_unmap;
+ }
+
+ db->addr = db_addr;
+ db->width = db_addr_width;
+ db->set = le32_to_cpu(rx->db_id);
+ fc->set_db = db;
+ }
+ fc->set_addr = addr;
+
+ break;
+ }
+
+ return 0;
+
+err_unmap:
+ /*
+ * The platform asked for a doorbell, so without one a request would
+ * never reach it. Leave the service on the mailbox.
+ */
+ devm_iounmap(domain->dev, addr);
+
+ return ret;
+}
+
+static int rpmi_perf_get_fast_channel_attributes(struct rpmi_perf_domain *perf_domain,
+ struct rpmi_fc_info *fc,
+ u32 service_id)
+{
+ struct rpmi_perf_get_fast_channel_attributes_tx tx;
+ struct rpmi_perf_get_fast_channel_attributes_rx rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ tx.domain_id = cpu_to_le32(perf_domain->id);
+ tx.service_id = cpu_to_le32(service_id);
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_PERF_SRV_GET_FAST_CHANNEL_ATTRS,
+ &tx, sizeof(tx), &rx, sizeof(rx));
+ ret = rpmi_perf_send_message(perf_domain->mpxy_ctx, &msg);
+ if (ret)
+ return ret;
+
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ /*
+ * The doorbell words are unused without a doorbell, and a platform
+ * may leave them out of the reply, so only require them with one.
+ */
+ if (msg.data.out_response_len < offsetof(typeof(rx), db_addr_low) ||
+ (SUPPORTS_DOORBELL(le32_to_cpu(rx.flags)) &&
+ msg.data.out_response_len < sizeof(rx)))
+ return -EPROTO;
+
+ /* A doorbell of the reserved width could not be rung at all. */
+ if (SUPPORTS_DOORBELL(le32_to_cpu(rx.flags)) &&
+ DOORBELL_REG_WIDTH(le32_to_cpu(rx.flags)) == DOORBELL_REG_WIDTH_RESERVED)
+ return -EPROTO;
+
+ ret = rpmi_perf_extract_attributes(&rx, perf_domain, fc, service_id);
+
+ return ret;
+}
+
+/*
+ * A service without a fast channel goes through the mailbox, so a fast channel
+ * that cannot be set up only costs speed. The platform may say it implements
+ * none; any other failure is reported, since nothing else would show it.
+ */
+static void rpmi_perf_setup_fast_channel(struct rpmi_perf_domain *domain,
+ struct rpmi_fc_info *fc, u32 service_id)
+{
+ int ret;
+
+ ret = rpmi_perf_get_fast_channel_attributes(domain, fc, service_id);
+ if (ret && ret != -EOPNOTSUPP)
+ dev_warn(domain->dev,
+ "perf domain #%u: no fast channel for service %#x: %d\n",
+ domain->id, service_id, ret);
+}
+
+static int rpmi_perf_cmp_freq(const void *a, const void *b)
+{
+ const struct rpmi_perf_level *la = a, *lb = b;
+
+ return (la->clock_freq > lb->clock_freq) - (la->clock_freq < lb->clock_freq);
+}
+
+static int rpmi_perf_enumerate(struct rpmi_perf *mpxy_perf, struct rpmi_perf_domain *domain)
+{
+ struct rpmi_fc_info *fc;
+ int ret, id;
+
+ id = domain->id;
+
+ ret = rpmi_perf_get_attrs(mpxy_perf, domain);
+ if (ret) {
+ dev_err(domain->dev,
+ "Failed to get attributes of perf domain: #%u\n", id);
+ return ret;
+ }
+
+ ret = rpmi_perf_get_supported_levels(domain);
+ if (ret) {
+ dev_err(domain->dev,
+ "Failed to get supported level of perf domain: #%u\n", id);
+ return ret;
+ }
+
+ /*
+ * The platform may return its levels in any order. A level index is a
+ * point on the platform's performance scale, so a higher index is a
+ * higher level, but the indices need not be contiguous. Keep the table
+ * in ascending order of frequency, which the frequency lookup and every
+ * front-end walking the levels rely on.
+ */
+ sort(domain->level, domain->level_count, sizeof(domain->level[0]),
+ rpmi_perf_cmp_freq, NULL);
+
+ if (domain->has_fastchannels) {
+ fc = devm_kcalloc(domain->dev, RPMI_PERF_FC_MAX, sizeof(*fc), GFP_KERNEL);
+ if (!fc)
+ return -ENOMEM;
+
+ if (domain->set_level)
+ rpmi_perf_setup_fast_channel(domain, &fc[RPMI_PERF_FC_LEVEL],
+ RPMI_PERF_SRV_SET_LEVEL);
+
+ rpmi_perf_setup_fast_channel(domain, &fc[RPMI_PERF_FC_LEVEL],
+ RPMI_PERF_SRV_GET_LEVEL);
+
+ /*
+ * Limits always go through the mailbox, whose reply says
+ * whether the platform took them, so their fast channels are
+ * not set up.
+ */
+
+ domain->fc_info = fc;
+ }
+
+ return 0;
+}
+
+/*
+ * Domain accessors. A consumer only ever sees an opaque handle, so the
+ * enumeration above stays private to this file.
+ */
+
+const char *rpmi_perf_domain_name(struct rpmi_perf_domain *pd)
+{
+ return pd->name;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_name);
+
+u32 rpmi_perf_domain_level_count(struct rpmi_perf_domain *pd)
+{
+ return pd->level_count;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_level_count);
+
+/**
+ * rpmi_perf_domain_level_info() - get one level of a performance domain
+ * @pd: performance domain
+ * @idx: position in the level table, from 0 to rpmi_perf_domain_level_count() - 1
+ * @level: filled in with the level
+ *
+ * The table is in ascending order of frequency. @idx is a position in it, not
+ * an RPMI level index, which is in @level->index.
+ *
+ * Return: 0 on success, -EINVAL if @idx is out of range.
+ */
+int rpmi_perf_domain_level_info(struct rpmi_perf_domain *pd, u32 idx,
+ struct rpmi_perf_level *level)
+{
+ if (idx >= pd->level_count)
+ return -EINVAL;
+
+ *level = pd->level[idx];
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_level_info);
+
+bool rpmi_perf_domain_can_set_level(struct rpmi_perf_domain *pd)
+{
+ return pd->set_level;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_can_set_level);
+
+bool rpmi_perf_domain_has_fast_channel(struct rpmi_perf_domain *pd)
+{
+ return pd->fc_info && pd->fc_info[RPMI_PERF_FC_LEVEL].set_addr;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_has_fast_channel);
+
+u32 rpmi_perf_domain_trans_latency_us(struct rpmi_perf_domain *pd)
+{
+ return pd->rate_limit_us;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_trans_latency_us);
+
+/**
+ * rpmi_perf_domain_by_id() - look up a performance domain
+ * @perf: performance service group
+ * @id: RPMI domain ID
+ *
+ * Return: the domain, or NULL if @id is out of range or the domain could not
+ * be enumerated. Such a domain has no level table to work from, so no
+ * consumer is ever handed one.
+ */
+struct rpmi_perf_domain *rpmi_perf_domain_by_id(struct rpmi_perf *perf, u32 id)
+{
+ if (!perf || id >= perf->num_domains || !perf->domain[id].enumerated)
+ return NULL;
+
+ return &perf->domain[id];
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_by_id);
+
+u32 rpmi_perf_num_domains(struct rpmi_perf *perf)
+{
+ return perf ? perf->num_domains : 0;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_num_domains);
+
+/**
+ * rpmi_perf_domain_get_level() - read the level a domain is running at
+ * @pd: performance domain
+ * @level: where the level index is stored
+ *
+ * Reads through the fast channel when the domain has one, and over the
+ * mailbox otherwise. Either way the value comes from the platform
+ * microcontroller rather than from a cached copy.
+ *
+ * Return: 0 on success, a negative errno otherwise.
+ */
+int rpmi_perf_domain_get_level(struct rpmi_perf_domain *pd, u32 *level)
+{
+ struct rpmi_perf_get_level_tx tx;
+ struct rpmi_perf_get_level_rx rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ if (pd->fc_info && pd->fc_info[RPMI_PERF_FC_LEVEL].get_addr) {
+ *level = ioread32(pd->fc_info[RPMI_PERF_FC_LEVEL].get_addr);
+ return 0;
+ }
+
+ tx.domain_id = cpu_to_le32(pd->id);
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_PERF_SRV_GET_LEVEL,
+ &tx, sizeof(tx), &rx, sizeof(rx));
+ ret = rpmi_perf_send_message(pd->mpxy_ctx, &msg);
+ if (ret)
+ return ret;
+
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ if (msg.data.out_response_len < sizeof(rx))
+ return -EPROTO;
+
+ *level = le32_to_cpu(rx.level_index);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_get_level);
+
+/**
+ * rpmi_perf_domain_set_level() - ask a domain to run at a level
+ * @pd: performance domain
+ * @level: level index, as advertised by the domain
+ *
+ * May sleep, because it goes over the mailbox. Use
+ * rpmi_perf_domain_set_level_fast() from a context that must not.
+ *
+ * Return: 0 on success, a negative errno otherwise.
+ */
+int rpmi_perf_domain_set_level(struct rpmi_perf_domain *pd, u32 level)
+{
+ struct rpmi_perf_set_level_tx tx;
+ struct rpmi_perf_set_level_rx rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ if (!pd->set_level)
+ return -EOPNOTSUPP;
+
+ tx.domain_id = cpu_to_le32(pd->id);
+ tx.level_index = cpu_to_le32(level);
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_PERF_SRV_SET_LEVEL,
+ &tx, sizeof(tx), &rx, sizeof(rx));
+ ret = rpmi_perf_send_message(pd->mpxy_ctx, &msg);
+ if (ret)
+ return ret;
+
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ if (msg.data.out_response_len < sizeof(rx))
+ return -EPROTO;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_set_level);
+
+/**
+ * rpmi_perf_domain_set_level_fast() - set a level without sleeping
+ * @pd: performance domain
+ * @level: level index, as advertised by the domain
+ *
+ * Writes the level through the domain's fast channel and rings its doorbell.
+ * The platform microcontroller applies it asynchronously and there is no
+ * status to read back, so this only reports whether the request was posted.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the domain has no fast channel.
+ */
+int rpmi_perf_domain_set_level_fast(struct rpmi_perf_domain *pd, u32 level)
+{
+ if (!rpmi_perf_domain_has_fast_channel(pd))
+ return -EOPNOTSUPP;
+
+ iowrite32(level, pd->fc_info[RPMI_PERF_FC_LEVEL].set_addr);
+ rpmi_perf_fastchannel_db_ring(pd->fc_info[RPMI_PERF_FC_LEVEL].set_db);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_set_level_fast);
+
+int rpmi_perf_domain_get_limit(struct rpmi_perf_domain *pd, u32 *min, u32 *max)
+{
+ struct rpmi_perf_get_limit_tx tx;
+ struct rpmi_perf_get_limit_rx rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ tx.domain_id = cpu_to_le32(pd->id);
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_PERF_SRV_GET_LIMIT,
+ &tx, sizeof(tx), &rx, sizeof(rx));
+ ret = rpmi_perf_send_message(pd->mpxy_ctx, &msg);
+ if (ret)
+ return ret;
+
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ if (msg.data.out_response_len < sizeof(rx))
+ return -EPROTO;
+
+ *max = le32_to_cpu(rx.max_level);
+ *min = le32_to_cpu(rx.min_level);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_get_limit);
+
+int rpmi_perf_domain_set_limit(struct rpmi_perf_domain *pd, u32 min, u32 max)
+{
+ struct rpmi_perf_set_limit_tx tx;
+ struct rpmi_perf_set_limit_rx rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ if (!pd->set_limit)
+ return -EOPNOTSUPP;
+
+ tx.domain_id = cpu_to_le32(pd->id);
+ tx.max_level = cpu_to_le32(max);
+ tx.min_level = cpu_to_le32(min);
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_PERF_SRV_SET_LIMIT,
+ &tx, sizeof(tx), &rx, sizeof(rx));
+ ret = rpmi_perf_send_message(pd->mpxy_ctx, &msg);
+ if (ret)
+ return ret;
+
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ if (msg.data.out_response_len < sizeof(rx))
+ return -EPROTO;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_set_limit);
+
+static int rpmi_perf_search_freq(const void *id, const void *level)
+{
+ u32 khz = *(const u32 *)id;
+ u32 freq = ((const struct rpmi_perf_level *)level)->clock_freq;
+
+ return (khz > freq) - (khz < freq);
+}
+
+int rpmi_perf_domain_level_to_freq(struct rpmi_perf_domain *pd, u32 level, u32 *khz)
+{
+ /* The table is sorted by frequency, not by level index. */
+ for (u32 i = 0; i < pd->level_count; i++) {
+ if (pd->level[i].index == level) {
+ *khz = pd->level[i].clock_freq;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_level_to_freq);
+
+int rpmi_perf_domain_freq_to_level(struct rpmi_perf_domain *pd, u32 khz, u32 *level)
+{
+ struct rpmi_perf_level *found;
+
+ found = bsearch(&khz, pd->level, pd->level_count,
+ sizeof(pd->level[0]), rpmi_perf_search_freq);
+ if (!found)
+ return -EINVAL;
+
+ *level = found->index;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_freq_to_level);
+
+/**
+ * rpmi_perf_domain_opps_add() - give @dev an OPP for every level of @pd
+ * @pd: performance domain
+ * @dev: device that runs in the domain
+ *
+ * The levels come from the platform microcontroller, so there is no
+ * operating-points-v2 table in the device tree to parse. The OPP frequency is
+ * the level's clock frequency in Hz and the OPP level is the RPMI level
+ * index, which is what the set and get services take.
+ *
+ * Return: 0 on success, a negative errno otherwise.
+ */
+int rpmi_perf_domain_opps_add(struct rpmi_perf_domain *pd, struct device *dev)
+{
+ struct dev_pm_opp_data data = {};
+ struct rpmi_perf_level *level;
+ int idx, ret;
+
+ for (level = pd->level, idx = 0; idx < pd->level_count; idx++, level++) {
+ /* Frequency from RPMI is in kHz */
+ data.freq = (unsigned long)level->clock_freq * 1000;
+ data.level = level->index;
+
+ ret = dev_pm_opp_add_dynamic(dev, &data);
+ if (ret) {
+ dev_warn(dev, "failed to add opp %luHz\n", data.freq);
+
+ while (idx-- > 0) {
+ /* Frequency from RPMI is in kHz */
+ dev_pm_opp_remove(dev,
+ (unsigned long)(--level)->clock_freq * 1000);
+ }
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rpmi_perf_domain_opps_add);
+
+static int rpmi_perf_attr_setup(struct device *dev, struct rpmi_ctx *mpxy_ctx)
+{
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ /* Validate RPMI specification version */
+ rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SPEC_VERSION);
+ ret = rpmi_perf_send_message(mpxy_ctx, &msg);
+ if (ret) {
+ dev_dbg(dev, "Failed to get spec version\n");
+ return ret;
+ }
+
+ if (msg.attr.value < RPMI_MKVER(1, 0)) {
+ dev_dbg(dev,
+ "msg protocol version mismatch, expected 0x%x, found 0x%x\n",
+ RPMI_MKVER(1, 0), msg.attr.value);
+ return -EINVAL;
+ }
+
+ /* Validate performance service group ID */
+ rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SERVICEGROUP_ID);
+ ret = rpmi_perf_send_message(mpxy_ctx, &msg);
+ if (ret) {
+ dev_dbg(dev, "Failed to get service group ID\n");
+ return ret;
+ }
+
+ if (msg.attr.value != RPMI_SRVGRP_PERFORMANCE) {
+ dev_dbg(dev,
+ "service group match failed, expected 0x%x, found 0x%x\n",
+ RPMI_SRVGRP_PERFORMANCE, msg.attr.value);
+ return -EINVAL;
+ }
+
+ /* Validate performance service group version */
+ rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SERVICEGROUP_VERSION);
+ ret = rpmi_perf_send_message(mpxy_ctx, &msg);
+ if (ret) {
+ dev_dbg(dev, "Failed to get service group version\n");
+ return ret;
+ }
+
+ if (msg.attr.value < RPMI_MKVER(1, 0)) {
+ dev_dbg(dev,
+ "service group version failed, expected 0x%x, found 0x%x\n",
+ RPMI_MKVER(1, 0), msg.attr.value);
+ return -EINVAL;
+ }
+
+ /* Get max message size */
+ rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE);
+ ret = rpmi_perf_send_message(mpxy_ctx, &msg);
+ if (ret) {
+ dev_dbg(dev, "Failed to get max message data size\n");
+ return ret;
+ }
+
+ if (msg.attr.value < sizeof(struct rpmi_perf_get_supported_level_rx) +
+ sizeof(struct rpmi_perf_supported_level)) {
+ dev_dbg(dev, "max message data size %u too small\n",
+ msg.attr.value);
+ return -EINVAL;
+ }
+ mpxy_ctx->max_msg_size = msg.attr.value;
+
+ return 0;
+}
+
+static void rpmi_perf_mbox_chan_release(void *data)
+{
+ mbox_free_channel((struct mbox_chan *)data);
+}
+
+static int rpmi_perf_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rpmi_perf *mpxy_perf;
+ struct rpmi_ctx *mpxy_ctx;
+ u32 num_domains = 0;
+ int ret;
+ u32 i;
+
+ mpxy_ctx = devm_kzalloc(dev, sizeof(*mpxy_ctx), GFP_KERNEL);
+ if (!mpxy_ctx)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(dev, &mpxy_ctx->lock);
+ if (ret)
+ return ret;
+
+ /* Setup mailbox client */
+ mpxy_ctx->client.dev = dev;
+ mpxy_ctx->client.rx_callback = NULL;
+ mpxy_ctx->client.tx_block = false;
+ mpxy_ctx->client.knows_txdone = true;
+ mpxy_ctx->client.tx_tout = 0;
+
+ /* Request mailbox channel */
+ mpxy_ctx->chan = mbox_request_channel(&mpxy_ctx->client, 0);
+ if (IS_ERR(mpxy_ctx->chan))
+ return PTR_ERR(mpxy_ctx->chan);
+
+ ret = devm_add_action_or_reset(dev, rpmi_perf_mbox_chan_release,
+ mpxy_ctx->chan);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to add rpmi mbox channel cleanup\n");
+
+ ret = rpmi_perf_attr_setup(dev, mpxy_ctx);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to verify RPMI attribute\n");
+
+ /* Get number of performance domain */
+ ret = rpmi_perf_get_num_domains(mpxy_ctx, &num_domains);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "invalid number of perf domains\n");
+
+ if (!num_domains)
+ return dev_err_probe(dev, -EINVAL, "No perf domains found!\n");
+
+ mpxy_perf = devm_kzalloc(dev, sizeof(*mpxy_perf), GFP_KERNEL);
+ if (!mpxy_perf)
+ return -ENOMEM;
+
+ mpxy_perf->domain = devm_kcalloc(dev, num_domains,
+ sizeof(struct rpmi_perf_domain),
+ GFP_KERNEL);
+ if (!mpxy_perf->domain)
+ return -ENOMEM;
+
+ mpxy_perf->num_domains = num_domains;
+ mpxy_perf->dev = dev;
+
+ /*
+ * Without a fast-channel region every domain goes through the mailbox.
+ * The platform may implement none; any other failure is reported.
+ */
+ ret = rpmi_perf_get_fast_channel_regions(mpxy_ctx, mpxy_perf);
+ if (ret && ret != -EOPNOTSUPP)
+ dev_warn(dev, "no fast channel region, using the mailbox: %d\n", ret);
+
+ for (i = 0; i < num_domains; i++) {
+ struct rpmi_perf_domain *domain = &mpxy_perf->domain[i];
+
+ domain->id = i;
+ domain->mpxy_ctx = mpxy_ctx;
+ domain->dev = dev;
+ domain->perf = mpxy_perf;
+
+ domain->enumerated = !rpmi_perf_enumerate(mpxy_perf, domain);
+ }
+
+ dev_set_drvdata(dev, mpxy_perf);
+
+ dev_info(dev, "%d MPXY performance domains registered\n", num_domains);
+
+ return 0;
+}
+
+static const struct of_device_id rpmi_perf_of_match[] = {
+ { .compatible = "riscv,rpmi-performance" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, rpmi_perf_of_match);
+
+static struct platform_driver rpmi_perf_platdrv = {
+ .driver = {
+ .name = "riscv-rpmi-performance",
+ .of_match_table = rpmi_perf_of_match,
+ },
+ .probe = rpmi_perf_probe,
+};
+
+module_platform_driver(rpmi_perf_platdrv);
+
+MODULE_AUTHOR("Joshua Yeong <joshua.yeong@starfivetech.com>");
+MODULE_DESCRIPTION("RISC-V RPMI performance service group core");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/firmware/riscv/riscv-rpmi-performance.h b/include/linux/firmware/riscv/riscv-rpmi-performance.h
new file mode 100644
index 000000000000..c65ea924b75d
--- /dev/null
+++ b/include/linux/firmware/riscv/riscv-rpmi-performance.h
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * RISC-V RPMI performance service group interface
+ *
+ * Copyright (C) 2026 Shanghai StarFive Technology Co., Ltd.
+ *
+ * The RPMI performance service group is a provider of performance domains.
+ * The front-ends that drive a domain reach the platform microcontroller
+ * through this interface, so that the mailbox channel and the domain
+ * enumeration have a single owner.
+ */
+
+#ifndef _LINUX_RISCV_RPMI_PERFORMANCE_H_
+#define _LINUX_RISCV_RPMI_PERFORMANCE_H_
+
+#include <linux/types.h>
+
+struct device;
+struct rpmi_perf;
+struct rpmi_perf_domain;
+
+/**
+ * struct rpmi_perf_level - one level of a performance domain
+ *
+ * @index: level index, which is what the RPMI services take and return
+ * @clock_freq: clock frequency of the level, in kHz
+ * @power_cost: power cost of the level, in uW
+ * @trans_latency_us: worst case time to switch to this level, in us
+ */
+struct rpmi_perf_level {
+ u32 index;
+ u32 clock_freq;
+ u32 power_cost;
+ u32 trans_latency_us;
+};
+
+#if IS_ENABLED(CONFIG_RISCV_RPMI_PERFORMANCE)
+
+struct rpmi_perf_domain *rpmi_perf_domain_by_id(struct rpmi_perf *perf, u32 id);
+u32 rpmi_perf_num_domains(struct rpmi_perf *perf);
+
+const char *rpmi_perf_domain_name(struct rpmi_perf_domain *pd);
+u32 rpmi_perf_domain_level_count(struct rpmi_perf_domain *pd);
+int rpmi_perf_domain_level_info(struct rpmi_perf_domain *pd, u32 idx,
+ struct rpmi_perf_level *level);
+bool rpmi_perf_domain_can_set_level(struct rpmi_perf_domain *pd);
+bool rpmi_perf_domain_has_fast_channel(struct rpmi_perf_domain *pd);
+u32 rpmi_perf_domain_trans_latency_us(struct rpmi_perf_domain *pd);
+
+int rpmi_perf_domain_get_level(struct rpmi_perf_domain *pd, u32 *level);
+int rpmi_perf_domain_set_level(struct rpmi_perf_domain *pd, u32 level);
+int rpmi_perf_domain_set_level_fast(struct rpmi_perf_domain *pd, u32 level);
+int rpmi_perf_domain_get_limit(struct rpmi_perf_domain *pd, u32 *min, u32 *max);
+int rpmi_perf_domain_set_limit(struct rpmi_perf_domain *pd, u32 min, u32 max);
+
+int rpmi_perf_domain_level_to_freq(struct rpmi_perf_domain *pd, u32 level, u32 *khz);
+int rpmi_perf_domain_freq_to_level(struct rpmi_perf_domain *pd, u32 khz, u32 *level);
+int rpmi_perf_domain_opps_add(struct rpmi_perf_domain *pd, struct device *dev);
+
+#else
+
+static inline struct rpmi_perf_domain *
+rpmi_perf_domain_by_id(struct rpmi_perf *perf, u32 id)
+{
+ return NULL;
+}
+
+static inline u32 rpmi_perf_num_domains(struct rpmi_perf *perf)
+{
+ return 0;
+}
+
+static inline const char *rpmi_perf_domain_name(struct rpmi_perf_domain *pd)
+{
+ return NULL;
+}
+
+static inline u32 rpmi_perf_domain_level_count(struct rpmi_perf_domain *pd)
+{
+ return 0;
+}
+
+static inline int rpmi_perf_domain_level_info(struct rpmi_perf_domain *pd, u32 idx,
+ struct rpmi_perf_level *level)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline bool rpmi_perf_domain_can_set_level(struct rpmi_perf_domain *pd)
+{
+ return false;
+}
+
+static inline bool rpmi_perf_domain_has_fast_channel(struct rpmi_perf_domain *pd)
+{
+ return false;
+}
+
+static inline u32 rpmi_perf_domain_trans_latency_us(struct rpmi_perf_domain *pd)
+{
+ return 0;
+}
+
+static inline int rpmi_perf_domain_get_level(struct rpmi_perf_domain *pd, u32 *level)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int rpmi_perf_domain_set_level(struct rpmi_perf_domain *pd, u32 level)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int rpmi_perf_domain_set_level_fast(struct rpmi_perf_domain *pd, u32 level)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int rpmi_perf_domain_get_limit(struct rpmi_perf_domain *pd, u32 *min, u32 *max)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int rpmi_perf_domain_set_limit(struct rpmi_perf_domain *pd, u32 min, u32 max)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int rpmi_perf_domain_level_to_freq(struct rpmi_perf_domain *pd, u32 level,
+ u32 *khz)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int rpmi_perf_domain_freq_to_level(struct rpmi_perf_domain *pd, u32 khz,
+ u32 *level)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int rpmi_perf_domain_opps_add(struct rpmi_perf_domain *pd, struct device *dev)
+{
+ return -EOPNOTSUPP;
+}
+
+#endif /* CONFIG_RISCV_RPMI_PERFORMANCE */
+
+#endif /* _LINUX_RISCV_RPMI_PERFORMANCE_H_ */
diff --git a/include/linux/mailbox/riscv-rpmi-message.h b/include/linux/mailbox/riscv-rpmi-message.h
index d5362b5821f9..fbef6e721435 100644
--- a/include/linux/mailbox/riscv-rpmi-message.h
+++ b/include/linux/mailbox/riscv-rpmi-message.h
@@ -94,6 +94,7 @@ static inline int rpmi_to_linux_error(int rpmi_error)
#define RPMI_SRVGRP_SYSTEM_MSI 0x00002
#define RPMI_SRVGRP_CLOCK 0x00008
#define RPMI_SRVGRP_DEVICE_POWER 0x00009
+#define RPMI_SRVGRP_PERFORMANCE 0x0000A
/* RPMI clock service IDs */
enum rpmi_clock_service_id {
@@ -130,6 +131,21 @@ enum rpmi_device_power_service_id {
RPMI_DP_SRV_ID_MAX_COUNT,
};
+/* RPMI performances service IDs */
+enum rpmi_perf_service_id {
+ RPMI_PERF_SRV_ENABLE_NOTIFICATION = 0x01,
+ RPMI_PERF_SRV_GET_NUM_DOMAINS = 0x02,
+ RPMI_PERF_SRV_GET_ATTRIBUTES = 0x03,
+ RPMI_PERF_SRV_GET_SUPPORTED_LEVELS = 0x04,
+ RPMI_PERF_SRV_GET_LEVEL = 0x05,
+ RPMI_PERF_SRV_SET_LEVEL = 0x06,
+ RPMI_PERF_SRV_GET_LIMIT = 0x07,
+ RPMI_PERF_SRV_SET_LIMIT = 0x08,
+ RPMI_PERF_SRV_GET_FAST_CHANNEL_REGION = 0x09,
+ RPMI_PERF_SRV_GET_FAST_CHANNEL_ATTRS = 0x0a,
+ RPMI_PERF_SRV_ID_MAX_COUNT,
+};
+
/* RPMI Linux mailbox attribute IDs */
enum rpmi_mbox_attribute_id {
RPMI_MBOX_ATTR_SPEC_VERSION,
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v2 5/7] cpufreq: Add RISC-V RPMI cpufreq driver
2026-10-08 9:10 [PATCH v2 0/7] Add RISC-V RPMI performance service support Joshua Yeong
` (3 preceding siblings ...)
2026-10-08 9:10 ` [PATCH v2 4/7] firmware: riscv: Add RPMI performance service Joshua Yeong
@ 2026-10-08 9:10 ` Joshua Yeong
2026-10-08 9:10 ` [PATCH v2 6/7] pmdomain: riscv: Add RPMI performance domains as power domains Joshua Yeong
2026-10-08 9:10 ` [PATCH v2 7/7] MAINTAINERS: Add RISC-V RPMI performance driver Joshua Yeong
6 siblings, 0 replies; 9+ messages in thread
From: Joshua Yeong @ 2026-10-08 9:10 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, pjw, palmer, aou, rafael, viresh.kumar,
ulfh, rahul, anup, lftan.linux
Cc: alex, joshua.yeong, linux-riscv, devicetree, linux-pm, linux-kernel
Add a cpufreq driver for the RISC-V RPMI performance domains that CPUs
name through "performance-domains". The CPUs that name the same domain
share a policy, the levels the domain advertises become its frequency
table, and the level is set through the domain's fast channel when it
has one, so that the governor can switch frequency from the scheduler.
An energy model is registered from the power cost of each level.
The driver is a front-end over the RPMI performance service group core,
which creates the device it binds to when a CPU names the provider.
Signed-off-by: Joshua Yeong <joshua.yeong@starfivetech.com>
---
drivers/cpufreq/Kconfig | 16 +
drivers/cpufreq/Makefile | 4 +
drivers/cpufreq/riscv-rpmi-cpufreq.c | 294 ++++++++++++++++++
.../firmware/riscv/riscv-rpmi-performance.c | 55 ++++
4 files changed, 369 insertions(+)
create mode 100644 drivers/cpufreq/riscv-rpmi-cpufreq.c
diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig
index db83f3365698..f012b8268b5e 100644
--- a/drivers/cpufreq/Kconfig
+++ b/drivers/cpufreq/Kconfig
@@ -364,6 +364,22 @@ config ACPI_CPPC_CPUFREQ_FIE
If in doubt, say N.
+config RISCV_RPMI_CPUFREQ
+ tristate "RISC-V RPMI Based CPUFreq driver"
+ depends on RISCV_RPMI_PERFORMANCE
+ default RISCV
+ select PM_OPP
+ help
+ This adds the CPUfreq driver support for RISC-V platforms whose CPU
+ performance is managed through the RPMI performance service group.
+ CPUs reference their domain through the "performance-domains"
+ property. The performance domains it drives are enumerated by the
+ RPMI performance service group core, which is what owns the mailbox
+ channel.
+
+ To compile this driver as a module, choose M here: the
+ module will be called riscv-rpmi-cpufreq.
+
endif # CPU_FREQ
endmenu
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 6c7a39b7f8d2..265a6bccef4b 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -96,6 +96,10 @@ obj-$(CONFIG_CPU_FREQ_PMAC64) += pmac64-cpufreq.o
obj-$(CONFIG_PPC_PASEMI_CPUFREQ) += pasemi-cpufreq.o
obj-$(CONFIG_POWERNV_CPUFREQ) += powernv-cpufreq.o
+##################################################################################
+# RISC-V platform drivers
+obj-$(CONFIG_RISCV_RPMI_CPUFREQ) += riscv-rpmi-cpufreq.o
+
##################################################################################
# Other platform drivers
obj-$(CONFIG_BMIPS_CPUFREQ) += bmips-cpufreq.o
diff --git a/drivers/cpufreq/riscv-rpmi-cpufreq.c b/drivers/cpufreq/riscv-rpmi-cpufreq.c
new file mode 100644
index 000000000000..1a5d0248f879
--- /dev/null
+++ b/drivers/cpufreq/riscv-rpmi-cpufreq.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RISC-V RPMI Based CPUFreq Driver
+ *
+ * Copyright (C) 2026 Shanghai StarFive Technology Co., Ltd.
+ *
+ * Drives the performance domains that CPUs share. 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.
+ */
+
+#define pr_fmt(fmt) "riscv-rpmi-cpufreq: " fmt
+
+#include <linux/cpufreq.h>
+#include <linux/energy_model.h>
+#include <linux/firmware/riscv/riscv-rpmi-performance.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+
+struct rpmi_perf_cpufreq_data {
+ int nr_opp;
+ struct device *cpu_dev;
+ struct rpmi_perf_domain *domain;
+};
+
+static int rpmi_perf_set_target_index(struct cpufreq_policy *policy, unsigned int index)
+{
+ struct rpmi_perf_cpufreq_data *data = policy->driver_data;
+ u32 level;
+ int ret;
+
+ /*
+ * cpufreq indexes its own frequency table, which is not the same thing
+ * as an RPMI level index. Go through the frequency so that the two
+ * only have to agree on what they mean, not on how they are numbered.
+ */
+ ret = rpmi_perf_domain_freq_to_level(data->domain,
+ policy->freq_table[index].frequency,
+ &level);
+ if (ret)
+ return ret;
+
+ if (rpmi_perf_domain_has_fast_channel(data->domain))
+ return rpmi_perf_domain_set_level_fast(data->domain, level);
+
+ return rpmi_perf_domain_set_level(data->domain, level);
+}
+
+static unsigned int rpmi_perf_fast_switch(struct cpufreq_policy *policy,
+ unsigned int target_freq)
+{
+ struct rpmi_perf_cpufreq_data *data = policy->driver_data;
+ u32 level;
+
+ if (rpmi_perf_domain_freq_to_level(data->domain, target_freq, &level))
+ return 0;
+
+ if (rpmi_perf_domain_set_level_fast(data->domain, level))
+ return 0;
+
+ return target_freq;
+}
+
+static unsigned int rpmi_perf_get_rate(unsigned int cpu)
+{
+ struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
+ struct rpmi_perf_cpufreq_data *data;
+ u32 cpufreq, level;
+
+ if (!policy)
+ return 0;
+
+ data = policy->driver_data;
+
+ if (rpmi_perf_domain_get_level(data->domain, &level))
+ return 0;
+
+ if (rpmi_perf_domain_level_to_freq(data->domain, level, &cpufreq))
+ return 0;
+
+ return cpufreq;
+}
+
+static int rpmi_perf_init(struct cpufreq_policy *policy)
+{
+ struct cpufreq_frequency_table *freq_table;
+ struct rpmi_perf_cpufreq_data *data;
+ struct rpmi_perf_domain *domain;
+ struct platform_device *pdev = cpufreq_get_driver_data();
+ struct rpmi_perf **mpxy_perf = dev_get_platdata(&pdev->dev);
+ struct of_phandle_args args;
+ int ret, nr_opp;
+ struct device *cpu_dev;
+
+ cpu_dev = get_cpu_device(policy->cpu);
+ if (!cpu_dev) {
+ pr_err("failed to get cpu%d device\n", policy->cpu);
+ return -ENODEV;
+ }
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ret = of_perf_domain_get_sharing_cpumask(policy->cpu,
+ "performance-domains",
+ "#performance-domain-cells",
+ policy->cpus, &args);
+ if (ret) {
+ dev_err(cpu_dev, "%s: failed to get performance domain info: %d\n",
+ __func__, ret);
+ goto out_free_priv;
+ }
+
+ /* A domain ID only means something to the provider the CPU names. */
+ if (args.np != dev_of_node(pdev->dev.parent)) {
+ dev_err(cpu_dev, "performance domain of %pOF, not of %pOF\n",
+ args.np, dev_of_node(pdev->dev.parent));
+ of_node_put(args.np);
+ ret = -ENODEV;
+ goto out_free_priv;
+ }
+
+ domain = rpmi_perf_domain_by_id(*mpxy_perf, args.args[0]);
+ of_node_put(args.np);
+ if (!domain) {
+ dev_err(cpu_dev, "performance domain %u is not usable\n",
+ args.args[0]);
+ ret = -EINVAL;
+ goto out_free_priv;
+ }
+
+ ret = rpmi_perf_domain_opps_add(domain, cpu_dev);
+ if (ret) {
+ dev_warn(cpu_dev, "failed to add opps to the device\n");
+ goto out_free_priv;
+ }
+
+ nr_opp = dev_pm_opp_get_opp_count(cpu_dev);
+ if (nr_opp <= 0) {
+ dev_err(cpu_dev, "performance domain has no operating points\n");
+ ret = -ENODEV;
+ goto out_free_opp;
+ }
+
+ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
+ goto out_free_opp;
+ }
+
+ data->cpu_dev = cpu_dev;
+ data->nr_opp = nr_opp;
+ data->domain = domain;
+
+ /* Allow DVFS request for any domain from any CPU */
+ policy->dvfs_possible_from_any_cpu = true;
+ policy->driver_data = data;
+ policy->freq_table = freq_table;
+
+ policy->cpuinfo.transition_latency =
+ rpmi_perf_domain_trans_latency_us(domain) * 1000;
+ policy->fast_switch_possible = rpmi_perf_domain_has_fast_channel(domain);
+
+ return 0;
+
+out_free_opp:
+ dev_pm_opp_remove_all_dynamic(cpu_dev);
+
+out_free_priv:
+ kfree(data);
+
+ return ret;
+}
+
+static void rpmi_perf_exit(struct cpufreq_policy *policy)
+{
+ struct rpmi_perf_cpufreq_data *data = policy->driver_data;
+
+ dev_pm_opp_free_cpufreq_table(data->cpu_dev, &policy->freq_table);
+ dev_pm_opp_remove_all_dynamic(data->cpu_dev);
+ kfree(data);
+}
+
+static int __maybe_unused
+rpmi_perf_get_cpu_power(struct device *cpu_dev, unsigned long *uW,
+ unsigned long *kHz)
+{
+ struct rpmi_perf_cpufreq_data *data;
+ struct rpmi_perf_level level;
+ struct cpufreq_policy *policy;
+ u32 idx, count;
+
+ policy = cpufreq_cpu_get_raw(cpu_dev->id);
+ if (!policy)
+ return -EINVAL;
+
+ data = policy->driver_data;
+ count = rpmi_perf_domain_level_count(data->domain);
+
+ /* The levels are in ascending order of frequency. */
+ for (idx = 0; idx < count; idx++) {
+ if (rpmi_perf_domain_level_info(data->domain, idx, &level))
+ return -EINVAL;
+
+ if (level.clock_freq < *kHz)
+ continue;
+
+ *uW = level.power_cost;
+ *kHz = level.clock_freq;
+ return 0;
+ }
+
+ /* No level at or above *kHz: there is no state to describe. */
+ return -EINVAL;
+}
+
+static void rpmi_perf_register_em(struct cpufreq_policy *policy)
+{
+ struct em_data_callback em_cb = EM_DATA_CB(rpmi_perf_get_cpu_power);
+ struct rpmi_perf_cpufreq_data *data = policy->driver_data;
+
+ em_dev_register_perf_domain(get_cpu_device(policy->cpu), data->nr_opp,
+ &em_cb, policy->cpus, true);
+}
+
+static struct cpufreq_driver rpmi_perf_cpufreq_driver = {
+ .name = "rpmi-cpufreq",
+ .flags = CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
+ CPUFREQ_NEED_INITIAL_FREQ_CHECK |
+ CPUFREQ_IS_COOLING_DEV,
+ .verify = cpufreq_generic_frequency_table_verify,
+ .target_index = rpmi_perf_set_target_index,
+ .fast_switch = rpmi_perf_fast_switch,
+ .get = rpmi_perf_get_rate,
+ .init = rpmi_perf_init,
+ .exit = rpmi_perf_exit,
+ .register_em = rpmi_perf_register_em,
+};
+
+static int rpmi_cpufreq_probe(struct platform_device *pdev)
+{
+ struct rpmi_perf **mpxy_perf = dev_get_platdata(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ if (!mpxy_perf || !*mpxy_perf)
+ return -EINVAL;
+
+ /*
+ * There is one cpufreq driver for the whole system, so only one
+ * provider can drive the CPUs. Refuse another one rather than point
+ * the registered driver at its domains.
+ */
+ if (rpmi_perf_cpufreq_driver.driver_data)
+ return dev_err_probe(dev, -EBUSY,
+ "CPUs are already driven by another provider\n");
+
+ rpmi_perf_cpufreq_driver.driver_data = pdev;
+
+ ret = cpufreq_register_driver(&rpmi_perf_cpufreq_driver);
+ if (ret) {
+ rpmi_perf_cpufreq_driver.driver_data = NULL;
+ return dev_err_probe(dev, ret, "registering cpufreq failed\n");
+ }
+
+ dev_info(dev, "%d MPXY cpufreq domains registered\n",
+ rpmi_perf_num_domains(*mpxy_perf));
+
+ return 0;
+}
+
+static void rpmi_cpufreq_remove(struct platform_device *pdev)
+{
+ cpufreq_unregister_driver(&rpmi_perf_cpufreq_driver);
+ rpmi_perf_cpufreq_driver.driver_data = NULL;
+}
+
+static struct platform_driver rpmi_cpufreq_platdrv = {
+ .driver = {
+ .name = "riscv-rpmi-cpufreq",
+ },
+ .probe = rpmi_cpufreq_probe,
+ .remove = rpmi_cpufreq_remove,
+};
+
+module_platform_driver(rpmi_cpufreq_platdrv);
+
+MODULE_ALIAS("platform:riscv-rpmi-cpufreq");
+MODULE_AUTHOR("Joshua Yeong <joshua.yeong@starfivetech.com>");
+MODULE_DESCRIPTION("RISC-V RPMI performance cpufreq driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/riscv/riscv-rpmi-performance.c b/drivers/firmware/riscv/riscv-rpmi-performance.c
index d44802f07ab0..f91f2d861dc0 100644
--- a/drivers/firmware/riscv/riscv-rpmi-performance.c
+++ b/drivers/firmware/riscv/riscv-rpmi-performance.c
@@ -19,6 +19,7 @@
#include <linux/mailbox/riscv-rpmi-message.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/slab.h>
@@ -995,6 +996,56 @@ static void rpmi_perf_mbox_chan_release(void *data)
mbox_free_channel((struct mbox_chan *)data);
}
+/*
+ * cpufreq has no device tree node of its own: a CPU names its domain through
+ * "performance-domains" on the CPU node. Create the device it binds to, but
+ * only when some CPU actually points back here.
+ */
+static bool rpmi_perf_cpus_present(struct device *dev)
+{
+ struct device_node *cpu_np;
+ struct of_phandle_args args;
+ int ret;
+
+ for_each_of_cpu_node(cpu_np) {
+ ret = of_parse_phandle_with_args(cpu_np, "performance-domains",
+ "#performance-domain-cells", 0,
+ &args);
+ if (ret)
+ continue;
+
+ if (args.np == dev_of_node(dev)) {
+ of_node_put(args.np);
+ of_node_put(cpu_np);
+ return true;
+ }
+ of_node_put(args.np);
+ }
+
+ return false;
+}
+
+static void rpmi_perf_frontend_unregister(void *data)
+{
+ platform_device_unregister((struct platform_device *)data);
+}
+
+static int rpmi_perf_cpufreq_register(struct device *dev, struct rpmi_perf *perf)
+{
+ struct platform_device *pdev;
+
+ if (!IS_ENABLED(CONFIG_RISCV_RPMI_CPUFREQ) || !rpmi_perf_cpus_present(dev))
+ return 0;
+
+ pdev = platform_device_register_data(dev, "riscv-rpmi-cpufreq",
+ 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;
@@ -1078,6 +1129,10 @@ static int rpmi_perf_probe(struct platform_device *pdev)
dev_set_drvdata(dev, mpxy_perf);
+ ret = rpmi_perf_cpufreq_register(dev, mpxy_perf);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register cpufreq device\n");
+
dev_info(dev, "%d MPXY performance domains registered\n", num_domains);
return 0;
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v2 6/7] pmdomain: riscv: Add RPMI performance domains as power domains
2026-10-08 9:10 [PATCH v2 0/7] Add RISC-V RPMI performance service support Joshua Yeong
` (4 preceding siblings ...)
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
2026-10-08 9:10 ` [PATCH v2 7/7] MAINTAINERS: Add RISC-V RPMI performance driver Joshua Yeong
6 siblings, 0 replies; 9+ messages in thread
From: Joshua Yeong @ 2026-10-08 9:10 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, pjw, palmer, aou, rafael, viresh.kumar,
ulfh, rahul, anup, lftan.linux
Cc: alex, joshua.yeong, linux-riscv, devicetree, linux-pm, linux-kernel
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
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v2 7/7] MAINTAINERS: Add RISC-V RPMI performance driver
2026-10-08 9:10 [PATCH v2 0/7] Add RISC-V RPMI performance service support Joshua Yeong
` (5 preceding siblings ...)
2026-10-08 9:10 ` [PATCH v2 6/7] pmdomain: riscv: Add RPMI performance domains as power domains Joshua Yeong
@ 2026-10-08 9:10 ` Joshua Yeong
6 siblings, 0 replies; 9+ messages in thread
From: Joshua Yeong @ 2026-10-08 9:10 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, pjw, palmer, aou, rafael, viresh.kumar,
ulfh, rahul, anup, lftan.linux
Cc: alex, joshua.yeong, linux-riscv, devicetree, linux-pm, linux-kernel
Add the RISC-V RPMI performance service group to the RPMI device power
entry, renamed to cover both service groups, so that its bindings, the
service group core and header, and the cpufreq and power domain drivers
built on it share one maintainer.
Signed-off-by: Joshua Yeong <joshua.yeong@starfivetech.com>
---
MAINTAINERS | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index bdee5c7ee83d..b6bb3c6ea8aa 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23600,13 +23600,18 @@ F: drivers/irqchip/irq-riscv-rpmi-sysmsi.c
F: drivers/mailbox/riscv-sbi-mpxy-mbox.c
F: include/linux/mailbox/riscv-rpmi-message.h
-RISC-V RPMI DEVICE POWER DRIVER
+RISC-V RPMI DEVICE POWER AND PERFORMANCE DRIVERS
M: Joshua Yeong <joshua.yeong@starfivetech.com>
L: linux-riscv@lists.infradead.org
S: Maintained
+F: Documentation/devicetree/bindings/dvfs/riscv,rpmi-mpxy-performance.yaml
+F: Documentation/devicetree/bindings/dvfs/riscv,rpmi-performance.yaml
F: Documentation/devicetree/bindings/power/riscv,rpmi-device-power.yaml
F: Documentation/devicetree/bindings/power/riscv,rpmi-mpxy-device-power.yaml
+F: drivers/cpufreq/riscv-rpmi-cpufreq.c
+F: drivers/firmware/riscv/
F: drivers/pmdomain/riscv/
+F: include/linux/firmware/riscv/riscv-rpmi-performance.h
RISC-V SPACEMIT SoC Support
M: Yixun Lan <dlan@kernel.org>
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread