[PATCH RFC 12/16] clk: qcom: gdsc: Link required power domains
From: Konrad Dybcio
Date: Wed Oct 14 2026 - 08:29:52 EST
From: Konrad Dybcio <konrad.dybcio@xxxxxxxxxxxxxxxx>
A GDSC may require one or more controller power-domain endpoints to be
available while it is powered, not necessarily all the domains that are
consumed by the clock controller it's a part of.
Model those enable-only requirements as GenPD subdomain links, without
propagating the GDSC performance state to the rail.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@xxxxxxxxxxxxxxxx>
---
drivers/clk/qcom/common.c | 4 +-
drivers/clk/qcom/gdsc.c | 141 +++++++++++++++++++++++++++++++++++++++++++---
drivers/clk/qcom/gdsc.h | 6 ++
3 files changed, 141 insertions(+), 10 deletions(-)
diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c
index 01c00c2568b2..48da90eac096 100644
--- a/drivers/clk/qcom/common.c
+++ b/drivers/clk/qcom/common.c
@@ -332,7 +332,7 @@ int qcom_cc_really_probe(struct device *dev,
struct clk_regmap **rclks = desc->clks;
size_t num_clk_hws = desc->num_clk_hws;
struct clk_hw **clk_hws = desc->clk_hws;
- struct dev_pm_domain_list *pd_list;
+ struct dev_pm_domain_list *pd_list = NULL;
const struct dev_pm_domain_attach_data pd_data = {
.pd_indices = desc->pd_indices,
.num_pd_indices = desc->num_pd_indices,
@@ -397,6 +397,8 @@ int qcom_cc_really_probe(struct device *dev,
scd->dev = dev;
scd->scs = desc->gdscs;
scd->num = desc->num_gdscs;
+ scd->pd_list = pd_list;
+ scd->no_external_parent = !!desc->num_dvfs_rail_voters;
ret = gdsc_register(scd, &reset->rcdev, regmap);
if (ret)
goto put_rpm;
diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c
index 2d0803e1eb3b..73054c600c29 100644
--- a/drivers/clk/qcom/gdsc.c
+++ b/drivers/clk/qcom/gdsc.c
@@ -12,10 +12,12 @@
#include <linux/kernel.h>
#include <linux/ktime.h>
#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/reset-controller.h>
#include <linux/slab.h>
+#include "clk-dvfs.h"
#include "gdsc.h"
#define PWR_ON_MASK BIT(31)
@@ -162,6 +164,8 @@ static int gdsc_toggle_logic(struct gdsc *sc, enum gdsc_status status,
}
ret = gdsc_update_collapse_bit(sc, status == GDSC_OFF);
+ if (ret)
+ return ret;
/* If disabling votable gdscs, don't poll on status */
if ((sc->flags & VOTABLE) && status == GDSC_OFF && !wait) {
@@ -343,6 +347,7 @@ static int gdsc_enable(struct generic_pm_domain *domain)
static int gdsc_disable(struct generic_pm_domain *domain)
{
struct gdsc *sc = domain_to_gdsc(domain);
+ bool wait_for_off = domain->synced_poweroff;
int ret;
if (sc->pwrsts == PWRSTS_ON)
@@ -379,7 +384,10 @@ static int gdsc_disable(struct generic_pm_domain *domain)
if (sc->pwrsts == PWRSTS_RET_ON)
return 0;
- ret = gdsc_toggle_logic(sc, GDSC_OFF, domain->synced_poweroff);
+ if (sc->num_required_pds && !(sc->flags & VOTABLE))
+ wait_for_off = true;
+
+ ret = gdsc_toggle_logic(sc, GDSC_OFF, wait_for_off);
if (ret)
return ret;
@@ -535,6 +543,93 @@ static int gdsc_init(struct gdsc *sc)
return ret;
}
+static void gdsc_put_rail_voters(struct gdsc_desc *desc, struct gdsc *sc, size_t num)
+{
+ size_t i;
+
+ for (i = num; i-- > 0;) {
+ struct device *voter;
+
+ voter = qcom_clk_dvfs_get_rail_voter(desc->dev, sc->required_pds[i]);
+ if (!IS_ERR(voter))
+ pm_runtime_put_sync(voter);
+ }
+}
+
+static int gdsc_get_rail_voters(struct gdsc_desc *desc, struct gdsc *sc)
+{
+ size_t i;
+ int ret;
+
+ for (i = 0; i < sc->num_required_pds; i++) {
+ struct device *voter;
+
+ voter = qcom_clk_dvfs_get_rail_voter(desc->dev, sc->required_pds[i]);
+ if (IS_ERR(voter)) {
+ ret = PTR_ERR(voter);
+ goto unwind;
+ }
+
+ ret = pm_runtime_resume_and_get(voter);
+ if (ret)
+ goto unwind;
+ }
+
+ return 0;
+
+unwind:
+ gdsc_put_rail_voters(desc, sc, i);
+
+ return ret;
+}
+
+static void gdsc_remove_required_subdomains(struct gdsc_desc *desc, struct gdsc *sc, size_t num)
+{
+ size_t i;
+
+ for (i = num; i-- > 0;) {
+ struct device *voter;
+
+ voter = qcom_clk_dvfs_get_rail_voter(desc->dev, sc->required_pds[i]);
+ if (IS_ERR(voter) || !voter->pm_domain)
+ continue;
+
+ pm_genpd_remove_subdomain(pd_to_genpd(voter->pm_domain), &sc->pd);
+ }
+}
+
+static int gdsc_add_required_subdomains(struct gdsc_desc *desc, struct gdsc *sc)
+{
+ size_t i;
+ int ret;
+
+ for (i = 0; i < sc->num_required_pds; i++) {
+ struct device *voter;
+
+ voter = qcom_clk_dvfs_get_rail_voter(desc->dev, sc->required_pds[i]);
+ if (IS_ERR(voter)) {
+ ret = PTR_ERR(voter);
+ goto unwind;
+ }
+ if (!voter->pm_domain) {
+ ret = -ENODEV;
+ goto unwind;
+ }
+
+ ret = pm_genpd_add_subdomain_with_flags(pd_to_genpd(voter->pm_domain), &sc->pd,
+ GENPD_SUBDOMAIN_NO_PERF_STATE_PROPAGATION);
+ if (ret)
+ goto unwind;
+ }
+
+ return 0;
+
+unwind:
+ gdsc_remove_required_subdomains(desc, sc, i);
+
+ return ret;
+}
+
static int gdsc_add_subdomain_list(struct dev_pm_domain_list *pd_list,
struct generic_pm_domain *subdomain)
{
@@ -598,11 +693,14 @@ static void gdsc_pm_subdomain_remove(struct gdsc_desc *desc, size_t num)
int gdsc_register(struct gdsc_desc *desc,
struct reset_controller_dev *rcdev, struct regmap *regmap)
{
+ unsigned int parent_flags = desc->no_external_parent ?
+ GENPD_SUBDOMAIN_NO_PERF_STATE_PROPAGATION : 0;
int i, ret;
struct genpd_onecell_data *data;
struct device *dev = desc->dev;
struct gdsc **scs = desc->scs;
size_t num = desc->num;
+ size_t initialized = 0, linked = 0;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
@@ -647,13 +745,24 @@ int gdsc_register(struct gdsc_desc *desc,
continue;
scs[i]->regmap = regmap;
scs[i]->rcdev = rcdev;
- ret = gdsc_init(scs[i]);
+ ret = gdsc_get_rail_voters(desc, scs[i]);
if (ret)
- return ret;
- data->domains[i] = &scs[i]->pd;
+ goto err_remove_domains;
+
+ ret = gdsc_init(scs[i]);
+ if (!ret) {
+ data->domains[i] = &scs[i]->pd;
+ initialized = i + 1;
+ ret = gdsc_add_required_subdomains(desc, scs[i]);
+ }
+ /* A GDSC that is on keeps its rails through the links. */
+ gdsc_put_rail_voters(desc, scs[i], scs[i]->num_required_pds);
+ if (ret)
+ goto err_remove_domains;
+ linked = i + 1;
}
- /* Add subdomains */
+ /* Add explicit collapse parents and legacy inferred parents. */
for (i = 0; i < num; i++) {
ret = 0;
@@ -661,7 +770,8 @@ int gdsc_register(struct gdsc_desc *desc,
continue;
if (scs[i]->parent)
- ret = pm_genpd_add_subdomain(scs[i]->parent, &scs[i]->pd);
+ ret = pm_genpd_add_subdomain_with_flags(scs[i]->parent,
+ &scs[i]->pd, parent_flags);
else if (!desc->no_external_parent &&
!IS_ERR_OR_NULL(dev->pm_domain))
ret = pm_genpd_add_subdomain(pd_to_genpd(dev->pm_domain), &scs[i]->pd);
@@ -669,13 +779,25 @@ int gdsc_register(struct gdsc_desc *desc,
ret = gdsc_add_subdomain_list(desc->pd_list, &scs[i]->pd);
if (ret)
- goto err_pm_subdomain_remove;
+ goto err_remove_parents;
}
- return of_genpd_add_provider_onecell(dev->of_node, data);
+ ret = of_genpd_add_provider_onecell(dev->of_node, data);
+ if (!ret)
+ return 0;
-err_pm_subdomain_remove:
+err_remove_parents:
gdsc_pm_subdomain_remove(desc, i);
+err_remove_domains:
+ while (linked--) {
+ if (scs[linked])
+ gdsc_remove_required_subdomains(desc, scs[linked],
+ scs[linked]->num_required_pds);
+ }
+ while (initialized--) {
+ if (scs[initialized])
+ pm_genpd_remove(&scs[initialized]->pd);
+ }
return ret;
}
@@ -693,6 +815,7 @@ void gdsc_unregister(struct gdsc_desc *desc)
for (i = 0; i < num; i++) {
if (!scs[i])
continue;
+ gdsc_remove_required_subdomains(desc, scs[i], scs[i]->num_required_pds);
pm_genpd_remove(&scs[i]->pd);
}
}
diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h
index 8275e266cfcc..b11438551225 100644
--- a/drivers/clk/qcom/gdsc.h
+++ b/drivers/clk/qcom/gdsc.h
@@ -10,6 +10,7 @@
#include <linux/pm_domain.h>
struct icc_path;
+struct device;
struct regmap;
struct regulator;
struct reset_controller_dev;
@@ -31,6 +32,9 @@ struct reset_controller_dev;
* @resets: ids of resets associated with this gdsc
* @reset_count: number of @resets
* @rcdev: reset controller
+ * @required_pds: ordered controller power-domain indices required
+ * while this GDSC is logically on
+ * @num_required_pds: number of entries in @required_pds
*/
struct gdsc {
struct generic_pm_domain pd;
@@ -79,6 +83,8 @@ struct gdsc {
bool needs_icc;
unsigned int icc_path_index;
struct icc_path *icc_path;
+ const unsigned int *required_pds;
+ size_t num_required_pds;
};
struct gdsc_desc {
--
2.56.0