[PATCH RFC 05/16] pmdomain: core: Support subdomains without pstate propagation

From: Konrad Dybcio

Date: Wed Oct 14 2026 - 08:25:41 EST


From: Konrad Dybcio <konrad.dybcio@xxxxxxxxxxxxxxxx>

A subdomain may need its parent to be powered on without requiring its
performance state to follow the child's. GenPD currently couples these
requirements, so increasing the child's performance state can also
raise the parent even when it only needs to remain on.

Allow subdomain links to opt out of performance-state propagation while
preserving power ordering and active-child accounting.

Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@xxxxxxxxxxxxxxxx>
---
drivers/pmdomain/core.c | 32 ++++++++++++++++++++++++++++----
include/linux/pm_domain.h | 14 ++++++++++++++
2 files changed, 42 insertions(+), 4 deletions(-)

diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
index 76ff0d9a229c..80ea70930cc6 100644
--- a/drivers/pmdomain/core.c
+++ b/drivers/pmdomain/core.c
@@ -463,6 +463,9 @@ static void _genpd_rollback_parent_state(struct gpd_link *link, int depth)
struct generic_pm_domain *parent = link->parent;
int parent_state;

+ if (link->flags & GENPD_SUBDOMAIN_NO_PERF_STATE_PROPAGATION)
+ return;
+
genpd_lock_nested(parent, depth + 1);

parent_state = link->prev_performance_state;
@@ -484,6 +487,9 @@ static int _genpd_set_parent_state(struct generic_pm_domain *genpd,
struct generic_pm_domain *parent = link->parent;
int parent_state, ret;

+ if (link->flags & GENPD_SUBDOMAIN_NO_PERF_STATE_PROPAGATION)
+ return 0;
+
/* Find parent's performance state */
ret = genpd_xlate_performance_state(genpd, parent, state);
if (unlikely(ret < 0))
@@ -2257,7 +2263,8 @@ int dev_pm_genpd_remove_notifier(struct device *dev)
EXPORT_SYMBOL_GPL(dev_pm_genpd_remove_notifier);

static int genpd_add_subdomain(struct generic_pm_domain *genpd,
- struct generic_pm_domain *subdomain)
+ struct generic_pm_domain *subdomain,
+ unsigned int flags)
{
struct gpd_link *link, *itr;
int ret = 0;
@@ -2265,6 +2272,8 @@ static int genpd_add_subdomain(struct generic_pm_domain *genpd,
if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(subdomain)
|| genpd == subdomain)
return -EINVAL;
+ if (flags & ~GENPD_SUBDOMAIN_NO_PERF_STATE_PROPAGATION)
+ return -EINVAL;

/*
* If the domain can be powered on/off in an IRQ safe
@@ -2298,6 +2307,7 @@ static int genpd_add_subdomain(struct generic_pm_domain *genpd,
}

link->parent = genpd;
+ link->flags = flags;
list_add_tail(&link->parent_node, &genpd->parent_links);
link->child = subdomain;
list_add_tail(&link->child_node, &subdomain->child_links);
@@ -2319,16 +2329,30 @@ static int genpd_add_subdomain(struct generic_pm_domain *genpd,
*/
int pm_genpd_add_subdomain(struct generic_pm_domain *genpd,
struct generic_pm_domain *subdomain)
+{
+ return pm_genpd_add_subdomain_with_flags(genpd, subdomain, 0);
+}
+EXPORT_SYMBOL_GPL(pm_genpd_add_subdomain);
+
+/**
+ * pm_genpd_add_subdomain_with_flags - Add a configured subdomain relationship.
+ * @genpd: Leader PM domain to add the subdomain to.
+ * @subdomain: Subdomain to be added.
+ * @flags: Subdomain-link configuration flags.
+ */
+int pm_genpd_add_subdomain_with_flags(struct generic_pm_domain *genpd,
+ struct generic_pm_domain *subdomain,
+ unsigned int flags)
{
int ret;

mutex_lock(&gpd_list_lock);
- ret = genpd_add_subdomain(genpd, subdomain);
+ ret = genpd_add_subdomain(genpd, subdomain, flags);
mutex_unlock(&gpd_list_lock);

return ret;
}
-EXPORT_SYMBOL_GPL(pm_genpd_add_subdomain);
+EXPORT_SYMBOL_GPL(pm_genpd_add_subdomain_with_flags);

/**
* pm_genpd_remove_subdomain - Remove a subdomain from an I/O PM domain.
@@ -3271,7 +3295,7 @@ int of_genpd_add_subdomain(const struct of_phandle_args *parent_spec,
goto out;
}

- ret = genpd_add_subdomain(parent, subdomain);
+ ret = genpd_add_subdomain(parent, subdomain, 0);

out:
mutex_unlock(&gpd_list_lock);
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
index 98aca4a4aede..322453da7fae 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -277,8 +277,12 @@ struct gpd_link {
/* Sub-domain's per-master domain performance state */
unsigned int performance_state;
unsigned int prev_performance_state;
+ unsigned int flags;
};

+/* Preserve power ordering and accounting, but do not forward performance states. */
+#define GENPD_SUBDOMAIN_NO_PERF_STATE_PROPAGATION BIT(0)
+
struct gpd_timing_data {
s64 suspend_latency_ns;
s64 resume_latency_ns;
@@ -323,6 +327,9 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev);
int pm_genpd_remove_device(struct device *dev);
int pm_genpd_add_subdomain(struct generic_pm_domain *genpd,
struct generic_pm_domain *subdomain);
+int pm_genpd_add_subdomain_with_flags(struct generic_pm_domain *genpd,
+ struct generic_pm_domain *subdomain,
+ unsigned int flags);
int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
struct generic_pm_domain *subdomain);
int pm_genpd_init(struct generic_pm_domain *genpd,
@@ -367,6 +374,13 @@ static inline int pm_genpd_add_subdomain(struct generic_pm_domain *genpd,
{
return -ENOSYS;
}
+static inline int pm_genpd_add_subdomain_with_flags(
+ struct generic_pm_domain *genpd,
+ struct generic_pm_domain *subdomain,
+ unsigned int flags)
+{
+ return -ENOSYS;
+}
static inline int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
struct generic_pm_domain *subdomain)
{

--
2.56.0