[PATCH v4 1/3] cpuidle: riscv-sbi: Split PM domain init out of the cpuidle driver
Nick Hu
nick.hu at sifive.com
Thu Jan 8 00:58:24 PST 2026
Move the PM domain initialization logic from the RISC-V SBI CPU idle
driver into a separate driver. This decouples the power domain setup
from cpuidle and allows the generic PM domain framework to be used
independently. This change also enables external power domain drivers to
operate with the RISC-V SBI CPU idle driver
Signed-off-by: Nick Hu <nick.hu at sifive.com>
---
MAINTAINERS | 2 +
drivers/cpuidle/Kconfig.riscv | 13 ++-
drivers/cpuidle/Makefile | 1 +
drivers/cpuidle/cpuidle-riscv-sbi-domain.c | 176 ++++++++++++++++++++++++++++
drivers/cpuidle/cpuidle-riscv-sbi.c | 178 ++---------------------------
drivers/cpuidle/cpuidle-riscv-sbi.h | 29 +++++
6 files changed, 228 insertions(+), 171 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index a0dd762f5648..b52f11602271 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6632,7 +6632,9 @@ M: Anup Patel <anup at brainfault.org>
L: linux-pm at vger.kernel.org
L: linux-riscv at lists.infradead.org
S: Maintained
+F: drivers/cpuidle/cpuidle-riscv-sbi-domain.c
F: drivers/cpuidle/cpuidle-riscv-sbi.c
+F: drivers/cpuidle/cpuidle-riscv-sbi.h
CPUMASK API [RUST]
M: Viresh Kumar <viresh.kumar at linaro.org>
diff --git a/drivers/cpuidle/Kconfig.riscv b/drivers/cpuidle/Kconfig.riscv
index 78518c26af74..b813018ce401 100644
--- a/drivers/cpuidle/Kconfig.riscv
+++ b/drivers/cpuidle/Kconfig.riscv
@@ -8,8 +8,17 @@ config RISCV_SBI_CPUIDLE
depends on RISCV_SBI
select DT_IDLE_STATES
select CPU_IDLE_MULTIPLE_DRIVERS
- select DT_IDLE_GENPD if PM_GENERIC_DOMAINS_OF
help
Select this option to enable RISC-V SBI firmware based CPU idle
- driver for RISC-V systems. This drivers also supports hierarchical
+ driver for RISC-V systems.
+
+config RISCV_SBI_CPUIDLE_DOMAIN
+ bool "RISC-V SBI CPU idle Domain"
+ depends on RISCV_SBI_CPUIDLE
+ depends on PM_GENERIC_DOMAINS_OF
+ select DT_IDLE_GENPD
+ default y
+ help
+ Select this option to enable RISC-V SBI firmware based CPU idle
+ driver to use PM domains, which is needed to support the hierarchical
DT based layout of the idle state.
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index 1de9e92c5b0f..82595849b75d 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -42,3 +42,4 @@ obj-$(CONFIG_POWERNV_CPUIDLE) += cpuidle-powernv.o
###############################################################################
# RISC-V drivers
obj-$(CONFIG_RISCV_SBI_CPUIDLE) += cpuidle-riscv-sbi.o
+obj-$(CONFIG_RISCV_SBI_CPUIDLE_DOMAIN) += cpuidle-riscv-sbi-domain.o
diff --git a/drivers/cpuidle/cpuidle-riscv-sbi-domain.c b/drivers/cpuidle/cpuidle-riscv-sbi-domain.c
new file mode 100644
index 000000000000..24cb70700c22
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-riscv-sbi-domain.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PM domains for CPUs via genpd - managed by cpuidle-riscv-sbi.
+ *
+ * Copyright (c) 2021 Western Digital Corporation or its affiliates.
+ * Copyright (c) 2022 Ventana Micro Systems Inc.
+ */
+
+#define pr_fmt(fmt) "cpuidle-riscv-sbi-domain: " fmt
+
+#include <linux/device.h>
+#include <linux/pm_domain.h>
+
+#include "cpuidle-riscv-sbi.h"
+#include "dt_idle_genpd.h"
+
+struct sbi_pd_provider {
+ struct list_head link;
+ struct device_node *node;
+};
+
+static LIST_HEAD(sbi_pd_providers);
+
+static int sbi_cpuidle_pd_power_off(struct generic_pm_domain *pd)
+{
+ struct genpd_power_state *state = &pd->states[pd->state_idx];
+ u32 *pd_state;
+
+ if (!state->data)
+ return 0;
+
+ /* OSI mode is enabled, set the corresponding domain state. */
+ pd_state = state->data;
+ sbi_set_domain_state(*pd_state);
+
+ return 0;
+}
+
+static int sbi_pd_init(struct device_node *np, bool use_osi)
+{
+ struct generic_pm_domain *pd;
+ struct sbi_pd_provider *pd_provider;
+ struct dev_power_governor *pd_gov;
+ int ret = -ENOMEM;
+
+ pd = dt_idle_pd_alloc(np, sbi_dt_parse_state_node);
+ if (!pd)
+ goto out;
+
+ pd_provider = kzalloc(sizeof(*pd_provider), GFP_KERNEL);
+ if (!pd_provider)
+ goto free_pd;
+
+ pd->flags |= GENPD_FLAG_IRQ_SAFE | GENPD_FLAG_CPU_DOMAIN;
+
+ /* Allow power off when OSI is available. */
+ if (use_osi)
+ pd->power_off = sbi_cpuidle_pd_power_off;
+ else
+ pd->flags |= GENPD_FLAG_ALWAYS_ON;
+
+ /* Use governor for CPU PM domains if it has some states to manage. */
+ pd_gov = pd->states ? &pm_domain_cpu_gov : NULL;
+
+ ret = pm_genpd_init(pd, pd_gov, false);
+ if (ret)
+ goto free_pd_prov;
+
+ ret = of_genpd_add_provider_simple(np, pd);
+ if (ret)
+ goto remove_pd;
+
+ pd_provider->node = of_node_get(np);
+ list_add(&pd_provider->link, &sbi_pd_providers);
+
+ pr_debug("init PM domain %s\n", pd->name);
+ return 0;
+
+remove_pd:
+ pm_genpd_remove(pd);
+free_pd_prov:
+ kfree(pd_provider);
+free_pd:
+ dt_idle_pd_free(pd);
+out:
+ pr_err("failed to init PM domain ret=%d %pOF\n", ret, np);
+ return ret;
+}
+
+static void sbi_pd_remove(void)
+{
+ struct sbi_pd_provider *pd_provider, *it;
+ struct generic_pm_domain *genpd;
+
+ list_for_each_entry_safe(pd_provider, it, &sbi_pd_providers, link) {
+ of_genpd_del_provider(pd_provider->node);
+
+ genpd = of_genpd_remove_last(pd_provider->node);
+ if (!IS_ERR(genpd))
+ kfree(genpd);
+
+ of_node_put(pd_provider->node);
+ list_del(&pd_provider->link);
+ kfree(pd_provider);
+ }
+}
+
+static int sbi_genpd_probe(struct device_node *np, bool use_osi)
+{
+ int ret = 0, pd_count = 0;
+
+ if (!np)
+ return -ENODEV;
+
+ /*
+ * Parse child nodes for the "#power-domain-cells" property and
+ * initialize a genpd/genpd-of-provider pair when it's found.
+ */
+ for_each_child_of_node_scoped(np, node) {
+ if (!of_property_present(node, "#power-domain-cells"))
+ continue;
+
+ ret = sbi_pd_init(node, use_osi);
+ if (ret)
+ goto remove_pd;
+
+ pd_count++;
+ }
+
+ /* Bail out if not using the hierarchical CPU topology. */
+ if (!pd_count)
+ goto no_pd;
+
+ /* Link genpd masters/subdomains to model the CPU topology. */
+ ret = dt_idle_pd_init_topology(np);
+ if (ret)
+ goto remove_pd;
+
+ return 0;
+
+remove_pd:
+ sbi_pd_remove();
+ pr_err("failed to create CPU PM domains ret=%d\n", ret);
+no_pd:
+ return ret;
+}
+
+static int __init riscv_sbi_idle_init_domains(void)
+{
+ bool use_osi = true;
+ int cpu;
+
+ /* Detect OSI support based on CPU DT nodes */
+ for_each_possible_cpu(cpu) {
+ struct device_node *np __free(device_node) = of_cpu_device_node_get(cpu);
+ if (np &&
+ of_property_present(np, "power-domains") &&
+ of_property_present(np, "power-domain-names")) {
+ continue;
+ } else {
+ use_osi = false;
+ break;
+ }
+ }
+
+ sbi_set_osi_mode(use_osi);
+
+ /* Populate generic power domains from DT nodes */
+ struct device_node *pds_node __free(device_node) =
+ of_find_node_by_path("/cpus/power-domains");
+ if (!pds_node)
+ return 0;
+
+ return sbi_genpd_probe(pds_node, use_osi);
+}
+core_initcall(riscv_sbi_idle_init_domains);
diff --git a/drivers/cpuidle/cpuidle-riscv-sbi.c b/drivers/cpuidle/cpuidle-riscv-sbi.c
index 19be6475d356..95a837a1ba31 100644
--- a/drivers/cpuidle/cpuidle-riscv-sbi.c
+++ b/drivers/cpuidle/cpuidle-riscv-sbi.c
@@ -28,6 +28,7 @@
#include <asm/suspend.h>
#include "cpuidle.h"
+#include "cpuidle-riscv-sbi.h"
#include "dt_idle_states.h"
#include "dt_idle_genpd.h"
@@ -46,7 +47,12 @@ static DEFINE_PER_CPU(struct sbi_domain_state, domain_state);
static bool sbi_cpuidle_use_osi;
static bool sbi_cpuidle_use_cpuhp;
-static inline void sbi_set_domain_state(u32 state)
+void sbi_set_osi_mode(bool use_osi)
+{
+ sbi_cpuidle_use_osi = use_osi;
+}
+
+void sbi_set_domain_state(u32 state)
{
struct sbi_domain_state *data = this_cpu_ptr(&domain_state);
@@ -188,7 +194,7 @@ static const struct of_device_id sbi_cpuidle_state_match[] = {
{ },
};
-static int sbi_dt_parse_state_node(struct device_node *np, u32 *state)
+int sbi_dt_parse_state_node(struct device_node *np, u32 *state)
{
int err = of_property_read_u32(np, "riscv,sbi-suspend-param", state);
@@ -345,177 +351,11 @@ static int sbi_cpuidle_init_cpu(struct device *dev, int cpu)
return ret;
}
-#ifdef CONFIG_DT_IDLE_GENPD
-
-static int sbi_cpuidle_pd_power_off(struct generic_pm_domain *pd)
-{
- struct genpd_power_state *state = &pd->states[pd->state_idx];
- u32 *pd_state;
-
- if (!state->data)
- return 0;
-
- /* OSI mode is enabled, set the corresponding domain state. */
- pd_state = state->data;
- sbi_set_domain_state(*pd_state);
-
- return 0;
-}
-
-struct sbi_pd_provider {
- struct list_head link;
- struct device_node *node;
-};
-
-static LIST_HEAD(sbi_pd_providers);
-
-static int sbi_pd_init(struct device_node *np)
-{
- struct generic_pm_domain *pd;
- struct sbi_pd_provider *pd_provider;
- struct dev_power_governor *pd_gov;
- int ret = -ENOMEM;
-
- pd = dt_idle_pd_alloc(np, sbi_dt_parse_state_node);
- if (!pd)
- goto out;
-
- pd_provider = kzalloc(sizeof(*pd_provider), GFP_KERNEL);
- if (!pd_provider)
- goto free_pd;
-
- pd->flags |= GENPD_FLAG_IRQ_SAFE | GENPD_FLAG_CPU_DOMAIN;
-
- /* Allow power off when OSI is available. */
- if (sbi_cpuidle_use_osi)
- pd->power_off = sbi_cpuidle_pd_power_off;
- else
- pd->flags |= GENPD_FLAG_ALWAYS_ON;
-
- /* Use governor for CPU PM domains if it has some states to manage. */
- pd_gov = pd->states ? &pm_domain_cpu_gov : NULL;
-
- ret = pm_genpd_init(pd, pd_gov, false);
- if (ret)
- goto free_pd_prov;
-
- ret = of_genpd_add_provider_simple(np, pd);
- if (ret)
- goto remove_pd;
-
- pd_provider->node = of_node_get(np);
- list_add(&pd_provider->link, &sbi_pd_providers);
-
- pr_debug("init PM domain %s\n", pd->name);
- return 0;
-
-remove_pd:
- pm_genpd_remove(pd);
-free_pd_prov:
- kfree(pd_provider);
-free_pd:
- dt_idle_pd_free(pd);
-out:
- pr_err("failed to init PM domain ret=%d %pOF\n", ret, np);
- return ret;
-}
-
-static void sbi_pd_remove(void)
-{
- struct sbi_pd_provider *pd_provider, *it;
- struct generic_pm_domain *genpd;
-
- list_for_each_entry_safe(pd_provider, it, &sbi_pd_providers, link) {
- of_genpd_del_provider(pd_provider->node);
-
- genpd = of_genpd_remove_last(pd_provider->node);
- if (!IS_ERR(genpd))
- kfree(genpd);
-
- of_node_put(pd_provider->node);
- list_del(&pd_provider->link);
- kfree(pd_provider);
- }
-}
-
-static int sbi_genpd_probe(struct device_node *np)
-{
- int ret = 0, pd_count = 0;
-
- if (!np)
- return -ENODEV;
-
- /*
- * Parse child nodes for the "#power-domain-cells" property and
- * initialize a genpd/genpd-of-provider pair when it's found.
- */
- for_each_child_of_node_scoped(np, node) {
- if (!of_property_present(node, "#power-domain-cells"))
- continue;
-
- ret = sbi_pd_init(node);
- if (ret)
- goto remove_pd;
-
- pd_count++;
- }
-
- /* Bail out if not using the hierarchical CPU topology. */
- if (!pd_count)
- goto no_pd;
-
- /* Link genpd masters/subdomains to model the CPU topology. */
- ret = dt_idle_pd_init_topology(np);
- if (ret)
- goto remove_pd;
-
- return 0;
-
-remove_pd:
- sbi_pd_remove();
- pr_err("failed to create CPU PM domains ret=%d\n", ret);
-no_pd:
- return ret;
-}
-
-#else
-
-static inline int sbi_genpd_probe(struct device_node *np)
-{
- return 0;
-}
-
-#endif
-
static int sbi_cpuidle_probe(struct platform_device *pdev)
{
int cpu, ret;
struct cpuidle_driver *drv;
struct cpuidle_device *dev;
- struct device_node *pds_node;
-
- /* Detect OSI support based on CPU DT nodes */
- sbi_cpuidle_use_osi = true;
- for_each_possible_cpu(cpu) {
- struct device_node *np __free(device_node) = of_cpu_device_node_get(cpu);
- if (np &&
- of_property_present(np, "power-domains") &&
- of_property_present(np, "power-domain-names")) {
- continue;
- } else {
- sbi_cpuidle_use_osi = false;
- break;
- }
- }
-
- /* Populate generic power domains from DT nodes */
- pds_node = of_find_node_by_path("/cpus/power-domains");
- if (pds_node) {
- ret = sbi_genpd_probe(pds_node);
- of_node_put(pds_node);
- if (ret)
- return ret;
- }
/* Initialize CPU idle driver for each present CPU */
for_each_present_cpu(cpu) {
@@ -576,4 +416,4 @@ static int __init sbi_cpuidle_init(void)
return 0;
}
-arch_initcall(sbi_cpuidle_init);
+device_initcall(sbi_cpuidle_init);
diff --git a/drivers/cpuidle/cpuidle-riscv-sbi.h b/drivers/cpuidle/cpuidle-riscv-sbi.h
new file mode 100644
index 000000000000..f9a0e81d1417
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-riscv-sbi.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __CPUIDLE_RISCV_SBI_H
+#define __CPUIDLE_RISCV_SBI_H
+
+#ifdef CONFIG_RISCV_SBI_CPUIDLE
+
+void sbi_set_osi_mode(bool use_osi);
+void sbi_set_domain_state(u32 state);
+int sbi_dt_parse_state_node(struct device_node *np, u32 *state);
+
+#else
+
+static inline void sbi_set_osi_mode(bool use_osi)
+{
+}
+
+static inline void sbi_set_domain_state(u32 state)
+{
+}
+
+static inline int sbi_dt_parse_state_node(struct device_node *np, u32 *state)
+{
+ return 0;
+}
+
+#endif
+
+#endif /* __CPUIDLE_RISCV_SBI_H */
--
2.43.7
More information about the linux-riscv
mailing list