From e484a765f543e866baa1fb8baa41af7b2755da59 Mon Sep 17 00:00:00 2001 From: Anurag Pateriya Date: Wed, 26 Aug 2026 21:54:53 +0800 Subject: [PATCH 01/36] dt-bindings: power: qcom,rpmhpd: Add NMXC power domain index Nord routes the NSP (compute DSP) memory rail through a dedicated NMXC resource instead of the shared MX rail. Add the index so device trees can reference it. Signed-off-by: Anurag Pateriya Signed-off-by: Shawn Guo Reviewed-by: Bartosz Golaszewski Acked-by: Krzysztof Kozlowski Signed-off-by: Ulf Hansson --- include/dt-bindings/power/qcom,rpmhpd.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/power/qcom,rpmhpd.h b/include/dt-bindings/power/qcom,rpmhpd.h index 07bd2a7b0150..92c8408dbc57 100644 --- a/include/dt-bindings/power/qcom,rpmhpd.h +++ b/include/dt-bindings/power/qcom,rpmhpd.h @@ -32,6 +32,7 @@ #define RPMHPD_GBX 22 #define RPMHPD_NSP3 23 #define RPMHPD_GFX1 24 +#define RPMHPD_NMXC 25 /* RPMh Power Domain performance levels */ #define RPMH_REGULATOR_LEVEL_RETENTION 16 From edd40d83383be993531946f7edaf89001f3fe617 Mon Sep 17 00:00:00 2001 From: Hardeep Sharma Date: Thu, 27 Aug 2026 23:21:05 +0530 Subject: [PATCH 02/36] dt-bindings: power: rpmpd: Document Kuno RPMh power domains Document the RPMh power domains for the Qualcomm Kuno SoC. Acked-by: Krzysztof Kozlowski Signed-off-by: Hardeep Sharma Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/power/qcom,rpmpd.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml b/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml index 0a45e1f55062..7fc3927efa27 100644 --- a/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml +++ b/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml @@ -21,6 +21,7 @@ properties: - qcom,glymur-rpmhpd - qcom,hawi-rpmhpd - qcom,kaanapali-rpmhpd + - qcom,kuno-rpmhpd - qcom,mdm9607-rpmpd - qcom,milos-rpmhpd - qcom,msm8226-rpmpd From e1d70a075a0335efcb254a3bdd1472f9687579dc Mon Sep 17 00:00:00 2001 From: Sreeshankar K Date: Tue, 1 Sep 2026 09:24:42 +0000 Subject: [PATCH 03/36] dt-bindings: power: qcom,rpmhpd: Document RPMh power domain for SM7250 Document the RPMh power domain for SM7250 SoC. Signed-off-by: Sreeshankar K Acked-by: Krzysztof Kozlowski Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/power/qcom,rpmpd.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml b/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml index 7fc3927efa27..a70bfacbc5d1 100644 --- a/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml +++ b/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml @@ -64,6 +64,7 @@ properties: - qcom,sm6350-rpmhpd - qcom,sm6375-rpmpd - qcom,sm7150-rpmhpd + - qcom,sm7250-rpmhpd - qcom,sm8150-rpmhpd - qcom,sm8250-rpmhpd - qcom,sm8350-rpmhpd From 8c1a6a79b16a6c8b17d1e9db458d563fbbb613c1 Mon Sep 17 00:00:00 2001 From: Ayman Chaudhry Date: Wed, 26 Aug 2026 10:13:12 +0000 Subject: [PATCH 04/36] pmdomain: renesas: rcar-sysc: Update description of chan_offs, chan_bit, isr_bit The descriptions of `rcar_sysc_area.chan_offs`, `rcar_sysc_area.chan_bits`, and `rcar_sysc_area.isr_bit` do not clearly document how these fields are used. When `rcar_sysc_area.flags` is set to `PD_ALWAYS_ON` (i.e. `PD_NO_CR`), these fields are ignored, therefore improve the description of `rcar_sysc_area.chan_offs`, `rcar_sysc_area.chan_bit`, and `rcar_sysc_area.isr_bit` to make it clear that the field is set to 0 if power is always on. Signed-off-by: Ayman Chaudhry Signed-off-by: Ulf Hansson --- drivers/pmdomain/renesas/rcar-sysc.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/pmdomain/renesas/rcar-sysc.h b/drivers/pmdomain/renesas/rcar-sysc.h index 07ffce310686..ef598ecc710a 100644 --- a/drivers/pmdomain/renesas/rcar-sysc.h +++ b/drivers/pmdomain/renesas/rcar-sysc.h @@ -29,9 +29,15 @@ struct rcar_sysc_area { const char *name; - u16 chan_offs; /* Offset of PWRSR register for this area */ - u8 chan_bit; /* Bit in PWR* (except for PWRUP in PWRSR) */ - u8 isr_bit; /* Bit in SYSCI*R */ + u16 chan_offs; /* + * PWRSR register offset; or 0 if area is + * always on + */ + u8 chan_bit; /* + * Bit in PWR* (except for PWRUP in PWRSR); + * 0 if area is always on + */ + u8 isr_bit; /* Bit in SYSCI*R; 0 if area is always on */ s8 parent; /* -1 if none */ u8 flags; /* See PD_* */ }; From 470ad751018342298134641d5ae0c244ff5603e0 Mon Sep 17 00:00:00 2001 From: Hardeep Sharma Date: Thu, 27 Aug 2026 23:21:06 +0530 Subject: [PATCH 05/36] pmdomain: qcom: rpmhpd: Add power domains for Kuno Add the RPMh power-domain data for the Qualcomm Kuno SoC to enable the new qcom,kuno-rpmhpd compatible to expose RPMh power domains. Kuno exposes CX, MX and MXC domains (including always-on variants). Reviewed-by: Konrad Dybcio Reviewed-by: Abel Vesa Signed-off-by: Hardeep Sharma Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/rpmhpd.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c index 90743275942d..725ce802f683 100644 --- a/drivers/pmdomain/qcom/rpmhpd.c +++ b/drivers/pmdomain/qcom/rpmhpd.c @@ -681,6 +681,21 @@ static const struct rpmhpd_desc kaanapali_desc = { .num_pds = ARRAY_SIZE(kaanapali_rpmhpds), }; +/* Kuno RPMH power domains */ +static struct rpmhpd *kuno_rpmhpds[] = { + [RPMHPD_CX] = &cx, + [RPMHPD_CX_AO] = &cx_ao, + [RPMHPD_MX] = &mx, + [RPMHPD_MX_AO] = &mx_ao, + [RPMHPD_MXC] = &mxc, + [RPMHPD_MXC_AO] = &mxc_ao, +}; + +static const struct rpmhpd_desc kuno_desc = { + .rpmhpds = kuno_rpmhpds, + .num_pds = ARRAY_SIZE(kuno_rpmhpds), +}; + /* Hawi RPMH powerdomains */ static struct rpmhpd *hawi_rpmhpds[] = { [RPMHPD_CX] = &cx, @@ -885,6 +900,7 @@ static const struct of_device_id rpmhpd_match_table[] = { { .compatible = "qcom,glymur-rpmhpd", .data = &glymur_desc }, { .compatible = "qcom,hawi-rpmhpd", .data = &hawi_desc }, { .compatible = "qcom,kaanapali-rpmhpd", .data = &kaanapali_desc }, + { .compatible = "qcom,kuno-rpmhpd", .data = &kuno_desc }, { .compatible = "qcom,milos-rpmhpd", .data = &milos_desc }, { .compatible = "qcom,nord-rpmhpd", .data = &nord_desc }, { .compatible = "qcom,qcs615-rpmhpd", .data = &qcs615_desc }, From 16d52b4e59deb2535dc6b8ac13792d9bbe693f8d Mon Sep 17 00:00:00 2001 From: Anurag Pateriya Date: Wed, 26 Aug 2026 21:54:54 +0800 Subject: [PATCH 06/36] pmdomain: qcom: rpmhpd: Add NMXC power domain for Nord Add the nmxc.lvl RPMh resource and register it in the Nord power domain table. Nord supplies the NSP memory rail from this dedicated resource rather than from the shared MX rail, so consumers need it exposed as its own power domain. Signed-off-by: Anurag Pateriya Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Signed-off-by: Shawn Guo Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/rpmhpd.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c index 725ce802f683..321a4e2cf1db 100644 --- a/drivers/pmdomain/qcom/rpmhpd.c +++ b/drivers/pmdomain/qcom/rpmhpd.c @@ -198,6 +198,11 @@ static struct rpmhpd mxc_ao = { .res_name = "mxc.lvl", }; +static struct rpmhpd nmxc = { + .pd = { .name = "nmxc", }, + .res_name = "nmxc.lvl", +}; + static struct rpmhpd nsp = { .pd = { .name = "nsp", }, .res_name = "nsp.lvl", @@ -327,6 +332,7 @@ static struct rpmhpd *nord_rpmhpds[] = { [RPMHPD_MX_AO] = &mx_ao, [RPMHPD_MXC] = &mxc, [RPMHPD_MXC_AO] = &mxc_ao, + [RPMHPD_NMXC] = &nmxc, [RPMHPD_NSP0] = &nsp0, [RPMHPD_NSP1] = &nsp1, [RPMHPD_NSP2] = &nsp2, From 26b9ea4f00732908c4f87501483c20ecaaf4eca5 Mon Sep 17 00:00:00 2001 From: Sreeshankar K Date: Tue, 1 Sep 2026 09:24:43 +0000 Subject: [PATCH 07/36] pmdomain: qcom: rpmhpd: Add RPMh power domains for SM7250 Add RPMh power domains for SM7250 SoC. Signed-off-by: Sreeshankar K Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/rpmhpd.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c index 321a4e2cf1db..fa8fed5bd3e5 100644 --- a/drivers/pmdomain/qcom/rpmhpd.c +++ b/drivers/pmdomain/qcom/rpmhpd.c @@ -492,6 +492,24 @@ static const struct rpmhpd_desc sm7150_desc = { .num_pds = ARRAY_SIZE(sm7150_rpmhpds), }; +/* SM7250 RPMH powerdomains */ +static struct rpmhpd *sm7250_rpmhpds[] = { + [RPMHPD_CX] = &cx_w_mx_parent, + [RPMHPD_CX_AO] = &cx_ao_w_mx_parent, + [RPMHPD_EBI] = &ebi, + [RPMHPD_GFX] = &gfx, + [RPMHPD_LCX] = &lcx, + [RPMHPD_LMX] = &lmx, + [RPMHPD_MSS] = &mss, + [RPMHPD_MX] = &mx, + [RPMHPD_MX_AO] = &mx_ao, +}; + +static const struct rpmhpd_desc sm7250_desc = { + .rpmhpds = sm7250_rpmhpds, + .num_pds = ARRAY_SIZE(sm7250_rpmhpds), +}; + /* SM8150 RPMH powerdomains */ static struct rpmhpd *sm8150_rpmhpds[] = { [SM8150_CX] = &cx_w_mx_parent, @@ -928,6 +946,7 @@ static const struct of_device_id rpmhpd_match_table[] = { { .compatible = "qcom,sm4450-rpmhpd", .data = &sm4450_desc }, { .compatible = "qcom,sm6350-rpmhpd", .data = &sm6350_desc }, { .compatible = "qcom,sm7150-rpmhpd", .data = &sm7150_desc }, + { .compatible = "qcom,sm7250-rpmhpd", .data = &sm7250_desc }, { .compatible = "qcom,sm8150-rpmhpd", .data = &sm8150_desc }, { .compatible = "qcom,sm8250-rpmhpd", .data = &sm8250_desc }, { .compatible = "qcom,sm8350-rpmhpd", .data = &sm8350_desc }, From 81f1099186fd501768ea4bd24523347211322fbe Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 14 Sep 2026 17:01:37 +0200 Subject: [PATCH 08/36] pmdomain: core: Rename genpd_status_on() Rename the genpd_status_on() to genpd_status_on_unlocked() to make the name available for a lock taking wrapper introduced by a subsequent change. Suggested-by: Abel Vesa Tested-by: Yuanfang Zhang Reviewed-by: Dhruva Gole Signed-off-by: Ulf Hansson Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 052ec478dfcc..6abe8b198949 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -173,7 +173,7 @@ static const struct genpd_lock_ops genpd_raw_spin_ops = { #define genpd_lock_interruptible(p) p->lock_ops->lock_interruptible(p) #define genpd_unlock(p) p->lock_ops->unlock(p) -#define genpd_status_on(genpd) (genpd->status == GENPD_STATE_ON) +#define genpd_status_on_unlocked(genpd) (genpd->status == GENPD_STATE_ON) #define genpd_is_irq_safe(genpd) (genpd->flags & GENPD_FLAG_IRQ_SAFE) #define genpd_is_always_on(genpd) (genpd->flags & GENPD_FLAG_ALWAYS_ON) #define genpd_is_active_wakeup(genpd) (genpd->flags & GENPD_FLAG_ACTIVE_WAKEUP) @@ -771,8 +771,7 @@ EXPORT_SYMBOL_GPL(dev_pm_genpd_rpm_always_on); * @dev: Device to get the current power status * * This function checks whether the generic power domain associated with the - * given device is on or not by verifying if genpd_status_on equals - * GENPD_STATE_ON. + * given device is on or not by checking if genpd->status equals GENPD_STATE_ON. * * Note: this function returns the power status of the genpd at the time of the * call. The power status may change after due to activity from other devices @@ -791,7 +790,7 @@ bool dev_pm_genpd_is_on(struct device *dev) return false; genpd_lock(genpd); - is_on = genpd_status_on(genpd); + is_on = genpd_status_on_unlocked(genpd); genpd_unlock(genpd); return is_on; @@ -963,7 +962,7 @@ static void genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, * The domain was on at boot and still need to stay on. * The domain has a subdomain being powered on. */ - if (!genpd_status_on(genpd) || genpd->prepared_count > 0 || + if (!genpd_status_on_unlocked(genpd) || genpd->prepared_count > 0 || genpd_is_always_on(genpd) || genpd_is_rpm_always_on(genpd) || genpd->stay_on || atomic_read(&genpd->sd_count) > 0) return; @@ -1040,7 +1039,7 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) struct gpd_link *link; int ret = 0; - if (genpd_status_on(genpd)) + if (genpd_status_on_unlocked(genpd)) return 0; /* Reflect over the entered idle-states residency for debugfs. */ @@ -1406,7 +1405,7 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock, { struct gpd_link *link; - if (!genpd_status_on(genpd) || genpd_is_always_on(genpd)) + if (!genpd_status_on_unlocked(genpd) || genpd_is_always_on(genpd)) return; if (genpd->suspended_count != genpd->device_count @@ -1472,7 +1471,7 @@ static void genpd_sync_power_on(struct generic_pm_domain *genpd, bool use_lock, { struct gpd_link *link; - if (genpd_status_on(genpd)) + if (genpd_status_on_unlocked(genpd)) return; list_for_each_entry(link, &genpd->child_links, child_node) { @@ -2170,7 +2169,8 @@ static int genpd_add_subdomain(struct generic_pm_domain *genpd, genpd_lock(subdomain); genpd_lock_nested(genpd, SINGLE_DEPTH_NESTING); - if (!genpd_status_on(genpd) && genpd_status_on(subdomain)) { + if (!genpd_status_on_unlocked(genpd) && + genpd_status_on_unlocked(subdomain)) { ret = -EINVAL; goto out; } @@ -2186,7 +2186,7 @@ static int genpd_add_subdomain(struct generic_pm_domain *genpd, list_add_tail(&link->parent_node, &genpd->parent_links); link->child = subdomain; list_add_tail(&link->child_node, &subdomain->child_links); - if (genpd_status_on(subdomain)) + if (genpd_status_on_unlocked(subdomain)) genpd_sd_counter_inc(genpd); out: @@ -2246,7 +2246,7 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, list_del(&link->parent_node); list_del(&link->child_node); kfree(link); - if (genpd_status_on(subdomain)) + if (genpd_status_on_unlocked(subdomain)) genpd_sd_counter_dec(genpd); ret = 0; @@ -2441,7 +2441,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd, /* Always-on domains must be powered on at initialization. */ if ((genpd_is_always_on(genpd) || genpd_is_rpm_always_on(genpd)) && - !genpd_status_on(genpd)) { + !genpd_status_on_unlocked(genpd)) { pr_err("always-on PM domain %s is not on\n", genpd->name); return -EINVAL; } @@ -3839,7 +3839,7 @@ static int genpd_summary_one(struct seq_file *s, if (WARN_ON(genpd->status >= ARRAY_SIZE(status_lookup))) goto exit; - if (!genpd_status_on(genpd)) + if (!genpd_status_on_unlocked(genpd)) snprintf(state, sizeof(state), "%s-%u", status_lookup[genpd->status], genpd->state_idx); else From 9e8dff8e097814e4d7c65cd211ccb1e2fceb460a Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 14 Sep 2026 17:01:38 +0200 Subject: [PATCH 09/36] pmdomain: core: Allow a non-CPU device in a CPU PM domain to do power on A driver for a non-CPU device that is attached to a CPU PM domain (the genpd has the GENPD_FLAG_CPU_DOMAIN configuration set), is currently not able to power on the PM domain. More precisely, to power on a CPU PM domain one of its corresponding CPUs needs to be woken up if they are idle. The current support for a non-CPU device is that its driver can only prevent an already powered on CPU PM domain from being powered off. This leads to problems for a driver while probing its device or when it needs to call pm_runtime_get_sync() to turn on the power for it. From the driver point of view it looks like it all works fine, but when accessing the device it may end up with various errors as the device may not be fully powered on. To fix the behavior for these types of devices, let's adjust the behaviour in genpd_power_on() to wake up an idle CPU that belongs to it, in cases when it's needed. Link: https://lore.kernel.org/all/CAPx+jO-sCierYj8jnoKQHckJG16dOBxnNrsZVYO=38R2cLV8nw@mail.gmail.com/ Reviewed-by: Abel Vesa Tested-by: Yuanfang Zhang Reviewed-by: Dhruva Gole Signed-off-by: Ulf Hansson Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 80 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 6abe8b198949..c593a3479ad0 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -19,11 +20,14 @@ #include #include #include +#include #include #include #include #include +#include + /* Provides a unique ID for each genpd device */ static DEFINE_IDA(genpd_ida); @@ -32,7 +36,9 @@ static const struct bus_type genpd_provider_bus_type = { .name = "genpd_provider", }; -#define GENPD_RETRY_MAX_MS 250 /* Approximate */ +#define GENPD_RETRY_MAX_MS 250 /* Approximate */ +#define GENPD_CPU_ON_POLL_PERIOD_US 100 /* 100us */ +#define GENPD_CPU_ON_TIMEOUT_US 300000 /* 300ms */ #define GENPD_DEV_CALLBACK(genpd, type, callback, dev) \ ({ \ @@ -1026,15 +1032,75 @@ static void genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, } } +static bool genpd_status_on(struct generic_pm_domain *genpd) +{ + bool is_on; + + genpd_lock(genpd); + is_on = genpd_status_on_unlocked(genpd); + genpd_unlock(genpd); + + return is_on; +} + +static int genpd_wakeup_cpu(struct generic_pm_domain *genpd) +{ + unsigned int cpu; + bool is_on; + int ret; + + /* Find the first online CPU in the genpd's cpumask. */ + cpu = cpumask_first_and(genpd->cpus, cpu_online_mask); + if (cpu >= nr_cpu_ids) + return -EAGAIN; + + genpd_unlock(genpd); + + /* Send a IPI to wakeup the selected CPU. */ + smp_send_reschedule(cpu); + + /* Poll to wait for it to complete the power on sequence. */ + ret = readx_poll_timeout_atomic(genpd_status_on, genpd, is_on, is_on, + GENPD_CPU_ON_POLL_PERIOD_US, + GENPD_CPU_ON_TIMEOUT_US); + + genpd_lock(genpd); + + /* Re-check the status as we have released the lock in between. */ + if (ret || !genpd_status_on_unlocked(genpd)) + return -EAGAIN; + + return 0; +} + +static bool genpd_need_alive_cpu(struct generic_pm_domain *genpd, + struct device *dev) +{ + if (!genpd_is_cpu_domain(genpd)) + return false; + + /* This is not for CPU devices as those are managed differently. */ + if (to_gpd_data(dev->power.subsys_data->domain_data)->cpu >= 0) + return false; + + /* + * If the current CPU doesn't belong to the genpd's cpumask, we need to + * wake up one of those idle CPUs to power on the CPU domain correctly. + */ + return !cpumask_test_cpu(smp_processor_id(), genpd->cpus); +} + /** * genpd_power_on - Restore power to a given PM domain and its parents. * @genpd: PM domain to power up. + * @dev: The device that needs the PM domain to power on. * @depth: nesting count for lockdep. * * Restore power to @genpd and all of its parents so that it is possible to * resume a device belonging to it. */ -static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) +static int genpd_power_on(struct generic_pm_domain *genpd, struct device *dev, + unsigned int depth) { struct gpd_link *link; int ret = 0; @@ -1042,6 +1108,10 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) if (genpd_status_on_unlocked(genpd)) return 0; + /* Special case for a device attached to a CPU domain. */ + if (genpd_need_alive_cpu(genpd, dev)) + return genpd_wakeup_cpu(genpd); + /* Reflect over the entered idle-states residency for debugfs. */ genpd_reflect_residency(genpd); @@ -1056,7 +1126,7 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) genpd_sd_counter_inc(parent); genpd_lock_nested(parent, depth + 1); - ret = genpd_power_on(parent, depth + 1); + ret = genpd_power_on(parent, dev, depth + 1); genpd_unlock(parent); if (ret) { @@ -1306,7 +1376,7 @@ static int genpd_runtime_resume(struct device *dev) genpd_lock(genpd); genpd_restore_performance_state(dev, gpd_data->rpm_pstate); - ret = genpd_power_on(genpd, 0); + ret = genpd_power_on(genpd, dev, 0); genpd_unlock(genpd); if (ret) @@ -3410,7 +3480,7 @@ static int __genpd_dev_pm_attach(struct device *dev, struct device *base_dev, if (power_on) { genpd_lock(pd); - ret = genpd_power_on(pd, 0); + ret = genpd_power_on(pd, dev, 0); genpd_unlock(pd); } From 2224d686e788d6ad3af4cc48bbf68723f8abd326 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 14 Sep 2026 17:01:39 +0200 Subject: [PATCH 10/36] pmdomain: core: Add a genpd config to support unknown initial status It's not always possible for a genpd provider to know the initial status for its corresponding PM domain(s). To register the PM domain in a safe state, the genpd provider driver may therefore have to initialize the genpd's status to be powered off, as a way to prevent the PM domain from being used when it actually could be powered off. In these cases we may end up to power on a PM domain through the genpd subsystem, while from the HW point of view it may already be powered on. Under these circumstances, it may also be required to keep the PM domain powered on until all the consumer devices have been probed. In other words, until the ->sync_state() callback for the genpd provider in question have been called. To support this behaviour for a genpd provider, let's introduce a new genpd configuration, GENPD_FLAG_POWER_UNKNOWN. Reported-by: Maulik Shah Link: https://lore.kernel.org/all/20260811-domain_off_ss3-v1-0-6a0a0fc023f5@oss.qualcomm.com/ Reviewed-by: Abel Vesa Tested-by: Yuanfang Zhang Reviewed-by: Dhruva Gole Signed-off-by: Ulf Hansson Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 6 +++++- include/linux/pm_domain.h | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index c593a3479ad0..e8473f5339e5 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -189,6 +189,7 @@ static const struct genpd_lock_ops genpd_raw_spin_ops = { #define genpd_is_dev_name_fw(genpd) (genpd->flags & GENPD_FLAG_DEV_NAME_FW) #define genpd_is_no_sync_state(genpd) (genpd->flags & GENPD_FLAG_NO_SYNC_STATE) #define genpd_is_no_stay_on(genpd) (genpd->flags & GENPD_FLAG_NO_STAY_ON) +#define genpd_is_power_unknown(genpd) (genpd->flags & GENPD_FLAG_POWER_UNKNOWN) static inline bool irq_safe_dev_in_sleep_domain(struct device *dev, const struct generic_pm_domain *genpd) @@ -2445,7 +2446,10 @@ static void genpd_lock_init(struct generic_pm_domain *genpd) #ifdef CONFIG_PM_GENERIC_DOMAINS_OF static void genpd_set_stay_on(struct generic_pm_domain *genpd, bool is_off) { - genpd->stay_on = !genpd_is_no_stay_on(genpd) && !is_off; + if (genpd_is_power_unknown(genpd)) + genpd->stay_on = is_off; + else + genpd->stay_on = !genpd_is_no_stay_on(genpd) && !is_off; } #else static void genpd_set_stay_on(struct generic_pm_domain *genpd, bool is_off) diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index f925614aebdb..14e0e346c610 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -121,6 +121,14 @@ struct dev_pm_domain_list { * powered-off until the ->sync_state() callback is * invoked. This flag informs genpd to allow a * power-off without waiting for ->sync_state(). + * + * GENPD_FLAG_POWER_UNKNOWN: Use this flag to inform genpd that its initial + * status for the PM domain is set to powered off, + * which may not correctly reflect the state of the + * HW, as it's unknown. If the PM domain becomes + * powered on during boot, genpd will prevent it + * from being powered off until the ->sync_state + * callback is invoked for it. */ #define GENPD_FLAG_PM_CLK (1U << 0) #define GENPD_FLAG_IRQ_SAFE (1U << 1) @@ -133,6 +141,7 @@ struct dev_pm_domain_list { #define GENPD_FLAG_DEV_NAME_FW (1U << 8) #define GENPD_FLAG_NO_SYNC_STATE (1U << 9) #define GENPD_FLAG_NO_STAY_ON (1U << 10) +#define GENPD_FLAG_POWER_UNKNOWN (1U << 11) enum gpd_status { GENPD_STATE_ON = 0, /* PM domain is on */ From 6d3080bfe007c88c38b6faf6d3ab23171af359f0 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 14 Sep 2026 17:01:40 +0200 Subject: [PATCH 11/36] cpuidle: psci: Initialize the PM domains in powered off state for OSI At the point when the PM domain and the topology are registered through the genpd subsystem, it's not really known whether corresponding CPUs are online and thus if the PM domain should be initialized as powered on or not. Instead this information becomes available when the CPU devices gets attached to their respective PM domain through dt_idle_attach_cpu(). This is a problem when using PSCI OS-initiated mode, as we may end up with a PM domain that has the genpd's status indicating it to be powered on, while it in fact may not be the case. In the less severe scenario, this leads to selecting a shallower domain idle state for the PM domain than necessary. A more critical problem is when a non-CPU device shares the PM domain, leading to their corresponding drivers not being able to trust the status of it. Let's fix these problems by initializing the state for the genpd's to be powered off and in the deepest possible domain idle state, when using OS-initiated mode. The support for ->sync_state() is maintained by setting the GENPD_FLAG_POWER_UNKNOWN for the genpds in question. Reported-by: Maulik Shah Link: https://lore.kernel.org/all/20260811-domain_off_ss3-v1-0-6a0a0fc023f5@oss.qualcomm.com/ Reviewed-by: Abel Vesa Tested-by: Yuanfang Zhang Signed-off-by: Ulf Hansson Reviewed-by: Dhruva Gole Signed-off-by: Ulf Hansson --- drivers/cpuidle/cpuidle-psci-domain.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/cpuidle/cpuidle-psci-domain.c b/drivers/cpuidle/cpuidle-psci-domain.c index b9e4ad7d43a3..5722365b943e 100644 --- a/drivers/cpuidle/cpuidle-psci-domain.c +++ b/drivers/cpuidle/cpuidle-psci-domain.c @@ -68,9 +68,8 @@ static int psci_pd_init(struct device_node *np, bool use_osi) */ if (use_osi) { pd->power_off = psci_pd_power_off; - pd->flags |= GENPD_FLAG_ACTIVE_WAKEUP; - if (IS_ENABLED(CONFIG_PREEMPT_RT)) - pd->flags |= GENPD_FLAG_RPM_ALWAYS_ON; + pd->flags |= GENPD_FLAG_ACTIVE_WAKEUP | GENPD_FLAG_POWER_UNKNOWN; + pd->state_idx = pd->state_count ? pd->state_count - 1 : 0; } else { pd->flags |= GENPD_FLAG_ALWAYS_ON; } @@ -78,7 +77,7 @@ static int psci_pd_init(struct device_node *np, bool use_osi) /* 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); + ret = pm_genpd_init(pd, pd_gov, use_osi); if (ret) goto free_pd_prov; From 65705b18162bbf0771adf742ee528521957fb496 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 14 Sep 2026 17:01:41 +0200 Subject: [PATCH 12/36] cpuidle: psci: Move initialization a bit earlier in the boot sequence When using PSCI OS-initiated mode along with the cpuidle-psci-domain driver it's of great benefit to attach the CPU devices to their CPU PM domains as soon as possible, as other non-CPU devices sharing the same PM domains depends on it to allow their devices to be probed. Let's therefore move to a subsys_initcall, which should be soon enough after the cpuidle-psci-domain have initialized the PM domain topology. Reviewed-by: Abel Vesa Tested-by: Yuanfang Zhang Signed-off-by: Ulf Hansson Reviewed-by: Dhruva Gole Signed-off-by: Ulf Hansson --- drivers/cpuidle/cpuidle-psci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpuidle/cpuidle-psci.c b/drivers/cpuidle/cpuidle-psci.c index b250d0dde760..0187d6a9096a 100644 --- a/drivers/cpuidle/cpuidle-psci.c +++ b/drivers/cpuidle/cpuidle-psci.c @@ -479,4 +479,4 @@ static int __init psci_idle_init(void) return 0; } -device_initcall(psci_idle_init); +subsys_initcall(psci_idle_init); From 53d17430e0ef1a5babfe4b889dccfd1a3c9adc8e Mon Sep 17 00:00:00 2001 From: Joshua Yeong Date: Thu, 3 Sep 2026 17:23:45 +0800 Subject: [PATCH 13/36] dt-bindings: power: Add RPMI device power service Add device tree bindings for the RISC-V Platform Management Interface (RPMI) device power service group, both for the supervisor-facing power domain controller and for the SBI MPXY channel which the SBI implementation uses to expose the service group. Signed-off-by: Joshua Yeong Reviewed-by: Conor Dooley Signed-off-by: Ulf Hansson --- .../power/riscv,rpmi-device-power.yaml | 65 +++++++++++++++++++ .../power/riscv,rpmi-mpxy-device-power.yaml | 65 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/riscv,rpmi-device-power.yaml create mode 100644 Documentation/devicetree/bindings/power/riscv,rpmi-mpxy-device-power.yaml diff --git a/Documentation/devicetree/bindings/power/riscv,rpmi-device-power.yaml b/Documentation/devicetree/bindings/power/riscv,rpmi-device-power.yaml new file mode 100644 index 000000000000..5afdc40df7e0 --- /dev/null +++ b/Documentation/devicetree/bindings/power/riscv,rpmi-device-power.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/riscv,rpmi-device-power.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: RISC-V RPMI device power service group based power domain controller + +maintainers: + - Joshua Yeong + +description: | + The RISC-V Platform Management Interface (RPMI) [1] defines a + messaging protocol which is modular and extensible. The supervisor + software can send/receive RPMI messages via SBI MPXY extension [2] + or some dedicated supervisor-mode RPMI transport. + + The RPMI specification [1] defines device power service group for + accessing and controlling the power state of platform devices managed + by a platform microcontroller. The supervisor software can access RPMI + device power service group via SBI MPXY channel or some dedicated + supervisor-mode RPMI transport. + + =========================================== + 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-device-power + + mboxes: + maxItems: 1 + description: + Mailbox channel of the underlying RPMI transport or SBI message proxy channel. + + "#power-domain-cells": + const: 1 + description: + Platform specific DOMAIN_ID as defined by the RISC-V Platform Management + Interface (RPMI) specification. + +required: + - compatible + - mboxes + - "#power-domain-cells" + +additionalProperties: false + +examples: + - | + power-controller { + compatible = "riscv,rpmi-device-power"; + mboxes = <&mpxy_mbox 0x1002 0x0>; + #power-domain-cells = <1>; + }; +... diff --git a/Documentation/devicetree/bindings/power/riscv,rpmi-mpxy-device-power.yaml b/Documentation/devicetree/bindings/power/riscv,rpmi-mpxy-device-power.yaml new file mode 100644 index 000000000000..2b7df66ba172 --- /dev/null +++ b/Documentation/devicetree/bindings/power/riscv,rpmi-mpxy-device-power.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/riscv,rpmi-mpxy-device-power.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: RISC-V RPMI device power service group based message proxy + +maintainers: + - Joshua Yeong + +description: | + The RISC-V Platform Management Interface (RPMI) [1] defines a + messaging protocol which is modular and extensible. The supervisor + software can send/receive RPMI messages via SBI MPXY extension [2] + or some dedicated supervisor-mode RPMI transport. + + The RPMI specification [1] defines device power service group for + accessing and controlling the power state of platform devices managed + by a platform microcontroller. The SBI implementation (machine mode + firmware or hypervisor) can implement an SBI MPXY channel to allow RPMI + device power service group access to the supervisor software. + + =========================================== + 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-device-power + + 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: + - | + device-power-service { + compatible = "riscv,rpmi-mpxy-device-power"; + mboxes = <&rpmi_shmem_mbox 0x9>; + riscv,sbi-mpxy-channel-id = <0x1002>; + }; +... From f94fe707c5b713eb0c271480c1f4889d961ce693 Mon Sep 17 00:00:00 2001 From: Joshua Yeong Date: Thu, 3 Sep 2026 17:23:46 +0800 Subject: [PATCH 14/36] pmdomain: riscv: Add RPMI device power service Add a generic power domain provider on top of the RISC-V Platform Management Interface (RPMI) device power service group, reached through an SBI MPXY channel. The driver enumerates the device power domains advertised by the platform microcontroller, queries their attributes and initial state, and registers them as an onecell genpd provider so that devices can reference them through the "power-domains" property. Signed-off-by: Joshua Yeong Signed-off-by: Ulf Hansson --- drivers/pmdomain/Kconfig | 1 + drivers/pmdomain/Makefile | 1 + drivers/pmdomain/riscv/Kconfig | 15 + drivers/pmdomain/riscv/Makefile | 3 + .../pmdomain/riscv/riscv-rpmi-device-power.c | 485 ++++++++++++++++++ include/linux/mailbox/riscv-rpmi-message.h | 11 + 6 files changed, 516 insertions(+) create mode 100644 drivers/pmdomain/riscv/Kconfig create mode 100644 drivers/pmdomain/riscv/Makefile create mode 100644 drivers/pmdomain/riscv/riscv-rpmi-device-power.c diff --git a/drivers/pmdomain/Kconfig b/drivers/pmdomain/Kconfig index 23076ae90e66..5341dff669be 100644 --- a/drivers/pmdomain/Kconfig +++ b/drivers/pmdomain/Kconfig @@ -11,6 +11,7 @@ source "drivers/pmdomain/marvell/Kconfig" source "drivers/pmdomain/mediatek/Kconfig" source "drivers/pmdomain/qcom/Kconfig" source "drivers/pmdomain/renesas/Kconfig" +source "drivers/pmdomain/riscv/Kconfig" source "drivers/pmdomain/rockchip/Kconfig" source "drivers/pmdomain/samsung/Kconfig" source "drivers/pmdomain/st/Kconfig" diff --git a/drivers/pmdomain/Makefile b/drivers/pmdomain/Makefile index ebc802f13eb9..d7aee13ae571 100644 --- a/drivers/pmdomain/Makefile +++ b/drivers/pmdomain/Makefile @@ -9,6 +9,7 @@ obj-y += marvell/ obj-y += mediatek/ obj-y += qcom/ obj-y += renesas/ +obj-y += riscv/ obj-y += rockchip/ obj-y += samsung/ obj-y += st/ diff --git a/drivers/pmdomain/riscv/Kconfig b/drivers/pmdomain/riscv/Kconfig new file mode 100644 index 000000000000..97232edc294a --- /dev/null +++ b/drivers/pmdomain/riscv/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config RISCV_RPMI_DEVICE_POWER + bool "RISC-V RPMI Based Device Power driver" + depends on RISCV || COMPILE_TEST + depends on MAILBOX + depends on PM + default RISCV + select PM_GENERIC_DOMAINS + help + Support for device power domains based on the device power service + group defined by the RISC-V platform management interface (RPMI) + 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. diff --git a/drivers/pmdomain/riscv/Makefile b/drivers/pmdomain/riscv/Makefile new file mode 100644 index 000000000000..2cb0ee8ad413 --- /dev/null +++ b/drivers/pmdomain/riscv/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_RISCV_RPMI_DEVICE_POWER) += riscv-rpmi-device-power.o diff --git a/drivers/pmdomain/riscv/riscv-rpmi-device-power.c b/drivers/pmdomain/riscv/riscv-rpmi-device-power.c new file mode 100644 index 000000000000..c7c46717dd73 --- /dev/null +++ b/drivers/pmdomain/riscv/riscv-rpmi-device-power.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RISC-V RPMI Based Device Power Driver through SBI MPXY + * + * Copyright (C) 2026 Shanghai StarFive Technology Co., Ltd. + * + * Implements a Device Power driver on top of SBI RPMI Message Proxy Extension (MPXY) + * + * Each SBI MPXY Device Power instance is associated, through the means of a proper DT + * entry description, to a specific Transport ID. + */ + +#include +#include +#include +#include +#include + +#define RPMI_PM_DOMAIN_NAME_LEN 16 + +/* power state for device power domains */ +#define RPMI_POWER_STATE_CONTEXT_MASK GENMASK(16, 16) +#define RPMI_POWER_STATE_CONTEXT_PRESERVED 0 +#define RPMI_POWER_STATE_CONTEXT_LOST 1 +#define RPMI_POWER_STATE_VALUE_MASK GENMASK(15, 0) +#define RPMI_POWER_STATE_VALUE_ON 0 +#define RPMI_POWER_STATE_VALUE_OFF 3 + +#define RPMI_POWER_STATE_PARAM(context_type, state_value) \ + ((FIELD_PREP(RPMI_POWER_STATE_CONTEXT_MASK, context_type)) | \ + (FIELD_PREP(RPMI_POWER_STATE_VALUE_MASK, state_value))) + +#define RPMI_POWER_STATE_GENERIC_ON \ + RPMI_POWER_STATE_PARAM(RPMI_POWER_STATE_CONTEXT_PRESERVED, \ + RPMI_POWER_STATE_VALUE_ON) + +#define RPMI_POWER_STATE_GENERIC_OFF \ + RPMI_POWER_STATE_PARAM(RPMI_POWER_STATE_CONTEXT_PRESERVED, \ + RPMI_POWER_STATE_VALUE_OFF) + +/** + * struct rpmi_ctx - RPMI transport context shared by all power domains + * + * @chan: mailbox channel of the underlying SBI MPXY transport + * @client: mailbox client bound to @chan + * @lock: serialises RPMI transactions on @chan + */ +struct rpmi_ctx { + struct mbox_chan *chan; + struct mbox_client client; + /* serialises RPMI transactions on @chan */ + struct mutex lock; +}; + +/** + * struct rpmi_device_power_domain - describe one available MPXY Device Power Domain + * + * @id: the power domain ID as advertised by PuC + * @transition_latency: worst case transition latency of power domain from one state + * to another + * @dev: device associated with this power domain + * @mpxy_ctx: RPMI transport context used to reach the PuC + * @genpd: generic power domain registered with the genpd core + * @name: device power domain name assigned by PuC + */ +struct rpmi_device_power_domain { + u32 id; + u32 transition_latency; + struct device *dev; + struct rpmi_ctx *mpxy_ctx; + struct generic_pm_domain genpd; + char name[RPMI_PM_DOMAIN_NAME_LEN]; +}; + +#define to_rpmi_pd(gpd) container_of(gpd, struct rpmi_device_power_domain, genpd) + +/* Service: GET_POWER_DOMAINS */ +struct rpmi_pm_get_num_domain_rx { + __le32 status; + __le32 num_domains; +}; + +/* Service: GET_POWER_DOMAIN_ATTRS */ +struct rpmi_pm_get_domain_attrs_tx { + __le32 domain_id; +}; + +/* pm domain attributes response data */ +struct rpmi_pm_get_domain_attrs_rx { + __le32 status; + __le32 flags; + __le32 transition_latency; + char name[RPMI_PM_DOMAIN_NAME_LEN]; +}; + +/* Service: SET_POWER_DOMAIN_STATE */ +struct rpmi_pm_set_power_state_tx { + __le32 domain_id; + __le32 power_state; +}; + +struct rpmi_pm_set_power_state_rx { + __le32 status; +}; + +/* Service: GET_POWER_DOMAIN_STATE */ +struct rpmi_pm_get_power_state_tx { + __le32 domain_id; +}; + +struct rpmi_pm_get_power_state_rx { + __le32 status; + __le32 power_state; +}; + +static int rpmi_pm_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_power_state_get(struct rpmi_device_power_domain *mpxy_pm_domain, + u32 domain_id, u32 *state) +{ + struct rpmi_pm_get_power_state_rx rx = { }; + struct rpmi_pm_get_power_state_tx tx; + struct rpmi_mbox_message msg; + int ret; + + tx.domain_id = cpu_to_le32(domain_id); + + rpmi_mbox_init_send_with_response(&msg, RPMI_DP_SRV_GET_STATE, + &tx, sizeof(tx), &rx, sizeof(rx)); + + ret = rpmi_pm_send_message(mpxy_pm_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; + + *state = le32_to_cpu(rx.power_state); + + return 0; +} + +static int rpmi_power_state_set(struct rpmi_device_power_domain *mpxy_pm_domain, + u32 domain_id, u32 state) +{ + struct rpmi_pm_set_power_state_rx rx = { }; + struct rpmi_pm_set_power_state_tx tx; + struct rpmi_mbox_message msg; + int ret; + + tx.domain_id = cpu_to_le32(domain_id); + tx.power_state = cpu_to_le32(state); + + rpmi_mbox_init_send_with_response(&msg, RPMI_DP_SRV_SET_STATE, + &tx, sizeof(tx), &rx, sizeof(rx)); + ret = rpmi_pm_send_message(mpxy_pm_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; + + return 0; +} + +static int rpmi_pd_power(struct generic_pm_domain *domain, bool power_on) +{ + struct rpmi_device_power_domain *mpxy_pm_domain; + u32 state, ret_state, domain_id; + int ret; + + if (power_on) + state = RPMI_POWER_STATE_GENERIC_ON; + else + state = RPMI_POWER_STATE_GENERIC_OFF; + + mpxy_pm_domain = to_rpmi_pd(domain); + domain_id = mpxy_pm_domain->id; + + ret = rpmi_power_state_set(mpxy_pm_domain, domain_id, state); + if (ret) + return ret; + + ret = rpmi_power_state_get(mpxy_pm_domain, domain_id, &ret_state); + if (ret) + return ret; + + if (FIELD_GET(RPMI_POWER_STATE_VALUE_MASK, ret_state) != + FIELD_GET(RPMI_POWER_STATE_VALUE_MASK, state)) + return -EIO; + + return ret; +} + +static int rpmi_pd_power_on(struct generic_pm_domain *domain) +{ + return rpmi_pd_power(domain, true); +} + +static int rpmi_pd_power_off(struct generic_pm_domain *domain) +{ + return rpmi_pd_power(domain, false); +} + +static int rpmi_pm_get_num_domains(struct rpmi_ctx *mpxy_ctx, u32 *domain) +{ + struct rpmi_pm_get_num_domain_rx rx = { }; + struct rpmi_mbox_message msg; + int ret; + + rpmi_mbox_init_send_with_response(&msg, RPMI_DP_SRV_GET_NUM_DOMAINS, + NULL, 0, &rx, sizeof(rx)); + ret = rpmi_pm_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; +} + +/* obtain the MPXY device power domain attributes */ +static int rpmi_device_power_get_attrs(u32 domain_id, + struct rpmi_device_power_domain *mpxy_pm_domain) +{ + struct rpmi_pm_get_domain_attrs_rx rx = { }; + struct rpmi_pm_get_domain_attrs_tx tx; + struct rpmi_mbox_message msg; + int ret; + + tx.domain_id = cpu_to_le32(domain_id); + + rpmi_mbox_init_send_with_response(&msg, RPMI_DP_SRV_GET_ATTRS, + &tx, sizeof(tx), &rx, sizeof(rx)); + ret = rpmi_pm_send_message(mpxy_pm_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; + + mpxy_pm_domain->transition_latency = le32_to_cpu(rx.transition_latency); + strscpy(mpxy_pm_domain->name, rx.name, sizeof(mpxy_pm_domain->name)); + + return 0; +} + +static int rpmi_pm_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_pm_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 device power service group ID */ + rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SERVICEGROUP_ID); + ret = rpmi_pm_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_DEVICE_POWER) { + dev_dbg(dev, + "service group match failed, expected 0x%x, found 0x%x\n", + RPMI_SRVGRP_DEVICE_POWER, msg.attr.value); + return -EINVAL; + } + + /* Validate device power service group version */ + rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SERVICEGROUP_VERSION); + ret = rpmi_pm_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; + } + + return 0; +} + +static void rpmi_pm_domain_mbox_chan_release(void *data) +{ + mbox_free_channel((struct mbox_chan *)data); +} + +static int rpmi_pm_domain_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct rpmi_device_power_domain *mpxy_pd; + struct genpd_onecell_data *mpxy_pd_data; + struct generic_pm_domain **domains; + struct device *dev = &pdev->dev; + struct rpmi_ctx *mpxy_ctx; + u32 num_domains = 0; + int ret; + u32 i; + + mpxy_ctx = devm_kzalloc(&pdev->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_pm_domain_mbox_chan_release, + mpxy_ctx->chan); + if (ret) + return dev_err_probe(dev, ret, + "failed to add rpmi mbox channel cleanup\n"); + + ret = rpmi_pm_attr_setup(dev, mpxy_ctx); + if (ret) + return dev_err_probe(dev, ret, + "failed to verify RPMI attribute\n"); + + /* Get number of device power domain */ + ret = rpmi_pm_get_num_domains(mpxy_ctx, &num_domains); + if (ret) + return dev_err_probe(dev, ret, + "failed to get number of pm domains\n"); + + if (!num_domains) + return dev_err_probe(dev, -EINVAL, "No PM domains found!\n"); + + dev_info(&pdev->dev, "%u MPXY PM domains are found\n", num_domains); + + mpxy_pd = devm_kcalloc(&pdev->dev, num_domains, sizeof(*mpxy_pd), GFP_KERNEL); + if (!mpxy_pd) + return -ENOMEM; + + domains = devm_kcalloc(&pdev->dev, num_domains, sizeof(*domains), GFP_KERNEL); + if (!domains) + return -ENOMEM; + + mpxy_pd_data = devm_kzalloc(&pdev->dev, sizeof(*mpxy_pd_data), GFP_KERNEL); + if (!mpxy_pd_data) + return -ENOMEM; + + for (i = 0; i < num_domains; i++, mpxy_pd++) { + u32 state; + + mpxy_pd->dev = &pdev->dev; + mpxy_pd->mpxy_ctx = mpxy_ctx; + mpxy_pd->id = i; + + ret = rpmi_device_power_get_attrs(i, mpxy_pd); + if (ret) { + dev_warn(mpxy_pd->dev, + "power domain %u initialization failed\n", + mpxy_pd->id); + domains[i] = NULL; + continue; + } + + ret = rpmi_power_state_get(mpxy_pd, i, &state); + if (ret) { + dev_warn(mpxy_pd->dev, + "failed to get state for power domain %u\n", + mpxy_pd->id); + domains[i] = NULL; + continue; + } + + state = FIELD_GET(RPMI_POWER_STATE_VALUE_MASK, state); + if (state != RPMI_POWER_STATE_VALUE_OFF && + state != RPMI_POWER_STATE_VALUE_ON) { + dev_warn(mpxy_pd->dev, + "unsupported state 0x%x for power domain %u\n", + state, mpxy_pd->id); + domains[i] = NULL; + continue; + } + + mpxy_pd->genpd.name = mpxy_pd->name; + mpxy_pd->genpd.power_off = rpmi_pd_power_off; + mpxy_pd->genpd.power_on = rpmi_pd_power_on; + /* + * Domain names come from the PuC and are not guaranteed to be + * unique or non-empty, so let genpd derive unique device names + * from them rather than using them verbatim. + */ + mpxy_pd->genpd.flags = GENPD_FLAG_DEV_NAME_FW; + + ret = pm_genpd_init(&mpxy_pd->genpd, NULL, + state == RPMI_POWER_STATE_VALUE_OFF); + if (ret) { + dev_warn(mpxy_pd->dev, + "failed to init power domain %u\n", + mpxy_pd->id); + domains[i] = NULL; + continue; + } + + domains[i] = &mpxy_pd->genpd; + } + + mpxy_pd_data->domains = domains; + mpxy_pd_data->num_domains = num_domains; + + ret = of_genpd_add_provider_onecell(np, mpxy_pd_data); + if (ret) { + while (i--) { + if (domains[i]) + pm_genpd_remove(domains[i]); + } + + return dev_err_probe(dev, ret, "failed to add genpd provider\n"); + } + + return 0; +} + +static const struct of_device_id rpmi_pm_domain_of_match[] = { + { .compatible = "riscv,rpmi-device-power" }, + {}, +}; + +static struct platform_driver rpmi_pm_domain_platdrv = { + .driver = { + .name = "riscv-rpmi-device-power", + .of_match_table = rpmi_pm_domain_of_match, + .suppress_bind_attrs = true, + }, + .probe = rpmi_pm_domain_probe, +}; + +builtin_platform_driver(rpmi_pm_domain_platdrv); + +MODULE_AUTHOR("Joshua Yeong "); +MODULE_DESCRIPTION("Device Power Driver based on RPMI message protocol"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mailbox/riscv-rpmi-message.h b/include/linux/mailbox/riscv-rpmi-message.h index e135c6564d0c..d5362b5821f9 100644 --- a/include/linux/mailbox/riscv-rpmi-message.h +++ b/include/linux/mailbox/riscv-rpmi-message.h @@ -93,6 +93,7 @@ static inline int rpmi_to_linux_error(int rpmi_error) /* RPMI service group IDs */ #define RPMI_SRVGRP_SYSTEM_MSI 0x00002 #define RPMI_SRVGRP_CLOCK 0x00008 +#define RPMI_SRVGRP_DEVICE_POWER 0x00009 /* RPMI clock service IDs */ enum rpmi_clock_service_id { @@ -119,6 +120,16 @@ enum rpmi_sysmsi_service_id { RPMI_SYSMSI_SRV_ID_MAX_COUNT }; +/* RPMI device power service IDs */ +enum rpmi_device_power_service_id { + RPMI_DP_SRV_ENABLE_NOTIFICATION = 0x01, + RPMI_DP_SRV_GET_NUM_DOMAINS = 0x02, + RPMI_DP_SRV_GET_ATTRS = 0x03, + RPMI_DP_SRV_SET_STATE = 0x04, + RPMI_DP_SRV_GET_STATE = 0x05, + RPMI_DP_SRV_ID_MAX_COUNT, +}; + /* RPMI Linux mailbox attribute IDs */ enum rpmi_mbox_attribute_id { RPMI_MBOX_ATTR_SPEC_VERSION, From 396a18344d124d0ebd206f46c5d56c8f1e323e72 Mon Sep 17 00:00:00 2001 From: Joshua Yeong Date: Thu, 3 Sep 2026 17:23:47 +0800 Subject: [PATCH 15/36] MAINTAINERS: Add RISC-V RPMI device power driver Add a separate entry for the RPMI device power driver and its bindings, rather than extending the existing RISC-V RPMI and MPXY drivers entry, which covers drivers maintained by others. Signed-off-by: Joshua Yeong Signed-off-by: Ulf Hansson --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb07770..d147c7803be4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23580,6 +23580,14 @@ 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 +M: Joshua Yeong +L: linux-riscv@lists.infradead.org +S: Maintained +F: Documentation/devicetree/bindings/power/riscv,rpmi-device-power.yaml +F: Documentation/devicetree/bindings/power/riscv,rpmi-mpxy-device-power.yaml +F: drivers/pmdomain/riscv/ + RISC-V SPACEMIT SoC Support M: Yixun Lan L: linux-riscv@lists.infradead.org From cbd916ef0c7794993aafbf781ae018c3a8f3c02e Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 3 Sep 2026 13:56:56 +0200 Subject: [PATCH 16/36] pmdomain: renesas: rcar: Make area arrays const Arrays of rcar_sysc_area and rcar_gen4_sysc_area items can be const, unless they are modified by quirk handling. Signed-off-by: Geert Uytterhoeven Reviewed-by: Abel Vesa Signed-off-by: Ulf Hansson --- drivers/pmdomain/renesas/r8a7795-sysc.c | 2 +- drivers/pmdomain/renesas/r8a779a0-sysc.c | 2 +- drivers/pmdomain/renesas/r8a779f0-sysc.c | 2 +- drivers/pmdomain/renesas/r8a779g0-sysc.c | 2 +- drivers/pmdomain/renesas/r8a779h0-sysc.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/pmdomain/renesas/r8a7795-sysc.c b/drivers/pmdomain/renesas/r8a7795-sysc.c index cbe1ff0fc583..2ed377b58846 100644 --- a/drivers/pmdomain/renesas/r8a7795-sysc.c +++ b/drivers/pmdomain/renesas/r8a7795-sysc.c @@ -13,7 +13,7 @@ #include "rcar-sysc.h" -static struct rcar_sysc_area r8a7795_areas[] __initdata = { +static const struct rcar_sysc_area r8a7795_areas[] __initconst = { { "always-on", 0, 0, R8A7795_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, { "ca57-scu", 0x1c0, 0, R8A7795_PD_CA57_SCU, R8A7795_PD_ALWAYS_ON, PD_SCU }, diff --git a/drivers/pmdomain/renesas/r8a779a0-sysc.c b/drivers/pmdomain/renesas/r8a779a0-sysc.c index 54cdf250f7c2..5f84b8d56a32 100644 --- a/drivers/pmdomain/renesas/r8a779a0-sysc.c +++ b/drivers/pmdomain/renesas/r8a779a0-sysc.c @@ -11,7 +11,7 @@ #include "rcar-gen4-sysc.h" -static struct rcar_gen4_sysc_area r8a779a0_areas[] __initdata = { +static const struct rcar_gen4_sysc_area r8a779a0_areas[] __initconst = { { "always-on", R8A779A0_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, { "a3e0", R8A779A0_PD_A3E0, R8A779A0_PD_ALWAYS_ON, PD_SCU }, { "a3e1", R8A779A0_PD_A3E1, R8A779A0_PD_ALWAYS_ON, PD_SCU }, diff --git a/drivers/pmdomain/renesas/r8a779f0-sysc.c b/drivers/pmdomain/renesas/r8a779f0-sysc.c index 6ed13cd1cb24..d22cbc33a604 100644 --- a/drivers/pmdomain/renesas/r8a779f0-sysc.c +++ b/drivers/pmdomain/renesas/r8a779f0-sysc.c @@ -11,7 +11,7 @@ #include "rcar-gen4-sysc.h" -static struct rcar_gen4_sysc_area r8a779f0_areas[] __initdata = { +static const struct rcar_gen4_sysc_area r8a779f0_areas[] __initconst = { { "always-on", R8A779F0_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, { "a3e0", R8A779F0_PD_A3E0, R8A779F0_PD_ALWAYS_ON, PD_SCU }, { "a3e1", R8A779F0_PD_A3E1, R8A779F0_PD_ALWAYS_ON, PD_SCU }, diff --git a/drivers/pmdomain/renesas/r8a779g0-sysc.c b/drivers/pmdomain/renesas/r8a779g0-sysc.c index 249cf43af45b..96d39a3ac460 100644 --- a/drivers/pmdomain/renesas/r8a779g0-sysc.c +++ b/drivers/pmdomain/renesas/r8a779g0-sysc.c @@ -11,7 +11,7 @@ #include "rcar-gen4-sysc.h" -static struct rcar_gen4_sysc_area r8a779g0_areas[] __initdata = { +static const struct rcar_gen4_sysc_area r8a779g0_areas[] __initconst = { { "always-on", R8A779G0_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, { "a3e0", R8A779G0_PD_A3E0, R8A779G0_PD_ALWAYS_ON, PD_SCU }, { "a2e0d0", R8A779G0_PD_A2E0D0, R8A779G0_PD_A3E0, PD_SCU }, diff --git a/drivers/pmdomain/renesas/r8a779h0-sysc.c b/drivers/pmdomain/renesas/r8a779h0-sysc.c index e13372cb80ee..3775735854f2 100644 --- a/drivers/pmdomain/renesas/r8a779h0-sysc.c +++ b/drivers/pmdomain/renesas/r8a779h0-sysc.c @@ -11,7 +11,7 @@ #include "rcar-gen4-sysc.h" -static struct rcar_gen4_sysc_area r8a779h0_areas[] __initdata = { +static const struct rcar_gen4_sysc_area r8a779h0_areas[] __initconst = { { "always-on", R8A779H0_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, { "c4", R8A779H0_PD_C4, R8A779H0_PD_ALWAYS_ON }, { "a2e0d0", R8A779H0_PD_A2E0D0, R8A779H0_PD_C4, PD_SCU }, From d3efba7921f3ebebd91fc481910a428c78414fd8 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 6 Sep 2026 11:38:06 +0800 Subject: [PATCH 17/36] pmdomain: imx: Propagate SCU provider registration failures imx_scu_init_pm_domains() ignores of_genpd_add_provider_onecell() and returns success even when none of the initialized domains are published to DT consumers. Return the provider error and remove the initialized domains in reverse order before devm releases their backing storage on probe failure. The issue was found by our static-analysis tool and manually reviewed. Fixes: c800cd7824bd ("firmware: imx: add SCU power domain driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/scu-pd.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/pmdomain/imx/scu-pd.c b/drivers/pmdomain/imx/scu-pd.c index e3f9e741daf7..05d4364f34be 100644 --- a/drivers/pmdomain/imx/scu-pd.c +++ b/drivers/pmdomain/imx/scu-pd.c @@ -474,7 +474,7 @@ static int imx_scu_init_pm_domains(struct device *dev, struct genpd_onecell_data *pd_data; struct imx_sc_pm_domain *sc_pd; u32 count = 0; - int i, j; + int i, j, ret; for (i = 0; i < pd_soc->num_ranges; i++) count += pd_ranges[i].num; @@ -503,7 +503,13 @@ static int imx_scu_init_pm_domains(struct device *dev, pd_data->num_domains = count; pd_data->xlate = imx_scu_pd_xlate; - of_genpd_add_provider_onecell(dev->of_node, pd_data); + ret = of_genpd_add_provider_onecell(dev->of_node, pd_data); + if (ret) { + while (count) + pm_genpd_remove(domains[--count]); + + return ret; + } return 0; } From f51306eaea241957fe9c17ff583317dd4788d492 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 11:42:37 +0530 Subject: [PATCH 18/36] pmdomain: fix typo "releated" in comment Correct "releated" to "related", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index e8473f5339e5..f3d9fd7659bd 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -946,7 +946,7 @@ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd) * genpd_power_off - Remove power from a given PM domain. * @genpd: PM domain to power down. * @one_dev_on: If invoked from genpd's ->runtime_suspend|resume() callback, the - * RPM status of the releated device is in an intermediate state, not yet turned + * RPM status of the related device is in an intermediate state, not yet turned * into RPM_SUSPENDED. This means genpd_power_off() must allow one device to not * be RPM_SUSPENDED, while it tries to power off the PM domain. * @depth: nesting count for lockdep. From ddacdefc38200bafa35e79925bbc1f4b1fdf41a8 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 7 Sep 2026 16:32:01 +0200 Subject: [PATCH 19/36] pmdomain: renesas: r8a78000: Fix lockdep false positive Lockdep reports a possible circular locking dependency when attaching devices to an MDLC PM domain: - During creation of the singleton PM domain: A. r8a78000_genpd_always_on_singleton() takes r8a78000_mdlc_lock, B. pm_genpd_init() takes gpd_list_lock. - During attachment of devices to PM domains: A. pm_genpd_add_device() takes gpd_list_lock, B. r8a78000_mdlc_attach_dev() takes r8a78000_mdlc_lock. As the former is done only once, before any devices are attached, such a AB-BA deadlock cannot really happen. Fix this false positive by introducing a separate lock to protect the creation of the singleton domain. Fixes: 9f79f93963b0 ("pmdomain: renesas: Add R-Car X5H MDLC driver") Signed-off-by: Geert Uytterhoeven Reviewed-by: Abel Vesa Signed-off-by: Ulf Hansson --- drivers/pmdomain/renesas/r8a78000-mdlc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/pmdomain/renesas/r8a78000-mdlc.c b/drivers/pmdomain/renesas/r8a78000-mdlc.c index 2668fc2ac9cb..1fd71bb3da77 100644 --- a/drivers/pmdomain/renesas/r8a78000-mdlc.c +++ b/drivers/pmdomain/renesas/r8a78000-mdlc.c @@ -45,8 +45,9 @@ struct r8a78000_mdlc_priv { }; static struct generic_pm_domain *r8a78000_genpd_always_on; + static HLIST_HEAD(r8a78000_mdlc_list); -static DEFINE_MUTEX(r8a78000_mdlc_lock); /* protects the two above */ +static DEFINE_MUTEX(r8a78000_mdlc_lock); /* protects the list above */ static struct generic_pm_domain *r8a78000_genpd_xlate( const struct of_phandle_args *spec, void *data) @@ -191,10 +192,11 @@ static void r8a78000_genpd_del_provider(void *data) static int r8a78000_genpd_always_on_singleton(struct device *dev) { + static DEFINE_MUTEX(singleton_lock); struct generic_pm_domain *genpd; int ret; - guard(mutex)(&r8a78000_mdlc_lock); + guard(mutex)(&singleton_lock); if (r8a78000_genpd_always_on) return 0; From 46ff460038d6c90ff491489e0ce5c5161c6b589a Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Mon, 14 Sep 2026 12:24:25 +0530 Subject: [PATCH 20/36] pmdomain: core: Fall back to node name for idle states The dt_idle_states uses idle-state-name when available and when missing falls back to the node name. Do the same for domain-idle-states. This lets pmdomain idle_states_desc debugfs show a name even when idle-state-name is omitted. Signed-off-by: Maulik Shah Reviewed-by: Abel Vesa Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index f3d9fd7659bd..b99c2dcf3d8f 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -3657,7 +3657,10 @@ static int genpd_parse_state(struct genpd_power_state *genpd_state, if (!err) genpd_state->residency_ns = 1000LL * residency; - of_property_read_string(state_node, "idle-state-name", &genpd_state->name); + err = of_property_read_string(state_node, "idle-state-name", + &genpd_state->name); + if (err) + genpd_state->name = state_node->name; genpd_state->power_on_latency_ns = 1000LL * exit_latency; genpd_state->power_off_latency_ns = 1000LL * entry_latency; From 53e506883206752e3861eb26f5feb2a587c5a9cb Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Sun, 20 Sep 2026 20:59:27 +0300 Subject: [PATCH 21/36] pmdomain: rockchip: Fix domain check in rockchip_pm_add_one_domain() In rockchip_pm_add_one_domain(), there's the check (most probably for the case where the "reg" prop has an unexpected value?) which doesn't make much sense as the variable pd_info will be NULL iff pmu->info-> domain_info is NULL and the variable id is 0. What does make sense there is checking pd_info->pwr_mask and pd_info->req_mask which can't both be 0 for a valid domain. Found by Linux Verification Center (linuxtesting.org) with the Svace static analysis tool. Signed-off-by: Sergey Shtylyov Reviewed-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index ba66ae719428..69792e2b215b 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -809,7 +809,7 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu, return 0; pd_info = &pmu->info->domain_info[id]; - if (!pd_info) { + if (!pd_info->pwr_mask && !pd_info->req_mask) { dev_err(pmu->dev, "%pOFn: undefined domain id %d\n", node, id); return -EINVAL; From 8c87f6d0a8aa6b4373bd9e88cf1e1dba3eda0273 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 21 Sep 2026 10:37:06 +0700 Subject: [PATCH 22/36] pmdomain: imx: gpcv2: Use pm_runtime_resume_and_get() Replace the open-coded pm_runtime_get_sync() + pm_runtime_put_noidle() error handling pattern with pm_runtime_resume_and_get(), which already does this internally. No functional change. Found by manual code inspection. Signed-off-by: bui duc phuc Reviewed-by: Frank Li Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/gpcv2.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/drivers/pmdomain/imx/gpcv2.c b/drivers/pmdomain/imx/gpcv2.c index a829f8da5be7..6a54ee734514 100644 --- a/drivers/pmdomain/imx/gpcv2.c +++ b/drivers/pmdomain/imx/gpcv2.c @@ -317,11 +317,9 @@ static int imx_pgc_power_up(struct generic_pm_domain *genpd) u32 reg_val, pgc; int ret; - ret = pm_runtime_get_sync(domain->dev); - if (ret < 0) { - pm_runtime_put_noidle(domain->dev); + ret = pm_runtime_resume_and_get(domain->dev); + if (ret < 0) return ret; - } if (!IS_ERR(domain->regulator)) { ret = regulator_enable(domain->regulator); @@ -1397,21 +1395,13 @@ static void imx_pgc_domain_remove(struct platform_device *pdev) #ifdef CONFIG_PM_SLEEP static int imx_pgc_domain_suspend(struct device *dev) { - int ret; - /* * This may look strange, but is done so the generic PM_SLEEP code * can power down our domain and more importantly power it up again * after resume, without tripping over our usage of runtime PM to * power up/down the nested domains. */ - ret = pm_runtime_get_sync(dev); - if (ret < 0) { - pm_runtime_put_noidle(dev); - return ret; - } - - return 0; + return pm_runtime_resume_and_get(dev); } static int imx_pgc_domain_resume(struct device *dev) From 3d4f1d7bbd4b23a1b304a59e620608d87d1e09cb Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 21 Sep 2026 10:37:07 +0700 Subject: [PATCH 23/36] pmdomain: imx8m-blk-ctrl: Use pm_runtime_resume_and_get() Replace the open-coded pm_runtime_get_sync() + pm_runtime_put_noidle() error handling pattern with pm_runtime_resume_and_get(), which already does this internally. No functional change. Found by manual code inspection. Signed-off-by: bui duc phuc Reviewed-by: Frank Li Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/imx8m-blk-ctrl.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/pmdomain/imx/imx8m-blk-ctrl.c b/drivers/pmdomain/imx/imx8m-blk-ctrl.c index 479789009c7f..fe083b1e7251 100644 --- a/drivers/pmdomain/imx/imx8m-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx8m-blk-ctrl.c @@ -99,9 +99,8 @@ static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd) int ret; /* make sure bus domain is awake */ - ret = pm_runtime_get_sync(bc->bus_power_dev); + ret = pm_runtime_resume_and_get(bc->bus_power_dev); if (ret < 0) { - pm_runtime_put_noidle(bc->bus_power_dev); dev_err(bc->dev, "failed to power up bus domain\n"); return ret; } @@ -382,20 +381,16 @@ static int imx8m_blk_ctrl_suspend(struct device *dev) * in the system suspend/resume paths due to the device parent/child * hierarchy. */ - ret = pm_runtime_get_sync(bc->bus_power_dev); - if (ret < 0) { - pm_runtime_put_noidle(bc->bus_power_dev); + ret = pm_runtime_resume_and_get(bc->bus_power_dev); + if (ret < 0) return ret; - } for (i = 0; i < bc->onecell_data.num_domains; i++) { struct imx8m_blk_ctrl_domain *domain = &bc->domains[i]; - ret = pm_runtime_get_sync(domain->power_dev); - if (ret < 0) { - pm_runtime_put_noidle(domain->power_dev); + ret = pm_runtime_resume_and_get(domain->power_dev); + if (ret < 0) goto out_fail; - } } return 0; From 38f9175fc8bd6228768ed3903a1f67d9e902de05 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 21 Sep 2026 10:37:08 +0700 Subject: [PATCH 24/36] pmdomain: imx93-blk-ctrl: Use pm_runtime_resume_and_get() Replace the open-coded pm_runtime_get_sync() + pm_runtime_put_noidle() error handling pattern with pm_runtime_resume_and_get(), which already does this internally. No functional change. Found by manual code inspection. Signed-off-by: bui duc phuc Reviewed-by: Frank Li Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/imx93-blk-ctrl.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/pmdomain/imx/imx93-blk-ctrl.c b/drivers/pmdomain/imx/imx93-blk-ctrl.c index 243ce939ba68..6d4c55a85c64 100644 --- a/drivers/pmdomain/imx/imx93-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx93-blk-ctrl.c @@ -152,9 +152,8 @@ static int imx93_blk_ctrl_power_on(struct generic_pm_domain *genpd) return ret; } - ret = pm_runtime_get_sync(bc->dev); + ret = pm_runtime_resume_and_get(bc->dev); if (ret < 0) { - pm_runtime_put_noidle(bc->dev); dev_err(bc->dev, "failed to power up domain\n"); goto disable_clk; } From 435e05b1ef3cdcba4752e8d35f44571042ff1afa Mon Sep 17 00:00:00 2001 From: Ayman Chaudhry Date: Mon, 21 Sep 2026 13:44:21 +0000 Subject: [PATCH 25/36] dt-bindings: power: renesas,rcar-sysc: Document vendor-prefixed header paths Update the top-level description to also explicitly mention vendor-prefixed header paths and what the asterisk wildcard represents. Signed-off-by: Ayman Chaudhry Reviewed-by: Geert Uytterhoeven Signed-off-by: Ulf Hansson --- .../devicetree/bindings/power/renesas,rcar-sysc.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/power/renesas,rcar-sysc.yaml b/Documentation/devicetree/bindings/power/renesas,rcar-sysc.yaml index b67aa170b2c1..68b4696d7c59 100644 --- a/Documentation/devicetree/bindings/power/renesas,rcar-sysc.yaml +++ b/Documentation/devicetree/bindings/power/renesas,rcar-sysc.yaml @@ -13,8 +13,11 @@ maintainers: description: | The R-Car (RZ/G) System Controller provides power management for the CPU cores and various coprocessors. - The power domain IDs for consumers are defined in header files: - include/dt-bindings/power/r8*-sysc.h + The power domain IDs for consumers are defined in the below header files + (where the asterisk serves as a wildcard matching the specific SoC + identifier): + include/dt-bindings/power/r8*-sysc.h or + include/dt-bindings/power/renesas,r8*-sysc.h properties: compatible: From 51c659ebdc10b1c4d1a0a83578868462ecb867f3 Mon Sep 17 00:00:00 2001 From: Ayman Chaudhry Date: Mon, 21 Sep 2026 13:44:22 +0000 Subject: [PATCH 26/36] dt-bindings: power: Add r8a774a3 SYSC power domain definitions Add power domain indices for the Renesas RZ/G2M v3.0 (a.k.a. R8A774A3) SoC. Signed-off-by: Ayman Chaudhry Reviewed-by: Geert Uytterhoeven Signed-off-by: Ulf Hansson --- .../dt-bindings/power/renesas,r8a774a3-sysc.h | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 include/dt-bindings/power/renesas,r8a774a3-sysc.h diff --git a/include/dt-bindings/power/renesas,r8a774a3-sysc.h b/include/dt-bindings/power/renesas,r8a774a3-sysc.h new file mode 100644 index 000000000000..ddee6812e9a4 --- /dev/null +++ b/include/dt-bindings/power/renesas,r8a774a3-sysc.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (C) 2026 Renesas Electronics Corp. + */ +#ifndef __DT_BINDINGS_POWER_RENESAS_R8A774A3_SYSC_H__ +#define __DT_BINDINGS_POWER_RENESAS_R8A774A3_SYSC_H__ + +/* + * These power domain indices match the numbers of the interrupt bits + * representing the power areas in the various Interrupt Registers + * (e.g. SYSCISR, Interrupt Status Register) + */ + +#define R8A774A3_PD_CA57_CPU0 0 +#define R8A774A3_PD_CA57_CPU1 1 +#define R8A774A3_PD_CA53_CPU0 5 +#define R8A774A3_PD_CA53_CPU1 6 +#define R8A774A3_PD_CA53_CPU2 7 +#define R8A774A3_PD_CA53_CPU3 8 +#define R8A774A3_PD_CA57_SCU 12 +#define R8A774A3_PD_A3VC 14 +#define R8A774A3_PD_3DG_A 17 +#define R8A774A3_PD_3DG_B 18 +#define R8A774A3_PD_CA53_SCU 21 +#define R8A774A3_PD_A2VC1 26 + +/* Always-on power area */ +#define R8A774A3_PD_ALWAYS_ON 32 + +#endif /* __DT_BINDINGS_POWER_RENESAS_R8A774A3_SYSC_H__ */ From 7260dfd672d89982f0819221788c3b2ba97b5b28 Mon Sep 17 00:00:00 2001 From: Ayman Chaudhry Date: Mon, 21 Sep 2026 13:44:23 +0000 Subject: [PATCH 27/36] pmdomain: renesas: rcar-sys: Add r8a774a3 support Add support for Renesas RZ/G2M v3.0 (a.k.a. R8A774A3) SoC power areas to the R-Car SYSC driver. The RZ/G2M v3.0 SYSC is similar to the RZ/G2M v1.3 SYSC, however it lacks the A2VC0 power area. Signed-off-by: Ayman Chaudhry Reviewed-by: Geert Uytterhoeven Signed-off-by: Ulf Hansson --- drivers/pmdomain/renesas/Kconfig | 4 +++ drivers/pmdomain/renesas/Makefile | 1 + drivers/pmdomain/renesas/r8a774a3-sysc.c | 45 ++++++++++++++++++++++++ drivers/pmdomain/renesas/rcar-sysc.c | 3 ++ drivers/pmdomain/renesas/rcar-sysc.h | 1 + 5 files changed, 54 insertions(+) create mode 100644 drivers/pmdomain/renesas/r8a774a3-sysc.c diff --git a/drivers/pmdomain/renesas/Kconfig b/drivers/pmdomain/renesas/Kconfig index f2f52d3c29a0..df19b95c4655 100644 --- a/drivers/pmdomain/renesas/Kconfig +++ b/drivers/pmdomain/renesas/Kconfig @@ -37,6 +37,10 @@ config SYSC_R8A774A1 bool "System Controller support for R8A774A1 (RZ/G2M)" if COMPILE_TEST select SYSC_RCAR +config SYSC_R8A774A3 + bool "System Controller support for R8A774A3 (RZ/G2M v3.0)" if COMPILE_TEST + select SYSC_RCAR + config SYSC_R8A774B1 bool "System Controller support for R8A774B1 (RZ/G2N)" if COMPILE_TEST select SYSC_RCAR diff --git a/drivers/pmdomain/renesas/Makefile b/drivers/pmdomain/renesas/Makefile index 17849aad37a5..8eda820478e4 100644 --- a/drivers/pmdomain/renesas/Makefile +++ b/drivers/pmdomain/renesas/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_SYSC_R8A7743) += r8a7743-sysc.o obj-$(CONFIG_SYSC_R8A7745) += r8a7745-sysc.o obj-$(CONFIG_SYSC_R8A77470) += r8a77470-sysc.o obj-$(CONFIG_SYSC_R8A774A1) += r8a774a1-sysc.o +obj-$(CONFIG_SYSC_R8A774A3) += r8a774a3-sysc.o obj-$(CONFIG_SYSC_R8A774B1) += r8a774b1-sysc.o obj-$(CONFIG_SYSC_R8A774C0) += r8a774c0-sysc.o obj-$(CONFIG_SYSC_R8A774E1) += r8a774e1-sysc.o diff --git a/drivers/pmdomain/renesas/r8a774a3-sysc.c b/drivers/pmdomain/renesas/r8a774a3-sysc.c new file mode 100644 index 000000000000..58c98700bd55 --- /dev/null +++ b/drivers/pmdomain/renesas/r8a774a3-sysc.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G2M v3.0 System Controller + * Copyright (C) 2026 Renesas Electronics Corp. + * + * Based on Renesas R-Car M3-W System Controller + * Copyright (C) 2016 Glider bvba + */ + +#include + +#include + +#include "rcar-sysc.h" + +static const struct rcar_sysc_area r8a774a3_areas[] __initconst = { + { "always-on", 0, 0, R8A774A3_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, + { "ca57-scu", 0x1c0, 0, R8A774A3_PD_CA57_SCU, R8A774A3_PD_ALWAYS_ON, + PD_SCU }, + { "ca57-cpu0", 0x80, 0, R8A774A3_PD_CA57_CPU0, R8A774A3_PD_CA57_SCU, + PD_CPU_NOCR }, + { "ca57-cpu1", 0x80, 1, R8A774A3_PD_CA57_CPU1, R8A774A3_PD_CA57_SCU, + PD_CPU_NOCR }, + { "ca53-scu", 0x140, 0, R8A774A3_PD_CA53_SCU, R8A774A3_PD_ALWAYS_ON, + PD_SCU }, + { "ca53-cpu0", 0x200, 0, R8A774A3_PD_CA53_CPU0, R8A774A3_PD_CA53_SCU, + PD_CPU_NOCR }, + { "ca53-cpu1", 0x200, 1, R8A774A3_PD_CA53_CPU1, R8A774A3_PD_CA53_SCU, + PD_CPU_NOCR }, + { "ca53-cpu2", 0x200, 2, R8A774A3_PD_CA53_CPU2, R8A774A3_PD_CA53_SCU, + PD_CPU_NOCR }, + { "ca53-cpu3", 0x200, 3, R8A774A3_PD_CA53_CPU3, R8A774A3_PD_CA53_SCU, + PD_CPU_NOCR }, + { "a3vc", 0x380, 0, R8A774A3_PD_A3VC, R8A774A3_PD_ALWAYS_ON }, + { "a2vc1", 0x3c0, 1, R8A774A3_PD_A2VC1, R8A774A3_PD_A3VC }, + { "3dg-a", 0x100, 0, R8A774A3_PD_3DG_A, R8A774A3_PD_ALWAYS_ON }, + { "3dg-b", 0x100, 1, R8A774A3_PD_3DG_B, R8A774A3_PD_3DG_A }, +}; + +const struct rcar_sysc_info r8a774a3_sysc_info __initconst = { + .areas = r8a774a3_areas, + .num_areas = ARRAY_SIZE(r8a774a3_areas), + .extmask_offs = 0x2f8, + .extmask_val = BIT(0), +}; diff --git a/drivers/pmdomain/renesas/rcar-sysc.c b/drivers/pmdomain/renesas/rcar-sysc.c index bd7bb9cbd9da..3c8a44c6d772 100644 --- a/drivers/pmdomain/renesas/rcar-sysc.c +++ b/drivers/pmdomain/renesas/rcar-sysc.c @@ -284,6 +284,9 @@ static const struct of_device_id rcar_sysc_matches[] __initconst = { #ifdef CONFIG_SYSC_R8A774A1 { .compatible = "renesas,r8a774a1-sysc", .data = &r8a774a1_sysc_info }, #endif +#ifdef CONFIG_SYSC_R8A774A3 + { .compatible = "renesas,r8a774a3-sysc", .data = &r8a774a3_sysc_info }, +#endif #ifdef CONFIG_SYSC_R8A774B1 { .compatible = "renesas,r8a774b1-sysc", .data = &r8a774b1_sysc_info }, #endif diff --git a/drivers/pmdomain/renesas/rcar-sysc.h b/drivers/pmdomain/renesas/rcar-sysc.h index ef598ecc710a..b264e727883b 100644 --- a/drivers/pmdomain/renesas/rcar-sysc.h +++ b/drivers/pmdomain/renesas/rcar-sysc.h @@ -61,6 +61,7 @@ extern const struct rcar_sysc_info r8a7743_sysc_info; extern const struct rcar_sysc_info r8a7745_sysc_info; extern const struct rcar_sysc_info r8a77470_sysc_info; extern const struct rcar_sysc_info r8a774a1_sysc_info; +extern const struct rcar_sysc_info r8a774a3_sysc_info; extern const struct rcar_sysc_info r8a774b1_sysc_info; extern const struct rcar_sysc_info r8a774c0_sysc_info; extern const struct rcar_sysc_info r8a774e1_sysc_info; From 4ff08254de73780e9a0d72a14060e5957070ff12 Mon Sep 17 00:00:00 2001 From: "Kevin Hilman (TI)" Date: Thu, 24 Sep 2026 13:29:56 -0700 Subject: [PATCH 28/36] PM / QoS: add flag to indicate latency applies system-wide By default, the QoS resume latency currenly only applied to runtime PM decisions. Add new PM_QOS_FLAG_LATENCY_SYS flag to indicate that the resume latency QoS constraint should be applied to system-wide PM *in addition to* runtime PM. Acked-by: Rafael J. Wysocki (Intel) Reviewed-by: Kendall Willis Signed-off-by: Kevin Hilman (TI) Signed-off-by: Ulf Hansson --- include/linux/pm_qos.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h index 6cea4455f867..aededda52b6b 100644 --- a/include/linux/pm_qos.h +++ b/include/linux/pm_qos.h @@ -37,6 +37,8 @@ enum pm_qos_flags_status { #define PM_QOS_LATENCY_TOLERANCE_NO_CONSTRAINT (-1) #define PM_QOS_FLAG_NO_POWER_OFF (1 << 0) +/* latency value applies to system-wide suspend/s2idle */ +#define PM_QOS_FLAG_LATENCY_SYS (2 << 0) enum pm_qos_type { PM_QOS_UNITIALIZED, From b7cbbc33bc4eaa9c1fd4485527ab167a736d5505 Mon Sep 17 00:00:00 2001 From: "Kevin Hilman (TI)" Date: Thu, 24 Sep 2026 13:29:57 -0700 Subject: [PATCH 29/36] PM / QoS: add lockless read for flags Add a lockless read for QoS flags similar to the lockless read for resume latency (dev_pm_qos_raw_resume_latency) which may be called from atomic context (e.g. genpd governors running under a raw spinlock or in the syscore suspend path), where taking that sleeping lock would be invalid on PREEMPT_RT. dev_pm_qos_raw_flags() now reads dev->power.qos->flags.effective_flags with READ_ONCE() so also add corresponding WRITE_ONCE() to the two effective_flags writes, the same way pm_qos_set_value() already does for target_value/pm_qos_read_value(). Reviewed-by: Kendall Willis Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Kevin Hilman (TI) Signed-off-by: Ulf Hansson --- include/linux/pm_qos.h | 7 +++++++ kernel/power/qos.c | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h index aededda52b6b..439a9e779d81 100644 --- a/include/linux/pm_qos.h +++ b/include/linux/pm_qos.h @@ -219,6 +219,12 @@ static inline s32 dev_pm_qos_raw_resume_latency(struct device *dev) PM_QOS_RESUME_LATENCY_NO_CONSTRAINT : pm_qos_read_value(&dev->power.qos->resume_latency); } + +static inline s32 dev_pm_qos_raw_flags(struct device *dev) +{ + return IS_ERR_OR_NULL(dev->power.qos) ? + 0 : READ_ONCE(dev->power.qos->flags.effective_flags); +} #else static inline enum pm_qos_flags_status __dev_pm_qos_flags(struct device *dev, s32 mask) @@ -300,6 +306,7 @@ static inline s32 dev_pm_qos_raw_resume_latency(struct device *dev) { return PM_QOS_RESUME_LATENCY_NO_CONSTRAINT; } +static inline s32 dev_pm_qos_raw_flags(struct device *dev) { return 0; } #endif static inline int freq_qos_request_active(struct freq_qos_request *req) diff --git a/kernel/power/qos.c b/kernel/power/qos.c index 1944dbeb0d4c..c241da5bccf6 100644 --- a/kernel/power/qos.c +++ b/kernel/power/qos.c @@ -159,7 +159,7 @@ static void pm_qos_flags_remove_req(struct pm_qos_flags *pqf, list_for_each_entry(req, &pqf->list, node) val |= req->flags; - pqf->effective_flags = val; + WRITE_ONCE(pqf->effective_flags, val); } /** @@ -193,7 +193,7 @@ bool pm_qos_update_flags(struct pm_qos_flags *pqf, req->flags = val; INIT_LIST_HEAD(&req->node); list_add_tail(&req->node, &pqf->list); - pqf->effective_flags |= val; + WRITE_ONCE(pqf->effective_flags, pqf->effective_flags | val); break; default: /* no action */ From 44ff5057d75c5680090743233de5a721eb9dabe4 Mon Sep 17 00:00:00 2001 From: "Kevin Hilman (TI)" Date: Thu, 24 Sep 2026 13:29:58 -0700 Subject: [PATCH 30/36] pmdomain: core: add genpd_for_each_child() helper Add a new internal helper function genpd_for_each_child() that recursively iterates over all devices in a PM domain and its child domains (subdomains). This helper is useful for governors and other core PM domain code that needs to examine or apply operations to all devices within a domain hierarchy. The function takes a callback that is invoked for each device, and supports early termination if the callback returns a non-zero value. The helper is defined in a new internal header drivers/pmdomain/core.h and implemented in drivers/pmdomain/core.c, making it available to other PM domain subsystem components. The first user of this helper is the cpu_system_power_down_ok() governor function, which uses it to check device QoS latency constraints across the entire domain hierarchy. Reviewed-by: Abel Vesa Signed-off-by: Kevin Hilman (TI) Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 44 +++++++++++++++++++++++++++++++++++++++++ drivers/pmdomain/core.h | 18 +++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 drivers/pmdomain/core.h diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index b99c2dcf3d8f..964e44c2f78f 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -27,6 +27,7 @@ #include #include +#include "core.h" /* Provides a unique ID for each genpd device */ static DEFINE_IDA(genpd_ida); @@ -283,6 +284,49 @@ static void genpd_sd_counter_inc(struct generic_pm_domain *genpd) smp_mb__after_atomic(); } +/** + * genpd_for_each_child - Recursively iterate over all devices + * in a PM domain and its subdomains. + * @genpd: PM domain to iterate over. + * @fn: Callback function to invoke for each device. + * @data: Data to pass to the callback function. + * + * This function recursively walks through all devices in the given PM domain + * and all devices in its child PM domains (subdomains). For each device found, + * the callback function @fn is invoked with the device and @data as arguments. + * + * Note: this function is inteded for use by the core and governors, + * not for pmdomain providers. + * + * Returns: 0 on success, or the first non-zero value returned by @fn. + */ +int genpd_for_each_child(struct generic_pm_domain *genpd, + int (*fn)(struct device *dev, void *data), + void *data) +{ + struct pm_domain_data *pdd; + struct gpd_link *link; + int ret; + + /* First, iterate over all devices in this domain */ + list_for_each_entry(pdd, &genpd->dev_list, list_node) { + ret = fn(pdd->dev, data); + if (ret) + return ret; + } + + /* Then, recursively iterate over all child domains (subdomains) */ + list_for_each_entry(link, &genpd->parent_links, parent_node) { + struct generic_pm_domain *child_pd = link->child; + + ret = genpd_for_each_child(child_pd, fn, data); + if (ret) + return ret; + } + + return 0; +} + #ifdef CONFIG_DEBUG_FS static struct dentry *genpd_debugfs_dir; diff --git a/drivers/pmdomain/core.h b/drivers/pmdomain/core.h new file mode 100644 index 000000000000..d1df535a834a --- /dev/null +++ b/drivers/pmdomain/core.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Internal header for PM domain core + * + * Copyright (C) 2026 Kevin Hilman , Texas Instruments + */ + +#ifndef __PM_DOMAIN_CORE_H__ +#define __PM_DOMAIN_CORE_H__ + +struct device; +struct generic_pm_domain; + +int genpd_for_each_child(struct generic_pm_domain *genpd, + int (*fn)(struct device *dev, void *data), + void *data); + +#endif /* __PM_DOMAIN_CORE_H__ */ From 06a695cdefc7a435c7a045e0b759d161589d294f Mon Sep 17 00:00:00 2001 From: "Kevin Hilman (TI)" Date: Thu, 24 Sep 2026 13:29:59 -0700 Subject: [PATCH 31/36] pmdomain: core: add support system-wide resume latency constraints In addition to checking for CPU latency constraints when checking if OK to power down a domain, also check for QoS latency constraints in all devices of a domain and use that in determining the final latency constraint to use for the domain. Since cpu_system_power_down_ok() is used for system-wide suspend, the per-device constratints are only relevant if the LATENCY_SYS QoS flag is set. Reviewed-by: Abel Vesa Reviewed-by: Kendall Willis Signed-off-by: Kevin Hilman (TI) Signed-off-by: Ulf Hansson --- drivers/pmdomain/governor.c | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/pmdomain/governor.c b/drivers/pmdomain/governor.c index 96737abbb496..38bba24ec26e 100644 --- a/drivers/pmdomain/governor.c +++ b/drivers/pmdomain/governor.c @@ -13,6 +13,8 @@ #include #include +#include "core.h" + static int dev_update_qos_constraint(struct device *dev, void *data) { s64 *constraint_ns_p = data; @@ -425,17 +427,60 @@ static bool cpu_power_down_ok(struct dev_pm_domain *pd) return true; } +/** + * check_device_qos_latency - Callback to check device QoS latency constraints + * @dev: Device to check + * @data: Pointer to s32 variable holding minimum latency found so far + * + * This callback checks if the device has a system-wide resume latency QoS + * constraint and updates the minimum latency if this device has a stricter + * constraint. + * + * The system-wide flag is checked first so that devices that have not opted + * in only incur a single lockless read. + * + * Returns: 0 to continue iteration. + */ +static int check_device_qos_latency(struct device *dev, void *data) +{ + s32 *min_dev_latency = data; + s32 dev_latency; + + if (!(dev_pm_qos_raw_flags(dev) & PM_QOS_FLAG_LATENCY_SYS)) + return 0; + + dev_latency = dev_pm_qos_raw_resume_latency(dev); + if (dev_latency != PM_QOS_RESUME_LATENCY_NO_CONSTRAINT) { + if (dev_latency < *min_dev_latency) + *min_dev_latency = dev_latency; + } + + return 0; +} + static bool cpu_system_power_down_ok(struct dev_pm_domain *pd) { s64 constraint_ns = cpu_wakeup_latency_qos_limit() * NSEC_PER_USEC; struct generic_pm_domain *genpd = pd_to_genpd(pd); int state_idx = genpd->state_count - 1; + s32 min_dev_latency = PM_QOS_RESUME_LATENCY_NO_CONSTRAINT; + s64 min_dev_latency_ns; if (!(genpd->flags & GENPD_FLAG_CPU_DOMAIN)) { genpd->state_idx = state_idx; return true; } + genpd_for_each_child(genpd, check_device_qos_latency, + &min_dev_latency); + + /* If device latency < CPU wakeup latency, use it instead */ + if (min_dev_latency != PM_QOS_RESUME_LATENCY_NO_CONSTRAINT) { + min_dev_latency_ns = (s64)min_dev_latency * NSEC_PER_USEC; + if (min_dev_latency_ns < constraint_ns) + constraint_ns = min_dev_latency_ns; + } + /* Find the deepest state for the latency constraint. */ while (state_idx >= 0) { s64 latency_ns = genpd->states[state_idx].power_off_latency_ns + From 3b43606fcca57e1781d9d3ed5bec8d541d1e1dee Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 21 Sep 2026 14:52:53 +0800 Subject: [PATCH 32/36] pmdomain: rockchip: drop duplicated errno from supply failure message dev_err_probe() already appends the error code. Signed-off-by: Shawn Lin Reviewed-by: Sebastian Reichel Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index bb63bf1baf2f..db3879846db6 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -721,7 +721,7 @@ static int rockchip_pd_power_on(struct generic_pm_domain *domain) ret = rockchip_pd_regulator_enable(pd); if (ret) return dev_err_probe(pd->pmu->dev, ret, - "Failed to enable supply: %d\n", ret); + "Failed to enable supply\n"); ret = rockchip_pd_power(pd, true); if (ret) From f24a579d762828f978844997a749eef4e6b89d77 Mon Sep 17 00:00:00 2001 From: Muzaffer Kadir Date: Mon, 21 Sep 2026 21:18:55 +0300 Subject: [PATCH 33/36] dt-bindings: power: rpmpd: Add MSM8952 power domains Document the compatible for the rpmpd found on MSM8952. Signed-off-by: Muzaffer Kadir Acked-by: Krzysztof Kozlowski Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/power/qcom,rpmpd.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml b/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml index a70bfacbc5d1..8ee1c25a7c9d 100644 --- a/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml +++ b/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml @@ -76,6 +76,7 @@ properties: - items: - enum: - qcom,msm8937-rpmpd + - qcom,msm8952-rpmpd - const: qcom,msm8917-rpmpd - items: - enum: From f9b94ef24d98a34799221cf11cd32e6eb45c7bdd Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 9 Oct 2026 14:15:04 +0200 Subject: [PATCH 34/36] dt-bindings: power: Convert Ux500 PM domains to schema Convert the legacy Ux500 power domain text binding to YAML. Move it under bindings/power. Reference the generic power-domain schema. Update MAINTAINERS for the new path. Assisted-by: LLM Reviewed-by: Krzysztof Kozlowski Signed-off-by: Linus Walleij Signed-off-by: Ulf Hansson --- .../bindings/arm/ux500/power_domain.txt | 35 ---------------- .../power/stericsson,ux500-pm-domains.yaml | 41 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 42 insertions(+), 35 deletions(-) delete mode 100644 Documentation/devicetree/bindings/arm/ux500/power_domain.txt create mode 100644 Documentation/devicetree/bindings/power/stericsson,ux500-pm-domains.yaml diff --git a/Documentation/devicetree/bindings/arm/ux500/power_domain.txt b/Documentation/devicetree/bindings/arm/ux500/power_domain.txt deleted file mode 100644 index 5679d1742d3e..000000000000 --- a/Documentation/devicetree/bindings/arm/ux500/power_domain.txt +++ /dev/null @@ -1,35 +0,0 @@ -* ST-Ericsson UX500 PM Domains - -UX500 supports multiple PM domains which are used to gate power to one or -more peripherals on the SOC. - -The implementation of PM domains for UX500 are based upon the generic PM domain -and use the corresponding DT bindings. - -==PM domain providers== - -Required properties: - - compatible: Must be "stericsson,ux500-pm-domains". - - #power-domain-cells : Number of cells in a power domain specifier, must be 1. - -Example: - pm_domains: pm_domains0 { - compatible = "stericsson,ux500-pm-domains"; - #power-domain-cells = <1>; - }; - -==PM domain consumers== - -Required properties: - - power-domains: A phandle and PM domain specifier. Below are the list of - valid specifiers: - - Index Specifier - ----- --------- - 0 DOMAIN_VAPE - -Example: - sdi0_per1@80126000 { - compatible = "arm,pl18x", "arm,primecell"; - power-domains = <&pm_domains DOMAIN_VAPE> - }; diff --git a/Documentation/devicetree/bindings/power/stericsson,ux500-pm-domains.yaml b/Documentation/devicetree/bindings/power/stericsson,ux500-pm-domains.yaml new file mode 100644 index 000000000000..bf83ce332674 --- /dev/null +++ b/Documentation/devicetree/bindings/power/stericsson,ux500-pm-domains.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/stericsson,ux500-pm-domains.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ST-Ericsson UX500 power domains + +maintainers: + - Linus Walleij + - Ulf Hansson + +description: + The UX500 power domain controller gates power to one or more peripherals on + the SoC. Domain specifiers use one cell containing one of the DOMAIN_* + indexes defined in include/dt-bindings/arm/ux500_pm_domains.h. + +allOf: + - $ref: power-domain.yaml# + +properties: + compatible: + const: stericsson,ux500-pm-domains + + '#power-domain-cells': + const: 1 + +required: + - compatible + - '#power-domain-cells' + +unevaluatedProperties: false + +examples: + - | + #include + + pm_domains: power-controller { + compatible = "stericsson,ux500-pm-domains"; + #power-domain-cells = <1>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..560079711469 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3273,6 +3273,7 @@ F: Documentation/devicetree/bindings/arm/ux500.yaml F: Documentation/devicetree/bindings/arm/ux500/ F: Documentation/devicetree/bindings/gpio/st,nomadik-gpio.yaml F: Documentation/devicetree/bindings/i2c/st,nomadik-i2c.yaml +F: Documentation/devicetree/bindings/power/stericsson,ux500-pm-domains.yaml F: arch/arm/boot/dts/st/ste-* F: arch/arm/mach-nomadik/ F: arch/arm/mach-ux500/ From 75a9f98c8f417dd17b8ab2c69f09039620dbd6db Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 9 Oct 2026 14:15:05 +0200 Subject: [PATCH 35/36] dt-bindings: arm: ux500: Drop NR_DOMAINS Keep the internal Ux500 PM domain count local to the only driver that uses it. Do not expose this implementation detail through the DT binding header. Assisted-by: LLM Suggested-by: Krzysztof Kozlowski Acked-by: Krzysztof Kozlowski Signed-off-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/pmdomain/st/ste-ux500-pm-domain.c | 2 ++ include/dt-bindings/arm/ux500_pm_domains.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pmdomain/st/ste-ux500-pm-domain.c b/drivers/pmdomain/st/ste-ux500-pm-domain.c index 6896cb4a7b71..c9129a3b1805 100644 --- a/drivers/pmdomain/st/ste-ux500-pm-domain.c +++ b/drivers/pmdomain/st/ste-ux500-pm-domain.c @@ -17,6 +17,8 @@ #include +#define NR_DOMAINS (DOMAIN_VAPE + 1) + static int pd_power_off(struct generic_pm_domain *domain) { /* diff --git a/include/dt-bindings/arm/ux500_pm_domains.h b/include/dt-bindings/arm/ux500_pm_domains.h index 9bd764f0c9e6..9d94801d8b89 100644 --- a/include/dt-bindings/arm/ux500_pm_domains.h +++ b/include/dt-bindings/arm/ux500_pm_domains.h @@ -9,7 +9,5 @@ #define DOMAIN_VAPE 0 -/* Number of PM domains. */ -#define NR_DOMAINS (DOMAIN_VAPE + 1) #endif From 531f8429a9f2c09ce7fe5d6dcec7167161d2d865 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 9 Oct 2026 14:15:06 +0200 Subject: [PATCH 36/36] dt-bindings: arm: Add the actual power domains on U8500 This file has been left in an unfinished state just defining the root power domain for the U8500 SoC. Fix it up by adding the actual existing power domains in this SoC. The PRCMU code and old regulator driver is mentioning some *_RET domains, this means "retention" and is a state in the domain and not a domain of its own. Acked-by: Krzysztof Kozlowski Signed-off-by: Linus Walleij Signed-off-by: Ulf Hansson --- include/dt-bindings/arm/ux500_pm_domains.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/include/dt-bindings/arm/ux500_pm_domains.h b/include/dt-bindings/arm/ux500_pm_domains.h index 9d94801d8b89..f7a9fc101fb2 100644 --- a/include/dt-bindings/arm/ux500_pm_domains.h +++ b/include/dt-bindings/arm/ux500_pm_domains.h @@ -8,6 +8,21 @@ #define _DT_BINDINGS_ARM_UX500_PM_DOMAINS_H #define DOMAIN_VAPE 0 +#define DOMAIN_VARM 1 +#define DOMAIN_VMODEM 2 +#define DOMAIN_VPLL 3 +#define DOMAIN_VSMPS1 4 +#define DOMAIN_VSMPS2 5 +#define DOMAIN_VSMPS3 6 +#define DOMAIN_VRF1 7 +#define DOMAIN_SVA_MMDSP 8 +#define DOMAIN_SVA_PIPE 9 +#define DOMAIN_SIA_MMDSP 10 +#define DOMAIN_SIA_PIPE 11 +#define DOMAIN_SGA 12 +#define DOMAIN_B2R2_MCDE 13 +#define DOMAIN_ESRAM_12 14 +#define DOMAIN_ESRAM_34 15 #endif