* [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks
@ 2026-10-14 12:19 Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 01/16] clk: Propagate rate programming failures Konrad Dybcio
` (16 more replies)
0 siblings, 17 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
On Qualcomm SoCs, both the PLLs and other types of clocks alike require
specific voltage rail levels to operate in-spec, but through the
separation and decentralization of clock and voltage votes, these
requirements are often not met and are never explicitly stated.
This results in clocks being undervolted, or at the very least a state
we're unable to reason about.
The devices consuming these clocks very rarely have their own
requirements in that regard. (1)
This series moves the frequency-power_domain-perf_state mappings inside
the clock drivers, ensuring a single, trusted source of truth is
established and that the silicon is no longer driven at out-of-spec
conditions.
To illustrate the nuances involved, here's a couple examples of hidden
dependencies, as found on SM8650:
* video_cc_mvs0_clk requires LOW_SVS to run at 900 MHz, but the
PLL it sources the signal from requires SVS (higher corner) to
produce that frequency. Similarly, to produce 1140 MHz, the clock
requires SVS, but the PLL then requires SVS_L1.
Analogous story for mvs1_clk.
* gpu_cc_gmu_clk requires MX to be at SVS to output 625 MHz, the PLL
requires SVS_L1 to produce that frequency.
* A number of camera clocks follow the same pattern.
* Additionally, some clocks like cam_cc_cci_{0,1,2}_clk require
MMCX to run at LOW_SVS, but the PLL has the additional requirement
of keeping MXC (a wholly different rail) at SVS_L1.
There's a broad agreement that a consumer should only have to care
about the clock it consumes, and that configuring its parents is the
responsibility of the CCF. Requiring the consumers to care about the
above cases (as is the expectation today) is a clear violation of
that contract.
A clock controller may have on the order of hundreds of clocks, each
one with its own frequency-power map, and potentially all of them
could be similarly affected.
Recently, some drivers (Iris, CAMSS) have attempted to circumvent these
problems by holding a direct reference to the power rails and managing
the votes manually, through placing direct votes on each rail via an
OPP table. That however has a number of drawbacks:
* Because more than just one clock needs to be scaled at runtime, the
OPP table either has to contain NUM_CLOCKS * NUM_RATES to handle
that (which blows up pretty quickly), assume the worst case (and
waste power), or ignore that problem (and undervolt the clock
controller), instead of just being able to call set_rate() on each
clock as needed
* The data ends up being dispersed across various consumers, even
though it logically belongs to the clock controller, making it
absolutely unauditable
* It's horrendously error-prone because of the number of clocks
involved, the depth of the clock tree and the fact that at the end
of the day, one still has to consult the clock controller
documentation
The approach proposed here also happens to more naturally fit into the
process of how an SoC is brought up - clock controllers are always
the very first pieces that need to be complete before other IP blocks
can be brought up. And because of (1), enclosing the frequency-corner
data within the clock drivers takes a weight off of everyone else's
shoulders.
On a related topic, the GDSCs are modeled as children of *all* power
domains consumed by a clock controller at once. GDSCs are on/off switches
with some fancy logic attached to them and may need some (or none) rails
enabled to power on or off while meeting the hardware timing
requirements. Voting on them was never quite right in the first place
and is once again only a side-effect of the clocks' power requirements
not being handled centrally.
This series removes the vote propagation and the overly broad parent-
child links, resulting in dropping unnecessary/incorrect votes that are
now encapsulated by the clk-dvfs infrastructure. Big emphasis on (1)
once again.
The implementation is as follows:
Each clock describes the rail levels it needs at each frequency. The
new clk-dvfs helper combines these requirements and submits the
resulting votes through GenPD and RPMHPD. Votes remain held while a
clock is prepared, and rate or parent changes reserve the new
requirements before any clocks are reprogrammed. This also requires CCF
to report programming failures and OPP to retry failed transitions
instead of relying on stale cached state.
Consumer: select rate, prepare clock
|
v
+------------------------ CCF ----------------------+
| Walk the clock tree and reserve resources |
| before programming rates or parents |
+--------------------------------------------------+
|
v
+------------------------------------------------------------------+
| |
| GPLL9 ------> SDCC2 RCG -------> SDCC2 apps clock |
| | | |
| | | Other GCC clocks |
| v v | |
| +------------+ +------------+ v |
| | PLL's own | | RCG's own | Per-clock requirements |
| | rate table | | rate table | | |
| +-----+------+ +-----+------+ | |
| | | | |
| | CX | CX + MX | |
| v v v |
| +--------------------------------------------------------+ |
| | Clock-owned DVFS | |
| | | |
| | Prepared votes + temporary transition votes | |
| | | | |
| | Maximum requirement per rail | |
| +-----------+--------------+--------------+--------------+ |
| | | | |
| CX voter CX_AO voter MX voter |
+----------------+-------------+-------------+---------------------+
| | |
v v v
+----------------------- GenPD ----------------------+
| Aggregate GCC votes with other consumers |
+---------------------------------------------------+
|
v
+-----------+
| RPMHPD |
+-----------+
Straightening out all of the above lets us tackle another problem
with the upstream description of the power grid - the dependency
between various rails. Most notably:
* CX does NOT need to follow the performance state of MMCX
* MX/MXA/MXC and CX are co-dependent only on some platforms (in coarse
terms, SM8350 and before)
However, the main part of series needs to land first to ensure that
dropping those relationships doesn't remove some wrongful votes the
system relied on to stay functional. Resolving that will save power and
finally ensure total stability as far as the providers are concerned.
SM8650 is used as an example in this series, but the conversion will
have to be done for all Qualcomm platforms. That will follow soon
after the patches are merged. DFS clocks are left out of this RFC to
reduce noise (the remaining diff is not particularly large but also
requires boilerplate changes to bus drivers).
dt-bindings changes are not included since it's an RFC.
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
Konrad Dybcio (16):
clk: Propagate rate programming failures
clk: Acquire resources for clock transitions
OPP: Retry failed default single-clock transitions
pmdomain: core: Support named indexed virtual attachments
pmdomain: core: Support subdomains without pstate propagation
pmdomain: core: Support indexed power-domain attachment lists
clk: qcom: Add clock-owned GenPD DVFS aggregation
clk: qcom: dvfs: Add debugfs
clk: qcom: clk-alpha-pll: Add DVFS (un)prepare
clk: qcom: clk-alpha-pll: Vote for the PLL calibration frequency
clk: qcom: gdsc: Make GDSC parenting optional
clk: qcom: gdsc: Link required power domains
[DNM] arm64: dts: qcom: sm8650: Describe clock controller power rails
[DNM] clk: qcom: Enable clk-dvfs on SM8650
[DNM] arm64: dts: qcom: sm8650: Drop required-opps
[DNM] pmdomain: qcom: rpmhpd: Stop forwarding MMCX pstate to CX
arch/arm64/boot/dts/qcom/sm8650.dtsi | 62 +--
drivers/base/power/common.c | 24 +-
drivers/clk/Makefile | 1 +
drivers/clk/clk.c | 793 +++++++++++++++++++++++-----
drivers/clk/clk_resource_test.c | 376 +++++++++++++
drivers/clk/clk_test.c | 194 ++++++-
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/camcc-sm8650.c | 282 ++++++++++
drivers/clk/qcom/clk-alpha-pll.c | 317 ++++++++++-
drivers/clk/qcom/clk-dvfs.c | 635 ++++++++++++++++++++++
drivers/clk/qcom/clk-dvfs.h | 41 ++
drivers/clk/qcom/clk-rcg2.c | 93 +++-
drivers/clk/qcom/clk-regmap.h | 6 +
drivers/clk/qcom/common.c | 28 +-
drivers/clk/qcom/common.h | 3 +
drivers/clk/qcom/dispcc-sm8550.c | 328 ++++++++++--
drivers/clk/qcom/gcc-sm8650.c | 261 +++++++++
drivers/clk/qcom/gdsc.c | 154 +++++-
drivers/clk/qcom/gdsc.h | 7 +
drivers/clk/qcom/gpucc-sm8650.c | 101 +++-
drivers/clk/qcom/videocc-sm8550.c | 135 ++++-
drivers/opp/core.c | 72 ++-
drivers/opp/opp.h | 2 +
drivers/pmdomain/core.c | 60 ++-
drivers/pmdomain/qcom/rpmhpd.c | 14 +-
include/dt-bindings/clock/qcom,sm8650-gcc.h | 2 +
include/linux/clk-provider.h | 39 ++
include/linux/pm_domain.h | 33 +-
28 files changed, 3734 insertions(+), 330 deletions(-)
---
base-commit: 4eb2586bd40db27d77cc5b724ca9386ed0c71d53
change-id: 20261001-topic-qc_clk_dvfs-ef907f059611
Best regards,
--
Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH RFC 01/16] clk: Propagate rate programming failures
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:35 ` sashiko-bot
2026-10-14 13:25 ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH RFC 02/16] clk: Acquire resources for clock transitions Konrad Dybcio
` (15 subsequent siblings)
16 siblings, 2 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
The clock rate-change walk ignores runtime PM, temporary prepare and
enable, and provider programming failures. clk_set_rate() can therefore
report success after a provider rejected the operation, while later
clocks in the affected tree continue to be programmed. Consumers can
then proceed with a configuration that was never successfully applied.
A failed parent switch must not leave the software topology pointing at
the rejected source or release the old parent's references. Likewise,
rate notifiers must not announce success before the whole walk succeeds,
or lose their ABORT notification when a PRE notifier vetoes the change.
Propagate errors and stop the walk at the first failure. Keep topology,
temporary references, and terminal notifications consistent with the
outcome.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/clk/clk.c | 163 +++++++++++++++++++++++++++++++++++++------------
drivers/clk/clk_test.c | 16 ++++-
2 files changed, 138 insertions(+), 41 deletions(-)
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 4d2cc86bfbeb..95669775f8cd 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -84,6 +84,7 @@ struct clk_parent_map {
* clk_core->rate every time the clock is reparented, and
* when we're doing the orphan -> !orphan transition.
* @new_rate: New rate to be set during a rate change operation.
+ * @old_rate: Rate before the current rate change operation.
* @new_parent: Pointer to new parent during parent change. This is also
* used when a clk's rate is changed.
* @new_child: Pointer to new child during reparenting. This is also
@@ -130,6 +131,7 @@ struct clk_core {
unsigned long rate;
unsigned long req_rate;
unsigned long new_rate;
+ unsigned long old_rate;
struct clk_core *new_parent;
struct clk_core *new_child;
unsigned long flags;
@@ -2163,11 +2165,14 @@ static void clk_reparent(struct clk_core *core, struct clk_core *new_parent)
core->parent = new_parent;
}
-static struct clk_core *__clk_set_parent_before(struct clk_core *core,
- struct clk_core *parent)
+static int __clk_set_parent_before(struct clk_core *core,
+ struct clk_core *parent,
+ struct clk_core **old_parent)
{
unsigned long flags;
- struct clk_core *old_parent = core->parent;
+ int ret;
+
+ *old_parent = core->parent;
/*
* 1. enable parents for CLK_OPS_PARENT_ENABLE clock
@@ -2191,14 +2196,24 @@ static struct clk_core *__clk_set_parent_before(struct clk_core *core,
/* enable old_parent & parent if CLK_OPS_PARENT_ENABLE is set */
if (core->flags & CLK_OPS_PARENT_ENABLE) {
- clk_core_prepare_enable(old_parent);
- clk_core_prepare_enable(parent);
+ ret = clk_core_prepare_enable(*old_parent);
+ if (ret)
+ return ret;
+
+ ret = clk_core_prepare_enable(parent);
+ if (ret)
+ goto disable_old_parent;
}
/* migrate prepare count if > 0 */
if (core->prepare_count) {
- clk_core_prepare_enable(parent);
- clk_core_enable_lock(core);
+ ret = clk_core_prepare_enable(parent);
+ if (ret)
+ goto disable_ops_parents;
+
+ ret = clk_core_enable_lock(core);
+ if (ret)
+ goto disable_migration_parent;
}
/* update the clk tree topology */
@@ -2206,7 +2221,18 @@ static struct clk_core *__clk_set_parent_before(struct clk_core *core,
clk_reparent(core, parent);
clk_enable_unlock(flags);
- return old_parent;
+ return 0;
+
+disable_migration_parent:
+ clk_core_disable_unprepare(parent);
+disable_ops_parents:
+ if (core->flags & CLK_OPS_PARENT_ENABLE)
+ clk_core_disable_unprepare(parent);
+disable_old_parent:
+ if (core->flags & CLK_OPS_PARENT_ENABLE)
+ clk_core_disable_unprepare(*old_parent);
+
+ return ret;
}
static void __clk_set_parent_after(struct clk_core *core,
@@ -2232,11 +2258,12 @@ static void __clk_set_parent_after(struct clk_core *core,
static int __clk_set_parent(struct clk_core *core, struct clk_core *parent,
u8 p_index)
{
- unsigned long flags;
int ret = 0;
struct clk_core *old_parent;
- old_parent = __clk_set_parent_before(core, parent);
+ ret = __clk_set_parent_before(core, parent, &old_parent);
+ if (ret)
+ return ret;
trace_clk_set_parent(core, parent);
@@ -2247,10 +2274,10 @@ static int __clk_set_parent(struct clk_core *core, struct clk_core *parent,
trace_clk_set_parent_complete(core, parent);
if (ret) {
- flags = clk_enable_lock();
+ unsigned long flags = clk_enable_lock();
+
clk_reparent(core, old_parent);
clk_enable_unlock(flags);
-
__clk_set_parent_after(core, old_parent, parent);
return ret;
@@ -2312,6 +2339,7 @@ static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate,
struct clk_core *child;
core->new_rate = new_rate;
+ core->old_rate = core->rate;
core->new_parent = new_parent;
core->new_parent_index = p_index;
/* include clk in new parent's PRE_RATE_CHANGE notifications */
@@ -2420,13 +2448,20 @@ static struct clk_core *clk_propagate_rate_change(struct clk_core *core,
unsigned long event)
{
struct clk_core *child, *tmp_clk, *fail_clk = NULL;
+ unsigned long old_rate = core->rate;
+ unsigned long new_rate = core->new_rate;
int ret = NOTIFY_DONE;
- if (core->rate == core->new_rate)
+ if (event != PRE_RATE_CHANGE)
+ old_rate = core->old_rate;
+ if (event == POST_RATE_CHANGE)
+ new_rate = core->rate;
+
+ if (old_rate == new_rate)
return NULL;
if (core->notifier_count) {
- ret = __clk_notify(core, event, core->rate, core->new_rate);
+ ret = __clk_notify(core, event, old_rate, new_rate);
if (ret & NOTIFY_STOP_MASK)
fail_clk = core;
}
@@ -2454,17 +2489,17 @@ static struct clk_core *clk_propagate_rate_change(struct clk_core *core,
* walk down a subtree and set the new rates notifying the rate
* change on the way
*/
-static void clk_change_rate(struct clk_core *core)
+static int clk_change_rate(struct clk_core *core)
{
struct clk_core *child;
struct hlist_node *tmp;
- unsigned long old_rate;
unsigned long best_parent_rate = 0;
bool skip_set_rate = false;
- struct clk_core *old_parent;
+ bool rate_ungated = false;
+ bool parent_enabled = false;
+ struct clk_core *old_parent = NULL;
struct clk_core *parent = NULL;
-
- old_rate = core->rate;
+ int ret = 0;
if (core->new_parent) {
parent = core->new_parent;
@@ -2474,53 +2509,83 @@ static void clk_change_rate(struct clk_core *core)
best_parent_rate = core->parent->rate;
}
- if (clk_pm_runtime_get(core))
- return;
+ ret = clk_pm_runtime_get(core);
+ if (ret)
+ return ret;
if (core->flags & CLK_SET_RATE_UNGATE) {
- clk_core_prepare(core);
- clk_core_enable_lock(core);
+ ret = clk_core_prepare(core);
+ if (ret)
+ goto runtime_put;
+
+ ret = clk_core_enable_lock(core);
+ if (ret) {
+ clk_core_unprepare(core);
+ goto runtime_put;
+ }
+ rate_ungated = true;
}
if (core->new_parent && core->new_parent != core->parent) {
- old_parent = __clk_set_parent_before(core, core->new_parent);
+ ret = __clk_set_parent_before(core, core->new_parent,
+ &old_parent);
+ if (ret)
+ goto cleanup;
trace_clk_set_parent(core, core->new_parent);
if (core->ops->set_rate_and_parent) {
skip_set_rate = true;
- core->ops->set_rate_and_parent(core->hw, core->new_rate,
+ ret = core->ops->set_rate_and_parent(core->hw, core->new_rate,
best_parent_rate,
core->new_parent_index);
} else if (core->ops->set_parent) {
- core->ops->set_parent(core->hw, core->new_parent_index);
+ ret = core->ops->set_parent(core->hw,
+ core->new_parent_index);
}
trace_clk_set_parent_complete(core, core->new_parent);
- __clk_set_parent_after(core, core->new_parent, old_parent);
+ if (ret) {
+ unsigned long flags = clk_enable_lock();
+
+ clk_reparent(core, old_parent);
+ clk_enable_unlock(flags);
+ __clk_set_parent_after(core, old_parent,
+ core->new_parent);
+ goto cleanup;
+ }
+
+ __clk_set_parent_after(core, core->new_parent,
+ old_parent);
}
- if (core->flags & CLK_OPS_PARENT_ENABLE)
- clk_core_prepare_enable(parent);
+ if (core->flags & CLK_OPS_PARENT_ENABLE) {
+ ret = clk_core_prepare_enable(parent);
+ if (ret)
+ goto cleanup;
+ parent_enabled = true;
+ }
trace_clk_set_rate(core, core->new_rate);
if (!skip_set_rate && core->ops->set_rate)
- core->ops->set_rate(core->hw, core->new_rate, best_parent_rate);
+ ret = core->ops->set_rate(core->hw, core->new_rate,
+ best_parent_rate);
trace_clk_set_rate_complete(core, core->new_rate);
core->rate = clk_recalc(core, best_parent_rate);
- if (core->flags & CLK_SET_RATE_UNGATE) {
+cleanup:
+ if (rate_ungated) {
clk_core_disable_lock(core);
clk_core_unprepare(core);
}
- if (core->flags & CLK_OPS_PARENT_ENABLE)
+ if (parent_enabled)
clk_core_disable_unprepare(parent);
- if (core->notifier_count && old_rate != core->rate)
- __clk_notify(core, POST_RATE_CHANGE, old_rate, core->rate);
+ if (ret)
+ goto runtime_put;
if (core->flags & CLK_RECALC_NEW_RATES)
(void)clk_calc_new_rates(core, core->new_rate);
@@ -2533,14 +2598,22 @@ static void clk_change_rate(struct clk_core *core)
/* Skip children who will be reparented to another clock */
if (child->new_parent && child->new_parent != core)
continue;
- clk_change_rate(child);
+ ret = clk_change_rate(child);
+ if (ret)
+ goto runtime_put;
}
/* handle the new child who might not be in core->children yet */
- if (core->new_child)
- clk_change_rate(core->new_child);
+ if (core->new_child) {
+ ret = clk_change_rate(core->new_child);
+ if (ret)
+ goto runtime_put;
+ }
+runtime_put:
clk_pm_runtime_put(core);
+
+ return ret;
}
static unsigned long clk_core_req_round_rate_nolock(struct clk_core *core,
@@ -2613,7 +2686,14 @@ static int clk_core_set_rate_nolock(struct clk_core *core,
}
/* change the rates */
- clk_change_rate(top);
+ ret = clk_change_rate(top);
+ if (ret) {
+ __clk_recalc_rates(top, true, 0);
+ clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
+ goto err;
+ }
+
+ clk_propagate_rate_change(top, POST_RATE_CHANGE);
core->req_rate = req_rate;
err:
@@ -3958,8 +4038,11 @@ static void clk_core_reparent_orphans_nolock(void)
*/
if (parent) {
/* update the clk tree topology */
- __clk_set_parent_before(orphan, parent);
- __clk_set_parent_after(orphan, parent, NULL);
+ struct clk_core *old_parent;
+
+ if (__clk_set_parent_before(orphan, parent, &old_parent))
+ continue;
+ __clk_set_parent_after(orphan, parent, old_parent);
__clk_recalc_accuracies(orphan);
__clk_recalc_rates(orphan, true, 0);
diff --git a/drivers/clk/clk_test.c b/drivers/clk/clk_test.c
index 0505ed357b20..8860e9a6e43e 100644
--- a/drivers/clk/clk_test.c
+++ b/drivers/clk/clk_test.c
@@ -29,6 +29,7 @@ static const struct clk_ops empty_clk_ops = { };
struct clk_dummy_context {
struct clk_hw hw;
unsigned long rate;
+ int set_rate_error;
struct clk_spread_spectrum sscs;
};
@@ -82,7 +83,7 @@ static int clk_dummy_set_rate(struct clk_hw *hw,
container_of(hw, struct clk_dummy_context, hw);
ctx->rate = rate;
- return 0;
+ return ctx->set_rate_error;
}
static int clk_dummy_set_spread_spectrum(struct clk_hw *hw,
@@ -236,6 +237,18 @@ static void clk_test_exit(struct kunit *test)
clk_hw_unregister(&ctx->hw);
}
+static void clk_set_rate_error_test(struct kunit *test)
+{
+ struct clk_dummy_context *ctx = test->priv;
+ struct clk *clk = clk_hw_get_clk_kunit(test, &ctx->hw, NULL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, clk);
+ ctx->set_rate_error = -EIO;
+
+ KUNIT_EXPECT_EQ(test, -EIO, clk_set_rate(clk, DUMMY_CLOCK_RATE_1));
+ KUNIT_EXPECT_EQ(test, DUMMY_CLOCK_RATE_1, clk_get_rate(clk));
+}
+
/*
* Test that the actual rate matches what is returned by clk_get_rate()
*/
@@ -338,6 +351,7 @@ static struct kunit_case clk_test_cases[] = {
KUNIT_CASE(clk_test_get_rate),
KUNIT_CASE(clk_test_set_get_rate),
KUNIT_CASE(clk_test_set_set_get_rate),
+ KUNIT_CASE(clk_set_rate_error_test),
KUNIT_CASE(clk_test_round_set_get_rate),
{}
};
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 02/16] clk: Acquire resources for clock transitions
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 01/16] clk: Propagate rate programming failures Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:40 ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions Konrad Dybcio
` (14 subsequent siblings)
16 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Preparing a clock protects its current requirements, not the resources
needed while changing its rate or parent. A transition can temporarily
use both parents or access an unprepared clock, and a parent rate change
can cause other clocks to be programmed too. Raising resources one clock
at a time leaves those operations exposed to insufficient power.
Failures make this worse: hardware may already have changed when the
operation returns an error. A matching cached rate is then not proof
that resource reconciliation finished. Retaining a temporary enable
reference without a corresponding prepare reference also breaks a
consumer's normal disable/unprepare sequence after a failed reparent.
Make CCF acquire the affected tree's and parent paths' resources before
notifiers or programming, and reconcile them only when the walk stops.
Keep fault recovery independent of rate shortcuts and subsequent
consumer reference-count changes. Require explicit recovery of an
ancestor's uncertain parent selection rather than reporting success
from a descendant request.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/clk/Makefile | 1 +
drivers/clk/clk.c | 678 +++++++++++++++++++++++++++++++++-------
drivers/clk/clk_resource_test.c | 376 ++++++++++++++++++++++
drivers/clk/clk_test.c | 178 +++++++++++
include/linux/clk-provider.h | 39 +++
5 files changed, 1161 insertions(+), 111 deletions(-)
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 52d138d4ffc8..de4611df8b6d 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -3,6 +3,7 @@
obj-$(CONFIG_HAVE_CLK) += clk-devres.o clk-bulk.o clkdev.o
obj-$(CONFIG_COMMON_CLK) += clk.o
obj-$(CONFIG_CLK_KUNIT_TEST) += clk-test.o
+obj-$(CONFIG_CLK_KUNIT_TEST) += clk_resource_test.o
clk-test-y := clk_test.o \
kunit_clk_assigned_rates_u64_one.dtbo.o \
kunit_clk_assigned_rates_u64_one_consumer.dtbo.o \
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 95669775f8cd..46e01d8254e5 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -48,6 +48,8 @@ static LIST_HEAD(clk_notifier_list);
static HLIST_HEAD(clk_rpm_list);
static DEFINE_MUTEX(clk_rpm_list_lock);
+static void __clk_release(struct kref *ref);
+
static const struct hlist_head *all_lists[] = {
&clk_root_list,
&clk_orphan_list,
@@ -64,6 +66,26 @@ struct clk_parent_map {
int index;
};
+struct clk_resource_acquisition {
+ struct list_head node;
+ struct clk_core *core;
+ struct clk_resource_transition transition;
+ void *cookie;
+};
+
+struct clk_resource_transaction {
+ struct list_head acquisitions;
+ struct list_head notifications;
+};
+
+struct clk_rate_notification {
+ struct list_head node;
+ struct clk_core *core;
+ unsigned long old_rate;
+ unsigned long new_rate;
+ bool sent;
+};
+
/**
* struct clk_core - The internal state of a clk in the clk tree.
* @name: Unique name of the clk for identification.
@@ -84,7 +106,6 @@ struct clk_parent_map {
* clk_core->rate every time the clock is reparented, and
* when we're doing the orphan -> !orphan transition.
* @new_rate: New rate to be set during a rate change operation.
- * @old_rate: Rate before the current rate change operation.
* @new_parent: Pointer to new parent during parent change. This is also
* used when a clk's rate is changed.
* @new_child: Pointer to new child during reparenting. This is also
@@ -93,6 +114,11 @@ struct clk_parent_map {
* `clk framework flags`.
* @orphan: True if this clk is currently orphaned.
* @rpm_enabled: True if runtime power management is enabled for this clk.
+ * @resource_fault: Resource state must be reconciled before a shortcut.
+ * @parent_fault: Parent programming left the hardware state uncertain.
+ * @fault_migration_refs: Retained migration references and a clock prepare pin.
+ * @fault_old_parent: Old parent retained after an uncertain parent change.
+ * @fault_new_parent: Target parent retained after an uncertain parent change.
* @enable_count: Reference count of enables.
* @prepare_count: Reference count of prepares.
* @protect_count: Protection reference count against disable.
@@ -131,12 +157,16 @@ struct clk_core {
unsigned long rate;
unsigned long req_rate;
unsigned long new_rate;
- unsigned long old_rate;
struct clk_core *new_parent;
struct clk_core *new_child;
unsigned long flags;
bool orphan;
bool rpm_enabled;
+ bool resource_fault;
+ bool parent_fault;
+ bool fault_migration_refs;
+ struct clk_core *fault_old_parent;
+ struct clk_core *fault_new_parent;
unsigned int enable_count;
unsigned int prepare_count;
unsigned int protect_count;
@@ -2255,9 +2285,59 @@ static void __clk_set_parent_after(struct clk_core *core,
}
}
-static int __clk_set_parent(struct clk_core *core, struct clk_core *parent,
- u8 p_index)
+static void clk_hold_parent_fault(struct clk_core *core,
+ struct clk_core *parent,
+ struct clk_core *old_parent)
{
+ core->parent_fault = true;
+ core->fault_migration_refs = core->prepare_count > 0;
+ core->fault_old_parent = old_parent;
+ core->fault_new_parent = parent;
+
+ /*
+ * Keep the retained enable reference prepared across consumer unprepare.
+ * The clock is already prepared, so taking this reference cannot fail.
+ */
+ if (core->fault_migration_refs)
+ clk_core_prepare(core);
+
+ if (old_parent)
+ kref_get(&old_parent->ref);
+ if (parent)
+ kref_get(&parent->ref);
+}
+
+static void clk_release_parent_fault(struct clk_core *core)
+{
+ if (!core->parent_fault)
+ return;
+
+ /* Unwind the references retained at failure, not the current state. */
+ if (core->fault_migration_refs) {
+ clk_core_disable_lock(core);
+ clk_core_disable_unprepare(core->fault_old_parent);
+ }
+ if (core->flags & CLK_OPS_PARENT_ENABLE) {
+ clk_core_disable_unprepare(core->fault_new_parent);
+ clk_core_disable_unprepare(core->fault_old_parent);
+ }
+ if (core->fault_migration_refs)
+ clk_core_unprepare(core);
+
+ if (core->fault_old_parent)
+ kref_put(&core->fault_old_parent->ref, __clk_release);
+ if (core->fault_new_parent)
+ kref_put(&core->fault_new_parent->ref, __clk_release);
+ core->fault_old_parent = NULL;
+ core->fault_new_parent = NULL;
+ core->fault_migration_refs = false;
+ core->parent_fault = false;
+}
+
+static int __clk_set_parent(struct clk_core *core, struct clk_core *parent,
+ u8 p_index, bool retain_on_failure)
+{
+ bool retrying_fault = core->parent_fault;
int ret = 0;
struct clk_core *old_parent;
@@ -2274,72 +2354,33 @@ static int __clk_set_parent(struct clk_core *core, struct clk_core *parent,
trace_clk_set_parent_complete(core, parent);
if (ret) {
- unsigned long flags = clk_enable_lock();
+ if (retrying_fault) {
+ __clk_set_parent_after(core, parent, old_parent);
+ } else if (retain_on_failure) {
+ clk_hold_parent_fault(core, parent, old_parent);
+ } else {
+ unsigned long flags = clk_enable_lock();
- clk_reparent(core, old_parent);
- clk_enable_unlock(flags);
- __clk_set_parent_after(core, old_parent, parent);
+ clk_reparent(core, old_parent);
+ clk_enable_unlock(flags);
+ __clk_set_parent_after(core, old_parent, parent);
+ }
return ret;
}
__clk_set_parent_after(core, parent, old_parent);
+ clk_release_parent_fault(core);
return 0;
}
-/**
- * __clk_speculate_rates - speculate all rates in the clk subtree
- * @core: first clk in the subtree
- * @parent_rate: the "future" rate of clk's parent
- *
- * Walks the subtree of clks starting with @core, speculating rates as it
- * goes and firing off PRE_RATE_CHANGE notifications as necessary.
- *
- * Unlike __clk_recalc_rates, __clk_speculate_rates exists only for sending
- * pre-rate change notifications and returns early if no clks in the
- * subtree have subscribed to the notifications. Note that if a clk does not
- * implement the .recalc_rate callback then it is assumed that the clock will
- * take on the rate of its parent.
- */
-static int __clk_speculate_rates(struct clk_core *core,
- unsigned long parent_rate)
-{
- struct clk_core *child;
- unsigned long new_rate;
- int ret = NOTIFY_DONE;
-
- lockdep_assert_held(&prepare_lock);
-
- new_rate = clk_recalc(core, parent_rate);
-
- /* abort rate change if a driver returns NOTIFY_BAD or NOTIFY_STOP */
- if (core->notifier_count)
- ret = __clk_notify(core, PRE_RATE_CHANGE, core->rate, new_rate);
-
- if (ret & NOTIFY_STOP_MASK) {
- pr_debug("%s: clk notifier callback for clock %s aborted with error %d\n",
- __func__, core->name, ret);
- goto out;
- }
-
- hlist_for_each_entry(child, &core->children, child_node) {
- ret = __clk_speculate_rates(child, new_rate);
- if (ret & NOTIFY_STOP_MASK)
- break;
- }
-
-out:
- return ret;
-}
-
static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate,
struct clk_core *new_parent, u8 p_index)
{
struct clk_core *child;
core->new_rate = new_rate;
- core->old_rate = core->rate;
core->new_parent = new_parent;
core->new_parent_index = p_index;
/* include clk in new parent's PRE_RATE_CHANGE notifications */
@@ -2353,6 +2394,38 @@ static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate,
}
}
+static void clk_clear_new_child(struct clk_core *core)
+{
+ struct clk_core *new_child = core->new_child;
+ struct clk_core *child;
+
+ if (core->new_parent && core->new_parent->new_child == core)
+ core->new_parent->new_child = NULL;
+ core->new_child = NULL;
+ hlist_for_each_entry(child, &core->children, child_node)
+ clk_clear_new_child(child);
+
+ if (new_child && new_child->parent != core)
+ clk_clear_new_child(new_child);
+}
+
+static bool clk_parent_fault_plan_valid(struct clk_core *core)
+{
+ struct clk_core *child;
+
+ if (core->parent_fault && core->new_parent != core->fault_new_parent)
+ return false;
+
+ hlist_for_each_entry(child, &core->children, child_node) {
+ if (child->new_parent && child->new_parent != core)
+ continue;
+ if (!clk_parent_fault_plan_valid(child))
+ return false;
+ }
+
+ return !core->new_child || clk_parent_fault_plan_valid(core->new_child);
+}
+
/*
* calculate the new rates returning the topmost clock that has to be
* changed.
@@ -2444,52 +2517,270 @@ static struct clk_core *clk_calc_new_rates(struct clk_core *core,
* so that in case of an error we can walk down the whole tree again and
* abort the change.
*/
-static struct clk_core *clk_propagate_rate_change(struct clk_core *core,
- unsigned long event)
+static int clk_collect_rate_notifications(struct clk_resource_transaction *tx,
+ struct clk_core *core)
{
- struct clk_core *child, *tmp_clk, *fail_clk = NULL;
- unsigned long old_rate = core->rate;
- unsigned long new_rate = core->new_rate;
- int ret = NOTIFY_DONE;
+ struct clk_rate_notification *notification;
+ struct clk_core *child;
+ int ret;
- if (event != PRE_RATE_CHANGE)
- old_rate = core->old_rate;
- if (event == POST_RATE_CHANGE)
- new_rate = core->rate;
+ list_for_each_entry(notification, &tx->notifications, node)
+ if (notification->core == core)
+ return 0;
- if (old_rate == new_rate)
- return NULL;
+ if (core->rate != core->new_rate && core->notifier_count) {
+ notification = kzalloc_obj(*notification);
+ if (!notification)
+ return -ENOMEM;
- if (core->notifier_count) {
- ret = __clk_notify(core, event, old_rate, new_rate);
- if (ret & NOTIFY_STOP_MASK)
- fail_clk = core;
+ notification->core = core;
+ notification->old_rate = core->rate;
+ notification->new_rate = core->new_rate;
+ list_add_tail(¬ification->node, &tx->notifications);
}
hlist_for_each_entry(child, &core->children, child_node) {
- /* Skip children who will be reparented to another clock */
if (child->new_parent && child->new_parent != core)
continue;
- tmp_clk = clk_propagate_rate_change(child, event);
- if (tmp_clk)
- fail_clk = tmp_clk;
+
+ ret = clk_collect_rate_notifications(tx, child);
+ if (ret)
+ return ret;
}
- /* handle the new child who might not be in core->children yet */
- if (core->new_child) {
- tmp_clk = clk_propagate_rate_change(core->new_child, event);
- if (tmp_clk)
- fail_clk = tmp_clk;
+ if (core->new_child)
+ return clk_collect_rate_notifications(tx, core->new_child);
+
+ return 0;
+}
+
+static int clk_send_rate_pre_notifications(struct clk_resource_transaction *tx)
+{
+ struct clk_rate_notification *notification;
+ int ret;
+
+ list_for_each_entry(notification, &tx->notifications, node) {
+ ret = __clk_notify(notification->core, PRE_RATE_CHANGE,
+ notification->old_rate, notification->new_rate);
+ notification->sent = true;
+ if (ret & NOTIFY_STOP_MASK)
+ return -EBUSY;
}
- return fail_clk;
+ return 0;
+}
+
+static void
+clk_finish_rate_notifications(struct clk_resource_transaction *tx,
+ unsigned long event)
+{
+ struct clk_rate_notification *notification, *tmp;
+
+ list_for_each_entry(notification, &tx->notifications, node) {
+ if (!notification->sent)
+ break;
+
+ __clk_notify(notification->core, event, notification->old_rate,
+ event == POST_RATE_CHANGE ?
+ notification->core->rate : notification->new_rate);
+ }
+
+ list_for_each_entry_safe(notification, tmp, &tx->notifications, node) {
+ list_del(¬ification->node);
+ kfree(notification);
+ }
+}
+
+static int clk_resource_acquire_one(struct clk_resource_transaction *tx,
+ struct clk_core *core, bool force)
+{
+ struct clk_resource_acquisition *acquisition;
+ struct clk_core *new_parent;
+ int ret;
+
+ if (!core)
+ return 0;
+
+ list_for_each_entry(acquisition, &tx->acquisitions, node)
+ if (acquisition->core == core)
+ return 0;
+
+ if (!core->ops->resource_acquire)
+ return 0;
+
+ new_parent = core->new_parent ?: core->parent;
+ acquisition = kzalloc_obj(*acquisition);
+ if (!acquisition)
+ return -ENOMEM;
+
+ acquisition->core = core;
+ acquisition->transition.old_rate = core->rate;
+ acquisition->transition.new_rate = force ? core->rate : core->new_rate;
+ acquisition->transition.old_parent = core->parent ?
+ core->parent->hw : NULL;
+ acquisition->transition.new_parent = force ?
+ acquisition->transition.old_parent :
+ (new_parent ? new_parent->hw : NULL);
+
+ ret = core->ops->resource_acquire(core->hw,
+ &acquisition->transition,
+ &acquisition->cookie);
+ if (ret) {
+ kfree(acquisition);
+ return ret;
+ }
+
+ list_add_tail(&acquisition->node, &tx->acquisitions);
+ return 0;
+}
+
+static int
+clk_resource_acquire_parent_path(struct clk_resource_transaction *tx,
+ struct clk_core *parent)
+{
+ int ret;
+
+ for (; parent; parent = parent->parent) {
+ /* A resource-only acquisition cannot repair parent programming. */
+ if (parent->parent_fault)
+ return -EBUSY;
+
+ ret = clk_resource_acquire_one(tx, parent, true);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int clk_resource_acquire_tree(struct clk_resource_transaction *tx,
+ struct clk_core *core)
+{
+ struct clk_core *child;
+ int ret;
+
+ ret = clk_resource_acquire_one(tx, core, false);
+ if (ret)
+ return ret;
+
+ hlist_for_each_entry(child, &core->children, child_node) {
+ if (child->new_parent && child->new_parent != core)
+ continue;
+
+ ret = clk_resource_acquire_tree(tx, child);
+ if (ret)
+ return ret;
+ }
+
+ if (core->new_child)
+ return clk_resource_acquire_tree(tx, core->new_child);
+
+ return 0;
+}
+
+static int
+clk_resource_release(struct clk_resource_transaction *tx,
+ enum clk_resource_change_result result)
+{
+ struct clk_resource_acquisition *acquisition, *tmp;
+ int first_error = 0;
+ int ret;
+
+ list_for_each_entry_reverse(acquisition, &tx->acquisitions, node) {
+ struct clk_core *core = acquisition->core;
+
+ if (result == CLK_RESOURCE_HW_CHANGED)
+ core->resource_fault = true;
+
+ acquisition->transition.actual_rate = core->rate;
+ acquisition->transition.actual_parent = core->parent ?
+ core->parent->hw : NULL;
+
+ ret = core->ops->resource_release(core->hw,
+ &acquisition->transition,
+ acquisition->cookie,
+ result);
+ if (ret) {
+ core->resource_fault = true;
+ if (!first_error)
+ first_error = ret;
+ } else if (result == CLK_RESOURCE_COMPLETE) {
+ core->resource_fault = false;
+ }
+ }
+
+ list_for_each_entry_safe(acquisition, tmp, &tx->acquisitions, node) {
+ list_del(&acquisition->node);
+ kfree(acquisition);
+ }
+
+ return first_error;
+}
+
+static bool clk_resource_tree_faulted(struct clk_core *core)
+{
+ struct clk_core *child;
+
+ if (core->resource_fault || core->parent_fault)
+ return true;
+
+ hlist_for_each_entry(child, &core->children, child_node) {
+ if (clk_resource_tree_faulted(child))
+ return true;
+ }
+
+ return false;
+}
+
+static bool clk_resource_parent_path_faulted(struct clk_core *core)
+{
+ for (core = core->parent; core; core = core->parent) {
+ if (core->resource_fault || core->parent_fault)
+ return true;
+ }
+
+ return false;
+}
+
+static bool clk_resource_tree_managed(struct clk_core *core)
+{
+ struct clk_core *child;
+
+ if (core->ops->resource_acquire)
+ return true;
+
+ hlist_for_each_entry(child, &core->children, child_node) {
+ if (child->new_parent && child->new_parent != core)
+ continue;
+ if (clk_resource_tree_managed(child))
+ return true;
+ }
+
+ return core->new_child && clk_resource_tree_managed(core->new_child);
+}
+
+static bool clk_resource_tree_recalculates(struct clk_core *core)
+{
+ struct clk_core *child;
+
+ if (core->flags & CLK_RECALC_NEW_RATES)
+ return true;
+
+ hlist_for_each_entry(child, &core->children, child_node) {
+ if (child->new_parent && child->new_parent != core)
+ continue;
+ if (clk_resource_tree_recalculates(child))
+ return true;
+ }
+
+ return core->new_child && clk_resource_tree_recalculates(core->new_child);
}
/*
* walk down a subtree and set the new rates notifying the rate
* change on the way
*/
-static int clk_change_rate(struct clk_core *core)
+static int clk_change_rate(struct clk_core *core, bool retain_on_failure)
{
struct clk_core *child;
struct hlist_node *tmp;
@@ -2497,6 +2788,7 @@ static int clk_change_rate(struct clk_core *core)
bool skip_set_rate = false;
bool rate_ungated = false;
bool parent_enabled = false;
+ bool retrying_parent_fault = core->parent_fault;
struct clk_core *old_parent = NULL;
struct clk_core *parent = NULL;
int ret = 0;
@@ -2526,7 +2818,8 @@ static int clk_change_rate(struct clk_core *core)
rate_ungated = true;
}
- if (core->new_parent && core->new_parent != core->parent) {
+ if ((core->new_parent && core->new_parent != core->parent) ||
+ retrying_parent_fault) {
ret = __clk_set_parent_before(core, core->new_parent,
&old_parent);
if (ret)
@@ -2545,17 +2838,26 @@ static int clk_change_rate(struct clk_core *core)
trace_clk_set_parent_complete(core, core->new_parent);
if (ret) {
- unsigned long flags = clk_enable_lock();
+ if (retrying_parent_fault) {
+ __clk_set_parent_after(core, core->new_parent,
+ old_parent);
+ } else if (retain_on_failure) {
+ clk_hold_parent_fault(core, core->new_parent,
+ old_parent);
+ } else {
+ unsigned long flags = clk_enable_lock();
- clk_reparent(core, old_parent);
- clk_enable_unlock(flags);
- __clk_set_parent_after(core, old_parent,
- core->new_parent);
+ clk_reparent(core, old_parent);
+ clk_enable_unlock(flags);
+ __clk_set_parent_after(core, old_parent,
+ core->new_parent);
+ }
goto cleanup;
}
__clk_set_parent_after(core, core->new_parent,
old_parent);
+ clk_release_parent_fault(core);
}
if (core->flags & CLK_OPS_PARENT_ENABLE) {
@@ -2598,14 +2900,14 @@ static int clk_change_rate(struct clk_core *core)
/* Skip children who will be reparented to another clock */
if (child->new_parent && child->new_parent != core)
continue;
- ret = clk_change_rate(child);
+ ret = clk_change_rate(child, retain_on_failure);
if (ret)
goto runtime_put;
}
/* handle the new child who might not be in core->children yet */
if (core->new_child) {
- ret = clk_change_rate(core->new_child);
+ ret = clk_change_rate(core->new_child, retain_on_failure);
if (ret)
goto runtime_put;
}
@@ -2649,8 +2951,14 @@ static unsigned long clk_core_req_round_rate_nolock(struct clk_core *core,
static int clk_core_set_rate_nolock(struct clk_core *core,
unsigned long req_rate)
{
- struct clk_core *top, *fail_clk;
+ struct clk_resource_transaction tx = {
+ .acquisitions = LIST_HEAD_INIT(tx.acquisitions),
+ .notifications = LIST_HEAD_INIT(tx.notifications),
+ };
+ struct clk_core *top;
+ bool resource_managed;
unsigned long rate;
+ int release_ret;
int ret;
if (!core)
@@ -2659,7 +2967,9 @@ static int clk_core_set_rate_nolock(struct clk_core *core,
rate = clk_core_req_round_rate_nolock(core, req_rate);
/* bail early if nothing to do */
- if (rate == clk_core_get_rate_nolock(core))
+ if (rate == clk_core_get_rate_nolock(core) &&
+ !clk_resource_tree_faulted(core) &&
+ !clk_resource_parent_path_faulted(core))
return 0;
/* fail on a direct rate set of a protected provider */
@@ -2670,36 +2980,98 @@ static int clk_core_set_rate_nolock(struct clk_core *core,
top = clk_calc_new_rates(core, req_rate);
if (!top)
return -EINVAL;
+ if (clk_resource_tree_managed(top) &&
+ clk_resource_tree_recalculates(top)) {
+ clk_clear_new_child(top);
+ return -EOPNOTSUPP;
+ }
+ if (!clk_parent_fault_plan_valid(top)) {
+ clk_clear_new_child(top);
+ return -EBUSY;
+ }
ret = clk_pm_runtime_get(core);
- if (ret)
+ if (ret) {
+ clk_clear_new_child(top);
return ret;
+ }
- /* notify that we are about to change rates */
- fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);
- if (fail_clk) {
- pr_debug("%s: failed to set %s rate\n", __func__,
- fail_clk->name);
- clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
- ret = -EBUSY;
+ ret = clk_resource_acquire_tree(&tx, top);
+ if (ret) {
+ core->resource_fault = true;
+ release_ret = clk_resource_release(&tx,
+ CLK_RESOURCE_NO_HW_CHANGE);
+ if (release_ret)
+ ret = release_ret;
+ goto err;
+ }
+ ret = clk_resource_acquire_parent_path(&tx, core->parent);
+ if (ret)
+ goto acquire_failed;
+ ret = clk_resource_acquire_parent_path(&tx, core->new_parent);
+ if (ret)
+ goto acquire_failed;
+
+ resource_managed = !list_empty(&tx.acquisitions);
+
+ ret = clk_collect_rate_notifications(&tx, top);
+ if (ret)
+ goto notify_failed;
+
+ ret = clk_send_rate_pre_notifications(&tx);
+ if (ret) {
+ clk_finish_rate_notifications(&tx, ABORT_RATE_CHANGE);
+ release_ret = clk_resource_release(&tx,
+ CLK_RESOURCE_NO_HW_CHANGE);
+ if (release_ret)
+ ret = release_ret;
goto err;
}
/* change the rates */
- ret = clk_change_rate(top);
+ ret = clk_change_rate(top, resource_managed);
if (ret) {
__clk_recalc_rates(top, true, 0);
- clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
+ clk_finish_rate_notifications(&tx, ABORT_RATE_CHANGE);
+ release_ret = clk_resource_release(&tx,
+ CLK_RESOURCE_HW_CHANGED);
+ if (release_ret)
+ ret = release_ret;
goto err;
}
- clk_propagate_rate_change(top, POST_RATE_CHANGE);
+ ret = clk_resource_release(&tx, CLK_RESOURCE_COMPLETE);
+ if (ret) {
+ core->resource_fault = true;
+ clk_finish_rate_notifications(&tx, ABORT_RATE_CHANGE);
+ goto err;
+ }
+ core->resource_fault = false;
+
+ clk_finish_rate_notifications(&tx, POST_RATE_CHANGE);
core->req_rate = req_rate;
err:
+ clk_clear_new_child(top);
clk_pm_runtime_put(core);
return ret;
+
+acquire_failed:
+ core->resource_fault = true;
+ release_ret = clk_resource_release(&tx,
+ CLK_RESOURCE_NO_HW_CHANGE);
+ if (release_ret)
+ ret = release_ret;
+ goto err;
+
+notify_failed:
+ clk_finish_rate_notifications(&tx, ABORT_RATE_CHANGE);
+ release_ret = clk_resource_release(&tx,
+ CLK_RESOURCE_NO_HW_CHANGE);
+ if (release_ret)
+ ret = release_ret;
+ goto err;
}
/**
@@ -3037,6 +3409,13 @@ EXPORT_SYMBOL_GPL(clk_has_parent);
static int clk_core_set_parent_nolock(struct clk_core *core,
struct clk_core *parent)
{
+ struct clk_resource_transaction tx = {
+ .acquisitions = LIST_HEAD_INIT(tx.acquisitions),
+ .notifications = LIST_HEAD_INIT(tx.notifications),
+ };
+ unsigned long new_rate;
+ bool resource_managed;
+ int release_ret;
int ret = 0;
int p_index = 0;
unsigned long p_rate = 0;
@@ -3046,8 +3425,11 @@ static int clk_core_set_parent_nolock(struct clk_core *core,
if (!core)
return 0;
- if (core->parent == parent)
+ if (core->parent == parent && !clk_resource_tree_faulted(core) &&
+ !clk_resource_parent_path_faulted(core))
return 0;
+ if (core->parent_fault && core->fault_new_parent != parent)
+ return -EBUSY;
/* verify ops for multi-parent clks */
if (core->num_parents > 1 && !core->ops->set_parent)
@@ -3075,28 +3457,86 @@ static int clk_core_set_parent_nolock(struct clk_core *core,
if (ret)
return ret;
- /* propagate PRE_RATE_CHANGE notifications */
- ret = __clk_speculate_rates(core, p_rate);
+ new_rate = clk_recalc(core, p_rate);
+ clk_calc_subtree(core, new_rate, parent, p_index);
- /* abort if a driver objects */
- if (ret & NOTIFY_STOP_MASK)
+ ret = clk_resource_acquire_tree(&tx, core);
+ if (ret) {
+ core->resource_fault = true;
+ release_ret = clk_resource_release(&tx,
+ CLK_RESOURCE_NO_HW_CHANGE);
+ if (release_ret)
+ ret = release_ret;
goto runtime_put;
+ }
+ ret = clk_resource_acquire_parent_path(&tx, core->parent);
+ if (ret)
+ goto acquire_failed;
+ ret = clk_resource_acquire_parent_path(&tx, parent);
+ if (ret)
+ goto acquire_failed;
+
+ resource_managed = !list_empty(&tx.acquisitions);
+
+ ret = clk_collect_rate_notifications(&tx, core);
+ if (ret)
+ goto notify_failed;
+
+ ret = clk_send_rate_pre_notifications(&tx);
+ if (ret) {
+ clk_finish_rate_notifications(&tx, ABORT_RATE_CHANGE);
+ release_ret = clk_resource_release(&tx,
+ CLK_RESOURCE_NO_HW_CHANGE);
+ if (release_ret)
+ ret = release_ret;
+ goto runtime_put;
+ }
/* do the re-parent */
- ret = __clk_set_parent(core, parent, p_index);
+ ret = __clk_set_parent(core, parent, p_index, resource_managed);
/* propagate rate an accuracy recalculation accordingly */
if (ret) {
- __clk_recalc_rates(core, true, ABORT_RATE_CHANGE);
+ __clk_recalc_rates(core, true, 0);
+ clk_finish_rate_notifications(&tx, ABORT_RATE_CHANGE);
+ release_ret = clk_resource_release(&tx,
+ CLK_RESOURCE_HW_CHANGED);
+ if (release_ret)
+ ret = release_ret;
} else {
- __clk_recalc_rates(core, true, POST_RATE_CHANGE);
+ __clk_recalc_rates(core, true, 0);
+ ret = clk_resource_release(&tx, CLK_RESOURCE_COMPLETE);
+ if (ret) {
+ core->resource_fault = true;
+ clk_finish_rate_notifications(&tx, ABORT_RATE_CHANGE);
+ goto runtime_put;
+ }
+ core->resource_fault = false;
+ clk_finish_rate_notifications(&tx, POST_RATE_CHANGE);
__clk_recalc_accuracies(core);
}
runtime_put:
+ clk_clear_new_child(core);
clk_pm_runtime_put(core);
return ret;
+
+acquire_failed:
+ core->resource_fault = true;
+ release_ret = clk_resource_release(&tx,
+ CLK_RESOURCE_NO_HW_CHANGE);
+ if (release_ret)
+ ret = release_ret;
+ goto runtime_put;
+
+notify_failed:
+ clk_finish_rate_notifications(&tx, ABORT_RATE_CHANGE);
+ release_ret = clk_resource_release(&tx,
+ CLK_RESOURCE_NO_HW_CHANGE);
+ if (release_ret)
+ ret = release_ret;
+ goto runtime_put;
}
int clk_hw_set_parent(struct clk_hw *hw, struct clk_hw *parent)
@@ -4532,6 +4972,10 @@ __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
ret = -EINVAL;
goto fail_ops;
}
+ if (!!init->ops->resource_acquire != !!init->ops->resource_release) {
+ ret = -EINVAL;
+ goto fail_ops;
+ }
core->ops = init->ops;
core->dev = dev;
@@ -4581,6 +5025,7 @@ __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
fail_name:
kref_put(&core->ref, __clk_release);
fail_out:
+ hw->init = init;
if (dev) {
dev_err_probe(dev, ret, "failed to register clk '%s' (%pS)\n",
init->name, hw);
@@ -4776,14 +5221,25 @@ void clk_unregister(struct clk *clk)
/* Reparent all children to the orphan list. */
hlist_for_each_entry_safe(child, t, &clk->core->children,
- child_node)
+ child_node) {
+ if (child->parent_fault) {
+ pr_warn("%s: releasing unresolved parent fault for %s\n",
+ __func__, child->name);
+ clk_release_parent_fault(child);
+ }
clk_core_set_parent_nolock(child, NULL);
+ }
}
clk_core_evict_parent_cache(clk->core);
hash_del(&clk->core->hashtable_node);
hlist_del_init(&clk->core->child_node);
+ if (clk->core->parent_fault) {
+ pr_warn("%s: releasing unresolved parent fault for %s\n",
+ __func__, clk->core->name);
+ clk_release_parent_fault(clk->core);
+ }
if (clk->core->prepare_count)
pr_warn("%s: unregistering prepared clock: %s\n",
diff --git a/drivers/clk/clk_resource_test.c b/drivers/clk/clk_resource_test.c
new file mode 100644
index 000000000000..99f6888e5480
--- /dev/null
+++ b/drivers/clk/clk_resource_test.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Tests for resource and parent-fault recovery during clock transitions. */
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+
+#include <kunit/clk.h>
+#include <kunit/test.h>
+
+struct clk_resource_test_clock {
+ struct clk_hw hw;
+ struct clk *clk;
+ unsigned long rate;
+ unsigned int acquire_calls;
+ unsigned int release_calls;
+ unsigned int set_rate_calls;
+ int release_error;
+ int parent_error;
+ u8 parent_index;
+};
+
+#define to_clk_resource_test(_hw) \
+ container_of(_hw, struct clk_resource_test_clock, hw)
+
+static unsigned long clk_resource_test_recalc(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ return to_clk_resource_test(hw)->rate;
+}
+
+static unsigned long clk_resource_test_pass_recalc(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ return parent_rate;
+}
+
+static int clk_resource_test_determine(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ return 0;
+}
+
+static int clk_resource_test_pass_determine(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_hw *parent = clk_hw_get_parent(hw);
+ struct clk_rate_request parent_req;
+ int ret;
+
+ clk_hw_forward_rate_request(hw, req, parent, &parent_req, req->rate);
+ ret = __clk_determine_rate(parent, &parent_req);
+ if (ret)
+ return ret;
+
+ req->rate = parent_req.rate;
+ req->best_parent_rate = parent_req.rate;
+ req->best_parent_hw = parent;
+
+ return 0;
+}
+
+static int clk_resource_test_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_resource_test_clock *clock = to_clk_resource_test(hw);
+
+ clock->rate = rate;
+ clock->set_rate_calls++;
+
+ return 0;
+}
+
+static int
+clk_resource_test_acquire(struct clk_hw *hw,
+ const struct clk_resource_transition *transition,
+ void **cookie)
+{
+ to_clk_resource_test(hw)->acquire_calls++;
+ *cookie = NULL;
+
+ return 0;
+}
+
+static int
+clk_resource_test_release(struct clk_hw *hw,
+ const struct clk_resource_transition *transition,
+ void *cookie, enum clk_resource_change_result result)
+{
+ struct clk_resource_test_clock *clock = to_clk_resource_test(hw);
+
+ clock->release_calls++;
+
+ return clock->release_error;
+}
+
+static u8 clk_resource_test_get_parent(struct clk_hw *hw)
+{
+ return to_clk_resource_test(hw)->parent_index;
+}
+
+static int clk_resource_test_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_resource_test_clock *clock = to_clk_resource_test(hw);
+
+ if (clock->parent_error)
+ return clock->parent_error;
+ clock->parent_index = index;
+
+ return 0;
+}
+
+static const struct clk_ops clk_resource_test_rate_ops = {
+ .recalc_rate = clk_resource_test_recalc,
+ .determine_rate = clk_resource_test_determine,
+ .set_rate = clk_resource_test_set_rate,
+ .resource_acquire = clk_resource_test_acquire,
+ .resource_release = clk_resource_test_release,
+};
+
+static const struct clk_ops clk_resource_test_pass_ops = {
+ .recalc_rate = clk_resource_test_pass_recalc,
+ .determine_rate = clk_resource_test_pass_determine,
+};
+
+static const struct clk_ops clk_resource_test_mux_ops = {
+ .determine_rate = __clk_mux_determine_rate,
+ .recalc_rate = clk_resource_test_pass_recalc,
+ .get_parent = clk_resource_test_get_parent,
+ .set_parent = clk_resource_test_set_parent,
+};
+
+static struct clk_resource_test_clock *
+clk_resource_test_register(struct kunit *test, const char *name,
+ const struct clk_ops *ops,
+ const struct clk_hw **parents, u8 num_parents,
+ unsigned long flags)
+{
+ struct clk_init_data init = {
+ .name = name,
+ .ops = ops,
+ .parent_hws = parents,
+ .num_parents = num_parents,
+ .flags = flags,
+ };
+ struct clk_resource_test_clock *clock;
+ int ret;
+
+ clock = kunit_kzalloc(test, sizeof(*clock), GFP_KERNEL);
+ if (!clock)
+ return ERR_PTR(-ENOMEM);
+
+ clock->rate = 100;
+ clock->hw.init = &init;
+ ret = clk_hw_register_kunit(test, NULL, &clock->hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ clock->clk = clk_hw_get_clk_kunit(test, &clock->hw, NULL);
+ if (IS_ERR(clock->clk))
+ return ERR_CAST(clock->clk);
+
+ return clock;
+}
+
+static void clk_resource_retry_reconciles_ancestor_test(struct kunit *test)
+{
+ struct clk_resource_test_clock *root, *middle, *leaf;
+ const struct clk_hw *parents[1];
+
+ root = clk_resource_test_register(test, "resource-root",
+ &clk_resource_test_rate_ops, NULL, 0, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, root);
+ parents[0] = &root->hw;
+ middle = clk_resource_test_register(test, "resource-middle",
+ &clk_resource_test_pass_ops, parents, 1,
+ CLK_SET_RATE_PARENT);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, middle);
+ parents[0] = &middle->hw;
+ leaf = clk_resource_test_register(test, "resource-leaf",
+ &clk_resource_test_pass_ops, parents, 1,
+ CLK_SET_RATE_PARENT);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, leaf);
+
+ root->release_error = -EIO;
+ KUNIT_ASSERT_EQ(test, -EIO, clk_set_rate(leaf->clk, 200));
+ KUNIT_ASSERT_EQ(test, 1U, root->release_calls);
+ root->release_error = 0;
+ KUNIT_EXPECT_EQ(test, 0, clk_set_rate(leaf->clk, 200));
+ KUNIT_EXPECT_EQ(test, 2U, root->release_calls);
+}
+
+static void clk_resource_unchanged_child_acquires_resources_test(struct kunit *test)
+{
+ struct clk_resource_test_clock *root, *child;
+ const struct clk_hw *parents[1];
+
+ root = clk_resource_test_register(test, "resource-root",
+ &clk_resource_test_rate_ops, NULL, 0, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, root);
+ parents[0] = &root->hw;
+ child = clk_resource_test_register(test, "resource-child",
+ &clk_resource_test_rate_ops, parents, 1, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, child);
+
+ KUNIT_ASSERT_EQ(test, 0, clk_set_rate(root->clk, 200));
+ KUNIT_ASSERT_EQ(test, 1U, child->set_rate_calls);
+ KUNIT_EXPECT_EQ(test, 1U, child->acquire_calls);
+}
+
+static void clk_resource_unprepare_after_parent_fault_test(struct kunit *test)
+{
+ struct clk_resource_test_clock *old_parent, *new_parent, *mux;
+ const struct clk_hw *parents[2];
+
+ old_parent = clk_resource_test_register(test, "resource-old",
+ &clk_resource_test_rate_ops, NULL, 0, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_parent);
+ new_parent = clk_resource_test_register(test, "resource-new",
+ &clk_resource_test_rate_ops, NULL, 0, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_parent);
+ parents[0] = &old_parent->hw;
+ parents[1] = &new_parent->hw;
+ mux = clk_resource_test_register(test, "resource-mux",
+ &clk_resource_test_mux_ops, parents, 2, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mux);
+
+ KUNIT_ASSERT_EQ(test, 0, clk_prepare_enable(mux->clk));
+ mux->parent_error = -EIO;
+ KUNIT_ASSERT_EQ(test, -EIO, clk_set_parent(mux->clk, new_parent->clk));
+ clk_disable_unprepare(mux->clk);
+ mux->parent_error = 0;
+ KUNIT_EXPECT_EQ(test, 0, clk_set_parent(mux->clk, new_parent->clk));
+ KUNIT_EXPECT_FALSE(test, clk_hw_is_prepared(&old_parent->hw));
+ KUNIT_EXPECT_EQ(test, 0U, __clk_get_enable_count(mux->clk));
+}
+
+static void clk_resource_prepare_after_parent_fault_test(struct kunit *test)
+{
+ struct clk_resource_test_clock *old_parent, *new_parent, *mux;
+ const struct clk_hw *parents[2];
+
+ old_parent = clk_resource_test_register(test, "resource-old",
+ &clk_resource_test_rate_ops, NULL, 0, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_parent);
+ new_parent = clk_resource_test_register(test, "resource-new",
+ &clk_resource_test_rate_ops, NULL, 0, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_parent);
+ parents[0] = &old_parent->hw;
+ parents[1] = &new_parent->hw;
+ mux = clk_resource_test_register(test, "resource-mux",
+ &clk_resource_test_mux_ops, parents, 2,
+ CLK_OPS_PARENT_ENABLE);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mux);
+
+ mux->parent_error = -EIO;
+ KUNIT_ASSERT_EQ(test, -EIO, clk_set_parent(mux->clk, new_parent->clk));
+ KUNIT_ASSERT_EQ(test, 0, clk_prepare_enable(mux->clk));
+ mux->parent_error = 0;
+ KUNIT_EXPECT_EQ(test, 0, clk_set_parent(mux->clk, new_parent->clk));
+ KUNIT_EXPECT_FALSE(test, clk_hw_is_prepared(&old_parent->hw));
+ KUNIT_EXPECT_EQ(test, 1U, __clk_get_enable_count(mux->clk));
+ KUNIT_EXPECT_TRUE(test, clk_hw_is_prepared(&mux->hw));
+ clk_disable_unprepare(mux->clk);
+ KUNIT_EXPECT_FALSE(test, clk_hw_is_prepared(&new_parent->hw));
+}
+
+static void clk_resource_ancestor_parent_fault_recovery_test(struct kunit *test)
+{
+ struct clk_resource_test_clock *old_parent, *new_parent, *mux, *leaf;
+ const struct clk_hw *parents[2];
+
+ old_parent = clk_resource_test_register(test, "resource-old",
+ &clk_resource_test_rate_ops, NULL, 0, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_parent);
+ new_parent = clk_resource_test_register(test, "resource-new",
+ &clk_resource_test_rate_ops, NULL, 0, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_parent);
+ parents[0] = &old_parent->hw;
+ parents[1] = &new_parent->hw;
+ mux = clk_resource_test_register(test, "resource-mux",
+ &clk_resource_test_mux_ops, parents, 2, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mux);
+ parents[0] = &mux->hw;
+ leaf = clk_resource_test_register(test, "resource-leaf",
+ &clk_resource_test_pass_ops, parents, 1,
+ CLK_SET_RATE_PARENT);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, leaf);
+
+ mux->parent_error = -EIO;
+ KUNIT_ASSERT_EQ(test, -EIO, clk_set_parent(mux->clk, new_parent->clk));
+ KUNIT_EXPECT_EQ(test, -EBUSY, clk_set_rate(leaf->clk, 100));
+ mux->parent_error = 0;
+ KUNIT_ASSERT_EQ(test, 0, clk_set_parent(mux->clk, new_parent->clk));
+ KUNIT_EXPECT_EQ(test, 0, clk_set_rate(leaf->clk, 100));
+}
+
+static void clk_resource_grandparent_fault_test(struct kunit *test, bool rate_change)
+{
+ struct clk_resource_test_clock *old_root, *new_root;
+ struct clk_resource_test_clock *old_parent, *new_parent, *mux;
+ const struct clk_hw *parents[2];
+ int ret;
+
+ old_root = clk_resource_test_register(test, "resource-old-root",
+ &clk_resource_test_rate_ops, NULL, 0, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_root);
+ new_root = clk_resource_test_register(test, "resource-new-root",
+ &clk_resource_test_rate_ops, NULL, 0, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_root);
+ KUNIT_ASSERT_EQ(test, 0, clk_set_rate(new_root->clk, 200));
+
+ parents[0] = &old_root->hw;
+ old_parent = clk_resource_test_register(test, "resource-old-parent",
+ &clk_resource_test_pass_ops, parents, 1, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_parent);
+ parents[0] = &new_root->hw;
+ new_parent = clk_resource_test_register(test, "resource-new-parent",
+ &clk_resource_test_pass_ops, parents, 1, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_parent);
+ parents[0] = &old_parent->hw;
+ parents[1] = &new_parent->hw;
+ mux = clk_resource_test_register(test, "resource-grandparent-mux",
+ &clk_resource_test_mux_ops, parents, 2, 0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mux);
+
+ KUNIT_ASSERT_EQ(test, 0, clk_prepare_enable(mux->clk));
+ mux->parent_error = -EIO;
+ if (rate_change)
+ ret = clk_set_rate(mux->clk, 200);
+ else
+ ret = clk_set_parent(mux->clk, new_parent->clk);
+ KUNIT_EXPECT_EQ(test, -EIO, ret);
+
+ clk_disable_unprepare(mux->clk);
+ KUNIT_EXPECT_TRUE(test, clk_hw_is_prepared(&old_root->hw));
+ KUNIT_EXPECT_TRUE(test, clk_hw_is_prepared(&new_root->hw));
+
+ mux->parent_error = 0;
+ if (rate_change)
+ ret = clk_set_rate(mux->clk, 200);
+ else
+ ret = clk_set_parent(mux->clk, new_parent->clk);
+ KUNIT_EXPECT_EQ(test, 0, ret);
+ KUNIT_EXPECT_FALSE(test, clk_hw_is_prepared(&old_root->hw));
+ KUNIT_EXPECT_FALSE(test, clk_hw_is_prepared(&new_root->hw));
+ KUNIT_EXPECT_EQ(test, 0U, __clk_get_enable_count(mux->clk));
+}
+
+static void clk_resource_grandparent_set_parent_fault_test(struct kunit *test)
+{
+ clk_resource_grandparent_fault_test(test, false);
+}
+
+static void clk_resource_grandparent_set_rate_fault_test(struct kunit *test)
+{
+ clk_resource_grandparent_fault_test(test, true);
+}
+
+static struct kunit_case clk_resource_recovery_test_cases[] = {
+ KUNIT_CASE(clk_resource_retry_reconciles_ancestor_test),
+ KUNIT_CASE(clk_resource_unchanged_child_acquires_resources_test),
+ KUNIT_CASE(clk_resource_unprepare_after_parent_fault_test),
+ KUNIT_CASE(clk_resource_prepare_after_parent_fault_test),
+ KUNIT_CASE(clk_resource_ancestor_parent_fault_recovery_test),
+ KUNIT_CASE(clk_resource_grandparent_set_parent_fault_test),
+ KUNIT_CASE(clk_resource_grandparent_set_rate_fault_test),
+ {}
+};
+
+static struct kunit_suite clk_resource_recovery_test_suite = {
+ .name = "clk-resource-recovery",
+ .test_cases = clk_resource_recovery_test_cases,
+};
+
+kunit_test_suite(clk_resource_recovery_test_suite);
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk_test.c b/drivers/clk/clk_test.c
index 8860e9a6e43e..5e045faed198 100644
--- a/drivers/clk/clk_test.c
+++ b/drivers/clk/clk_test.c
@@ -26,6 +26,183 @@ static const struct clk_ops empty_clk_ops = { };
#define DUMMY_CLOCK_RATE_1 (142 * HZ_PER_MHZ)
#define DUMMY_CLOCK_RATE_2 (242 * HZ_PER_MHZ)
+enum clk_resource_test_event {
+ CLK_RESOURCE_TEST_PREPARE,
+ CLK_RESOURCE_TEST_UNPREPARE,
+ CLK_RESOURCE_TEST_ACQUIRE,
+ CLK_RESOURCE_TEST_SET_RATE,
+ CLK_RESOURCE_TEST_RELEASE,
+};
+
+struct clk_resource_test_context {
+ struct clk_hw hw;
+ struct clk *clk;
+ enum clk_resource_test_event events[16];
+ unsigned int num_events;
+ int resource_release_error;
+ unsigned long rate;
+};
+
+static void clk_resource_test_record(struct clk_resource_test_context *ctx,
+ enum clk_resource_test_event event)
+{
+ ctx->events[ctx->num_events++] = event;
+}
+
+static int clk_resource_test_prepare(struct clk_hw *hw)
+{
+ struct clk_resource_test_context *ctx =
+ container_of(hw, struct clk_resource_test_context, hw);
+
+ clk_resource_test_record(ctx, CLK_RESOURCE_TEST_PREPARE);
+
+ return 0;
+}
+
+static void clk_resource_test_unprepare(struct clk_hw *hw)
+{
+ struct clk_resource_test_context *ctx =
+ container_of(hw, struct clk_resource_test_context, hw);
+
+ clk_resource_test_record(ctx, CLK_RESOURCE_TEST_UNPREPARE);
+}
+
+static int
+clk_resource_test_acquire(struct clk_hw *hw,
+ const struct clk_resource_transition *transition,
+ void **cookie)
+{
+ struct clk_resource_test_context *ctx =
+ container_of(hw, struct clk_resource_test_context, hw);
+
+ clk_resource_test_record(ctx, CLK_RESOURCE_TEST_ACQUIRE);
+
+ return 0;
+}
+
+static int
+clk_resource_test_release(struct clk_hw *hw,
+ const struct clk_resource_transition *transition,
+ void *cookie,
+ enum clk_resource_change_result result)
+{
+ struct clk_resource_test_context *ctx =
+ container_of(hw, struct clk_resource_test_context, hw);
+
+ clk_resource_test_record(ctx, CLK_RESOURCE_TEST_RELEASE);
+
+ return ctx->resource_release_error;
+}
+
+static unsigned long
+clk_resource_test_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ struct clk_resource_test_context *ctx =
+ container_of(hw, struct clk_resource_test_context, hw);
+
+ return ctx->rate;
+}
+
+static int clk_resource_test_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ return 0;
+}
+
+static int clk_resource_test_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_resource_test_context *ctx =
+ container_of(hw, struct clk_resource_test_context, hw);
+
+ clk_resource_test_record(ctx, CLK_RESOURCE_TEST_SET_RATE);
+ ctx->rate = rate;
+
+ return 0;
+}
+
+static const struct clk_ops clk_resource_test_ops = {
+ .prepare = clk_resource_test_prepare,
+ .unprepare = clk_resource_test_unprepare,
+ .recalc_rate = clk_resource_test_recalc_rate,
+ .determine_rate = clk_resource_test_determine_rate,
+ .set_rate = clk_resource_test_set_rate,
+ .resource_acquire = clk_resource_test_acquire,
+ .resource_release = clk_resource_test_release,
+};
+
+static int clk_resource_test_init(struct kunit *test)
+{
+ struct clk_resource_test_context *ctx;
+ struct clk_init_data init = {
+ .name = "clk_resource_test",
+ .ops = &clk_resource_test_ops,
+ };
+ int ret;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->hw.init = &init;
+ ctx->rate = DUMMY_CLOCK_INIT_RATE;
+ test->priv = ctx;
+
+ ret = clk_hw_register_kunit(test, NULL, &ctx->hw);
+ if (ret)
+ return ret;
+
+ ctx->clk = clk_hw_get_clk_kunit(test, &ctx->hw, NULL);
+ if (IS_ERR(ctx->clk))
+ return PTR_ERR(ctx->clk);
+
+ return 0;
+}
+
+static void clk_resource_set_rate_test(struct kunit *test)
+{
+ struct clk_resource_test_context *ctx = test->priv;
+
+ KUNIT_ASSERT_EQ(test, 0, clk_set_rate(ctx->clk, DUMMY_CLOCK_RATE_1));
+ KUNIT_ASSERT_EQ(test, 3, ctx->num_events);
+ KUNIT_EXPECT_EQ(test, CLK_RESOURCE_TEST_ACQUIRE, ctx->events[0]);
+ KUNIT_EXPECT_EQ(test, CLK_RESOURCE_TEST_SET_RATE, ctx->events[1]);
+ KUNIT_EXPECT_EQ(test, CLK_RESOURCE_TEST_RELEASE, ctx->events[2]);
+}
+
+static void clk_resource_set_rate_release_failure_test(struct kunit *test)
+{
+ struct clk_resource_test_context *ctx = test->priv;
+ unsigned int first_event = ctx->num_events;
+
+ ctx->resource_release_error = -EIO;
+ KUNIT_EXPECT_EQ(test, -EIO,
+ clk_set_rate(ctx->clk, DUMMY_CLOCK_RATE_1));
+ KUNIT_EXPECT_EQ(test, DUMMY_CLOCK_RATE_1, clk_get_rate(ctx->clk));
+
+ ctx->resource_release_error = 0;
+ KUNIT_EXPECT_EQ(test, 0, clk_set_rate(ctx->clk, DUMMY_CLOCK_RATE_1));
+ KUNIT_ASSERT_EQ(test, first_event + 6, ctx->num_events);
+ KUNIT_EXPECT_EQ(test, CLK_RESOURCE_TEST_ACQUIRE,
+ ctx->events[first_event + 3]);
+ KUNIT_EXPECT_EQ(test, CLK_RESOURCE_TEST_SET_RATE,
+ ctx->events[first_event + 4]);
+ KUNIT_EXPECT_EQ(test, CLK_RESOURCE_TEST_RELEASE,
+ ctx->events[first_event + 5]);
+}
+
+static struct kunit_case clk_resource_test_cases[] = {
+ KUNIT_CASE(clk_resource_set_rate_test),
+ KUNIT_CASE(clk_resource_set_rate_release_failure_test),
+ {}
+};
+
+static struct kunit_suite clk_resource_test_suite = {
+ .name = "clk-resource",
+ .init = clk_resource_test_init,
+ .test_cases = clk_resource_test_cases,
+};
+
struct clk_dummy_context {
struct clk_hw hw;
unsigned long rate;
@@ -3895,6 +4072,7 @@ kunit_test_suites(
&clk_assigned_sscs_suite,
&clk_hw_get_dev_of_node_test_suite,
&clk_parse_clkspec_test_suite,
+ &clk_resource_test_suite,
&clk_leaf_mux_set_rate_parent_test_suite,
&clk_test_suite,
&clk_multiple_parents_mux_test_suite,
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index a355d1ee5189..f2b4053ee8c0 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -119,6 +119,21 @@ struct clk_spread_spectrum {
enum clk_ssc_method method;
};
+enum clk_resource_change_result {
+ CLK_RESOURCE_NO_HW_CHANGE,
+ CLK_RESOURCE_HW_CHANGED,
+ CLK_RESOURCE_COMPLETE,
+};
+
+struct clk_resource_transition {
+ unsigned long old_rate;
+ unsigned long new_rate;
+ unsigned long actual_rate;
+ struct clk_hw *old_parent;
+ struct clk_hw *new_parent;
+ struct clk_hw *actual_parent;
+};
+
/**
* struct clk_ops - Callback operations for hardware clocks; these are to
* be provided by the clock implementation, and will be called by drivers
@@ -256,6 +271,23 @@ struct clk_spread_spectrum {
* directory is provided as an argument. Called with
* prepare_lock held. Returns 0 on success, -EERROR otherwise.
*
+ * @resource_acquire: Acquire the resources required for a planned rate or parent
+ * transition. Called for the affected tree before PRE rate notifiers.
+ * @resource_release: Reconcile resource requirements and release temporary
+ * resources after the whole affected tree has completed or stopped.
+ * The result describes whether hardware was untouched, may have
+ * changed, or completed successfully. The actual clock state is
+ * reported in the transition.
+ *
+ * The resource operations allow a clock provider to describe resources which
+ * must remain available during a rate or parent transition. Callback pairs are
+ * mandatory: a provider defining @resource_acquire must define
+ * @resource_release. Both callbacks may sleep and run under the CCF prepare
+ * lock. A successful @resource_acquire transfers ownership of the cookie to
+ * @resource_release, which must consume it for every result even when
+ * release returns an error. For CLK_RESOURCE_HW_CHANGED, the actual fields
+ * contain CCF's best configured state and may not describe partially programmed
+ * hardware.
*
* The clk_enable/clk_disable and clk_prepare/clk_unprepare pairs allow
* implementations to split any work between atomic (enable) and sleepable
@@ -303,6 +335,13 @@ struct clk_ops {
int (*init)(struct clk_hw *hw);
void (*terminate)(struct clk_hw *hw);
void (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
+ int (*resource_acquire)(struct clk_hw *hw,
+ const struct clk_resource_transition *transition,
+ void **cookie);
+ int (*resource_release)(struct clk_hw *hw,
+ const struct clk_resource_transition *transition,
+ void *cookie,
+ enum clk_resource_change_result result);
};
/**
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 01/16] clk: Propagate rate programming failures Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 02/16] clk: Acquire resources for clock transitions Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:34 ` sashiko-bot
2026-10-14 13:35 ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH RFC 04/16] pmdomain: core: Support named indexed virtual attachments Konrad Dybcio
` (13 subsequent siblings)
16 siblings, 2 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
A failed OPP transition need not leave the clock at the last successful
OPP's frequency. If hardware changed before the error, the default
single-clock path can subsequently skip a necessary retry because its
cached OPP matches the request. It can also choose voltage and dependency
ordering relative to the wrong operating point.
Treat an incomplete transition as dirty and use the observed clock rate
to establish retry direction. Do not trust same-OPP or same-rate
shortcuts, or allow table disable, until the state has been reconciled.
Limit this to the default single-clock path: custom callbacks can have
additional state which cannot be recovered from a clock-rate readback.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/opp/core.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++-------
drivers/opp/opp.h | 2 ++
2 files changed, 65 insertions(+), 9 deletions(-)
diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 4ccce7b6afcb..340911d2971e 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -1073,11 +1073,11 @@ _opp_config_clk_single(struct device *dev, struct opp_table *opp_table,
}
ret = clk_set_rate(opp_table->clk, freq);
+ opp_table->current_rate_single_clk = clk_get_rate(opp_table->clk);
if (ret) {
+ opp_table->config_dirty_single_clk = true;
dev_err(dev, "%s: failed to set clock rate: %d\n", __func__,
ret);
- } else {
- opp_table->current_rate_single_clk = freq;
}
return ret;
@@ -1261,6 +1261,28 @@ static void _find_current_opp(struct device *dev, struct opp_table *opp_table)
opp_table->current_opp = opp;
}
+static bool _opp_uses_default_single_clk(struct opp_table *opp_table)
+{
+ return opp_table->config_clks == _opp_config_clk_single &&
+ opp_table->clk_count == 1;
+}
+
+static struct dev_pm_opp *_find_current_opp_strict(struct opp_table *opp_table)
+{
+ unsigned long freq;
+
+ if (IS_ERR(opp_table->clk))
+ return ERR_CAST(opp_table->clk);
+
+ freq = clk_get_rate(opp_table->clk);
+ if (!freq)
+ return ERR_PTR(-ERANGE);
+
+ opp_table->current_rate_single_clk = freq;
+
+ return _find_freq_ceil(opp_table, &freq);
+}
+
static int _disable_opp_table(struct device *dev, struct opp_table *opp_table)
{
int ret;
@@ -1297,20 +1319,40 @@ static int _disable_opp_table(struct device *dev, struct opp_table *opp_table)
static int _set_opp(struct device *dev, struct opp_table *opp_table,
struct dev_pm_opp *opp, void *clk_data, bool forced)
{
+ bool default_single = _opp_uses_default_single_clk(opp_table);
+ struct dev_pm_opp *actual_opp __free(put_opp) = NULL;
struct dev_pm_opp *old_opp;
int scaling_down, ret;
- if (unlikely(!opp))
- return _disable_opp_table(dev, opp_table);
+ if (unlikely(!opp)) {
+ if (default_single && opp_table->config_dirty_single_clk)
+ return -EBUSY;
+
+ if (default_single)
+ opp_table->config_dirty_single_clk = true;
+
+ ret = _disable_opp_table(dev, opp_table);
+ if (!ret && default_single)
+ opp_table->config_dirty_single_clk = false;
+
+ return ret;
+ }
/* Find the currently set OPP if we don't know already */
if (unlikely(!opp_table->current_opp))
_find_current_opp(dev, opp_table);
old_opp = opp_table->current_opp;
+ if (default_single && opp_table->config_dirty_single_clk) {
+ actual_opp = _find_current_opp_strict(opp_table);
+ if (IS_ERR(actual_opp))
+ return PTR_ERR(actual_opp);
+ old_opp = actual_opp;
+ }
/* Return early if nothing to do */
- if (!forced && old_opp == opp && opp_table->enabled) {
+ if (!forced && !opp_table->config_dirty_single_clk &&
+ old_opp == opp && opp_table->enabled) {
dev_dbg_ratelimited(dev, "%s: OPPs are same, nothing to do\n", __func__);
return 0;
}
@@ -1324,6 +1366,9 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table,
if (scaling_down == -1)
scaling_down = 0;
+ if (default_single)
+ opp_table->config_dirty_single_clk = true;
+
/* Scaling up? Configure required OPPs before frequency */
if (!scaling_down) {
ret = _set_required_opps(dev, opp_table, opp, true);
@@ -1391,10 +1436,12 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table,
}
opp_table->enabled = true;
- dev_pm_opp_put(old_opp);
+ dev_pm_opp_put(opp_table->current_opp);
/* Make sure current_opp doesn't get freed */
opp_table->current_opp = dev_pm_opp_get(opp);
+ if (default_single)
+ opp_table->config_dirty_single_clk = false;
return ret;
}
@@ -1432,8 +1479,14 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
* equivalent to a clk_set_rate()
*/
if (!_get_opp_count(opp_table)) {
- return opp_table->config_clks(dev, opp_table, NULL,
- &target_freq, false);
+ int ret;
+
+ ret = opp_table->config_clks(dev, opp_table, NULL,
+ &target_freq, false);
+ if (!ret && _opp_uses_default_single_clk(opp_table))
+ opp_table->config_dirty_single_clk = false;
+
+ return ret;
}
freq = clk_round_rate(opp_table->clk, target_freq);
@@ -1460,7 +1513,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
* value of the frequency. In such a case, do not abort but
* configure the hardware to the desired frequency forcefully.
*/
- forced = opp_table->current_rate_single_clk != freq;
+ forced = opp_table->config_dirty_single_clk ||
+ opp_table->current_rate_single_clk != freq;
}
return _set_opp(dev, opp_table, opp, &freq, forced);
diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h
index 9eba63e01a9e..4f0ec888effa 100644
--- a/drivers/opp/opp.h
+++ b/drivers/opp/opp.h
@@ -172,6 +172,7 @@ enum opp_table_access {
* @parsed_static_opps: Count of devices for which OPPs are initialized from DT.
* @shared_opp: OPP is shared between multiple devices.
* @current_rate_single_clk: Currently configured frequency for single clk.
+ * @config_dirty_single_clk: Whether the single clk cached state is untrustworthy
* @current_opp: Currently configured OPP for the table.
* @suspend_opp: Pointer to OPP to be used during device suspend.
* @required_opp_tables: List of device OPP tables that are required by OPPs in
@@ -221,6 +222,7 @@ struct opp_table {
unsigned int parsed_static_opps;
enum opp_table_access shared_opp;
unsigned long current_rate_single_clk;
+ bool config_dirty_single_clk;
struct dev_pm_opp *current_opp;
struct dev_pm_opp *suspend_opp;
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 04/16] pmdomain: core: Support named indexed virtual attachments
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (2 preceding siblings ...)
2026-10-14 12:19 ` [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 05/16] pmdomain: core: Support subdomains without pstate propagation Konrad Dybcio
` (12 subsequent siblings)
16 siblings, 0 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
A device may need separate virtual GenPD attachments to the same
power-domain index for distinct purposes. dev_pm_domain_attach_list()
can make indexed attachments, but cannot give the resulting virtual
devices distinct names.
genpd_dev_pm_attach_by_id() derives a virtual-device name from only
the consumer device and power-domain index. Repeating the attachment
therefore collides with the existing virtual device.
Add an optional suffix to indexed virtual attachments and let
dev_pm_domain_attach_list() pass it through.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/base/power/common.c | 4 +++-
drivers/pmdomain/core.c | 28 +++++++++++++++++++++++++---
include/linux/pm_domain.h | 11 +++++++++++
3 files changed, 39 insertions(+), 4 deletions(-)
diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c
index 9bef9248a705..8beedde6dba7 100644
--- a/drivers/base/power/common.c
+++ b/drivers/base/power/common.c
@@ -241,7 +241,9 @@ int dev_pm_domain_attach_list(struct device *dev,
for (i = 0; i < num_pds; i++) {
if (by_id)
- pd_dev = dev_pm_domain_attach_by_id(dev, i);
+ pd_dev = data && data->pd_suffix ?
+ genpd_dev_pm_attach_by_id_suffix(dev, i, data->pd_suffix) :
+ dev_pm_domain_attach_by_id(dev, i);
else
pd_dev = dev_pm_domain_attach_by_name(dev,
data->pd_names[i]);
diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
index 964e44c2f78f..76ff0d9a229c 100644
--- a/drivers/pmdomain/core.c
+++ b/drivers/pmdomain/core.c
@@ -3596,8 +3596,9 @@ EXPORT_SYMBOL_GPL(genpd_dev_pm_attach);
* turned on, then ERR_PTR(-EPROBE_DEFER) is returned to ensure that the device
* is not probed and to re-try again later.
*/
-struct device *genpd_dev_pm_attach_by_id(struct device *dev,
- unsigned int index)
+static struct device *__genpd_dev_pm_attach_by_id(struct device *dev,
+ unsigned int index,
+ const char *suffix)
{
struct device *virt_dev;
int num_domains;
@@ -3620,7 +3621,11 @@ struct device *genpd_dev_pm_attach_by_id(struct device *dev,
if (!virt_dev)
return ERR_PTR(-ENOMEM);
- dev_set_name(virt_dev, "genpd:%u:%s", index, dev_name(dev));
+ if (suffix)
+ dev_set_name(virt_dev, "genpd:%u:%s:%s", index, dev_name(dev),
+ suffix);
+ else
+ dev_set_name(virt_dev, "genpd:%u:%s", index, dev_name(dev));
virt_dev->bus = &genpd_bus_type;
virt_dev->release = genpd_release_dev;
virt_dev->of_node = of_node_get(dev->of_node);
@@ -3643,8 +3648,25 @@ struct device *genpd_dev_pm_attach_by_id(struct device *dev,
return virt_dev;
}
+
+struct device *genpd_dev_pm_attach_by_id(struct device *dev,
+ unsigned int index)
+{
+ return __genpd_dev_pm_attach_by_id(dev, index, NULL);
+}
EXPORT_SYMBOL_GPL(genpd_dev_pm_attach_by_id);
+struct device *genpd_dev_pm_attach_by_id_suffix(struct device *dev,
+ unsigned int index,
+ const char *suffix)
+{
+ if (!suffix || !*suffix)
+ return ERR_PTR(-EINVAL);
+
+ return __genpd_dev_pm_attach_by_id(dev, index, suffix);
+}
+EXPORT_SYMBOL_GPL(genpd_dev_pm_attach_by_id_suffix);
+
/**
* genpd_dev_pm_attach_by_name - Associate a device with one of its PM domains.
* @dev: The device used to lookup the PM domain.
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
index 14e0e346c610..98aca4a4aede 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -50,6 +50,7 @@
struct dev_pm_domain_attach_data {
const char * const *pd_names;
u32 num_pd_names;
+ const char *pd_suffix;
u32 pd_flags;
};
@@ -484,6 +485,9 @@ int of_genpd_remove_child_ids(struct device_node *np,
int genpd_dev_pm_attach(struct device *dev);
struct device *genpd_dev_pm_attach_by_id(struct device *dev,
unsigned int index);
+struct device *genpd_dev_pm_attach_by_id_suffix(struct device *dev,
+ unsigned int index,
+ const char *suffix);
struct device *genpd_dev_pm_attach_by_name(struct device *dev,
const char *name);
#else /* !CONFIG_PM_GENERIC_DOMAINS_OF */
@@ -538,6 +542,13 @@ static inline struct device *genpd_dev_pm_attach_by_id(struct device *dev,
return NULL;
}
+static inline struct device *
+genpd_dev_pm_attach_by_id_suffix(struct device *dev, unsigned int index,
+ const char *suffix)
+{
+ return NULL;
+}
+
static inline struct device *genpd_dev_pm_attach_by_name(struct device *dev,
const char *name)
{
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 05/16] pmdomain: core: Support subdomains without pstate propagation
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (3 preceding siblings ...)
2026-10-14 12:19 ` [PATCH RFC 04/16] pmdomain: core: Support named indexed virtual attachments Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:41 ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 06/16] pmdomain: core: Support indexed power-domain attachment lists Konrad Dybcio
` (11 subsequent siblings)
16 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
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@oss.qualcomm.com>
---
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
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 06/16] pmdomain: core: Support indexed power-domain attachment lists
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (4 preceding siblings ...)
2026-10-14 12:19 ` [PATCH RFC 05/16] pmdomain: core: Support subdomains without pstate propagation Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation Konrad Dybcio
` (10 subsequent siblings)
16 siblings, 0 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
dev_pm_domain_attach_list() can attach domains selected by name or attach
all domains listed for a device. Allow callers to select an explicit list
of power-domain indices as well, retaining the original DT indices for
the virtual attachments.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/base/power/common.c | 24 +++++++++++++++++-------
include/linux/pm_domain.h | 8 +++++---
2 files changed, 22 insertions(+), 10 deletions(-)
diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c
index 8beedde6dba7..6a2d8ee473cd 100644
--- a/drivers/base/power/common.c
+++ b/drivers/base/power/common.c
@@ -179,9 +179,9 @@ EXPORT_SYMBOL_GPL(dev_pm_domain_attach_by_name);
*
* This function helps to attach a device to its multiple PM domains. The
* caller, which is typically a driver's probe function, may provide a list of
- * names for the PM domains that we should try to attach the device to, but it
- * may also provide an empty list, in case the attach should be done for all of
- * the available PM domains.
+ * names or indices for the PM domains that we should try to attach the device
+ * to, but it may also provide an empty list, in case the attach should be done
+ * for all of the available PM domains.
*
* Callers must ensure proper synchronization of this function with power
* management callbacks.
@@ -211,9 +211,14 @@ int dev_pm_domain_attach_list(struct device *dev,
if (!np)
return 0;
+ if (data && data->pd_names && data->pd_indices)
+ return -EINVAL;
+
if (data && data->pd_names) {
num_pds = data->num_pd_names;
by_id = false;
+ } else if (data && data->pd_indices) {
+ num_pds = data->num_pd_indices;
} else {
num_pds = of_count_phandle_with_args(np, "power-domains",
"#power-domain-cells");
@@ -240,13 +245,18 @@ int dev_pm_domain_attach_list(struct device *dev,
link_flags |= DL_FLAG_RPM_ACTIVE;
for (i = 0; i < num_pds; i++) {
- if (by_id)
+ if (by_id) {
+ unsigned int pd_index = data && data->pd_indices ?
+ data->pd_indices[i] : i;
+
pd_dev = data && data->pd_suffix ?
- genpd_dev_pm_attach_by_id_suffix(dev, i, data->pd_suffix) :
- dev_pm_domain_attach_by_id(dev, i);
- else
+ genpd_dev_pm_attach_by_id_suffix(dev, pd_index,
+ data->pd_suffix) :
+ dev_pm_domain_attach_by_id(dev, pd_index);
+ } else {
pd_dev = dev_pm_domain_attach_by_name(dev,
data->pd_names[i]);
+ }
if (IS_ERR_OR_NULL(pd_dev)) {
ret = pd_dev ? PTR_ERR(pd_dev) : -ENODEV;
goto err_attach;
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
index 322453da7fae..f5e340e32f48 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -32,9 +32,9 @@
*
* PD_FLAG_REQUIRED_OPP: Assign required_devs for the required OPPs. The
* index of the required OPP must correspond to the
- * index in the array of the pd_names. If pd_names
- * isn't specified, the index just follows the
- * index for the attached PM domain.
+ * index in the pd_names or pd_indices array. If neither
+ * is specified, the index just follows the index for the
+ * attached PM domain.
*
* PD_FLAG_ATTACH_POWER_ON: Power on the domain during attach.
*
@@ -50,6 +50,8 @@
struct dev_pm_domain_attach_data {
const char * const *pd_names;
u32 num_pd_names;
+ const unsigned int *pd_indices;
+ u32 num_pd_indices;
const char *pd_suffix;
u32 pd_flags;
};
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (5 preceding siblings ...)
2026-10-14 12:19 ` [PATCH RFC 06/16] pmdomain: core: Support indexed power-domain attachment lists Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:35 ` sashiko-bot
2026-10-14 14:36 ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH RFC 08/16] clk: qcom: dvfs: Add debugfs Konrad Dybcio
` (9 subsequent siblings)
16 siblings, 2 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Clocks on Qualcomm SoCs need a minimum power rail performance state to
operate at a given frequency. We currently leave this to clock
consumers, so a driver has to know which power domains supply both the
clock and its parents. This ties knowledge of the clock tree to
individual consumers and doesn't protect other users that prepare or
change the same clock without making those power requests.
We can't simply pass each clock's request to a shared GenPD device
either. The device has one performance-state vote, so each request
replaces the previous one. If two clocks need different levels on the
same rail, lowering one clock's request can leave the other clock
running without the level it needs.
Move these requirements into the clock provider and aggregate the votes
per power domain. Keep the highest level needed by prepared clocks and
in-progress rate or parent changes so that one clock can't withdraw
another clock's requirement. Use separate virtual GenPD devices for
these votes and controller register access so their power requirements
can be managed independently.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/clk-dvfs.c | 574 ++++++++++++++++++++++++++++++++++++++++++
drivers/clk/qcom/clk-dvfs.h | 40 +++
drivers/clk/qcom/clk-rcg2.c | 93 ++++++-
drivers/clk/qcom/clk-regmap.h | 6 +
drivers/clk/qcom/common.c | 26 +-
drivers/clk/qcom/common.h | 3 +
7 files changed, 725 insertions(+), 18 deletions(-)
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 6affb5073f26..b1330047fd58 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -2,6 +2,7 @@
obj-$(CONFIG_COMMON_CLK_QCOM) += clk-qcom.o
clk-qcom-y += common.o
+clk-qcom-y += clk-dvfs.o
clk-qcom-y += clk-regmap.o
clk-qcom-y += clk-alpha-pll.o
clk-qcom-y += clk-pll.o
diff --git a/drivers/clk/qcom/clk-dvfs.c b/drivers/clk/qcom/clk-dvfs.c
new file mode 100644
index 000000000000..e6490c4dab94
--- /dev/null
+++ b/drivers/clk/qcom/clk-dvfs.c
@@ -0,0 +1,574 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/device.h>
+#include <linux/device/bus.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include "clk-dvfs.h"
+#include "clk-regmap.h"
+
+struct qcom_clk_dvfs_vote {
+ struct list_head node;
+ unsigned int level;
+};
+
+struct qcom_clk_dvfs_rail_voter {
+ struct device *dev;
+ struct device_link *link;
+ struct notifier_block nb;
+ /* Serializes the vote list and rail voter state. */
+ struct mutex lock;
+ struct list_head votes;
+ unsigned int last_successful_vote_level;
+ /*
+ * Local conservative floor included in subsequent aggregation after a
+ * failed request.
+ */
+ unsigned int safety_floor_level;
+ bool active;
+ bool must_contribute;
+};
+
+struct qcom_clk_dvfs_clock {
+ struct qcom_clk_dvfs_rail_voter *rail_voters;
+ const struct qcom_clk_dvfs_data *data;
+ struct qcom_clk_dvfs_vote *votes;
+ struct clk_regmap *rclk;
+};
+
+struct qcom_clk_dvfs_transition {
+ struct qcom_clk_dvfs_vote *votes;
+ size_t num_votes;
+};
+
+struct qcom_clk_dvfs {
+ struct device *dev;
+ struct qcom_clk_dvfs_rail_voter *rail_voters;
+ struct qcom_clk_dvfs_clock *clocks;
+ size_t num_rail_voters;
+ size_t num_clocks;
+};
+
+static int qcom_clk_dvfs_find_level(const struct qcom_clk_dvfs_data *data,
+ unsigned long rate, unsigned int *level)
+{
+ for (size_t i = 0; i < data->num_rates; i++) {
+ /* PLL-derived rates are not Hz-precise. */
+ if (DIV_ROUND_CLOSEST(rate, 1000) <=
+ DIV_ROUND_CLOSEST(data->rates[i].max_rate, 1000)) {
+ *level = data->rates[i].level;
+ return 0;
+ }
+ }
+
+ return -ERANGE;
+}
+
+static unsigned int
+qcom_clk_dvfs_aggregate(struct qcom_clk_dvfs_rail_voter *rail_voter)
+{
+ struct qcom_clk_dvfs_vote *vote;
+ unsigned int level = 0;
+
+ list_for_each_entry(vote, &rail_voter->votes, node)
+ level = max(level, vote->level);
+
+ return max(level, rail_voter->safety_floor_level);
+}
+
+static int qcom_clk_dvfs_activate(struct qcom_clk_dvfs_rail_voter *rail_voter)
+{
+ int ret;
+
+ if (!rail_voter->active) {
+ ret = pm_runtime_resume_and_get(rail_voter->dev);
+ if (ret)
+ return ret;
+ rail_voter->active = true;
+ }
+
+ WRITE_ONCE(rail_voter->must_contribute, true);
+
+ return 0;
+}
+
+static int qcom_clk_dvfs_apply(struct qcom_clk_dvfs_rail_voter *rail_voter,
+ unsigned int level)
+{
+ int ret;
+
+ ret = qcom_clk_dvfs_activate(rail_voter);
+ if (ret)
+ return ret;
+ ret = dev_pm_genpd_set_performance_state(rail_voter->dev, level);
+ if (ret) {
+ rail_voter->safety_floor_level =
+ max(rail_voter->safety_floor_level,
+ max(rail_voter->last_successful_vote_level, level));
+ return ret;
+ }
+
+ rail_voter->last_successful_vote_level = level;
+ rail_voter->safety_floor_level = 0;
+
+ return 0;
+}
+
+static int qcom_clk_dvfs_update(struct qcom_clk_dvfs_rail_voter *rail_voter)
+{
+ unsigned int level = qcom_clk_dvfs_aggregate(rail_voter);
+ int ret;
+
+ if (level)
+ return qcom_clk_dvfs_apply(rail_voter, level);
+
+ if (!rail_voter->active)
+ return 0;
+
+ ret = dev_pm_genpd_set_performance_state(rail_voter->dev, 0);
+ if (ret) {
+ rail_voter->safety_floor_level =
+ max(rail_voter->safety_floor_level,
+ rail_voter->last_successful_vote_level);
+ return ret;
+ }
+
+ rail_voter->last_successful_vote_level = 0;
+ WRITE_ONCE(rail_voter->must_contribute, false);
+ rail_voter->active = false;
+ ret = pm_runtime_put_sync_suspend(rail_voter->dev);
+ if (ret < 0)
+ dev_warn(rail_voter->dev, "failed to suspend DVFS rail_voter: %d\n",
+ ret);
+
+ return 0;
+}
+
+static int qcom_clk_dvfs_replace_vote(struct qcom_clk_dvfs_rail_voter *rail_voter,
+ struct qcom_clk_dvfs_vote *vote,
+ unsigned int level)
+{
+ unsigned int old_level = vote->level;
+ int ret;
+
+ guard(mutex)(&rail_voter->lock);
+ vote->level = level;
+ ret = qcom_clk_dvfs_update(rail_voter);
+ if (ret)
+ vote->level = old_level;
+
+ return ret;
+}
+
+static int qcom_clk_dvfs_add_vote(struct qcom_clk_dvfs_rail_voter *rail_voter,
+ struct qcom_clk_dvfs_vote *vote,
+ unsigned int level)
+{
+ int ret;
+
+ guard(mutex)(&rail_voter->lock);
+ vote->level = level;
+ list_add_tail(&vote->node, &rail_voter->votes);
+ ret = qcom_clk_dvfs_update(rail_voter);
+ if (ret) {
+ list_del_init(&vote->node);
+ vote->level = 0;
+ }
+
+ return ret;
+}
+
+static int qcom_clk_dvfs_remove_vote(struct qcom_clk_dvfs_rail_voter *rail_voter,
+ struct qcom_clk_dvfs_vote *vote)
+{
+ unsigned int old_level = vote->level;
+ int ret;
+
+ guard(mutex)(&rail_voter->lock);
+ list_del_init(&vote->node);
+ vote->level = 0;
+ ret = qcom_clk_dvfs_update(rail_voter);
+ if (ret) {
+ /* The transaction record is freed; retain its error owner as a floor. */
+ rail_voter->safety_floor_level = max(rail_voter->safety_floor_level,
+ old_level);
+ WRITE_ONCE(rail_voter->must_contribute, true);
+ }
+
+ return ret;
+}
+
+int qcom_clk_dvfs_prepare(struct clk_hw *hw)
+{
+ struct qcom_clk_dvfs_clock *clock = to_clk_regmap(hw)->dvfs;
+ unsigned long rate = clk_hw_get_rate(hw);
+ unsigned int level;
+ size_t i;
+ int ret;
+
+ if (!clock)
+ return 0;
+
+ ret = qcom_clk_dvfs_find_level(clock->data, rate, &level);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < clock->data->num_pd_indices; i++) {
+ unsigned int pd_index = clock->data->pd_indices[i];
+
+ ret = qcom_clk_dvfs_replace_vote(&clock->rail_voters[pd_index],
+ &clock->votes[i], level);
+ if (ret)
+ goto unwind;
+ }
+
+ return 0;
+
+unwind:
+ while (i--) {
+ unsigned int pd_index = clock->data->pd_indices[i];
+
+ qcom_clk_dvfs_replace_vote(&clock->rail_voters[pd_index],
+ &clock->votes[i], 0);
+ }
+
+ return ret;
+}
+
+void qcom_clk_dvfs_unprepare(struct clk_hw *hw)
+{
+ struct qcom_clk_dvfs_clock *clock = to_clk_regmap(hw)->dvfs;
+
+ if (!clock)
+ return;
+
+ for (size_t i = clock->data->num_pd_indices; i-- > 0;) {
+ unsigned int pd_index = clock->data->pd_indices[i];
+
+ if (qcom_clk_dvfs_replace_vote(&clock->rail_voters[pd_index],
+ &clock->votes[i], 0))
+ dev_warn(clock->rail_voters[pd_index].dev,
+ "failed to release %s DVFS requirement\n",
+ clk_hw_get_name(hw));
+ }
+}
+
+int qcom_clk_dvfs_resource_acquire(struct clk_hw *hw,
+ const struct clk_resource_transition *transition,
+ void **cookie)
+{
+ struct qcom_clk_dvfs_clock *clock = to_clk_regmap(hw)->dvfs;
+ struct qcom_clk_dvfs_transition *dvfs_transition __free(kfree) = NULL;
+ struct qcom_clk_dvfs_vote *votes __free(kfree) = NULL;
+ unsigned int level;
+ size_t i;
+ int ret;
+
+ if (!clock) {
+ *cookie = NULL;
+ return 0;
+ }
+
+ dvfs_transition = kzalloc_obj(*dvfs_transition);
+ if (!dvfs_transition)
+ return -ENOMEM;
+
+ votes = kcalloc(clock->data->num_pd_indices, sizeof(*votes),
+ GFP_KERNEL);
+ if (!votes)
+ return -ENOMEM;
+
+ dvfs_transition->votes = votes;
+ dvfs_transition->num_votes = clock->data->num_pd_indices;
+ ret = qcom_clk_dvfs_find_level(clock->data, transition->new_rate, &level);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < dvfs_transition->num_votes; i++) {
+ unsigned int pd_index = clock->data->pd_indices[i];
+
+ ret = qcom_clk_dvfs_add_vote(&clock->rail_voters[pd_index],
+ &dvfs_transition->votes[i], level);
+ if (ret)
+ goto unwind;
+ }
+
+ dvfs_transition->votes = no_free_ptr(votes);
+ *cookie = no_free_ptr(dvfs_transition);
+
+ return 0;
+
+unwind:
+ while (i--) {
+ unsigned int pd_index = clock->data->pd_indices[i];
+
+ qcom_clk_dvfs_remove_vote(&clock->rail_voters[pd_index],
+ &dvfs_transition->votes[i]);
+ }
+
+ return ret;
+}
+
+int qcom_clk_dvfs_resource_release(struct clk_hw *hw,
+ const struct clk_resource_transition *transition,
+ void *cookie,
+ enum clk_resource_change_result result)
+{
+ struct qcom_clk_dvfs_transition *dvfs_transition = cookie;
+ struct qcom_clk_dvfs_clock *clock = to_clk_regmap(hw)->dvfs;
+ int first_error = 0;
+ unsigned int level;
+ int ret;
+
+ if (!clock)
+ return 0;
+ if (result == CLK_RESOURCE_COMPLETE) {
+ ret = qcom_clk_dvfs_find_level(clock->data, transition->actual_rate,
+ &level);
+ if (ret)
+ first_error = ret;
+ }
+
+ for (size_t i = 0; i < dvfs_transition->num_votes; i++) {
+ unsigned int pd_index = clock->data->pd_indices[i];
+
+ if (!clock->votes[i].level)
+ continue;
+
+ if (result == CLK_RESOURCE_COMPLETE) {
+ ret = first_error;
+ } else if (result == CLK_RESOURCE_HW_CHANGED) {
+ level = max(clock->votes[i].level,
+ dvfs_transition->votes[i].level);
+ ret = 0;
+ } else {
+ continue;
+ }
+
+ if (!ret) {
+ ret = qcom_clk_dvfs_replace_vote(&clock->rail_voters[pd_index],
+ &clock->votes[i], level);
+ }
+ if (ret && !first_error)
+ first_error = ret;
+ }
+
+ for (size_t i = dvfs_transition->num_votes; i-- > 0;) {
+ unsigned int pd_index = clock->data->pd_indices[i];
+
+ ret = qcom_clk_dvfs_remove_vote(&clock->rail_voters[pd_index],
+ &dvfs_transition->votes[i]);
+ if (ret && !first_error)
+ first_error = ret;
+ }
+
+ kfree(dvfs_transition->votes);
+ kfree(dvfs_transition);
+
+ return first_error;
+}
+
+static int qcom_clk_dvfs_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct qcom_clk_dvfs_rail_voter *rail_voter =
+ container_of(nb, struct qcom_clk_dvfs_rail_voter, nb);
+
+ if (event == GENPD_NOTIFY_PRE_OFF &&
+ READ_ONCE(rail_voter->must_contribute))
+ return notifier_from_errno(-EBUSY);
+
+ return NOTIFY_OK;
+}
+
+static void qcom_clk_dvfs_cleanup(void *data)
+{
+ struct qcom_clk_dvfs *dvfs = data;
+
+ for (size_t i = 0; i < dvfs->num_clocks; i++)
+ dvfs->clocks[i].rclk->dvfs = NULL;
+
+ for (size_t i = dvfs->num_rail_voters; i-- > 0;) {
+ struct qcom_clk_dvfs_rail_voter *rail_voter = &dvfs->rail_voters[i];
+
+ if (rail_voter->active) {
+ dev_pm_genpd_set_performance_state(rail_voter->dev, 0);
+ WRITE_ONCE(rail_voter->must_contribute, false);
+ pm_runtime_put_sync_suspend(rail_voter->dev);
+ }
+ dev_pm_genpd_remove_notifier(rail_voter->dev);
+ device_link_del(rail_voter->link);
+ dev_pm_domain_detach(rail_voter->dev, true);
+ mutex_destroy(&rail_voter->lock);
+ }
+}
+
+static int
+qcom_clk_dvfs_validate_clock(size_t num_rail_voters,
+ const struct qcom_clk_dvfs_data *data)
+{
+ if (!data->num_pd_indices || !data->pd_indices || !data->num_rates ||
+ !data->rates)
+ return -EINVAL;
+
+ for (size_t i = 0; i < data->num_pd_indices; i++) {
+ if (data->pd_indices[i] >= num_rail_voters)
+ return -EINVAL;
+ for (size_t j = 0; j < i; j++)
+ if (data->pd_indices[j] == data->pd_indices[i])
+ return -EINVAL;
+ }
+
+ for (size_t i = 0; i < data->num_rates; i++) {
+ if (!data->rates[i].max_rate || !data->rates[i].level)
+ return -EINVAL;
+ if (i && data->rates[i - 1].max_rate >= data->rates[i].max_rate)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct device *qcom_clk_dvfs_get_rail_voter(struct device *dev,
+ unsigned int pd_index)
+{
+ struct qcom_clk_dvfs *dvfs = dev_get_drvdata(dev);
+
+ if (!dvfs || pd_index >= dvfs->num_rail_voters)
+ return ERR_PTR(-EINVAL);
+
+ return dvfs->rail_voters[pd_index].dev;
+}
+
+int qcom_clk_dvfs_register(struct device *dev,
+ size_t num_rail_voters,
+ struct clk_regmap **clks,
+ size_t num_clks)
+{
+ struct qcom_clk_dvfs *dvfs;
+ size_t num_dvfs_clocks = 0;
+ int ret;
+
+ if (!dev || !dev->of_node || !clks || !num_rail_voters)
+ return -EINVAL;
+
+ for (size_t i = 0; i < num_clks; i++) {
+ const struct clk_init_data *init;
+ bool has_dvfs_data;
+ bool has_dvfs_resource_ops;
+
+ if (!clks[i])
+ continue;
+
+ init = clks[i]->hw.init;
+ has_dvfs_data = !!clks[i]->dvfs_data;
+ has_dvfs_resource_ops = init && init->ops &&
+ init->ops->resource_acquire == qcom_clk_dvfs_resource_acquire &&
+ init->ops->resource_release == qcom_clk_dvfs_resource_release;
+ if (has_dvfs_data && !has_dvfs_resource_ops) {
+ dev_warn(dev, "%s has DVFS data without matching DVFS resource ops\n",
+ init ? init->name : "an uninitialized clock");
+ return -EINVAL;
+ }
+ if (!has_dvfs_data)
+ continue;
+ if (qcom_clk_dvfs_validate_clock(num_rail_voters,
+ clks[i]->dvfs_data))
+ return -EINVAL;
+ num_dvfs_clocks++;
+ }
+ if (!num_dvfs_clocks)
+ return -EINVAL;
+
+ dvfs = devm_kzalloc(dev, sizeof(*dvfs), GFP_KERNEL);
+ if (!dvfs)
+ return -ENOMEM;
+ dvfs->dev = dev;
+ dvfs->rail_voters = devm_kcalloc(dev, num_rail_voters,
+ sizeof(*dvfs->rail_voters), GFP_KERNEL);
+ dvfs->clocks = devm_kcalloc(dev, num_dvfs_clocks, sizeof(*dvfs->clocks),
+ GFP_KERNEL);
+ if (!dvfs->rail_voters || !dvfs->clocks)
+ return -ENOMEM;
+
+ for (size_t i = 0, j = 0; i < num_clks; i++) {
+ struct clk_regmap *rclk = clks[i];
+
+ if (!rclk || !rclk->dvfs_data)
+ continue;
+
+ dvfs->clocks[j].rclk = rclk;
+ j++;
+ }
+
+ for (size_t i = 0; i < num_rail_voters; i++) {
+ struct qcom_clk_dvfs_rail_voter *rail_voter = &dvfs->rail_voters[i];
+
+ mutex_init(&rail_voter->lock);
+ INIT_LIST_HEAD(&rail_voter->votes);
+ rail_voter->dev = genpd_dev_pm_attach_by_id_suffix(dev, i, "dvfs");
+ if (IS_ERR_OR_NULL(rail_voter->dev)) {
+ ret = rail_voter->dev ? PTR_ERR(rail_voter->dev) : -ENODEV;
+ goto err_cleanup;
+ }
+
+ rail_voter->link = device_link_add(dev, rail_voter->dev,
+ DL_FLAG_STATELESS);
+ if (!rail_voter->link) {
+ ret = -EINVAL;
+ dev_pm_domain_detach(rail_voter->dev, true);
+ goto err_cleanup;
+ }
+
+ rail_voter->nb.notifier_call = qcom_clk_dvfs_notifier;
+ ret = dev_pm_genpd_add_notifier(rail_voter->dev, &rail_voter->nb);
+ if (ret) {
+ device_link_del(rail_voter->link);
+ dev_pm_domain_detach(rail_voter->dev, true);
+ goto err_cleanup;
+ }
+ dvfs->num_rail_voters++;
+ }
+
+ for (size_t i = 0; i < num_dvfs_clocks; i++) {
+ struct qcom_clk_dvfs_clock *clock = &dvfs->clocks[i];
+ const struct qcom_clk_dvfs_data *data = clock->rclk->dvfs_data;
+
+ clock->votes = devm_kcalloc(dev, data->num_pd_indices,
+ sizeof(*clock->votes), GFP_KERNEL);
+ if (!clock->votes) {
+ ret = -ENOMEM;
+ goto err_cleanup;
+ }
+
+ clock->data = data;
+ clock->rail_voters = dvfs->rail_voters;
+ for (size_t j = 0; j < data->num_pd_indices; j++) {
+ struct qcom_clk_dvfs_rail_voter *rail_voter;
+ unsigned int pd_index;
+
+ pd_index = data->pd_indices[j];
+ rail_voter = &dvfs->rail_voters[pd_index];
+ guard(mutex)(&rail_voter->lock);
+ list_add_tail(&clock->votes[j].node, &rail_voter->votes);
+ }
+
+ clock->rclk->dvfs = clock;
+ dvfs->num_clocks++;
+ }
+
+ dev_set_drvdata(dev, dvfs);
+
+ return devm_add_action_or_reset(dev, qcom_clk_dvfs_cleanup, dvfs);
+
+err_cleanup:
+ qcom_clk_dvfs_cleanup(dvfs);
+
+ return ret;
+}
diff --git a/drivers/clk/qcom/clk-dvfs.h b/drivers/clk/qcom/clk-dvfs.h
new file mode 100644
index 000000000000..48c7487614c1
--- /dev/null
+++ b/drivers/clk/qcom/clk-dvfs.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __QCOM_CLK_DVFS_H__
+#define __QCOM_CLK_DVFS_H__
+
+#include <linux/clk-provider.h>
+#include <linux/types.h>
+
+struct device;
+struct clk_regmap;
+
+struct qcom_clk_dvfs_rate {
+ unsigned long max_rate;
+ unsigned int level;
+};
+
+struct qcom_clk_dvfs_data {
+ const unsigned int *pd_indices;
+ size_t num_pd_indices;
+ const struct qcom_clk_dvfs_rate *rates;
+ size_t num_rates;
+};
+
+int qcom_clk_dvfs_resource_acquire(struct clk_hw *hw,
+ const struct clk_resource_transition *transition,
+ void **cookie);
+int qcom_clk_dvfs_resource_release(struct clk_hw *hw,
+ const struct clk_resource_transition *transition,
+ void *cookie,
+ enum clk_resource_change_result result);
+int qcom_clk_dvfs_prepare(struct clk_hw *hw);
+void qcom_clk_dvfs_unprepare(struct clk_hw *hw);
+
+int qcom_clk_dvfs_register(struct device *dev,
+ size_t num_rail_voters,
+ struct clk_regmap **clks,
+ size_t num_clks);
+struct device *qcom_clk_dvfs_get_rail_voter(struct device *dev,
+ unsigned int pd_index);
+
+#endif
diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c
index c44c51d860e1..0b13cf4ee01f 100644
--- a/drivers/clk/qcom/clk-rcg2.c
+++ b/drivers/clk/qcom/clk-rcg2.c
@@ -21,6 +21,7 @@
#include <asm/div64.h>
#include "clk-rcg.h"
+#include "clk-dvfs.h"
#include "common.h"
#define CMD_REG 0x0
@@ -799,6 +800,10 @@ static int clk_rcg2_set_duty_cycle(struct clk_hw *hw, struct clk_duty *duty)
}
const struct clk_ops clk_rcg2_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
@@ -812,6 +817,10 @@ const struct clk_ops clk_rcg2_ops = {
EXPORT_SYMBOL_GPL(clk_rcg2_ops);
const struct clk_ops clk_rcg2_gp_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
@@ -824,6 +833,10 @@ const struct clk_ops clk_rcg2_gp_ops = {
EXPORT_SYMBOL_GPL(clk_rcg2_gp_ops);
const struct clk_ops clk_rcg2_floor_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
@@ -837,6 +850,10 @@ const struct clk_ops clk_rcg2_floor_ops = {
EXPORT_SYMBOL_GPL(clk_rcg2_floor_ops);
const struct clk_ops clk_rcg2_fm_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
@@ -850,6 +867,10 @@ const struct clk_ops clk_rcg2_fm_ops = {
EXPORT_SYMBOL_GPL(clk_rcg2_fm_ops);
const struct clk_ops clk_rcg2_mux_closest_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.determine_rate = __clk_mux_determine_rate_closest,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
@@ -973,6 +994,10 @@ static int clk_edp_pixel_determine_rate(struct clk_hw *hw,
}
const struct clk_ops clk_edp_pixel_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
@@ -1031,6 +1056,10 @@ static int clk_byte_set_rate_and_parent(struct clk_hw *hw,
}
const struct clk_ops clk_byte_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
@@ -1101,6 +1130,10 @@ static int clk_byte2_set_rate_and_parent(struct clk_hw *hw,
}
const struct clk_ops clk_byte2_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
@@ -1194,6 +1227,10 @@ static int clk_pixel_set_rate_and_parent(struct clk_hw *hw, unsigned long rate,
}
const struct clk_ops clk_pixel_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
@@ -1309,6 +1346,10 @@ static int clk_gfx3d_set_rate(struct clk_hw *hw, unsigned long rate,
}
const struct clk_ops clk_gfx3d_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
@@ -1357,19 +1398,21 @@ static int
clk_rcg2_shared_force_enable_clear(struct clk_hw *hw, const struct freq_tbl *f)
{
struct clk_rcg2 *rcg = to_clk_rcg2(hw);
- int ret;
+ int ret, cleanup_ret;
ret = clk_rcg2_set_force_enable(hw);
if (ret)
- return ret;
+ goto clear_force_enable;
ret = clk_rcg2_configure(rcg, f);
- if (ret) {
- clk_rcg2_clear_force_enable(hw);
- return ret;
- }
- return clk_rcg2_clear_force_enable(hw);
+clear_force_enable:
+ cleanup_ret = clk_rcg2_clear_force_enable(hw);
+ if (cleanup_ret)
+ pr_warn("%s: failed to clear force-enable: %d\n",
+ clk_hw_get_name(hw), cleanup_ret);
+
+ return ret ?: cleanup_ret;
}
static int __clk_rcg2_shared_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -1428,7 +1471,7 @@ static int clk_rcg2_shared_set_floor_rate_and_parent(struct clk_hw *hw,
static int clk_rcg2_shared_enable(struct clk_hw *hw)
{
struct clk_rcg2 *rcg = to_clk_rcg2(hw);
- int ret;
+ int ret, cleanup_ret;
/*
* Set the update bit because required configuration has already
@@ -1436,18 +1479,22 @@ static int clk_rcg2_shared_enable(struct clk_hw *hw)
*/
ret = clk_rcg2_set_force_enable(hw);
if (ret)
- return ret;
+ goto clear_force_enable;
/* Write back the stored configuration corresponding to current rate */
ret = regmap_write(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, rcg->parked_cfg);
if (ret)
- return ret;
+ goto clear_force_enable;
ret = update_config(rcg);
- if (ret)
- return ret;
- return clk_rcg2_clear_force_enable(hw);
+clear_force_enable:
+ cleanup_ret = clk_rcg2_clear_force_enable(hw);
+ if (cleanup_ret)
+ pr_warn("%s: failed to clear force-enable: %d\n",
+ clk_hw_get_name(hw), cleanup_ret);
+
+ return ret ?: cleanup_ret;
}
static void clk_rcg2_shared_disable(struct clk_hw *hw)
@@ -1548,6 +1595,10 @@ static int clk_rcg2_shared_init(struct clk_hw *hw)
}
const struct clk_ops clk_rcg2_shared_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.init = clk_rcg2_shared_init,
.enable = clk_rcg2_shared_enable,
.disable = clk_rcg2_shared_disable,
@@ -1573,7 +1624,11 @@ static int clk_rcg2_shared_no_init_park(struct clk_hw *hw)
}
const struct clk_ops clk_rcg2_shared_floor_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
.init = clk_rcg2_shared_no_init_park,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_rcg2_shared_enable,
.disable = clk_rcg2_shared_disable,
.get_parent = clk_rcg2_shared_get_parent,
@@ -1590,6 +1645,10 @@ EXPORT_SYMBOL_GPL(clk_rcg2_shared_floor_ops);
* unchanged at registration time.
*/
const struct clk_ops clk_rcg2_shared_no_init_park_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.init = clk_rcg2_shared_no_init_park,
.enable = clk_rcg2_shared_enable,
.disable = clk_rcg2_shared_disable,
@@ -1734,6 +1793,10 @@ clk_rcg2_dfs_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
}
static const struct clk_ops clk_rcg2_dfs_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.determine_rate = clk_rcg2_dfs_determine_rate,
@@ -1855,6 +1918,10 @@ static int clk_rcg2_dp_determine_rate(struct clk_hw *hw,
}
const struct clk_ops clk_dp_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.is_enabled = clk_rcg2_is_enabled,
.get_parent = clk_rcg2_get_parent,
.set_parent = clk_rcg2_set_parent,
diff --git a/drivers/clk/qcom/clk-regmap.h b/drivers/clk/qcom/clk-regmap.h
index 14ec659a3a77..7ce38bbe12fd 100644
--- a/drivers/clk/qcom/clk-regmap.h
+++ b/drivers/clk/qcom/clk-regmap.h
@@ -7,6 +7,8 @@
#include <linux/clk-provider.h>
struct regmap;
+struct qcom_clk_dvfs_clock;
+struct qcom_clk_dvfs_data;
/**
* struct clk_regmap - regmap supporting clock
@@ -16,6 +18,8 @@ struct regmap;
* @enable_mask: mask when using regmap enable/disable ops
* @enable_is_inverted: flag to indicate set enable_mask bits to disable
* when using clock_enable_regmap and friends APIs.
+ * @dvfs_data: immutable clock-owned DVFS requirements
+ * @dvfs: clock-owned DVFS runtime state
*/
struct clk_regmap {
struct clk_hw hw;
@@ -23,6 +27,8 @@ struct clk_regmap {
unsigned int enable_reg;
unsigned int enable_mask;
bool enable_is_inverted;
+ const struct qcom_clk_dvfs_data *dvfs_data;
+ struct qcom_clk_dvfs_clock *dvfs;
};
static inline struct clk_regmap *to_clk_regmap(struct clk_hw *hw)
diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c
index 08fd62ac780d..01c00c2568b2 100644
--- a/drivers/clk/qcom/common.c
+++ b/drivers/clk/qcom/common.c
@@ -16,6 +16,7 @@
#include "common.h"
#include "clk-alpha-pll.h"
#include "clk-branch.h"
+#include "clk-dvfs.h"
#include "clk-rcg.h"
#include "clk-regmap.h"
#include "reset.h"
@@ -25,7 +26,6 @@ struct qcom_cc {
struct qcom_reset_controller reset;
struct clk_regmap **rclks;
size_t num_rclks;
- struct dev_pm_domain_list *pd_list;
};
const
@@ -332,14 +332,31 @@ 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;
+ const struct dev_pm_domain_attach_data pd_data = {
+ .pd_indices = desc->pd_indices,
+ .num_pd_indices = desc->num_pd_indices,
+ .pd_suffix = "access",
+ };
cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL);
if (!cc)
return -ENOMEM;
- ret = devm_pm_domain_attach_list(dev, NULL, &cc->pd_list);
- if (ret < 0 && ret != -EEXIST)
- return ret;
+ if (desc->num_dvfs_rail_voters) {
+ ret = qcom_clk_dvfs_register(dev, desc->num_dvfs_rail_voters, rclks,
+ num_clks);
+ if (ret)
+ return ret;
+ }
+
+ if (!desc->num_dvfs_rail_voters || desc->num_pd_indices) {
+ ret = devm_pm_domain_attach_list(dev,
+ desc->num_pd_indices ? &pd_data : NULL,
+ &pd_list);
+ if (ret < 0 && ret != -EEXIST)
+ return ret;
+ }
if (desc->use_rpm) {
ret = devm_pm_runtime_enable(dev);
@@ -380,7 +397,6 @@ int qcom_cc_really_probe(struct device *dev,
scd->dev = dev;
scd->scs = desc->gdscs;
scd->num = desc->num_gdscs;
- scd->pd_list = cc->pd_list;
ret = gdsc_register(scd, &reset->rcdev, regmap);
if (ret)
goto put_rpm;
diff --git a/drivers/clk/qcom/common.h b/drivers/clk/qcom/common.h
index 6f2406f8839e..769b3f5c1486 100644
--- a/drivers/clk/qcom/common.h
+++ b/drivers/clk/qcom/common.h
@@ -49,6 +49,9 @@ struct qcom_cc_desc {
size_t num_icc_hws;
unsigned int icc_first_node_id;
bool use_rpm;
+ const unsigned int *pd_indices;
+ size_t num_pd_indices;
+ size_t num_dvfs_rail_voters;
const struct qcom_cc_driver_data *driver_data;
};
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 08/16] clk: qcom: dvfs: Add debugfs
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (6 preceding siblings ...)
2026-10-14 12:19 ` [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:37 ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 09/16] clk: qcom: clk-alpha-pll: Add DVFS (un)prepare Konrad Dybcio
` (8 subsequent siblings)
16 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
It's really useful to be able to analyze the current set of votes and
what holds them. Add debugfs to expose that information.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/clk/qcom/clk-dvfs.c | 63 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 60 insertions(+), 3 deletions(-)
diff --git a/drivers/clk/qcom/clk-dvfs.c b/drivers/clk/qcom/clk-dvfs.c
index e6490c4dab94..4af78eb0c78d 100644
--- a/drivers/clk/qcom/clk-dvfs.c
+++ b/drivers/clk/qcom/clk-dvfs.c
@@ -2,26 +2,32 @@
#include <linux/device.h>
#include <linux/device/bus.h>
+#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
+#include <linux/seq_file.h>
#include <linux/slab.h>
+#include <linux/string.h>
#include "clk-dvfs.h"
#include "clk-regmap.h"
struct qcom_clk_dvfs_vote {
struct list_head node;
+ const char *clock_name;
unsigned int level;
+ bool transition;
};
struct qcom_clk_dvfs_rail_voter {
struct device *dev;
struct device_link *link;
struct notifier_block nb;
+ const char *name;
/* Serializes the vote list and rail voter state. */
struct mutex lock;
struct list_head votes;
@@ -55,8 +61,12 @@ struct qcom_clk_dvfs {
size_t num_clocks;
};
-static int qcom_clk_dvfs_find_level(const struct qcom_clk_dvfs_data *data,
- unsigned long rate, unsigned int *level)
+static DEFINE_MUTEX(qcom_clk_dvfs_debugfs_lock);
+static struct dentry *qcom_clk_dvfs_debugfs_root;
+
+static int
+qcom_clk_dvfs_find_level(const struct qcom_clk_dvfs_data *data, unsigned long rate,
+ unsigned int *level)
{
for (size_t i = 0; i < data->num_rates; i++) {
/* PLL-derived rates are not Hz-precise. */
@@ -82,6 +92,36 @@ qcom_clk_dvfs_aggregate(struct qcom_clk_dvfs_rail_voter *rail_voter)
return max(level, rail_voter->safety_floor_level);
}
+static int qcom_clk_dvfs_debugfs_show(struct seq_file *s, void *data)
+{
+ struct qcom_clk_dvfs *dvfs = dev_get_drvdata(s->private);
+
+ for (size_t i = 0; i < dvfs->num_rail_voters; i++) {
+ struct qcom_clk_dvfs_rail_voter *rail_voter = &dvfs->rail_voters[i];
+ struct qcom_clk_dvfs_vote *vote;
+
+ guard(mutex)(&rail_voter->lock);
+ seq_printf(s, "%s (aggregate=%u)\n", rail_voter->name,
+ qcom_clk_dvfs_aggregate(rail_voter));
+ seq_printf(s, " last_successful=%u safety_floor=%u must_contribute=%u\n",
+ rail_voter->last_successful_vote_level,
+ rail_voter->safety_floor_level, rail_voter->must_contribute);
+ list_for_each_entry(vote, &rail_voter->votes, node) {
+ int padding;
+
+ if (!vote->level)
+ continue;
+
+ padding = max(32 - (int)strlen(vote->clock_name), 0);
+ seq_printf(s, " %s:%*s%s level=%u\n", vote->clock_name,
+ padding, "",
+ vote->transition ? "transition" : "prepared", vote->level);
+ }
+ }
+
+ return 0;
+}
+
static int qcom_clk_dvfs_activate(struct qcom_clk_dvfs_rail_voter *rail_voter)
{
int ret;
@@ -293,6 +333,8 @@ int qcom_clk_dvfs_resource_acquire(struct clk_hw *hw,
for (i = 0; i < dvfs_transition->num_votes; i++) {
unsigned int pd_index = clock->data->pd_indices[i];
+ dvfs_transition->votes[i].clock_name = clk_hw_get_name(hw);
+ dvfs_transition->votes[i].transition = true;
ret = qcom_clk_dvfs_add_vote(&clock->rail_voters[pd_index],
&dvfs_transition->votes[i], level);
if (ret)
@@ -517,6 +559,7 @@ int qcom_clk_dvfs_register(struct device *dev,
ret = rail_voter->dev ? PTR_ERR(rail_voter->dev) : -ENODEV;
goto err_cleanup;
}
+ rail_voter->name = pd_to_genpd(rail_voter->dev->pm_domain)->name;
rail_voter->link = device_link_add(dev, rail_voter->dev,
DL_FLAG_STATELESS);
@@ -555,6 +598,7 @@ int qcom_clk_dvfs_register(struct device *dev,
pd_index = data->pd_indices[j];
rail_voter = &dvfs->rail_voters[pd_index];
+ clock->votes[j].clock_name = clock->rclk->hw.init->name;
guard(mutex)(&rail_voter->lock);
list_add_tail(&clock->votes[j].node, &rail_voter->votes);
}
@@ -565,7 +609,20 @@ int qcom_clk_dvfs_register(struct device *dev,
dev_set_drvdata(dev, dvfs);
- return devm_add_action_or_reset(dev, qcom_clk_dvfs_cleanup, dvfs);
+ ret = devm_add_action_or_reset(dev, qcom_clk_dvfs_cleanup, dvfs);
+ if (ret)
+ return ret;
+
+ scoped_guard(mutex, &qcom_clk_dvfs_debugfs_lock) {
+ if (!qcom_clk_dvfs_debugfs_root)
+ qcom_clk_dvfs_debugfs_root = debugfs_create_dir("qcom_clk_dvfs", NULL);
+
+ debugfs_create_devm_seqfile(dvfs->dev, dev_name(dvfs->dev),
+ qcom_clk_dvfs_debugfs_root,
+ qcom_clk_dvfs_debugfs_show);
+ }
+
+ return 0;
err_cleanup:
qcom_clk_dvfs_cleanup(dvfs);
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 09/16] clk: qcom: clk-alpha-pll: Add DVFS (un)prepare
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (7 preceding siblings ...)
2026-10-14 12:19 ` [PATCH RFC 08/16] clk: qcom: dvfs: Add debugfs Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 10/16] clk: qcom: clk-alpha-pll: Vote for the PLL calibration frequency Konrad Dybcio
` (7 subsequent siblings)
16 siblings, 0 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Alpha PLLs have strict power requirements for a given frequency.
Ensure they can be accounted for with the new clk-dvfs (un)prepare
operations.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/clk/qcom/clk-alpha-pll.c | 232 +++++++++++++++++++++++++++++++++++++--
1 file changed, 223 insertions(+), 9 deletions(-)
diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c
index 60173b076cc5..5dc6b1adc929 100644
--- a/drivers/clk/qcom/clk-alpha-pll.c
+++ b/drivers/clk/qcom/clk-alpha-pll.c
@@ -11,6 +11,7 @@
#include <linux/delay.h>
#include "clk-alpha-pll.h"
+#include "clk-dvfs.h"
#include "common.h"
#define PLL_MODE(p) ((p)->offset + 0x0)
@@ -1182,6 +1183,10 @@ clk_trion_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
}
const struct clk_ops clk_alpha_pll_fixed_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_alpha_pll_enable,
.disable = clk_alpha_pll_disable,
.is_enabled = clk_alpha_pll_is_enabled,
@@ -1190,6 +1195,10 @@ const struct clk_ops clk_alpha_pll_fixed_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_fixed_ops);
const struct clk_ops clk_alpha_pll_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_alpha_pll_enable,
.disable = clk_alpha_pll_disable,
.is_enabled = clk_alpha_pll_is_enabled,
@@ -1200,6 +1209,10 @@ const struct clk_ops clk_alpha_pll_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_ops);
const struct clk_ops clk_alpha_pll_huayra_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_alpha_pll_enable,
.disable = clk_alpha_pll_disable,
.is_enabled = clk_alpha_pll_is_enabled,
@@ -1210,6 +1223,10 @@ const struct clk_ops clk_alpha_pll_huayra_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_huayra_ops);
const struct clk_ops clk_alpha_pll_hwfsm_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_alpha_pll_hwfsm_enable,
.disable = clk_alpha_pll_hwfsm_disable,
.is_enabled = clk_alpha_pll_hwfsm_is_enabled,
@@ -1220,6 +1237,10 @@ const struct clk_ops clk_alpha_pll_hwfsm_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_hwfsm_ops);
const struct clk_ops clk_alpha_pll_fixed_trion_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_trion_pll_enable,
.disable = clk_trion_pll_disable,
.is_enabled = clk_trion_pll_is_enabled,
@@ -1310,6 +1331,10 @@ static int clk_alpha_pll_postdiv_set_rate(struct clk_hw *hw, unsigned long rate,
}
const struct clk_ops clk_alpha_pll_postdiv_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.recalc_rate = clk_alpha_pll_postdiv_recalc_rate,
.determine_rate = clk_alpha_pll_postdiv_determine_rate,
.set_rate = clk_alpha_pll_postdiv_set_rate,
@@ -1317,6 +1342,10 @@ const struct clk_ops clk_alpha_pll_postdiv_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_postdiv_ops);
const struct clk_ops clk_alpha_pll_postdiv_ro_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.determine_rate = clk_alpha_pll_postdiv_ro_determine_rate,
.recalc_rate = clk_alpha_pll_postdiv_recalc_rate,
};
@@ -1500,7 +1529,7 @@ static int alpha_pll_fabia_set_rate(struct clk_hw *hw, unsigned long rate,
return __clk_alpha_pll_update_latch(pll);
}
-static int alpha_pll_fabia_prepare(struct clk_hw *hw)
+static int __alpha_pll_fabia_prepare(struct clk_hw *hw)
{
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
const struct pll_vco *vco;
@@ -1555,8 +1584,26 @@ static int alpha_pll_fabia_prepare(struct clk_hw *hw)
return 0;
}
+static int alpha_pll_fabia_prepare(struct clk_hw *hw)
+{
+ int ret;
+
+ ret = qcom_clk_dvfs_prepare(hw);
+ if (ret)
+ return ret;
+
+ ret = __alpha_pll_fabia_prepare(hw);
+ if (ret)
+ qcom_clk_dvfs_unprepare(hw);
+
+ return ret;
+}
+
const struct clk_ops clk_alpha_pll_fabia_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
.prepare = alpha_pll_fabia_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = alpha_pll_fabia_enable,
.disable = alpha_pll_fabia_disable,
.is_enabled = clk_alpha_pll_is_enabled,
@@ -1567,6 +1614,10 @@ const struct clk_ops clk_alpha_pll_fabia_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_fabia_ops);
const struct clk_ops clk_alpha_pll_fixed_fabia_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = alpha_pll_fabia_enable,
.disable = alpha_pll_fabia_disable,
.is_enabled = clk_alpha_pll_is_enabled,
@@ -1653,6 +1704,10 @@ clk_trion_pll_postdiv_set_rate(struct clk_hw *hw, unsigned long rate,
}
const struct clk_ops clk_alpha_pll_postdiv_trion_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.recalc_rate = clk_trion_pll_postdiv_recalc_rate,
.determine_rate = clk_trion_pll_postdiv_determine_rate,
.set_rate = clk_trion_pll_postdiv_set_rate,
@@ -1699,6 +1754,10 @@ static int clk_alpha_pll_postdiv_fabia_set_rate(struct clk_hw *hw,
}
const struct clk_ops clk_alpha_pll_postdiv_fabia_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.recalc_rate = clk_alpha_pll_postdiv_fabia_recalc_rate,
.determine_rate = clk_alpha_pll_postdiv_fabia_determine_rate,
.set_rate = clk_alpha_pll_postdiv_fabia_set_rate,
@@ -1785,12 +1844,32 @@ static int __alpha_pll_trion_prepare(struct clk_hw *hw, u32 pcal_done)
static int alpha_pll_trion_prepare(struct clk_hw *hw)
{
- return __alpha_pll_trion_prepare(hw, TRION_PCAL_DONE);
+ int ret;
+
+ ret = qcom_clk_dvfs_prepare(hw);
+ if (ret)
+ return ret;
+
+ ret = __alpha_pll_trion_prepare(hw, TRION_PCAL_DONE);
+ if (ret)
+ qcom_clk_dvfs_unprepare(hw);
+
+ return ret;
}
static int alpha_pll_lucid_prepare(struct clk_hw *hw)
{
- return __alpha_pll_trion_prepare(hw, LUCID_PCAL_DONE);
+ int ret;
+
+ ret = qcom_clk_dvfs_prepare(hw);
+ if (ret)
+ return ret;
+
+ ret = __alpha_pll_trion_prepare(hw, LUCID_PCAL_DONE);
+ if (ret)
+ qcom_clk_dvfs_unprepare(hw);
+
+ return ret;
}
static int __alpha_pll_trion_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -1847,7 +1926,10 @@ static int alpha_pll_trion_set_rate(struct clk_hw *hw, unsigned long rate,
}
const struct clk_ops clk_alpha_pll_trion_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
.prepare = alpha_pll_trion_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_trion_pll_enable,
.disable = clk_trion_pll_disable,
.is_enabled = clk_trion_pll_is_enabled,
@@ -1858,7 +1940,10 @@ const struct clk_ops clk_alpha_pll_trion_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_trion_ops);
const struct clk_ops clk_alpha_pll_lucid_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
.prepare = alpha_pll_lucid_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_trion_pll_enable,
.disable = clk_trion_pll_disable,
.is_enabled = clk_trion_pll_is_enabled,
@@ -1869,6 +1954,10 @@ const struct clk_ops clk_alpha_pll_lucid_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_lucid_ops);
const struct clk_ops clk_alpha_pll_postdiv_lucid_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.recalc_rate = clk_alpha_pll_postdiv_fabia_recalc_rate,
.determine_rate = clk_alpha_pll_postdiv_fabia_determine_rate,
.set_rate = clk_alpha_pll_postdiv_fabia_set_rate,
@@ -1918,6 +2007,10 @@ static int clk_alpha_pll_agera_set_rate(struct clk_hw *hw, unsigned long rate,
}
const struct clk_ops clk_alpha_pll_agera_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_alpha_pll_enable,
.disable = clk_alpha_pll_disable,
.is_enabled = clk_alpha_pll_is_enabled,
@@ -2054,7 +2147,7 @@ static void alpha_pll_lucid_5lpe_disable(struct clk_hw *hw)
* The Lucid 5LPE PLL requires a power-on self-calibration which happens
* when the PLL comes out of reset. Calibrate in case it is not completed.
*/
-static int alpha_pll_lucid_5lpe_prepare(struct clk_hw *hw)
+static int __alpha_pll_lucid_5lpe_prepare(struct clk_hw *hw)
{
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
struct clk_hw *p;
@@ -2079,6 +2172,21 @@ static int alpha_pll_lucid_5lpe_prepare(struct clk_hw *hw)
return 0;
}
+static int alpha_pll_lucid_5lpe_prepare(struct clk_hw *hw)
+{
+ int ret;
+
+ ret = qcom_clk_dvfs_prepare(hw);
+ if (ret)
+ return ret;
+
+ ret = __alpha_pll_lucid_5lpe_prepare(hw);
+ if (ret)
+ qcom_clk_dvfs_unprepare(hw);
+
+ return ret;
+}
+
static int alpha_pll_lucid_5lpe_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long prate)
{
@@ -2133,7 +2241,10 @@ static int clk_lucid_5lpe_pll_postdiv_set_rate(struct clk_hw *hw, unsigned long
}
const struct clk_ops clk_alpha_pll_lucid_5lpe_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
.prepare = alpha_pll_lucid_5lpe_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = alpha_pll_lucid_5lpe_enable,
.disable = alpha_pll_lucid_5lpe_disable,
.is_enabled = clk_trion_pll_is_enabled,
@@ -2144,6 +2255,10 @@ const struct clk_ops clk_alpha_pll_lucid_5lpe_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_lucid_5lpe_ops);
const struct clk_ops clk_alpha_pll_fixed_lucid_5lpe_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = alpha_pll_lucid_5lpe_enable,
.disable = alpha_pll_lucid_5lpe_disable,
.is_enabled = clk_trion_pll_is_enabled,
@@ -2153,6 +2268,10 @@ const struct clk_ops clk_alpha_pll_fixed_lucid_5lpe_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_fixed_lucid_5lpe_ops);
const struct clk_ops clk_alpha_pll_postdiv_lucid_5lpe_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.recalc_rate = clk_alpha_pll_postdiv_fabia_recalc_rate,
.determine_rate = clk_alpha_pll_postdiv_fabia_determine_rate,
.set_rate = clk_lucid_5lpe_pll_postdiv_set_rate,
@@ -2319,6 +2438,10 @@ static int clk_zonda_pll_set_rate(struct clk_hw *hw, unsigned long rate,
}
const struct clk_ops clk_alpha_pll_zonda_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_zonda_pll_enable,
.disable = clk_zonda_pll_disable,
.is_enabled = clk_trion_pll_is_enabled,
@@ -2520,7 +2643,17 @@ static void alpha_pll_lucid_evo_disable(struct clk_hw *hw)
static int alpha_pll_lucid_evo_prepare(struct clk_hw *hw)
{
- return _alpha_pll_lucid_evo_prepare(hw, false);
+ int ret;
+
+ ret = qcom_clk_dvfs_prepare(hw);
+ if (ret)
+ return ret;
+
+ ret = _alpha_pll_lucid_evo_prepare(hw, false);
+ if (ret)
+ qcom_clk_dvfs_unprepare(hw);
+
+ return ret;
}
static void alpha_pll_reset_lucid_evo_disable(struct clk_hw *hw)
@@ -2530,7 +2663,17 @@ static void alpha_pll_reset_lucid_evo_disable(struct clk_hw *hw)
static int alpha_pll_reset_lucid_evo_prepare(struct clk_hw *hw)
{
- return _alpha_pll_lucid_evo_prepare(hw, true);
+ int ret;
+
+ ret = qcom_clk_dvfs_prepare(hw);
+ if (ret)
+ return ret;
+
+ ret = _alpha_pll_lucid_evo_prepare(hw, true);
+ if (ret)
+ qcom_clk_dvfs_unprepare(hw);
+
+ return ret;
}
static unsigned long alpha_pll_lucid_evo_recalc_rate(struct clk_hw *hw,
@@ -2557,6 +2700,10 @@ static int clk_lucid_evo_pll_postdiv_set_rate(struct clk_hw *hw, unsigned long r
}
const struct clk_ops clk_alpha_pll_fixed_lucid_evo_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = alpha_pll_lucid_evo_enable,
.disable = alpha_pll_lucid_evo_disable,
.is_enabled = clk_trion_pll_is_enabled,
@@ -2566,6 +2713,10 @@ const struct clk_ops clk_alpha_pll_fixed_lucid_evo_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_fixed_lucid_evo_ops);
const struct clk_ops clk_alpha_pll_postdiv_lucid_evo_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.recalc_rate = clk_alpha_pll_postdiv_fabia_recalc_rate,
.determine_rate = clk_alpha_pll_postdiv_fabia_determine_rate,
.set_rate = clk_lucid_evo_pll_postdiv_set_rate,
@@ -2573,7 +2724,10 @@ const struct clk_ops clk_alpha_pll_postdiv_lucid_evo_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_postdiv_lucid_evo_ops);
const struct clk_ops clk_alpha_pll_lucid_evo_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
.prepare = alpha_pll_lucid_evo_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = alpha_pll_lucid_evo_enable,
.disable = alpha_pll_lucid_evo_disable,
.is_enabled = clk_trion_pll_is_enabled,
@@ -2584,7 +2738,10 @@ const struct clk_ops clk_alpha_pll_lucid_evo_ops = {
EXPORT_SYMBOL_GPL(clk_alpha_pll_lucid_evo_ops);
const struct clk_ops clk_alpha_pll_reset_lucid_evo_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
.prepare = alpha_pll_reset_lucid_evo_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = alpha_pll_lucid_evo_enable,
.disable = alpha_pll_reset_lucid_evo_disable,
.is_enabled = clk_trion_pll_is_enabled,
@@ -2594,7 +2751,7 @@ const struct clk_ops clk_alpha_pll_reset_lucid_evo_ops = {
};
EXPORT_SYMBOL_GPL(clk_alpha_pll_reset_lucid_evo_ops);
-static int alpha_pll_pongo_elu_prepare(struct clk_hw *hw)
+static int __alpha_pll_pongo_elu_prepare(struct clk_hw *hw)
{
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
struct regmap *regmap = pll->clkr.regmap;
@@ -2624,6 +2781,21 @@ static int alpha_pll_pongo_elu_prepare(struct clk_hw *hw)
return 0;
}
+static int alpha_pll_pongo_elu_prepare(struct clk_hw *hw)
+{
+ int ret;
+
+ ret = qcom_clk_dvfs_prepare(hw);
+ if (ret)
+ return ret;
+
+ ret = __alpha_pll_pongo_elu_prepare(hw);
+ if (ret)
+ qcom_clk_dvfs_unprepare(hw);
+
+ return ret;
+}
+
static int alpha_pll_pongo_elu_enable(struct clk_hw *hw)
{
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
@@ -2687,7 +2859,10 @@ static unsigned long alpha_pll_pongo_elu_recalc_rate(struct clk_hw *hw,
}
const struct clk_ops clk_alpha_pll_pongo_elu_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
.prepare = alpha_pll_pongo_elu_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = alpha_pll_pongo_elu_enable,
.disable = alpha_pll_pongo_elu_disable,
.recalc_rate = alpha_pll_pongo_elu_recalc_rate,
@@ -2786,6 +2961,10 @@ static int clk_rivian_evo_pll_determine_rate(struct clk_hw *hw,
}
const struct clk_ops clk_alpha_pll_rivian_evo_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = alpha_pll_lucid_5lpe_enable,
.disable = alpha_pll_lucid_5lpe_disable,
.is_enabled = clk_trion_pll_is_enabled,
@@ -2899,6 +3078,10 @@ static int clk_alpha_pll_stromer_set_rate(struct clk_hw *hw, unsigned long rate,
}
const struct clk_ops clk_alpha_pll_stromer_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_alpha_pll_enable,
.disable = clk_alpha_pll_disable,
.is_enabled = clk_alpha_pll_is_enabled,
@@ -2964,9 +3147,32 @@ static int clk_alpha_pll_stromer_plus_set_rate(struct clk_hw *hw,
return 0;
}
+static int clk_alpha_pll_stromer_plus_prepare(struct clk_hw *hw)
+{
+ int ret;
+
+ ret = qcom_clk_dvfs_prepare(hw);
+ if (ret)
+ return ret;
+
+ ret = clk_alpha_pll_enable(hw);
+ if (ret)
+ qcom_clk_dvfs_unprepare(hw);
+
+ return ret;
+}
+
+static void clk_alpha_pll_stromer_plus_unprepare(struct clk_hw *hw)
+{
+ clk_alpha_pll_disable(hw);
+ qcom_clk_dvfs_unprepare(hw);
+}
+
const struct clk_ops clk_alpha_pll_stromer_plus_ops = {
- .prepare = clk_alpha_pll_enable,
- .unprepare = clk_alpha_pll_disable,
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = clk_alpha_pll_stromer_plus_prepare,
+ .unprepare = clk_alpha_pll_stromer_plus_unprepare,
.is_enabled = clk_alpha_pll_is_enabled,
.recalc_rate = clk_alpha_pll_recalc_rate,
.determine_rate = clk_alpha_pll_stromer_determine_rate,
@@ -2995,6 +3201,10 @@ void clk_regera_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap,
EXPORT_SYMBOL_GPL(clk_regera_pll_configure);
const struct clk_ops clk_alpha_pll_regera_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_zonda_pll_enable,
.disable = clk_zonda_pll_disable,
.is_enabled = clk_alpha_pll_is_enabled,
@@ -3202,6 +3412,10 @@ static int clk_alpha_pll_slew_enable(struct clk_hw *hw)
}
const struct clk_ops clk_alpha_pll_slew_ops = {
+ .resource_acquire = qcom_clk_dvfs_resource_acquire,
+ .resource_release = qcom_clk_dvfs_resource_release,
+ .prepare = qcom_clk_dvfs_prepare,
+ .unprepare = qcom_clk_dvfs_unprepare,
.enable = clk_alpha_pll_slew_enable,
.disable = clk_alpha_pll_disable,
.recalc_rate = clk_alpha_pll_recalc_rate,
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 10/16] clk: qcom: clk-alpha-pll: Vote for the PLL calibration frequency
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (8 preceding siblings ...)
2026-10-14 12:19 ` [PATCH RFC 09/16] clk: qcom: clk-alpha-pll: Add DVFS (un)prepare Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:34 ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional Konrad Dybcio
` (6 subsequent siblings)
16 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Lucid EVO class PLLs calibrate themselves the first time they are
prepared, running at (CAL_L * parent_rate) Hz. That is typically well
above the rate the PLL is later operated at, but the prepared vote only
covers the operating rate, so calibration can run without the rail
requirement for the frequency it actually uses.
Raise the vote to cover the calibration frequency while a PLL that still
needs calibration is brought up, and return to the operating rate once it
is done. Failing to drop the vote again leaves the higher one in place
until unprepare and is only reported.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/clk/qcom/clk-alpha-pll.c | 113 +++++++++++++++++++++++++++++----------
drivers/clk/qcom/clk-dvfs.c | 8 ++-
drivers/clk/qcom/clk-dvfs.h | 1 +
3 files changed, 91 insertions(+), 31 deletions(-)
diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c
index 5dc6b1adc929..f6e73e2fbe76 100644
--- a/drivers/clk/qcom/clk-alpha-pll.c
+++ b/drivers/clk/qcom/clk-alpha-pll.c
@@ -4,6 +4,7 @@
* Copyright (c) 2021, 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved.
*/
+#include <linux/bitfield.h>
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/clk-provider.h>
@@ -369,6 +370,7 @@ EXPORT_SYMBOL_GPL(clk_alpha_pll_regs);
#define LUCID_EVO_ENABLE_VOTE_RUN BIT(25)
#define LUCID_EVO_PLL_L_VAL_MASK GENMASK(15, 0)
#define LUCID_EVO_PLL_CAL_L_VAL_SHIFT 16
+#define LUCID_EVO_PLL_CAL_L_VAL_MASK GENMASK(23, 16)
#define LUCID_OLE_PLL_RINGOSC_CAL_L_VAL_SHIFT 24
/* PONGO ELU PLL specific setting and offsets */
@@ -2577,7 +2579,7 @@ static int alpha_pll_lucid_evo_enable(struct clk_hw *hw)
return ret;
}
-static void _alpha_pll_lucid_evo_disable(struct clk_hw *hw, bool reset)
+static int alpha_pll_lucid_evo_disable_checked(struct clk_hw *hw, bool reset)
{
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
struct regmap *regmap = pll->clkr.regmap;
@@ -2586,29 +2588,45 @@ static void _alpha_pll_lucid_evo_disable(struct clk_hw *hw, bool reset)
ret = regmap_read(regmap, PLL_USER_CTL(pll), &val);
if (ret)
- return;
+ return ret;
/* If in FSM mode, just unvote it */
if (val & LUCID_EVO_ENABLE_VOTE_RUN) {
- clk_disable_regmap(hw);
- return;
+ return regmap_update_bits(regmap, pll->clkr.enable_reg,
+ pll->clkr.enable_mask,
+ pll->clkr.enable_is_inverted ?
+ pll->clkr.enable_mask : 0);
}
/* Disable the global PLL output */
ret = regmap_update_bits(regmap, PLL_MODE(pll), PLL_OUTCTRL, 0);
if (ret)
- return;
+ return ret;
/* Disable the PLL outputs */
ret = regmap_update_bits(regmap, PLL_USER_CTL(pll), PLL_OUT_MASK, 0);
if (ret)
- return;
+ return ret;
/* Place the PLL mode in STANDBY */
- regmap_write(regmap, PLL_OPMODE(pll), PLL_STANDBY);
+ ret = regmap_write(regmap, PLL_OPMODE(pll), PLL_STANDBY);
+ if (ret)
+ return ret;
if (reset)
- regmap_update_bits(regmap, PLL_MODE(pll), PLL_RESET_N, 0);
+ return regmap_update_bits(regmap, PLL_MODE(pll), PLL_RESET_N, 0);
+
+ return 0;
+}
+
+static void _alpha_pll_lucid_evo_disable(struct clk_hw *hw, bool reset)
+{
+ int ret;
+
+ ret = alpha_pll_lucid_evo_disable_checked(hw, reset);
+ if (ret)
+ pr_warn("%s: failed to disable PLL: %d\n",
+ clk_hw_get_name(hw), ret);
}
static int _alpha_pll_lucid_evo_prepare(struct clk_hw *hw, bool reset)
@@ -2616,10 +2634,12 @@ static int _alpha_pll_lucid_evo_prepare(struct clk_hw *hw, bool reset)
struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
struct clk_hw *p;
u32 val = 0;
- int ret;
+ int ret, cleanup_ret;
/* Return early if calibration is not needed. */
- regmap_read(pll->clkr.regmap, PLL_MODE(pll), &val);
+ ret = regmap_read(pll->clkr.regmap, PLL_MODE(pll), &val);
+ if (ret)
+ return ret;
if (!(val & LUCID_EVO_PCAL_NOT_DONE))
return 0;
@@ -2628,12 +2648,9 @@ static int _alpha_pll_lucid_evo_prepare(struct clk_hw *hw, bool reset)
return -EINVAL;
ret = alpha_pll_lucid_evo_enable(hw);
- if (ret)
- return ret;
+ cleanup_ret = alpha_pll_lucid_evo_disable_checked(hw, reset);
- _alpha_pll_lucid_evo_disable(hw, reset);
-
- return 0;
+ return ret ?: cleanup_ret;
}
static void alpha_pll_lucid_evo_disable(struct clk_hw *hw)
@@ -2641,21 +2658,69 @@ static void alpha_pll_lucid_evo_disable(struct clk_hw *hw)
_alpha_pll_lucid_evo_disable(hw, false);
}
-static int alpha_pll_lucid_evo_prepare(struct clk_hw *hw)
+/*
+ * Calibration runs at CAL_L * parent_rate, which may exceed the operating rate
+ * covered by the normal DVFS vote. Raise the vote temporarily to keep the
+ * hardware in-spec during that step.
+ */
+static int alpha_pll_lucid_evo_dvfs_prepare(struct clk_hw *hw, bool reset)
{
+ struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
+ unsigned long rate = clk_hw_get_rate(hw);
+ unsigned long cal_rate = 0;
+ struct clk_hw *p;
+ u32 val;
int ret;
ret = qcom_clk_dvfs_prepare(hw);
if (ret)
return ret;
- ret = _alpha_pll_lucid_evo_prepare(hw, false);
+ ret = regmap_read(pll->clkr.regmap, PLL_MODE(pll), &val);
if (ret)
- qcom_clk_dvfs_unprepare(hw);
+ goto err_unprepare;
+ if (val & LUCID_EVO_PCAL_NOT_DONE) {
+ p = clk_hw_get_parent(hw);
+ if (!p) {
+ ret = -EINVAL;
+ goto err_unprepare;
+ }
+
+ ret = regmap_read(pll->clkr.regmap, PLL_L_VAL(pll), &val);
+ if (ret)
+ goto err_unprepare;
+
+ cal_rate = (unsigned long)FIELD_GET(LUCID_EVO_PLL_CAL_L_VAL_MASK, val) *
+ clk_hw_get_rate(p);
+ }
+
+ if (cal_rate > rate) {
+ ret = qcom_clk_dvfs_prepare_rate(hw, cal_rate);
+ if (ret)
+ goto err_unprepare;
+ }
+
+ ret = _alpha_pll_lucid_evo_prepare(hw, reset);
+ if (ret)
+ goto err_unprepare;
+
+ if (cal_rate > rate && qcom_clk_dvfs_prepare_rate(hw, rate))
+ pr_warn("%s: failed to drop calibration DVFS vote\n",
+ clk_hw_get_name(hw));
+
+ return 0;
+
+err_unprepare:
+ qcom_clk_dvfs_unprepare(hw);
return ret;
}
+static int alpha_pll_lucid_evo_prepare(struct clk_hw *hw)
+{
+ return alpha_pll_lucid_evo_dvfs_prepare(hw, false);
+}
+
static void alpha_pll_reset_lucid_evo_disable(struct clk_hw *hw)
{
_alpha_pll_lucid_evo_disable(hw, true);
@@ -2663,17 +2728,7 @@ static void alpha_pll_reset_lucid_evo_disable(struct clk_hw *hw)
static int alpha_pll_reset_lucid_evo_prepare(struct clk_hw *hw)
{
- int ret;
-
- ret = qcom_clk_dvfs_prepare(hw);
- if (ret)
- return ret;
-
- ret = _alpha_pll_lucid_evo_prepare(hw, true);
- if (ret)
- qcom_clk_dvfs_unprepare(hw);
-
- return ret;
+ return alpha_pll_lucid_evo_dvfs_prepare(hw, true);
}
static unsigned long alpha_pll_lucid_evo_recalc_rate(struct clk_hw *hw,
diff --git a/drivers/clk/qcom/clk-dvfs.c b/drivers/clk/qcom/clk-dvfs.c
index 4af78eb0c78d..145ad57a48e4 100644
--- a/drivers/clk/qcom/clk-dvfs.c
+++ b/drivers/clk/qcom/clk-dvfs.c
@@ -244,10 +244,9 @@ static int qcom_clk_dvfs_remove_vote(struct qcom_clk_dvfs_rail_voter *rail_voter
return ret;
}
-int qcom_clk_dvfs_prepare(struct clk_hw *hw)
+int qcom_clk_dvfs_prepare_rate(struct clk_hw *hw, unsigned long rate)
{
struct qcom_clk_dvfs_clock *clock = to_clk_regmap(hw)->dvfs;
- unsigned long rate = clk_hw_get_rate(hw);
unsigned int level;
size_t i;
int ret;
@@ -281,6 +280,11 @@ int qcom_clk_dvfs_prepare(struct clk_hw *hw)
return ret;
}
+int qcom_clk_dvfs_prepare(struct clk_hw *hw)
+{
+ return qcom_clk_dvfs_prepare_rate(hw, clk_hw_get_rate(hw));
+}
+
void qcom_clk_dvfs_unprepare(struct clk_hw *hw)
{
struct qcom_clk_dvfs_clock *clock = to_clk_regmap(hw)->dvfs;
diff --git a/drivers/clk/qcom/clk-dvfs.h b/drivers/clk/qcom/clk-dvfs.h
index 48c7487614c1..bb8edd466818 100644
--- a/drivers/clk/qcom/clk-dvfs.h
+++ b/drivers/clk/qcom/clk-dvfs.h
@@ -28,6 +28,7 @@ int qcom_clk_dvfs_resource_release(struct clk_hw *hw,
void *cookie,
enum clk_resource_change_result result);
int qcom_clk_dvfs_prepare(struct clk_hw *hw);
+int qcom_clk_dvfs_prepare_rate(struct clk_hw *hw, unsigned long rate);
void qcom_clk_dvfs_unprepare(struct clk_hw *hw);
int qcom_clk_dvfs_register(struct device *dev,
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (9 preceding siblings ...)
2026-10-14 12:19 ` [PATCH RFC 10/16] clk: qcom: clk-alpha-pll: Vote for the PLL calibration frequency Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:30 ` sashiko-bot
2026-10-14 14:17 ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH RFC 12/16] clk: qcom: gdsc: Link required power domains Konrad Dybcio
` (5 subsequent siblings)
16 siblings, 2 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Clock controller power-domain attachments currently become parents of all
top-level GDSCs exported by that controller. This conflates resources
needed to access or operate the controller with actual power-collapse
hierarchy.
Allow callers of gdsc_register() to suppress parents inferred from the
controller device and its attached domain list.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/clk/qcom/gdsc.c | 13 +++++++++----
drivers/clk/qcom/gdsc.h | 1 +
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c
index b99d70149af3..2d0803e1eb3b 100644
--- a/drivers/clk/qcom/gdsc.c
+++ b/drivers/clk/qcom/gdsc.c
@@ -587,9 +587,10 @@ static void gdsc_pm_subdomain_remove(struct gdsc_desc *desc, size_t num)
continue;
if (scs[i]->parent)
pm_genpd_remove_subdomain(scs[i]->parent, &scs[i]->pd);
- else if (!IS_ERR_OR_NULL(dev->pm_domain))
+ else if (!desc->no_external_parent &&
+ !IS_ERR_OR_NULL(dev->pm_domain))
pm_genpd_remove_subdomain(pd_to_genpd(dev->pm_domain), &scs[i]->pd);
- else if (desc->pd_list)
+ else if (!desc->no_external_parent && desc->pd_list)
gdsc_remove_subdomain_list(desc->pd_list, &scs[i]->pd);
}
}
@@ -654,13 +655,17 @@ int gdsc_register(struct gdsc_desc *desc,
/* Add subdomains */
for (i = 0; i < num; i++) {
+ ret = 0;
+
if (!scs[i])
continue;
+
if (scs[i]->parent)
ret = pm_genpd_add_subdomain(scs[i]->parent, &scs[i]->pd);
- else if (!IS_ERR_OR_NULL(dev->pm_domain))
+ 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);
- else if (desc->pd_list)
+ else if (!desc->no_external_parent && desc->pd_list)
ret = gdsc_add_subdomain_list(desc->pd_list, &scs[i]->pd);
if (ret)
diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h
index 2f9665b664e6..8275e266cfcc 100644
--- a/drivers/clk/qcom/gdsc.h
+++ b/drivers/clk/qcom/gdsc.h
@@ -86,6 +86,7 @@ struct gdsc_desc {
struct gdsc **scs;
size_t num;
struct dev_pm_domain_list *pd_list;
+ bool no_external_parent;
};
#ifdef CONFIG_QCOM_GDSC
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH RFC 12/16] clk: qcom: gdsc: Link required power domains
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (10 preceding siblings ...)
2026-10-14 12:19 ` [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:32 ` sashiko-bot
2026-10-14 12:19 ` [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails Konrad Dybcio
` (4 subsequent siblings)
16 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
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@oss.qualcomm.com>
---
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
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (11 preceding siblings ...)
2026-10-14 12:19 ` [PATCH RFC 12/16] clk: qcom: gdsc: Link required power domains Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:41 ` sashiko-bot
2026-10-14 14:19 ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH DNM RFC 14/16] clk: qcom: Enable clk-dvfs on SM8650 Konrad Dybcio
` (3 subsequent siblings)
16 siblings, 2 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
The SM8650 clock-controller nodes omit rails needed by their clocks and
PLLs. Without those connections, the clock-owned DVFS implementation
cannot attach its voters to all required domains and controller probe
can fail before any clocks are registered.
Fixes: d2350377997f ("arm64: dts: qcom: add initial SM8650 dtsi")
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/sm8650.dtsi | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/sm8650.dtsi b/arch/arm64/boot/dts/qcom/sm8650.dtsi
index 86fbb0fd6472..0847cfb35954 100644
--- a/arch/arm64/boot/dts/qcom/sm8650.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8650.dtsi
@@ -2017,8 +2017,9 @@ gcc: clock-controller@100000 {
<&ufs_mem_phy 1>,
<&ufs_mem_phy 2>,
<&usb_dp_qmpphy QMP_USB43DP_USB3_PIPE_CLK>;
-
- power-domains = <&rpmhpd RPMHPD_CX>;
+ power-domains = <&rpmhpd RPMHPD_CX>,
+ <&rpmhpd RPMHPD_CX_AO>,
+ <&rpmhpd RPMHPD_MX>;
#clock-cells = <1>;
#reset-cells = <1>;
@@ -4364,6 +4365,10 @@ gpucc: clock-controller@3d90000 {
clocks = <&bi_tcxo_div2>,
<&gcc GCC_GPU_GPLL0_CLK_SRC>,
<&gcc GCC_GPU_GPLL0_DIV_CLK_SRC>;
+ power-domains = <&rpmhpd RPMHPD_MX>,
+ <&rpmhpd RPMHPD_CX>,
+ <&rpmhpd RPMHPD_MXC>,
+ <&rpmhpd RPMHPD_GFX>;
#clock-cells = <1>;
#reset-cells = <1>;
@@ -5639,7 +5644,8 @@ camcc: clock-controller@ade0000 {
<&bi_tcxo_ao_div2>,
<&sleep_clk>;
power-domains = <&rpmhpd RPMHPD_MMCX>,
- <&rpmhpd RPMHPD_MXC>;
+ <&rpmhpd RPMHPD_MXC>,
+ <&rpmhpd RPMHPD_MX>;
#clock-cells = <1>;
#reset-cells = <1>;
#power-domain-cells = <1>;
@@ -6049,8 +6055,8 @@ dispcc: clock-controller@af00000 {
<0>, /* dp3 */
<0>;
- power-domains = <&rpmhpd RPMHPD_MMCX>;
- required-opps = <&rpmhpd_opp_low_svs>;
+ power-domains = <&rpmhpd RPMHPD_MMCX>,
+ <&rpmhpd RPMHPD_MX>;
#clock-cells = <1>;
#reset-cells = <1>;
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH DNM RFC 14/16] clk: qcom: Enable clk-dvfs on SM8650
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (12 preceding siblings ...)
2026-10-14 12:19 ` [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:36 ` sashiko-bot
2026-10-14 14:40 ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH DNM RFC 15/16] arm64: dts: qcom: sm8650: Drop required-opps Konrad Dybcio
` (2 subsequent siblings)
16 siblings, 2 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Make use of the clock-dvfs infrastructure and fill in the power
requirements for SM8650's clocks to ensure the hardware can operate in
accordance with the specification.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
This commit is a DNM since some of the affected drivers are shared
between multiple platforms, this will be properly reworked if we agree
on merging the clk-DVFS infra.
---
drivers/clk/qcom/camcc-sm8650.c | 282 ++++++++++++++++++++++++
drivers/clk/qcom/dispcc-sm8550.c | 328 ++++++++++++++++++++++++----
drivers/clk/qcom/gcc-sm8650.c | 261 ++++++++++++++++++++++
drivers/clk/qcom/gpucc-sm8650.c | 101 ++++++++-
drivers/clk/qcom/videocc-sm8550.c | 135 ++++++++++--
include/dt-bindings/clock/qcom,sm8650-gcc.h | 2 +
6 files changed, 1041 insertions(+), 68 deletions(-)
diff --git a/drivers/clk/qcom/camcc-sm8650.c b/drivers/clk/qcom/camcc-sm8650.c
index 3aad816ed233..188649368564 100644
--- a/drivers/clk/qcom/camcc-sm8650.c
+++ b/drivers/clk/qcom/camcc-sm8650.c
@@ -6,12 +6,15 @@
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/pm.h>
#include <linux/regmap.h>
#include <dt-bindings/clock/qcom,sm8650-camcc.h>
+#include <dt-bindings/power/qcom,rpmhpd.h>
#include "clk-alpha-pll.h"
#include "clk-branch.h"
+#include "clk-dvfs.h"
#include "clk-rcg.h"
#include "clk-regmap.h"
#include "common.h"
@@ -46,6 +49,110 @@ enum {
P_SLEEP_CLK,
};
+/*
+ * Downstream uses CRM/CESTA for PLL3-7/10 and the CAMNOC AXI RT, CPHY RX,
+ * CSID, IFE0-2 and SFE0-2 RCGs. Until upstream supports that path, manage
+ * their rail requirements through clock-owned DVFS.
+ */
+enum {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+ DT_PMDOMAIN_MX,
+};
+
+static const struct qcom_clk_dvfs_data cam_cc_pll0_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MXC },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 615000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 1100000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 1600000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 2000000000, RPMH_REGULATOR_LEVEL_NOM },
+ { 2100000000, RPMH_REGULATOR_LEVEL_TURBO_L1 },
+ },
+ .num_rates = 5,
+};
+
+static const struct qcom_clk_dvfs_data cam_cc_camnoc_axi_rt_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX, DT_PMDOMAIN_MXC },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 300000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 400000000, RPMH_REGULATOR_LEVEL_SVS },
+ },
+ .num_rates = 2,
+};
+
+static const struct qcom_clk_dvfs_data cam_cc_cci_0_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 37500000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+};
+
+static const struct qcom_clk_dvfs_data cam_cc_cphy_rx_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX, DT_PMDOMAIN_MX, DT_PMDOMAIN_MXC,
+ },
+ .num_pd_indices = 3,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 400000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 480000000, RPMH_REGULATOR_LEVEL_SVS },
+ },
+ .num_rates = 2,
+};
+
+static const struct qcom_clk_dvfs_data cam_cc_csi0phytimer_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MXC },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 400000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+};
+
+static const struct qcom_clk_dvfs_data cam_cc_csi2phytimer_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 400000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+};
+
+static const struct qcom_clk_dvfs_data cam_cc_ife_0_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX, DT_PMDOMAIN_MXC },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 466000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 594000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 675000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 785000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 4,
+};
+
+static const struct qcom_clk_dvfs_data cam_cc_ife_lite_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX, DT_PMDOMAIN_MXC },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 400000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 480000000, RPMH_REGULATOR_LEVEL_SVS },
+ },
+ .num_rates = 2,
+};
+
+static const struct qcom_clk_dvfs_data cam_cc_mclk0_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 68571429, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+};
+
static const struct pll_vco lucid_ole_vco[] = {
{ 249600000, 2300000000, 0 },
};
@@ -75,6 +182,7 @@ static struct clk_alpha_pll cam_cc_pll0 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &cam_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll0",
.parent_data = &(const struct clk_parent_data) {
@@ -153,6 +261,7 @@ static struct clk_alpha_pll cam_cc_pll1 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &cam_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll1",
.parent_data = &(const struct clk_parent_data) {
@@ -204,6 +313,16 @@ static struct clk_alpha_pll cam_cc_pll2 = {
.num_vco = ARRAY_SIZE(rivian_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_RIVIAN_EVO],
.clkr = {
+ .dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MX,
+ },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 1285000000, RPMH_REGULATOR_LEVEL_SVS },
+ },
+ .num_rates = 1,
+ },
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll2",
.parent_data = &(const struct clk_parent_data) {
@@ -236,6 +355,7 @@ static struct clk_alpha_pll cam_cc_pll3 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &cam_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll3",
.parent_data = &(const struct clk_parent_data) {
@@ -291,6 +411,7 @@ static struct clk_alpha_pll cam_cc_pll4 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &cam_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll4",
.parent_data = &(const struct clk_parent_data) {
@@ -346,6 +467,7 @@ static struct clk_alpha_pll cam_cc_pll5 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &cam_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll5",
.parent_data = &(const struct clk_parent_data) {
@@ -401,6 +523,7 @@ static struct clk_alpha_pll cam_cc_pll6 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &cam_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll6",
.parent_data = &(const struct clk_parent_data) {
@@ -456,6 +579,7 @@ static struct clk_alpha_pll cam_cc_pll7 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &cam_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll7",
.parent_data = &(const struct clk_parent_data) {
@@ -511,6 +635,7 @@ static struct clk_alpha_pll cam_cc_pll8 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &cam_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll8",
.parent_data = &(const struct clk_parent_data) {
@@ -566,6 +691,7 @@ static struct clk_alpha_pll cam_cc_pll9 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &cam_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll9",
.parent_data = &(const struct clk_parent_data) {
@@ -644,6 +770,7 @@ static struct clk_alpha_pll cam_cc_pll10 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &cam_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_pll10",
.parent_data = &(const struct clk_parent_data) {
@@ -819,6 +946,20 @@ static struct clk_rcg2 cam_cc_bps_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_2,
.freq_tbl = ftbl_cam_cc_bps_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 200000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 400000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 480000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 785000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 4,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_bps_clk_src",
.parent_data = cam_cc_parent_data_2,
@@ -840,6 +981,7 @@ static struct clk_rcg2 cam_cc_camnoc_axi_rt_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_camnoc_axi_rt_clk_src,
+ .clkr.dvfs_data = &cam_cc_camnoc_axi_rt_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_camnoc_axi_rt_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -861,6 +1003,7 @@ static struct clk_rcg2 cam_cc_cci_0_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_cci_0_clk_src,
+ .clkr.dvfs_data = &cam_cc_cci_0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_cci_0_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -876,6 +1019,7 @@ static struct clk_rcg2 cam_cc_cci_1_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_cci_0_clk_src,
+ .clkr.dvfs_data = &cam_cc_cci_0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_cci_1_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -891,6 +1035,7 @@ static struct clk_rcg2 cam_cc_cci_2_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_cci_0_clk_src,
+ .clkr.dvfs_data = &cam_cc_cci_0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_cci_2_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -913,6 +1058,7 @@ static struct clk_rcg2 cam_cc_cphy_rx_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_cphy_rx_clk_src,
+ .clkr.dvfs_data = &cam_cc_cphy_rx_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_cphy_rx_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -936,6 +1082,19 @@ static struct clk_rcg2 cam_cc_cre_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_cre_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 200000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 400000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 480000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 600000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 4,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_cre_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -957,6 +1116,7 @@ static struct clk_rcg2 cam_cc_csi0phytimer_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src,
+ .clkr.dvfs_data = &cam_cc_csi0phytimer_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_csi0phytimer_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -972,6 +1132,7 @@ static struct clk_rcg2 cam_cc_csi1phytimer_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src,
+ .clkr.dvfs_data = &cam_cc_csi0phytimer_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_csi1phytimer_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -987,6 +1148,7 @@ static struct clk_rcg2 cam_cc_csi2phytimer_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src,
+ .clkr.dvfs_data = &cam_cc_csi2phytimer_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_csi2phytimer_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1002,6 +1164,7 @@ static struct clk_rcg2 cam_cc_csi3phytimer_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src,
+ .clkr.dvfs_data = &cam_cc_csi0phytimer_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_csi3phytimer_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1017,6 +1180,7 @@ static struct clk_rcg2 cam_cc_csi4phytimer_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src,
+ .clkr.dvfs_data = &cam_cc_csi2phytimer_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_csi4phytimer_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1032,6 +1196,7 @@ static struct clk_rcg2 cam_cc_csi5phytimer_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src,
+ .clkr.dvfs_data = &cam_cc_csi0phytimer_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_csi5phytimer_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1047,6 +1212,7 @@ static struct clk_rcg2 cam_cc_csi6phytimer_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src,
+ .clkr.dvfs_data = &cam_cc_csi0phytimer_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_csi6phytimer_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1062,6 +1228,7 @@ static struct clk_rcg2 cam_cc_csi7phytimer_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src,
+ .clkr.dvfs_data = &cam_cc_csi0phytimer_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_csi7phytimer_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1083,6 +1250,7 @@ static struct clk_rcg2 cam_cc_csid_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csid_clk_src,
+ .clkr.dvfs_data = &cam_cc_ife_lite_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_csid_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1105,6 +1273,18 @@ static struct clk_rcg2 cam_cc_fast_ahb_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_fast_ahb_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 300000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 400000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 2,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_fast_ahb_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1128,6 +1308,19 @@ static struct clk_rcg2 cam_cc_icp_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_icp_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 400000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 480000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 600000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ },
+ .num_rates = 3,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_icp_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1152,6 +1345,7 @@ static struct clk_rcg2 cam_cc_ife_0_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_3,
.freq_tbl = ftbl_cam_cc_ife_0_clk_src,
+ .clkr.dvfs_data = &cam_cc_ife_0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_ife_0_clk_src",
.parent_data = cam_cc_parent_data_3,
@@ -1176,6 +1370,7 @@ static struct clk_rcg2 cam_cc_ife_1_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_4,
.freq_tbl = ftbl_cam_cc_ife_1_clk_src,
+ .clkr.dvfs_data = &cam_cc_ife_0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_ife_1_clk_src",
.parent_data = cam_cc_parent_data_4,
@@ -1200,6 +1395,7 @@ static struct clk_rcg2 cam_cc_ife_2_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_5,
.freq_tbl = ftbl_cam_cc_ife_2_clk_src,
+ .clkr.dvfs_data = &cam_cc_ife_0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_ife_2_clk_src",
.parent_data = cam_cc_parent_data_5,
@@ -1215,6 +1411,7 @@ static struct clk_rcg2 cam_cc_ife_lite_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csid_clk_src,
+ .clkr.dvfs_data = &cam_cc_ife_lite_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_ife_lite_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1230,6 +1427,7 @@ static struct clk_rcg2 cam_cc_ife_lite_csid_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_csid_clk_src,
+ .clkr.dvfs_data = &cam_cc_ife_lite_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_ife_lite_csid_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1253,6 +1451,20 @@ static struct clk_rcg2 cam_cc_ipe_nps_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_6,
.freq_tbl = ftbl_cam_cc_ipe_nps_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 475000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 575000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 675000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 825000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 4,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_ipe_nps_clk_src",
.parent_data = cam_cc_parent_data_6,
@@ -1277,6 +1489,20 @@ static struct clk_rcg2 cam_cc_jpeg_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_jpeg_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 200000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 400000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 480000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 600000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 4,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_jpeg_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1299,6 +1525,7 @@ static struct clk_rcg2 cam_cc_mclk0_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_1,
.freq_tbl = ftbl_cam_cc_mclk0_clk_src,
+ .clkr.dvfs_data = &cam_cc_mclk0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_mclk0_clk_src",
.parent_data = cam_cc_parent_data_1,
@@ -1314,6 +1541,7 @@ static struct clk_rcg2 cam_cc_mclk1_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_1,
.freq_tbl = ftbl_cam_cc_mclk0_clk_src,
+ .clkr.dvfs_data = &cam_cc_mclk0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_mclk1_clk_src",
.parent_data = cam_cc_parent_data_1,
@@ -1329,6 +1557,7 @@ static struct clk_rcg2 cam_cc_mclk2_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_1,
.freq_tbl = ftbl_cam_cc_mclk0_clk_src,
+ .clkr.dvfs_data = &cam_cc_mclk0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_mclk2_clk_src",
.parent_data = cam_cc_parent_data_1,
@@ -1344,6 +1573,7 @@ static struct clk_rcg2 cam_cc_mclk3_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_1,
.freq_tbl = ftbl_cam_cc_mclk0_clk_src,
+ .clkr.dvfs_data = &cam_cc_mclk0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_mclk3_clk_src",
.parent_data = cam_cc_parent_data_1,
@@ -1359,6 +1589,7 @@ static struct clk_rcg2 cam_cc_mclk4_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_1,
.freq_tbl = ftbl_cam_cc_mclk0_clk_src,
+ .clkr.dvfs_data = &cam_cc_mclk0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_mclk4_clk_src",
.parent_data = cam_cc_parent_data_1,
@@ -1374,6 +1605,7 @@ static struct clk_rcg2 cam_cc_mclk5_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_1,
.freq_tbl = ftbl_cam_cc_mclk0_clk_src,
+ .clkr.dvfs_data = &cam_cc_mclk0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_mclk5_clk_src",
.parent_data = cam_cc_parent_data_1,
@@ -1389,6 +1621,7 @@ static struct clk_rcg2 cam_cc_mclk6_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_1,
.freq_tbl = ftbl_cam_cc_mclk0_clk_src,
+ .clkr.dvfs_data = &cam_cc_mclk0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_mclk6_clk_src",
.parent_data = cam_cc_parent_data_1,
@@ -1404,6 +1637,7 @@ static struct clk_rcg2 cam_cc_mclk7_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_1,
.freq_tbl = ftbl_cam_cc_mclk0_clk_src,
+ .clkr.dvfs_data = &cam_cc_mclk0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_mclk7_clk_src",
.parent_data = cam_cc_parent_data_1,
@@ -1427,6 +1661,18 @@ static struct clk_rcg2 cam_cc_qdss_debug_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_qdss_debug_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 75000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 150000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 300000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ },
+ .num_rates = 3,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_qdss_debug_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -1450,6 +1696,7 @@ static struct clk_rcg2 cam_cc_sfe_0_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_7,
.freq_tbl = ftbl_cam_cc_sfe_0_clk_src,
+ .clkr.dvfs_data = &cam_cc_ife_0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_sfe_0_clk_src",
.parent_data = cam_cc_parent_data_7,
@@ -1473,6 +1720,7 @@ static struct clk_rcg2 cam_cc_sfe_1_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_8,
.freq_tbl = ftbl_cam_cc_sfe_1_clk_src,
+ .clkr.dvfs_data = &cam_cc_ife_0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_sfe_1_clk_src",
.parent_data = cam_cc_parent_data_8,
@@ -1496,6 +1744,7 @@ static struct clk_rcg2 cam_cc_sfe_2_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_9,
.freq_tbl = ftbl_cam_cc_sfe_2_clk_src,
+ .clkr.dvfs_data = &cam_cc_ife_0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_sfe_2_clk_src",
.parent_data = cam_cc_parent_data_9,
@@ -1516,6 +1765,16 @@ static struct clk_rcg2 cam_cc_sleep_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_10,
.freq_tbl = ftbl_cam_cc_sleep_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 32000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_sleep_clk_src",
.parent_data = cam_cc_parent_data_10,
@@ -1537,6 +1796,17 @@ static struct clk_rcg2 cam_cc_slow_ahb_clk_src = {
.hid_width = 5,
.parent_map = cam_cc_parent_map_0,
.freq_tbl = ftbl_cam_cc_slow_ahb_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 80000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "cam_cc_slow_ahb_clk_src",
.parent_data = cam_cc_parent_data_0,
@@ -3214,6 +3484,9 @@ static struct gdsc cam_cc_titan_top_gdsc = {
},
.pwrsts = PWRSTS_OFF_ON,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
+ .required_pds = (const unsigned int[]){ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC },
+ .num_required_pds = 2,
};
static struct gdsc cam_cc_bps_gdsc = {
@@ -3554,6 +3827,12 @@ static const struct qcom_cc_driver_data cam_cc_sm8650_driver_data = {
.num_clk_cbcrs = ARRAY_SIZE(cam_cc_sm8650_critical_cbcrs),
};
+static const unsigned int cam_cc_sm8650_access_pd_indices[] = {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+ DT_PMDOMAIN_MX,
+};
+
static const struct qcom_cc_desc cam_cc_sm8650_desc = {
.config = &cam_cc_sm8650_regmap_config,
.clks = cam_cc_sm8650_clocks,
@@ -3563,6 +3842,9 @@ static const struct qcom_cc_desc cam_cc_sm8650_desc = {
.gdscs = cam_cc_sm8650_gdscs,
.num_gdscs = ARRAY_SIZE(cam_cc_sm8650_gdscs),
.use_rpm = true,
+ .pd_indices = cam_cc_sm8650_access_pd_indices,
+ .num_pd_indices = ARRAY_SIZE(cam_cc_sm8650_access_pd_indices),
+ .num_dvfs_rail_voters = 3,
.driver_data = &cam_cc_sm8650_driver_data,
};
diff --git a/drivers/clk/qcom/dispcc-sm8550.c b/drivers/clk/qcom/dispcc-sm8550.c
index 030fc7ef1fbf..0b737e366037 100644
--- a/drivers/clk/qcom/dispcc-sm8550.c
+++ b/drivers/clk/qcom/dispcc-sm8550.c
@@ -5,15 +5,16 @@
*/
#include <linux/clk-provider.h>
-#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
-#include <linux/pm_runtime.h>
#include <dt-bindings/clock/qcom,sm8550-dispcc.h>
+#include <dt-bindings/power/qcom,rpmhpd.h>
+#include "clk-dvfs.h"
#include "common.h"
#include "clk-alpha-pll.h"
#include "clk-branch.h"
@@ -69,6 +70,85 @@ enum {
P_SLEEP_CLK,
};
+enum {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MX,
+};
+
+static const unsigned int disp_cc_sm8650_block_pd_indices[] = {
+ DT_PMDOMAIN_MMCX,
+};
+
+static const struct qcom_clk_dvfs_data disp_cc_pll0_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 615000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 1100000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 1600000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 2000000000, RPMH_REGULATOR_LEVEL_NOM },
+ { 2100000000, RPMH_REGULATOR_LEVEL_TURBO_L1 },
+ },
+ .num_rates = 5,
+};
+
+static const struct qcom_clk_dvfs_data disp_cc_mdss_byte0_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 140630000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 187500000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 300000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 358000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ },
+ .num_rates = 4,
+};
+
+static const struct qcom_clk_dvfs_data disp_cc_mdss_dptx0_aux_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 19200000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ },
+ .num_rates = 1,
+};
+
+static const struct qcom_clk_dvfs_data disp_cc_mdss_dptx0_link_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 19200000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 270000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 540000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 810000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 4,
+};
+
+static const struct qcom_clk_dvfs_data disp_cc_mdss_dptx0_pixel0_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 19200000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 337500000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 405000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 675000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 4,
+};
+
+static const struct qcom_clk_dvfs_data disp_cc_mdss_pclk0_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 225000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 300000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 480000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 625000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ },
+ .num_rates = 4,
+};
+
static struct pll_vco lucid_ole_vco[] = {
{ 249600000, 2000000000, 0 },
};
@@ -89,10 +169,12 @@ static struct alpha_pll_config disp_cc_pll0_config = {
static struct clk_alpha_pll disp_cc_pll0 = {
.offset = 0x0,
+ .config = &disp_cc_pll0_config,
.vco_table = lucid_ole_vco,
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &disp_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_pll0",
.parent_data = &(const struct clk_parent_data) {
@@ -120,10 +202,12 @@ static struct alpha_pll_config disp_cc_pll1_config = {
static struct clk_alpha_pll disp_cc_pll1 = {
.offset = 0x1000,
+ .config = &disp_cc_pll1_config,
.vco_table = lucid_ole_vco,
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &disp_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_pll1",
.parent_data = &(const struct clk_parent_data) {
@@ -284,6 +368,16 @@ static struct clk_rcg2 disp_cc_mdss_ahb_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_6,
.freq_tbl = ftbl_disp_cc_mdss_ahb_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 19200000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 37500000, RPMH_REGULATOR_LEVEL_SVS },
+ { 75000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 3,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_ahb_clk_src",
.parent_data = disp_cc_parent_data_6,
@@ -304,6 +398,7 @@ static struct clk_rcg2 disp_cc_mdss_byte0_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_2,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_byte0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_byte0_clk_src",
.parent_data = disp_cc_parent_data_2,
@@ -319,6 +414,7 @@ static struct clk_rcg2 disp_cc_mdss_byte1_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_2,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_byte0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_byte1_clk_src",
.parent_data = disp_cc_parent_data_2,
@@ -334,6 +430,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx0_aux_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_0,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_aux_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx0_aux_clk_src",
.parent_data = disp_cc_parent_data_0,
@@ -348,6 +445,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx0_link_clk_src = {
.mnd_width = 0,
.hid_width = 5,
.parent_map = disp_cc_parent_map_7,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_link_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx0_link_clk_src",
.parent_data = disp_cc_parent_data_7,
@@ -363,6 +461,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx0_pixel0_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_4,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_pixel0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx0_pixel0_clk_src",
.parent_data = disp_cc_parent_data_4,
@@ -378,6 +477,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx0_pixel1_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_4,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_pixel0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx0_pixel1_clk_src",
.parent_data = disp_cc_parent_data_4,
@@ -393,6 +493,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx1_aux_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_0,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_aux_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx1_aux_clk_src",
.parent_data = disp_cc_parent_data_0,
@@ -407,6 +508,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx1_link_clk_src = {
.mnd_width = 0,
.hid_width = 5,
.parent_map = disp_cc_parent_map_3,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_link_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx1_link_clk_src",
.parent_data = disp_cc_parent_data_3,
@@ -422,6 +524,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx1_pixel0_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_1,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_pixel0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx1_pixel0_clk_src",
.parent_data = disp_cc_parent_data_1,
@@ -437,6 +540,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx1_pixel1_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_1,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_pixel0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx1_pixel1_clk_src",
.parent_data = disp_cc_parent_data_1,
@@ -452,6 +556,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx2_aux_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_0,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_aux_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx2_aux_clk_src",
.parent_data = disp_cc_parent_data_0,
@@ -466,6 +571,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx2_link_clk_src = {
.mnd_width = 0,
.hid_width = 5,
.parent_map = disp_cc_parent_map_3,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_link_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx2_link_clk_src",
.parent_data = disp_cc_parent_data_3,
@@ -481,6 +587,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx2_pixel0_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_1,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_pixel0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx2_pixel0_clk_src",
.parent_data = disp_cc_parent_data_1,
@@ -496,6 +603,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx2_pixel1_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_1,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_pixel0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx2_pixel1_clk_src",
.parent_data = disp_cc_parent_data_1,
@@ -511,6 +619,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx3_aux_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_0,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_aux_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx3_aux_clk_src",
.parent_data = disp_cc_parent_data_0,
@@ -525,6 +634,17 @@ static struct clk_rcg2 disp_cc_mdss_dptx3_link_clk_src = {
.mnd_width = 0,
.hid_width = 5,
.parent_map = disp_cc_parent_map_3,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 19200000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 270000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 594000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 810000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 4,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx3_link_clk_src",
.parent_data = disp_cc_parent_data_3,
@@ -540,6 +660,7 @@ static struct clk_rcg2 disp_cc_mdss_dptx3_pixel0_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_1,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_pixel0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_dptx3_pixel0_clk_src",
.parent_data = disp_cc_parent_data_1,
@@ -555,6 +676,7 @@ static struct clk_rcg2 disp_cc_mdss_esc0_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_5,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_aux_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_esc0_clk_src",
.parent_data = disp_cc_parent_data_5,
@@ -570,6 +692,7 @@ static struct clk_rcg2 disp_cc_mdss_esc1_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_5,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_aux_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_esc1_clk_src",
.parent_data = disp_cc_parent_data_5,
@@ -617,6 +740,21 @@ static struct clk_rcg2 disp_cc_mdss_mdp_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_8,
.freq_tbl = ftbl_disp_cc_mdss_mdp_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MX,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 150000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 200000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 325000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 402000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 514000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 5,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_mdp_clk_src",
.parent_data = disp_cc_parent_data_8,
@@ -632,6 +770,7 @@ static struct clk_rcg2 disp_cc_mdss_pclk0_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_2,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_pclk0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_pclk0_clk_src",
.parent_data = disp_cc_parent_data_2,
@@ -647,6 +786,7 @@ static struct clk_rcg2 disp_cc_mdss_pclk1_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_2,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_pclk0_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_pclk1_clk_src",
.parent_data = disp_cc_parent_data_2,
@@ -662,6 +802,7 @@ static struct clk_rcg2 disp_cc_mdss_vsync_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_0,
.freq_tbl = ftbl_disp_cc_mdss_byte0_clk_src,
+ .clkr.dvfs_data = &disp_cc_mdss_dptx0_aux_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_mdss_vsync_clk_src",
.parent_data = disp_cc_parent_data_0,
@@ -682,6 +823,14 @@ static struct clk_rcg2 disp_cc_sleep_clk_src = {
.hid_width = 5,
.parent_map = disp_cc_parent_map_9,
.freq_tbl = ftbl_disp_cc_sleep_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 32000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ },
+ .num_rates = 1,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "disp_cc_sleep_clk_src",
.parent_data = disp_cc_parent_data_9,
@@ -1629,6 +1778,8 @@ static struct gdsc mdss_gdsc = {
},
.pwrsts = PWRSTS_OFF_ON,
.flags = POLL_CFG_GDSCR | HW_CTRL | RETAIN_FF_ENABLE,
+ .required_pds = disp_cc_sm8650_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(disp_cc_sm8650_block_pd_indices),
};
static struct gdsc mdss_int2_gdsc = {
@@ -1638,6 +1789,8 @@ static struct gdsc mdss_int2_gdsc = {
},
.pwrsts = PWRSTS_OFF_ON,
.flags = POLL_CFG_GDSCR | HW_CTRL | RETAIN_FF_ENABLE,
+ .required_pds = disp_cc_sm8650_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(disp_cc_sm8650_block_pd_indices),
};
static struct clk_regmap *disp_cc_sm8550_clocks[] = {
@@ -1725,6 +1878,91 @@ static struct clk_regmap *disp_cc_sm8550_clocks[] = {
[DISP_CC_XO_CLK_SRC] = &disp_cc_xo_clk_src.clkr,
};
+static struct clk_regmap *disp_cc_sm8650_clocks[] = {
+ [DISP_CC_MDSS_ACCU_CLK] = &disp_cc_mdss_accu_clk.clkr,
+ [DISP_CC_MDSS_AHB1_CLK] = &disp_cc_mdss_ahb1_clk.clkr,
+ [DISP_CC_MDSS_AHB_CLK] = &disp_cc_mdss_ahb_clk.clkr,
+ [DISP_CC_MDSS_AHB_CLK_SRC] = &disp_cc_mdss_ahb_clk_src.clkr,
+ [DISP_CC_MDSS_BYTE0_CLK] = &disp_cc_mdss_byte0_clk.clkr,
+ [DISP_CC_MDSS_BYTE0_CLK_SRC] = &disp_cc_mdss_byte0_clk_src.clkr,
+ [DISP_CC_MDSS_BYTE0_DIV_CLK_SRC] = &disp_cc_mdss_byte0_div_clk_src.clkr,
+ [DISP_CC_MDSS_BYTE0_INTF_CLK] = &disp_cc_mdss_byte0_intf_clk.clkr,
+ [DISP_CC_MDSS_BYTE1_CLK] = &disp_cc_mdss_byte1_clk.clkr,
+ [DISP_CC_MDSS_BYTE1_CLK_SRC] = &disp_cc_mdss_byte1_clk_src.clkr,
+ [DISP_CC_MDSS_BYTE1_DIV_CLK_SRC] = &disp_cc_mdss_byte1_div_clk_src.clkr,
+ [DISP_CC_MDSS_BYTE1_INTF_CLK] = &disp_cc_mdss_byte1_intf_clk.clkr,
+ [DISP_CC_MDSS_DPTX0_AUX_CLK] = &disp_cc_mdss_dptx0_aux_clk.clkr,
+ [DISP_CC_MDSS_DPTX0_AUX_CLK_SRC] = &disp_cc_mdss_dptx0_aux_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX0_CRYPTO_CLK] = &disp_cc_mdss_dptx0_crypto_clk.clkr,
+ [DISP_CC_MDSS_DPTX0_LINK_CLK] = &disp_cc_mdss_dptx0_link_clk.clkr,
+ [DISP_CC_MDSS_DPTX0_LINK_CLK_SRC] = &disp_cc_mdss_dptx0_link_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX0_LINK_DIV_CLK_SRC] = &disp_cc_mdss_dptx0_link_div_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX0_LINK_INTF_CLK] = &disp_cc_mdss_dptx0_link_intf_clk.clkr,
+ [DISP_CC_MDSS_DPTX0_PIXEL0_CLK] = &disp_cc_mdss_dptx0_pixel0_clk.clkr,
+ [DISP_CC_MDSS_DPTX0_PIXEL0_CLK_SRC] = &disp_cc_mdss_dptx0_pixel0_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX0_PIXEL1_CLK] = &disp_cc_mdss_dptx0_pixel1_clk.clkr,
+ [DISP_CC_MDSS_DPTX0_PIXEL1_CLK_SRC] = &disp_cc_mdss_dptx0_pixel1_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX0_USB_ROUTER_LINK_INTF_CLK] =
+ &disp_cc_mdss_dptx0_usb_router_link_intf_clk.clkr,
+ [DISP_CC_MDSS_DPTX1_AUX_CLK] = &disp_cc_mdss_dptx1_aux_clk.clkr,
+ [DISP_CC_MDSS_DPTX1_AUX_CLK_SRC] = &disp_cc_mdss_dptx1_aux_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX1_CRYPTO_CLK] = &disp_cc_mdss_dptx1_crypto_clk.clkr,
+ [DISP_CC_MDSS_DPTX1_LINK_CLK] = &disp_cc_mdss_dptx1_link_clk.clkr,
+ [DISP_CC_MDSS_DPTX1_LINK_CLK_SRC] = &disp_cc_mdss_dptx1_link_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX1_LINK_DIV_CLK_SRC] = &disp_cc_mdss_dptx1_link_div_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX1_LINK_INTF_CLK] = &disp_cc_mdss_dptx1_link_intf_clk.clkr,
+ [DISP_CC_MDSS_DPTX1_PIXEL0_CLK] = &disp_cc_mdss_dptx1_pixel0_clk.clkr,
+ [DISP_CC_MDSS_DPTX1_PIXEL0_CLK_SRC] = &disp_cc_mdss_dptx1_pixel0_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX1_PIXEL1_CLK] = &disp_cc_mdss_dptx1_pixel1_clk.clkr,
+ [DISP_CC_MDSS_DPTX1_PIXEL1_CLK_SRC] = &disp_cc_mdss_dptx1_pixel1_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX1_USB_ROUTER_LINK_INTF_CLK] =
+ &disp_cc_mdss_dptx1_usb_router_link_intf_clk.clkr,
+ [DISP_CC_MDSS_DPTX2_AUX_CLK] = &disp_cc_mdss_dptx2_aux_clk.clkr,
+ [DISP_CC_MDSS_DPTX2_AUX_CLK_SRC] = &disp_cc_mdss_dptx2_aux_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX2_CRYPTO_CLK] = &disp_cc_mdss_dptx2_crypto_clk.clkr,
+ [DISP_CC_MDSS_DPTX2_LINK_CLK] = &disp_cc_mdss_dptx2_link_clk.clkr,
+ [DISP_CC_MDSS_DPTX2_LINK_CLK_SRC] = &disp_cc_mdss_dptx2_link_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX2_LINK_DIV_CLK_SRC] = &disp_cc_mdss_dptx2_link_div_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX2_LINK_INTF_CLK] = &disp_cc_mdss_dptx2_link_intf_clk.clkr,
+ [DISP_CC_MDSS_DPTX2_PIXEL0_CLK] = &disp_cc_mdss_dptx2_pixel0_clk.clkr,
+ [DISP_CC_MDSS_DPTX2_PIXEL0_CLK_SRC] = &disp_cc_mdss_dptx2_pixel0_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX2_PIXEL1_CLK] = &disp_cc_mdss_dptx2_pixel1_clk.clkr,
+ [DISP_CC_MDSS_DPTX2_PIXEL1_CLK_SRC] = &disp_cc_mdss_dptx2_pixel1_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX3_AUX_CLK] = &disp_cc_mdss_dptx3_aux_clk.clkr,
+ [DISP_CC_MDSS_DPTX3_AUX_CLK_SRC] = &disp_cc_mdss_dptx3_aux_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX3_CRYPTO_CLK] = &disp_cc_mdss_dptx3_crypto_clk.clkr,
+ [DISP_CC_MDSS_DPTX3_LINK_CLK] = &disp_cc_mdss_dptx3_link_clk.clkr,
+ [DISP_CC_MDSS_DPTX3_LINK_CLK_SRC] = &disp_cc_mdss_dptx3_link_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX3_LINK_DIV_CLK_SRC] = &disp_cc_mdss_dptx3_link_div_clk_src.clkr,
+ [DISP_CC_MDSS_DPTX3_LINK_INTF_CLK] = &disp_cc_mdss_dptx3_link_intf_clk.clkr,
+ [DISP_CC_MDSS_DPTX3_PIXEL0_CLK] = &disp_cc_mdss_dptx3_pixel0_clk.clkr,
+ [DISP_CC_MDSS_DPTX3_PIXEL0_CLK_SRC] = &disp_cc_mdss_dptx3_pixel0_clk_src.clkr,
+ [DISP_CC_MDSS_ESC0_CLK] = &disp_cc_mdss_esc0_clk.clkr,
+ [DISP_CC_MDSS_ESC0_CLK_SRC] = &disp_cc_mdss_esc0_clk_src.clkr,
+ [DISP_CC_MDSS_ESC1_CLK] = &disp_cc_mdss_esc1_clk.clkr,
+ [DISP_CC_MDSS_ESC1_CLK_SRC] = &disp_cc_mdss_esc1_clk_src.clkr,
+ [DISP_CC_MDSS_MDP1_CLK] = &disp_cc_mdss_mdp1_clk.clkr,
+ [DISP_CC_MDSS_MDP_CLK] = &disp_cc_mdss_mdp_clk.clkr,
+ [DISP_CC_MDSS_MDP_CLK_SRC] = &disp_cc_mdss_mdp_clk_src.clkr,
+ [DISP_CC_MDSS_MDP_LUT1_CLK] = &disp_cc_mdss_mdp_lut1_clk.clkr,
+ [DISP_CC_MDSS_MDP_LUT_CLK] = &disp_cc_mdss_mdp_lut_clk.clkr,
+ [DISP_CC_MDSS_NON_GDSC_AHB_CLK] = &disp_cc_mdss_non_gdsc_ahb_clk.clkr,
+ [DISP_CC_MDSS_PCLK0_CLK] = &disp_cc_mdss_pclk0_clk.clkr,
+ [DISP_CC_MDSS_PCLK0_CLK_SRC] = &disp_cc_mdss_pclk0_clk_src.clkr,
+ [DISP_CC_MDSS_PCLK1_CLK] = &disp_cc_mdss_pclk1_clk.clkr,
+ [DISP_CC_MDSS_PCLK1_CLK_SRC] = &disp_cc_mdss_pclk1_clk_src.clkr,
+ [DISP_CC_MDSS_RSCC_AHB_CLK] = &disp_cc_mdss_rscc_ahb_clk.clkr,
+ [DISP_CC_MDSS_RSCC_VSYNC_CLK] = &disp_cc_mdss_rscc_vsync_clk.clkr,
+ [DISP_CC_MDSS_VSYNC1_CLK] = &disp_cc_mdss_vsync1_clk.clkr,
+ [DISP_CC_MDSS_VSYNC_CLK] = &disp_cc_mdss_vsync_clk.clkr,
+ [DISP_CC_MDSS_VSYNC_CLK_SRC] = &disp_cc_mdss_vsync_clk_src.clkr,
+ [DISP_CC_PLL0] = &disp_cc_pll0.clkr,
+ [DISP_CC_PLL1] = &disp_cc_pll1.clkr,
+ [DISP_CC_SLEEP_CLK] = &disp_cc_sleep_clk.clkr,
+ [DISP_CC_SLEEP_CLK_SRC] = &disp_cc_sleep_clk_src.clkr,
+ [DISP_CC_XO_CLK_SRC] = &disp_cc_xo_clk_src.clkr,
+};
+
static const struct qcom_reset_map disp_cc_sm8550_resets[] = {
[DISP_CC_MDSS_CORE_BCR] = { 0x8000 },
[DISP_CC_MDSS_CORE_INT2_BCR] = { 0xa000 },
@@ -1744,13 +1982,25 @@ static const struct regmap_config disp_cc_sm8550_regmap_config = {
.fast_io = true,
};
-static const u32 disp_cc_sm8550_critical_cbcrs[] = {
- 0xe054, /* DISP_CC_XO_CLK */
+static struct clk_alpha_pll *disp_cc_sm8550_plls[] = {
+ &disp_cc_pll0,
+ &disp_cc_pll1,
};
+static void disp_cc_sm8550_clk_regs_configure(struct device *dev,
+ struct regmap *regmap)
+{
+ /* Enable clock gating for MDP clocks. */
+ regmap_update_bits(regmap, DISP_CC_MISC_CMD, 0x10, 0x10);
+
+ /* Keep the XO clock always on. */
+ qcom_branch_set_clk_en(regmap, 0xe054);
+}
+
static const struct qcom_cc_driver_data disp_cc_sm8550_driver_data = {
- .clk_cbcrs = disp_cc_sm8550_critical_cbcrs,
- .num_clk_cbcrs = ARRAY_SIZE(disp_cc_sm8550_critical_cbcrs),
+ .alpha_plls = disp_cc_sm8550_plls,
+ .num_alpha_plls = ARRAY_SIZE(disp_cc_sm8550_plls),
+ .clk_regs_configure = disp_cc_sm8550_clk_regs_configure,
};
static const struct qcom_cc_desc disp_cc_sm8550_desc = {
@@ -1762,36 +2012,43 @@ static const struct qcom_cc_desc disp_cc_sm8550_desc = {
.num_resets = ARRAY_SIZE(disp_cc_sm8550_resets),
.gdscs = disp_cc_sm8550_gdscs,
.num_gdscs = ARRAY_SIZE(disp_cc_sm8550_gdscs),
+ .use_rpm = true,
+};
+
+static const unsigned int disp_cc_sm8650_access_pd_indices[] = {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MX,
+};
+
+static const struct qcom_cc_desc disp_cc_sm8650_desc = {
+ .config = &disp_cc_sm8550_regmap_config,
+ .clks = disp_cc_sm8650_clocks,
+ .num_clks = ARRAY_SIZE(disp_cc_sm8650_clocks),
+ .resets = disp_cc_sm8550_resets,
+ .num_resets = ARRAY_SIZE(disp_cc_sm8550_resets),
+ .gdscs = disp_cc_sm8550_gdscs,
+ .num_gdscs = ARRAY_SIZE(disp_cc_sm8550_gdscs),
+ .use_rpm = true,
+ .pd_indices = disp_cc_sm8650_access_pd_indices,
+ .num_pd_indices = ARRAY_SIZE(disp_cc_sm8650_access_pd_indices),
+ .num_dvfs_rail_voters = 2,
+ .driver_data = &disp_cc_sm8550_driver_data,
};
static const struct of_device_id disp_cc_sm8550_match_table[] = {
- { .compatible = "qcom,sar2130p-dispcc" },
- { .compatible = "qcom,sm8550-dispcc" },
- { .compatible = "qcom,sm8650-dispcc" },
+ { .compatible = "qcom,sar2130p-dispcc", .data = &disp_cc_sm8550_desc },
+ { .compatible = "qcom,sm8550-dispcc", .data = &disp_cc_sm8550_desc },
+ { .compatible = "qcom,sm8650-dispcc", .data = &disp_cc_sm8650_desc },
{ }
};
MODULE_DEVICE_TABLE(of, disp_cc_sm8550_match_table);
static int disp_cc_sm8550_probe(struct platform_device *pdev)
{
- struct regmap *regmap;
- int ret;
+ const struct qcom_cc_desc *desc = of_device_get_match_data(&pdev->dev);
+ bool sm8650 = desc == &disp_cc_sm8650_desc;
- ret = devm_pm_runtime_enable(&pdev->dev);
- if (ret)
- return ret;
-
- ret = pm_runtime_resume_and_get(&pdev->dev);
- if (ret)
- return ret;
-
- regmap = qcom_cc_map(pdev, &disp_cc_sm8550_desc);
- if (IS_ERR(regmap)) {
- ret = PTR_ERR(regmap);
- goto err_put_rpm;
- }
-
- if (of_device_is_compatible(pdev->dev.of_node, "qcom,sm8650-dispcc")) {
+ if (sm8650) {
lucid_ole_vco[0].max_freq = 2100000000;
disp_cc_mdss_mdp_clk_src.freq_tbl = ftbl_disp_cc_mdss_mdp_clk_src_sm8650;
disp_cc_mdss_dptx1_usb_router_link_intf_clk.clkr.hw.init->parent_hws[0] =
@@ -1804,24 +2061,7 @@ static int disp_cc_sm8550_probe(struct platform_device *pdev)
disp_cc_mdss_mdp_clk_src.freq_tbl = ftbl_disp_cc_mdss_mdp_clk_src_sar2130p;
}
- clk_lucid_ole_pll_configure(&disp_cc_pll0, regmap, &disp_cc_pll0_config);
- clk_lucid_ole_pll_configure(&disp_cc_pll1, regmap, &disp_cc_pll1_config);
-
- /* Enable clock gating for MDP clocks */
- regmap_update_bits(regmap, DISP_CC_MISC_CMD, 0x10, 0x10);
-
- ret = qcom_cc_really_probe(&pdev->dev, &disp_cc_sm8550_desc, regmap);
- if (ret)
- goto err_put_rpm;
-
- pm_runtime_put(&pdev->dev);
-
- return 0;
-
-err_put_rpm:
- pm_runtime_put_sync(&pdev->dev);
-
- return ret;
+ return qcom_cc_probe(pdev, desc);
}
static struct platform_driver disp_cc_sm8550_driver = {
diff --git a/drivers/clk/qcom/gcc-sm8650.c b/drivers/clk/qcom/gcc-sm8650.c
index e439c958ae16..8e1fd75b606c 100644
--- a/drivers/clk/qcom/gcc-sm8650.c
+++ b/drivers/clk/qcom/gcc-sm8650.c
@@ -11,9 +11,11 @@
#include <linux/regmap.h>
#include <dt-bindings/clock/qcom,sm8650-gcc.h>
+#include <dt-bindings/power/qcom,rpmhpd.h>
#include "clk-alpha-pll.h"
#include "clk-branch.h"
+#include "clk-dvfs.h"
#include "clk-rcg.h"
#include "clk-regmap.h"
#include "clk-regmap-divider.h"
@@ -59,10 +61,100 @@ enum {
P_USB3_PHY_WRAPPER_GCC_USB30_PIPE_CLK,
};
+enum {
+ DT_PMDOMAIN_CX,
+ DT_PMDOMAIN_CX_AO,
+ DT_PMDOMAIN_MX,
+};
+
+static const unsigned int gcc_sm8650_cx_block_pd_indices[] = {
+ DT_PMDOMAIN_CX,
+};
+
+static const unsigned int gcc_sm8650_mx_block_pd_indices[] = {
+ DT_PMDOMAIN_MX,
+};
+
+static const struct qcom_clk_dvfs_data gcc_gpll0_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_CX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 615000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 1100000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 1600000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 2000000000, RPMH_REGULATOR_LEVEL_NOM },
+ { 2100000000, RPMH_REGULATOR_LEVEL_TURBO_L1 },
+ },
+ .num_rates = 5,
+};
+
+static const struct qcom_clk_dvfs_data gcc_gpll0_ao_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_CX_AO },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 615000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 1100000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 1600000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 2000000000, RPMH_REGULATOR_LEVEL_NOM },
+ { 2100000000, RPMH_REGULATOR_LEVEL_TURBO_L1 },
+ },
+ .num_rates = 5,
+};
+
+static const struct qcom_clk_dvfs_data gcc_gp1_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_CX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 50000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 100000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 200000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 3,
+};
+
+static const struct qcom_clk_dvfs_data gcc_pcie_0_aux_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_CX, DT_PMDOMAIN_MX },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 19200000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+};
+
+static const struct qcom_clk_dvfs_data gcc_pcie_0_phy_rchng_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_CX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 100000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+};
+
+static const struct qcom_clk_dvfs_data gcc_qupv3_i2c_s0_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_CX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 19200000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+};
+
+static const struct qcom_clk_dvfs_data gcc_ufs_phy_axi_clk_src_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_CX, DT_PMDOMAIN_MX },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 100000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 201500000, RPMH_REGULATOR_LEVEL_SVS },
+ { 403000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 3,
+};
+
static struct clk_alpha_pll gcc_gpll0 = {
.offset = 0x0,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_dvfs,
.enable_reg = 0x52020,
.enable_mask = BIT(0),
.hw.init = &(const struct clk_init_data) {
@@ -80,6 +172,7 @@ static struct clk_alpha_pll gcc_gpll0_ao = {
.offset = 0x0,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_ao_dvfs,
.enable_reg = 0x57020,
.enable_mask = BIT(0),
.hw.init = &(const struct clk_init_data) {
@@ -136,6 +229,7 @@ static struct clk_alpha_pll gcc_gpll1 = {
.offset = 0x4000,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_dvfs,
.enable_reg = 0x52020,
.enable_mask = BIT(1),
.hw.init = &(const struct clk_init_data) {
@@ -153,6 +247,7 @@ static struct clk_alpha_pll gcc_gpll1_ao = {
.offset = 0x1000,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_ao_dvfs,
.enable_reg = 0x57020,
.enable_mask = BIT(1),
.hw.init = &(const struct clk_init_data) {
@@ -170,6 +265,7 @@ static struct clk_alpha_pll gcc_gpll3 = {
.offset = 0x3000,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_dvfs,
.enable_reg = 0x52020,
.enable_mask = BIT(3),
.hw.init = &(const struct clk_init_data) {
@@ -187,6 +283,7 @@ static struct clk_alpha_pll gcc_gpll3_ao = {
.offset = 0x3000,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_ao_dvfs,
.enable_reg = 0x57020,
.enable_mask = BIT(3),
.hw.init = &(const struct clk_init_data) {
@@ -204,6 +301,7 @@ static struct clk_alpha_pll gcc_gpll4 = {
.offset = 0x4000,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_dvfs,
.enable_reg = 0x52020,
.enable_mask = BIT(4),
.hw.init = &(const struct clk_init_data) {
@@ -221,6 +319,7 @@ static struct clk_alpha_pll gcc_gpll4_ao = {
.offset = 0x4000,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_ao_dvfs,
.enable_reg = 0x57020,
.enable_mask = BIT(4),
.hw.init = &(const struct clk_init_data) {
@@ -238,6 +337,7 @@ static struct clk_alpha_pll gcc_gpll6 = {
.offset = 0x6000,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_dvfs,
.enable_reg = 0x52020,
.enable_mask = BIT(6),
.hw.init = &(const struct clk_init_data) {
@@ -255,6 +355,7 @@ static struct clk_alpha_pll gcc_gpll6_ao = {
.offset = 0x6000,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_ao_dvfs,
.enable_reg = 0x57020,
.enable_mask = BIT(6),
.hw.init = &(const struct clk_init_data) {
@@ -272,6 +373,7 @@ static struct clk_alpha_pll gcc_gpll7 = {
.offset = 0x7000,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_dvfs,
.enable_reg = 0x52020,
.enable_mask = BIT(7),
.hw.init = &(const struct clk_init_data) {
@@ -289,6 +391,7 @@ static struct clk_alpha_pll gcc_gpll9 = {
.offset = 0x9000,
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &gcc_gpll0_dvfs,
.enable_reg = 0x52020,
.enable_mask = BIT(9),
.hw.init = &(const struct clk_init_data) {
@@ -344,6 +447,18 @@ static const struct clk_parent_data gcc_parent_data_2[] = {
{ .hw = &gcc_gpll0_out_even.clkr.hw },
};
+static const struct parent_map gcc_parent_map_6[] = {
+ { P_GCC_GPLL0_OUT_EVEN, 6 },
+ { P_GCC_GPLL1_OUT_MAIN, 4 },
+ { P_GCC_GPLL4_OUT_MAIN, 5 },
+};
+
+static const struct clk_parent_data gcc_parent_data_6_ao[] = {
+ { .hw = &gcc_gpll0_out_even_ao.clkr.hw },
+ { .hw = &gcc_gpll1_ao.clkr.hw },
+ { .hw = &gcc_gpll4_ao.clkr.hw },
+};
+
static const struct parent_map gcc_parent_map_3[] = {
{ P_BI_TCXO, 0 },
{ P_GCC_GPLL0_OUT_MAIN, 1 },
@@ -566,12 +681,47 @@ static const struct freq_tbl ftbl_gcc_gp1_clk_src[] = {
{ }
};
+static const struct freq_tbl ftbl_gcc_cpuss_ubwcp_clk_src[] = {
+ F(300000000, P_GCC_GPLL0_OUT_EVEN, 1, 0, 0),
+ F(403000000, P_GCC_GPLL4_OUT_MAIN, 2, 0, 0),
+ F(533000000, P_GCC_GPLL1_OUT_MAIN, 2, 0, 0),
+ F(710666667, P_GCC_GPLL1_OUT_MAIN, 1.5, 0, 0),
+ { }
+};
+
+static struct clk_rcg2 gcc_cpuss_ubwcp_clk_src = {
+ .cmd_rcgr = 0x63004,
+ .mnd_width = 0,
+ .hid_width = 5,
+ .parent_map = gcc_parent_map_6,
+ .freq_tbl = ftbl_gcc_cpuss_ubwcp_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_CX_AO },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 300000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 403000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 533000000, RPMH_REGULATOR_LEVEL_NOM },
+ { 710666667, RPMH_REGULATOR_LEVEL_TURBO },
+ },
+ .num_rates = 4,
+ },
+ .clkr.hw.init = &(const struct clk_init_data) {
+ .name = "gcc_cpuss_ubwcp_clk_src",
+ .parent_data = gcc_parent_data_6_ao,
+ .num_parents = ARRAY_SIZE(gcc_parent_data_6_ao),
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_rcg2_shared_ops,
+ },
+};
+
static struct clk_rcg2 gcc_gp1_clk_src = {
.cmd_rcgr = 0x64004,
.mnd_width = 16,
.hid_width = 5,
.parent_map = gcc_parent_map_1,
.freq_tbl = ftbl_gcc_gp1_clk_src,
+ .clkr.dvfs_data = &gcc_gp1_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "gcc_gp1_clk_src",
.parent_data = gcc_parent_data_1,
@@ -587,6 +737,7 @@ static struct clk_rcg2 gcc_gp2_clk_src = {
.hid_width = 5,
.parent_map = gcc_parent_map_1,
.freq_tbl = ftbl_gcc_gp1_clk_src,
+ .clkr.dvfs_data = &gcc_gp1_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "gcc_gp2_clk_src",
.parent_data = gcc_parent_data_1,
@@ -602,6 +753,7 @@ static struct clk_rcg2 gcc_gp3_clk_src = {
.hid_width = 5,
.parent_map = gcc_parent_map_1,
.freq_tbl = ftbl_gcc_gp1_clk_src,
+ .clkr.dvfs_data = &gcc_gp1_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "gcc_gp3_clk_src",
.parent_data = gcc_parent_data_1,
@@ -622,6 +774,7 @@ static struct clk_rcg2 gcc_pcie_0_aux_clk_src = {
.hid_width = 5,
.parent_map = gcc_parent_map_4,
.freq_tbl = ftbl_gcc_pcie_0_aux_clk_src,
+ .clkr.dvfs_data = &gcc_pcie_0_aux_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "gcc_pcie_0_aux_clk_src",
.parent_data = gcc_parent_data_4,
@@ -642,6 +795,7 @@ static struct clk_rcg2 gcc_pcie_0_phy_rchng_clk_src = {
.hid_width = 5,
.parent_map = gcc_parent_map_0,
.freq_tbl = ftbl_gcc_pcie_0_phy_rchng_clk_src,
+ .clkr.dvfs_data = &gcc_pcie_0_phy_rchng_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "gcc_pcie_0_phy_rchng_clk_src",
.parent_data = gcc_parent_data_0,
@@ -657,6 +811,7 @@ static struct clk_rcg2 gcc_pcie_1_aux_clk_src = {
.hid_width = 5,
.parent_map = gcc_parent_map_4,
.freq_tbl = ftbl_gcc_pcie_0_aux_clk_src,
+ .clkr.dvfs_data = &gcc_pcie_0_aux_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "gcc_pcie_1_aux_clk_src",
.parent_data = gcc_parent_data_4,
@@ -672,6 +827,7 @@ static struct clk_rcg2 gcc_pcie_1_phy_rchng_clk_src = {
.hid_width = 5,
.parent_map = gcc_parent_map_0,
.freq_tbl = ftbl_gcc_pcie_0_phy_rchng_clk_src,
+ .clkr.dvfs_data = &gcc_pcie_0_phy_rchng_clk_src_dvfs,
.clkr.hw.init = &(const struct clk_init_data) {
.name = "gcc_pcie_1_phy_rchng_clk_src",
.parent_data = gcc_parent_data_0,
@@ -692,6 +848,14 @@ static struct clk_rcg2 gcc_pdm2_clk_src = {
.hid_width = 5,
.parent_map = gcc_parent_map_0,
.freq_tbl = ftbl_gcc_pdm2_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_CX },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 60000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "gcc_pdm2_clk_src",
.parent_data = gcc_parent_data_0,
@@ -702,6 +866,7 @@ static struct clk_rcg2 gcc_pdm2_clk_src = {
};
static struct clk_rcg2 gcc_qupv3_i2c_s0_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x17008,
.mnd_width = 0,
.hid_width = 5,
@@ -717,6 +882,7 @@ static struct clk_rcg2 gcc_qupv3_i2c_s0_clk_src = {
};
static struct clk_rcg2 gcc_qupv3_i2c_s1_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x17024,
.mnd_width = 0,
.hid_width = 5,
@@ -732,6 +898,7 @@ static struct clk_rcg2 gcc_qupv3_i2c_s1_clk_src = {
};
static struct clk_rcg2 gcc_qupv3_i2c_s2_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x17040,
.mnd_width = 0,
.hid_width = 5,
@@ -747,6 +914,7 @@ static struct clk_rcg2 gcc_qupv3_i2c_s2_clk_src = {
};
static struct clk_rcg2 gcc_qupv3_i2c_s3_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x1705c,
.mnd_width = 0,
.hid_width = 5,
@@ -762,6 +930,7 @@ static struct clk_rcg2 gcc_qupv3_i2c_s3_clk_src = {
};
static struct clk_rcg2 gcc_qupv3_i2c_s4_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x17078,
.mnd_width = 0,
.hid_width = 5,
@@ -777,6 +946,7 @@ static struct clk_rcg2 gcc_qupv3_i2c_s4_clk_src = {
};
static struct clk_rcg2 gcc_qupv3_i2c_s5_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x17094,
.mnd_width = 0,
.hid_width = 5,
@@ -792,6 +962,7 @@ static struct clk_rcg2 gcc_qupv3_i2c_s5_clk_src = {
};
static struct clk_rcg2 gcc_qupv3_i2c_s6_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x170b0,
.mnd_width = 0,
.hid_width = 5,
@@ -807,6 +978,7 @@ static struct clk_rcg2 gcc_qupv3_i2c_s6_clk_src = {
};
static struct clk_rcg2 gcc_qupv3_i2c_s7_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x170cc,
.mnd_width = 0,
.hid_width = 5,
@@ -822,6 +994,7 @@ static struct clk_rcg2 gcc_qupv3_i2c_s7_clk_src = {
};
static struct clk_rcg2 gcc_qupv3_i2c_s8_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x170e8,
.mnd_width = 0,
.hid_width = 5,
@@ -837,6 +1010,7 @@ static struct clk_rcg2 gcc_qupv3_i2c_s8_clk_src = {
};
static struct clk_rcg2 gcc_qupv3_i2c_s9_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x17104,
.mnd_width = 0,
.hid_width = 5,
@@ -1048,6 +1222,16 @@ static const struct freq_tbl ftbl_gcc_qupv3_wrap2_ibi_ctrl_0_clk_src[] = {
};
static struct clk_rcg2 gcc_qupv3_wrap2_ibi_ctrl_0_clk_src = {
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_CX, DT_PMDOMAIN_MX,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 37500000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+ },
.cmd_rcgr = 0x1e9d4,
.mnd_width = 0,
.hid_width = 5,
@@ -1246,6 +1430,17 @@ static const struct freq_tbl ftbl_gcc_sdcc2_apps_clk_src[] = {
};
static struct clk_rcg2 gcc_sdcc2_apps_clk_src = {
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_CX, DT_PMDOMAIN_MX,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 100000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 202000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ },
+ .num_rates = 2,
+ },
.cmd_rcgr = 0x14018,
.mnd_width = 8,
.hid_width = 5,
@@ -1268,6 +1463,16 @@ static const struct freq_tbl ftbl_gcc_sdcc4_apps_clk_src[] = {
};
static struct clk_rcg2 gcc_sdcc4_apps_clk_src = {
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_CX, DT_PMDOMAIN_MX,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 75000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ },
+ .num_rates = 1,
+ },
.cmd_rcgr = 0x16018,
.mnd_width = 8,
.hid_width = 5,
@@ -1291,6 +1496,7 @@ static const struct freq_tbl ftbl_gcc_ufs_phy_axi_clk_src[] = {
};
static struct clk_rcg2 gcc_ufs_phy_axi_clk_src = {
+ .clkr.dvfs_data = &gcc_ufs_phy_axi_clk_src_dvfs,
.cmd_rcgr = 0x77030,
.mnd_width = 8,
.hid_width = 5,
@@ -1313,6 +1519,7 @@ static const struct freq_tbl ftbl_gcc_ufs_phy_ice_core_clk_src[] = {
};
static struct clk_rcg2 gcc_ufs_phy_ice_core_clk_src = {
+ .clkr.dvfs_data = &gcc_ufs_phy_axi_clk_src_dvfs,
.cmd_rcgr = 0x77080,
.mnd_width = 0,
.hid_width = 5,
@@ -1334,6 +1541,7 @@ static const struct freq_tbl ftbl_gcc_ufs_phy_phy_aux_clk_src[] = {
};
static struct clk_rcg2 gcc_ufs_phy_phy_aux_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x770b4,
.mnd_width = 0,
.hid_width = 5,
@@ -1349,6 +1557,7 @@ static struct clk_rcg2 gcc_ufs_phy_phy_aux_clk_src = {
};
static struct clk_rcg2 gcc_ufs_phy_unipro_core_clk_src = {
+ .clkr.dvfs_data = &gcc_ufs_phy_axi_clk_src_dvfs,
.cmd_rcgr = 0x77098,
.mnd_width = 0,
.hid_width = 5,
@@ -1372,6 +1581,19 @@ static const struct freq_tbl ftbl_gcc_usb30_prim_master_clk_src[] = {
};
static struct clk_rcg2 gcc_usb30_prim_master_clk_src = {
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_CX, DT_PMDOMAIN_MX,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 66666667, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 133333333, RPMH_REGULATOR_LEVEL_SVS },
+ { 200000000, RPMH_REGULATOR_LEVEL_NOM },
+ { 240000000, RPMH_REGULATOR_LEVEL_TURBO },
+ },
+ .num_rates = 4,
+ },
.cmd_rcgr = 0x3902c,
.mnd_width = 8,
.hid_width = 5,
@@ -1387,6 +1609,7 @@ static struct clk_rcg2 gcc_usb30_prim_master_clk_src = {
};
static struct clk_rcg2 gcc_usb30_prim_mock_utmi_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x39044,
.mnd_width = 0,
.hid_width = 5,
@@ -1402,6 +1625,7 @@ static struct clk_rcg2 gcc_usb30_prim_mock_utmi_clk_src = {
};
static struct clk_rcg2 gcc_usb3_prim_phy_aux_clk_src = {
+ .clkr.dvfs_data = &gcc_qupv3_i2c_s0_clk_src_dvfs,
.cmd_rcgr = 0x39070,
.mnd_width = 0,
.hid_width = 5,
@@ -1631,6 +1855,26 @@ static struct clk_branch gcc_cnoc_pcie_sf_axi_clk = {
},
};
+static struct clk_branch gcc_cpuss_ubwcp_clk = {
+ .halt_reg = 0x63000,
+ .halt_check = BRANCH_HALT_VOTED,
+ .hwcg_reg = 0x63000,
+ .hwcg_bit = 1,
+ .clkr = {
+ .enable_reg = 0x63000,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gcc_cpuss_ubwcp_clk",
+ .parent_hws = (const struct clk_hw *[]) {
+ &gcc_cpuss_ubwcp_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
static struct clk_branch gcc_ddrss_gpu_axi_clk = {
.halt_reg = 0x71154,
.halt_check = BRANCH_HALT_SKIP,
@@ -3438,6 +3682,8 @@ static struct gdsc pcie_0_gdsc = {
},
.pwrsts = PWRSTS_RET_ON,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE | VOTABLE,
+ .required_pds = gcc_sm8650_cx_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(gcc_sm8650_cx_block_pd_indices),
};
static struct gdsc pcie_0_phy_gdsc = {
@@ -3449,6 +3695,8 @@ static struct gdsc pcie_0_phy_gdsc = {
},
.pwrsts = PWRSTS_RET_ON,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE | VOTABLE,
+ .required_pds = gcc_sm8650_mx_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(gcc_sm8650_mx_block_pd_indices),
};
static struct gdsc pcie_1_gdsc = {
@@ -3460,6 +3708,8 @@ static struct gdsc pcie_1_gdsc = {
},
.pwrsts = PWRSTS_RET_ON,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE | VOTABLE,
+ .required_pds = gcc_sm8650_cx_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(gcc_sm8650_cx_block_pd_indices),
};
static struct gdsc pcie_1_phy_gdsc = {
@@ -3471,6 +3721,8 @@ static struct gdsc pcie_1_phy_gdsc = {
},
.pwrsts = PWRSTS_RET_ON,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE | VOTABLE,
+ .required_pds = gcc_sm8650_mx_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(gcc_sm8650_mx_block_pd_indices),
};
static struct gdsc ufs_phy_gdsc = {
@@ -3480,6 +3732,8 @@ static struct gdsc ufs_phy_gdsc = {
},
.pwrsts = PWRSTS_OFF_ON,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
+ .required_pds = gcc_sm8650_cx_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(gcc_sm8650_cx_block_pd_indices),
};
static struct gdsc ufs_mem_phy_gdsc = {
@@ -3489,6 +3743,8 @@ static struct gdsc ufs_mem_phy_gdsc = {
},
.pwrsts = PWRSTS_OFF_ON,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
+ .required_pds = gcc_sm8650_mx_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(gcc_sm8650_mx_block_pd_indices),
};
static struct gdsc usb30_prim_gdsc = {
@@ -3507,6 +3763,8 @@ static struct gdsc usb3_phy_gdsc = {
},
.pwrsts = PWRSTS_RET_ON,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
+ .required_pds = gcc_sm8650_mx_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(gcc_sm8650_mx_block_pd_indices),
};
static struct clk_regmap *gcc_sm8650_clocks[] = {
@@ -3520,6 +3778,8 @@ static struct clk_regmap *gcc_sm8650_clocks[] = {
[GCC_CFG_NOC_PCIE_ANOC_AHB_CLK] = &gcc_cfg_noc_pcie_anoc_ahb_clk.clkr,
[GCC_CFG_NOC_USB3_PRIM_AXI_CLK] = &gcc_cfg_noc_usb3_prim_axi_clk.clkr,
[GCC_CNOC_PCIE_SF_AXI_CLK] = &gcc_cnoc_pcie_sf_axi_clk.clkr,
+ [GCC_CPUSS_UBWCP_CLK] = &gcc_cpuss_ubwcp_clk.clkr,
+ [GCC_CPUSS_UBWCP_CLK_SRC] = &gcc_cpuss_ubwcp_clk_src.clkr,
[GCC_DDRSS_GPU_AXI_CLK] = &gcc_ddrss_gpu_axi_clk.clkr,
[GCC_DDRSS_PCIE_SF_QTB_CLK] = &gcc_ddrss_pcie_sf_qtb_clk.clkr,
[GCC_DISP_HF_AXI_CLK] = &gcc_disp_hf_axi_clk.clkr,
@@ -3815,6 +4075,7 @@ static const struct qcom_cc_desc gcc_sm8650_desc = {
.gdscs = gcc_sm8650_gdscs,
.num_gdscs = ARRAY_SIZE(gcc_sm8650_gdscs),
.use_rpm = true,
+ .num_dvfs_rail_voters = 3,
};
static const struct of_device_id gcc_sm8650_match_table[] = {
diff --git a/drivers/clk/qcom/gpucc-sm8650.c b/drivers/clk/qcom/gpucc-sm8650.c
index a84fa35bc2ba..3fe4ca1ad3be 100644
--- a/drivers/clk/qcom/gpucc-sm8650.c
+++ b/drivers/clk/qcom/gpucc-sm8650.c
@@ -10,10 +10,12 @@
#include <linux/regmap.h>
#include <dt-bindings/clock/qcom,sm8650-gpucc.h>
+#include <dt-bindings/power/qcom,rpmhpd.h>
#include <dt-bindings/reset/qcom,sm8650-gpucc.h>
#include "clk-alpha-pll.h"
#include "clk-branch.h"
+#include "clk-dvfs.h"
#include "clk-rcg.h"
#include "clk-regmap.h"
#include "clk-regmap-divider.h"
@@ -36,6 +38,13 @@ enum {
P_GPU_CC_PLL1_OUT_MAIN,
};
+enum {
+ DT_PMDOMAIN_MX,
+ DT_PMDOMAIN_CX,
+ DT_PMDOMAIN_MXC,
+ DT_PMDOMAIN_GFX,
+};
+
static const struct pll_vco lucid_ole_vco[] = {
{ 249600000, 2100000000, 0 },
};
@@ -59,7 +68,22 @@ static struct clk_alpha_pll gpu_cc_pll0 = {
.vco_table = lucid_ole_vco,
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
+ .config = &gpu_cc_pll0_config,
.clkr = {
+ .dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MXC,
+ },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 615000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 1100000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 1600000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 2000000000, RPMH_REGULATOR_LEVEL_NOM },
+ { 2100000000, RPMH_REGULATOR_LEVEL_TURBO_L1 },
+ },
+ .num_rates = 5,
+ },
.hw.init = &(struct clk_init_data){
.name = "gpu_cc_pll0",
.parent_data = &(const struct clk_parent_data){
@@ -90,7 +114,22 @@ static struct clk_alpha_pll gpu_cc_pll1 = {
.vco_table = lucid_ole_vco,
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
+ .config = &gpu_cc_pll1_config,
.clkr = {
+ .dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MX,
+ },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 615000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 1100000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 1600000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 2000000000, RPMH_REGULATOR_LEVEL_NOM },
+ { 2100000000, RPMH_REGULATOR_LEVEL_TURBO_L1 },
+ },
+ .num_rates = 5,
+ },
.hw.init = &(struct clk_init_data){
.name = "gpu_cc_pll1",
.parent_data = &(const struct clk_parent_data){
@@ -156,6 +195,16 @@ static struct clk_rcg2 gpu_cc_ff_clk_src = {
.parent_map = gpu_cc_parent_map_0,
.freq_tbl = ftbl_gpu_cc_ff_clk_src,
.hw_clk_ctrl = true,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_CX,
+ },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 200000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ },
+ .num_rates = 1,
+ },
.clkr.hw.init = &(struct clk_init_data){
.name = "gpu_cc_ff_clk_src",
.parent_data = gpu_cc_parent_data_0,
@@ -179,6 +228,18 @@ static struct clk_rcg2 gpu_cc_gmu_clk_src = {
.parent_map = gpu_cc_parent_map_1,
.freq_tbl = ftbl_gpu_cc_gmu_clk_src,
.hw_clk_ctrl = true,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_CX,
+ DT_PMDOMAIN_MX,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 260000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 625000000, RPMH_REGULATOR_LEVEL_SVS },
+ },
+ .num_rates = 2,
+ },
.clkr.hw.init = &(struct clk_init_data){
.name = "gpu_cc_gmu_clk_src",
.parent_data = gpu_cc_parent_data_1,
@@ -201,6 +262,18 @@ static struct clk_rcg2 gpu_cc_hub_clk_src = {
.parent_map = gpu_cc_parent_map_2,
.freq_tbl = ftbl_gpu_cc_hub_clk_src,
.hw_clk_ctrl = true,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_CX,
+ },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 200000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 300000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 312500000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ },
+ .num_rates = 3,
+ },
.clkr.hw.init = &(struct clk_init_data){
.name = "gpu_cc_hub_clk_src",
.parent_data = gpu_cc_parent_data_2,
@@ -547,6 +620,8 @@ static struct gdsc gpu_cx_gdsc = {
},
.pwrsts = PWRSTS_OFF_ON,
.flags = VOTABLE | RETAIN_FF_ENABLE,
+ .required_pds = (const unsigned int[]){ DT_PMDOMAIN_CX },
+ .num_required_pds = 1,
};
static struct gdsc gpu_gx_gdsc = {
@@ -562,6 +637,9 @@ static struct gdsc gpu_gx_gdsc = {
},
.pwrsts = PWRSTS_OFF_ON,
.flags = CLAMP_IO | AON_RESET | SW_RESET | POLL_CFG_GDSCR,
+ .required_pds = (const unsigned int[]){ DT_PMDOMAIN_GFX,
+ DT_PMDOMAIN_MXC },
+ .num_required_pds = 2,
};
static struct clk_regmap *gpu_cc_sm8650_clocks[] = {
@@ -619,6 +697,16 @@ static const struct regmap_config gpu_cc_sm8650_regmap_config = {
.fast_io = true,
};
+static struct clk_alpha_pll *gpu_cc_sm8650_plls[] = {
+ &gpu_cc_pll0,
+ &gpu_cc_pll1,
+};
+
+static const struct qcom_cc_driver_data gpu_cc_sm8650_driver_data = {
+ .alpha_plls = gpu_cc_sm8650_plls,
+ .num_alpha_plls = ARRAY_SIZE(gpu_cc_sm8650_plls),
+};
+
static const struct qcom_cc_desc gpu_cc_sm8650_desc = {
.config = &gpu_cc_sm8650_regmap_config,
.clks = gpu_cc_sm8650_clocks,
@@ -627,6 +715,8 @@ static const struct qcom_cc_desc gpu_cc_sm8650_desc = {
.num_resets = ARRAY_SIZE(gpu_cc_sm8650_resets),
.gdscs = gpu_cc_sm8650_gdscs,
.num_gdscs = ARRAY_SIZE(gpu_cc_sm8650_gdscs),
+ .num_dvfs_rail_voters = 4,
+ .driver_data = &gpu_cc_sm8650_driver_data,
};
static const struct of_device_id gpu_cc_sm8650_match_table[] = {
@@ -637,16 +727,7 @@ MODULE_DEVICE_TABLE(of, gpu_cc_sm8650_match_table);
static int gpu_cc_sm8650_probe(struct platform_device *pdev)
{
- struct regmap *regmap;
-
- regmap = qcom_cc_map(pdev, &gpu_cc_sm8650_desc);
- if (IS_ERR(regmap))
- return PTR_ERR(regmap);
-
- clk_lucid_ole_pll_configure(&gpu_cc_pll0, regmap, &gpu_cc_pll0_config);
- clk_lucid_ole_pll_configure(&gpu_cc_pll1, regmap, &gpu_cc_pll1_config);
-
- return qcom_cc_really_probe(&pdev->dev, &gpu_cc_sm8650_desc, regmap);
+ return qcom_cc_probe(pdev, &gpu_cc_sm8650_desc);
}
static struct platform_driver gpu_cc_sm8650_driver = {
diff --git a/drivers/clk/qcom/videocc-sm8550.c b/drivers/clk/qcom/videocc-sm8550.c
index c2f11489a222..682ee4f3f3ed 100644
--- a/drivers/clk/qcom/videocc-sm8550.c
+++ b/drivers/clk/qcom/videocc-sm8550.c
@@ -9,9 +9,11 @@
#include <linux/regmap.h>
#include <dt-bindings/clock/qcom,sm8650-videocc.h>
+#include <dt-bindings/power/qcom,rpmhpd.h>
#include "clk-alpha-pll.h"
#include "clk-branch.h"
+#include "clk-dvfs.h"
#include "clk-rcg.h"
#include "clk-regmap.h"
#include "clk-regmap-divider.h"
@@ -29,6 +31,29 @@ enum {
P_VIDEO_CC_PLL1_OUT_MAIN,
};
+enum {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+};
+
+static const unsigned int video_cc_sm8650_block_pd_indices[] = {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+};
+
+static const struct qcom_clk_dvfs_data video_cc_pll0_dvfs = {
+ .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MXC },
+ .num_pd_indices = 1,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 615000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 1100000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 1600000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 2000000000, RPMH_REGULATOR_LEVEL_NOM },
+ { 2100000000, RPMH_REGULATOR_LEVEL_TURBO_L1 },
+ },
+ .num_rates = 5,
+};
+
static const struct pll_vco lucid_ole_vco[] = {
{ 249600000, 2300000000, 0 },
};
@@ -54,6 +79,7 @@ static struct clk_alpha_pll video_cc_pll0 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &video_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "video_cc_pll0",
.parent_data = &(const struct clk_parent_data) {
@@ -86,6 +112,7 @@ static struct clk_alpha_pll video_cc_pll1 = {
.num_vco = ARRAY_SIZE(lucid_ole_vco),
.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
.clkr = {
+ .dvfs_data = &video_cc_pll0_dvfs,
.hw.init = &(const struct clk_init_data) {
.name = "video_cc_pll1",
.parent_data = &(const struct clk_parent_data) {
@@ -160,6 +187,22 @@ static struct clk_rcg2 video_cc_mvs0_clk_src = {
.hid_width = 5,
.parent_map = video_cc_parent_map_0,
.freq_tbl = ftbl_video_cc_mvs0_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 588000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 900000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 1140000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 1305000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 1440000000, RPMH_REGULATOR_LEVEL_NOM },
+ { 1600000000, RPMH_REGULATOR_LEVEL_TURBO },
+ },
+ .num_rates = 6,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "video_cc_mvs0_clk_src",
.parent_data = video_cc_parent_data_0,
@@ -201,6 +244,21 @@ static struct clk_rcg2 video_cc_mvs1_clk_src = {
.hid_width = 5,
.parent_map = video_cc_parent_map_1,
.freq_tbl = ftbl_video_cc_mvs1_clk_src,
+ .clkr.dvfs_data = &(const struct qcom_clk_dvfs_data) {
+ .pd_indices = (const unsigned int[]) {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+ },
+ .num_pd_indices = 2,
+ .rates = (const struct qcom_clk_dvfs_rate[]) {
+ { 840000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
+ { 1110000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
+ { 1350000000, RPMH_REGULATOR_LEVEL_SVS },
+ { 1500000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
+ { 1650000000, RPMH_REGULATOR_LEVEL_NOM },
+ },
+ .num_rates = 5,
+ },
.clkr.hw.init = &(const struct clk_init_data) {
.name = "video_cc_mvs1_clk_src",
.parent_data = video_cc_parent_data_1,
@@ -456,6 +514,8 @@ static struct gdsc video_cc_mvs0c_gdsc = {
},
.pwrsts = PWRSTS_OFF_ON,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
+ .required_pds = video_cc_sm8650_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(video_cc_sm8650_block_pd_indices),
};
static struct gdsc video_cc_mvs0_gdsc = {
@@ -469,6 +529,8 @@ static struct gdsc video_cc_mvs0_gdsc = {
.pwrsts = PWRSTS_OFF_ON,
.parent = &video_cc_mvs0c_gdsc.pd,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE | HW_CTRL_TRIGGER,
+ .required_pds = video_cc_sm8650_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(video_cc_sm8650_block_pd_indices),
};
static struct gdsc video_cc_mvs1c_gdsc = {
@@ -481,6 +543,8 @@ static struct gdsc video_cc_mvs1c_gdsc = {
},
.pwrsts = PWRSTS_OFF_ON,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
+ .required_pds = video_cc_sm8650_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(video_cc_sm8650_block_pd_indices),
};
static struct gdsc video_cc_mvs1_gdsc = {
@@ -494,6 +558,8 @@ static struct gdsc video_cc_mvs1_gdsc = {
.pwrsts = PWRSTS_OFF_ON,
.parent = &video_cc_mvs1c_gdsc.pd,
.flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE | HW_CTRL_TRIGGER,
+ .required_pds = video_cc_sm8650_block_pd_indices,
+ .num_required_pds = ARRAY_SIZE(video_cc_sm8650_block_pd_indices),
};
static struct clk_regmap *video_cc_sm8550_clocks[] = {
@@ -512,6 +578,26 @@ static struct clk_regmap *video_cc_sm8550_clocks[] = {
[VIDEO_CC_XO_CLK_SRC] = NULL,
};
+static struct clk_regmap *video_cc_sm8650_clocks[] = {
+ [VIDEO_CC_MVS0_CLK] = &video_cc_mvs0_clk.clkr,
+ [VIDEO_CC_MVS0_CLK_SRC] = &video_cc_mvs0_clk_src.clkr,
+ [VIDEO_CC_MVS0_DIV_CLK_SRC] = &video_cc_mvs0_div_clk_src.clkr,
+ [VIDEO_CC_MVS0_SHIFT_CLK] = &video_cc_mvs0_shift_clk.clkr,
+ [VIDEO_CC_MVS0C_CLK] = &video_cc_mvs0c_clk.clkr,
+ [VIDEO_CC_MVS0C_DIV2_DIV_CLK_SRC] = &video_cc_mvs0c_div2_div_clk_src.clkr,
+ [VIDEO_CC_MVS0C_SHIFT_CLK] = &video_cc_mvs0c_shift_clk.clkr,
+ [VIDEO_CC_MVS1_CLK] = &video_cc_mvs1_clk.clkr,
+ [VIDEO_CC_MVS1_CLK_SRC] = &video_cc_mvs1_clk_src.clkr,
+ [VIDEO_CC_MVS1_DIV_CLK_SRC] = &video_cc_mvs1_div_clk_src.clkr,
+ [VIDEO_CC_MVS1_SHIFT_CLK] = &video_cc_mvs1_shift_clk.clkr,
+ [VIDEO_CC_MVS1C_CLK] = &video_cc_mvs1c_clk.clkr,
+ [VIDEO_CC_MVS1C_DIV2_DIV_CLK_SRC] = &video_cc_mvs1c_div2_div_clk_src.clkr,
+ [VIDEO_CC_MVS1C_SHIFT_CLK] = &video_cc_mvs1c_shift_clk.clkr,
+ [VIDEO_CC_PLL0] = &video_cc_pll0.clkr,
+ [VIDEO_CC_PLL1] = &video_cc_pll1.clkr,
+ [VIDEO_CC_XO_CLK_SRC] = &video_cc_xo_clk_src.clkr,
+};
+
static struct gdsc *video_cc_sm8550_gdscs[] = {
[VIDEO_CC_MVS0C_GDSC] = &video_cc_mvs0c_gdsc,
[VIDEO_CC_MVS0_GDSC] = &video_cc_mvs0_gdsc,
@@ -562,6 +648,13 @@ static struct qcom_cc_driver_data video_cc_sm8550_driver_data = {
.num_clk_cbcrs = ARRAY_SIZE(video_cc_sm8550_critical_cbcrs),
};
+static const struct qcom_cc_driver_data video_cc_sm8650_driver_data = {
+ .alpha_plls = video_cc_sm8550_plls,
+ .num_alpha_plls = ARRAY_SIZE(video_cc_sm8550_plls),
+ .clk_cbcrs = video_cc_sm8650_critical_cbcrs,
+ .num_clk_cbcrs = ARRAY_SIZE(video_cc_sm8650_critical_cbcrs),
+};
+
static const struct qcom_cc_desc video_cc_sm8550_desc = {
.config = &video_cc_sm8550_regmap_config,
.clks = video_cc_sm8550_clocks,
@@ -574,16 +667,39 @@ static const struct qcom_cc_desc video_cc_sm8550_desc = {
.driver_data = &video_cc_sm8550_driver_data,
};
+static const unsigned int video_cc_sm8650_access_pd_indices[] = {
+ DT_PMDOMAIN_MMCX,
+ DT_PMDOMAIN_MXC,
+};
+
+static const struct qcom_cc_desc video_cc_sm8650_desc = {
+ .config = &video_cc_sm8550_regmap_config,
+ .clks = video_cc_sm8650_clocks,
+ .num_clks = ARRAY_SIZE(video_cc_sm8650_clocks),
+ .resets = video_cc_sm8550_resets,
+ .num_resets = ARRAY_SIZE(video_cc_sm8550_resets),
+ .gdscs = video_cc_sm8550_gdscs,
+ .num_gdscs = ARRAY_SIZE(video_cc_sm8550_gdscs),
+ .use_rpm = true,
+ .pd_indices = video_cc_sm8650_access_pd_indices,
+ .num_pd_indices = ARRAY_SIZE(video_cc_sm8650_access_pd_indices),
+ .num_dvfs_rail_voters = 2,
+ .driver_data = &video_cc_sm8650_driver_data,
+};
+
static const struct of_device_id video_cc_sm8550_match_table[] = {
- { .compatible = "qcom,sm8550-videocc" },
- { .compatible = "qcom,sm8650-videocc" },
- { .compatible = "qcom,x1e80100-videocc" },
+ { .compatible = "qcom,sm8550-videocc", .data = &video_cc_sm8550_desc },
+ { .compatible = "qcom,sm8650-videocc", .data = &video_cc_sm8650_desc },
+ { .compatible = "qcom,x1e80100-videocc", .data = &video_cc_sm8550_desc },
{ }
};
MODULE_DEVICE_TABLE(of, video_cc_sm8550_match_table);
static int video_cc_sm8550_probe(struct platform_device *pdev)
{
+ const struct qcom_cc_desc *desc = of_device_get_match_data(&pdev->dev);
+ bool sm8650 = desc == &video_cc_sm8650_desc;
+
if (of_device_is_compatible(pdev->dev.of_node, "qcom,x1e80100-videocc")) {
video_cc_pll0_config.l = 0x1e;
video_cc_pll0_config.alpha = 0x0000;
@@ -593,25 +709,16 @@ static int video_cc_sm8550_probe(struct platform_device *pdev)
video_cc_mvs1_clk_src.freq_tbl = ftbl_video_cc_mvs1_clk_src_x1e80100;
}
- if (of_device_is_compatible(pdev->dev.of_node, "qcom,sm8650-videocc")) {
+ if (sm8650) {
video_cc_pll0_config.l = 0x1e;
video_cc_pll0_config.alpha = 0xa000;
video_cc_pll1_config.l = 0x2b;
video_cc_pll1_config.alpha = 0xc000;
video_cc_mvs0_clk_src.freq_tbl = ftbl_video_cc_mvs0_clk_src_sm8650;
video_cc_mvs1_clk_src.freq_tbl = ftbl_video_cc_mvs1_clk_src_sm8650;
- video_cc_sm8550_clocks[VIDEO_CC_MVS0_SHIFT_CLK] = &video_cc_mvs0_shift_clk.clkr;
- video_cc_sm8550_clocks[VIDEO_CC_MVS0C_SHIFT_CLK] = &video_cc_mvs0c_shift_clk.clkr;
- video_cc_sm8550_clocks[VIDEO_CC_MVS1_SHIFT_CLK] = &video_cc_mvs1_shift_clk.clkr;
- video_cc_sm8550_clocks[VIDEO_CC_MVS1C_SHIFT_CLK] = &video_cc_mvs1c_shift_clk.clkr;
- video_cc_sm8550_clocks[VIDEO_CC_XO_CLK_SRC] = &video_cc_xo_clk_src.clkr;
-
- video_cc_sm8550_driver_data.clk_cbcrs = video_cc_sm8650_critical_cbcrs;
- video_cc_sm8550_driver_data.num_clk_cbcrs =
- ARRAY_SIZE(video_cc_sm8650_critical_cbcrs);
}
- return qcom_cc_probe(pdev, &video_cc_sm8550_desc);
+ return qcom_cc_probe(pdev, desc);
}
static struct platform_driver video_cc_sm8550_driver = {
diff --git a/include/dt-bindings/clock/qcom,sm8650-gcc.h b/include/dt-bindings/clock/qcom,sm8650-gcc.h
index 0c543ba46079..bbf33fba2c75 100644
--- a/include/dt-bindings/clock/qcom,sm8650-gcc.h
+++ b/include/dt-bindings/clock/qcom,sm8650-gcc.h
@@ -202,6 +202,8 @@
#define GCC_GPLL3_AO 191
#define GCC_GPLL4_AO 192
#define GCC_GPLL6_AO 193
+#define GCC_CPUSS_UBWCP_CLK 194
+#define GCC_CPUSS_UBWCP_CLK_SRC 195
/* GCC resets */
#define GCC_CAMERA_BCR 0
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH DNM RFC 15/16] arm64: dts: qcom: sm8650: Drop required-opps
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (13 preceding siblings ...)
2026-10-14 12:19 ` [PATCH DNM RFC 14/16] clk: qcom: Enable clk-dvfs on SM8650 Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:39 ` sashiko-bot
2026-10-14 14:23 ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH DNM RFC 16/16] pmdomain: qcom: rpmhpd: Stop forwarding MMCX pstate to CX Konrad Dybcio
2026-10-14 14:37 ` [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Dmitry Baryshkov
16 siblings, 2 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Now that the clock controllers take care of their power requirements,
drop the required-opps that were only there because they previously
hadn't.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
DFS clocks (for QUPs) are left out for now, as they need some more
code changes.
---
arch/arm64/boot/dts/qcom/sm8650.dtsi | 46 ------------------------------------
1 file changed, 46 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/sm8650.dtsi b/arch/arm64/boot/dts/qcom/sm8650.dtsi
index 0847cfb35954..47d7be98128e 100644
--- a/arch/arm64/boot/dts/qcom/sm8650.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8650.dtsi
@@ -3717,7 +3717,6 @@ pcie0_opp_table: opp-table {
/* 2.5 GT/s x1 */
opp-2500000-1 {
opp-hz = /bits/ 64 <2500000>;
- required-opps = <&rpmhpd_opp_low_svs>;
opp-peak-kBps = <250000 1>;
opp-level = <1>;
};
@@ -3725,7 +3724,6 @@ opp-2500000-1 {
/* 2.5 GT/s x2 */
opp-5000000-1 {
opp-hz = /bits/ 64 <5000000>;
- required-opps = <&rpmhpd_opp_low_svs>;
opp-peak-kBps = <500000 1>;
opp-level = <1>;
};
@@ -3733,7 +3731,6 @@ opp-5000000-1 {
/* 5 GT/s x1 */
opp-5000000-2 {
opp-hz = /bits/ 64 <5000000>;
- required-opps = <&rpmhpd_opp_low_svs>;
opp-peak-kBps = <500000 1>;
opp-level = <2>;
};
@@ -3741,7 +3738,6 @@ opp-5000000-2 {
/* 5 GT/s x2 */
opp-10000000-2 {
opp-hz = /bits/ 64 <10000000>;
- required-opps = <&rpmhpd_opp_low_svs>;
opp-peak-kBps = <1000000 1>;
opp-level = <2>;
};
@@ -3749,7 +3745,6 @@ opp-10000000-2 {
/* 8 GT/s x1 */
opp-8000000-3 {
opp-hz = /bits/ 64 <8000000>;
- required-opps = <&rpmhpd_opp_nom>;
opp-peak-kBps = <984500 1>;
opp-level = <3>;
};
@@ -3757,7 +3752,6 @@ opp-8000000-3 {
/* 8 GT/s x2 */
opp-16000000-3 {
opp-hz = /bits/ 64 <16000000>;
- required-opps = <&rpmhpd_opp_nom>;
opp-peak-kBps = <1969000 1>;
opp-level = <3>;
};
@@ -3909,7 +3903,6 @@ pcie1_opp_table: opp-table {
/* 2.5 GT/s x1 */
opp-2500000-1 {
opp-hz = /bits/ 64 <2500000>;
- required-opps = <&rpmhpd_opp_low_svs>;
opp-peak-kBps = <250000 1>;
opp-level = <1>;
};
@@ -3917,7 +3910,6 @@ opp-2500000-1 {
/* 2.5 GT/s x2 */
opp-5000000-1 {
opp-hz = /bits/ 64 <5000000>;
- required-opps = <&rpmhpd_opp_low_svs>;
opp-peak-kBps = <500000 1>;
opp-level = <1>;
};
@@ -3925,7 +3917,6 @@ opp-5000000-1 {
/* 5 GT/s x1 */
opp-5000000-2 {
opp-hz = /bits/ 64 <5000000>;
- required-opps = <&rpmhpd_opp_low_svs>;
opp-peak-kBps = <500000 1>;
opp-level = <2>;
};
@@ -3933,7 +3924,6 @@ opp-5000000-2 {
/* 5 GT/s x2 */
opp-10000000-2 {
opp-hz = /bits/ 64 <10000000>;
- required-opps = <&rpmhpd_opp_low_svs>;
opp-peak-kBps = <1000000 1>;
opp-level = <2>;
};
@@ -3941,7 +3931,6 @@ opp-10000000-2 {
/* 8 GT/s x1 */
opp-8000000-3 {
opp-hz = /bits/ 64 <8000000>;
- required-opps = <&rpmhpd_opp_nom>;
opp-peak-kBps = <984500 1>;
opp-level = <3>;
};
@@ -3949,7 +3938,6 @@ opp-8000000-3 {
/* 8 GT/s x2 */
opp-16000000-3 {
opp-hz = /bits/ 64 <16000000>;
- required-opps = <&rpmhpd_opp_nom>;
opp-peak-kBps = <1969000 1>;
opp-level = <3>;
};
@@ -3957,7 +3945,6 @@ opp-16000000-3 {
/* 16 GT/s x1 */
opp-16000000-4 {
opp-hz = /bits/ 64 <16000000>;
- required-opps = <&rpmhpd_opp_nom>;
opp-peak-kBps = <1969000 1>;
opp-level = <4>;
};
@@ -3965,7 +3952,6 @@ opp-16000000-4 {
/* 16 GT/s x2 */
opp-32000000-4 {
opp-hz = /bits/ 64 <32000000>;
- required-opps = <&rpmhpd_opp_nom>;
opp-peak-kBps = <3938000 1>;
opp-level = <4>;
};
@@ -4073,7 +4059,6 @@ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
"cpu-ufs";
power-domains = <&gcc UFS_PHY_GDSC>;
- required-opps = <&rpmhpd_opp_nom>;
operating-points-v2 = <&ufs_opp_table>;
@@ -4103,7 +4088,6 @@ opp-100000000 {
/bits/ 64 <0>,
/bits/ 64 <0>,
/bits/ 64 <0>;
- required-opps = <&rpmhpd_opp_low_svs>;
};
opp-201500000 {
@@ -4115,7 +4099,6 @@ opp-201500000 {
/bits/ 64 <0>,
/bits/ 64 <0>,
/bits/ 64 <0>;
- required-opps = <&rpmhpd_opp_svs>;
};
opp-403000000 {
@@ -4127,7 +4110,6 @@ opp-403000000 {
/bits/ 64 <0>,
/bits/ 64 <0>,
/bits/ 64 <0>;
- required-opps = <&rpmhpd_opp_nom>;
};
};
};
@@ -5057,22 +5039,18 @@ sdhc2_opp_table: opp-table {
opp-19200000 {
opp-hz = /bits/ 64 <19200000>;
- required-opps = <&rpmhpd_opp_min_svs>;
};
opp-50000000 {
opp-hz = /bits/ 64 <50000000>;
- required-opps = <&rpmhpd_opp_low_svs>;
};
opp-100000000 {
opp-hz = /bits/ 64 <100000000>;
- required-opps = <&rpmhpd_opp_svs>;
};
opp-202000000 {
opp-hz = /bits/ 64 <202000000>;
- required-opps = <&rpmhpd_opp_svs_l1>;
};
};
};
@@ -5200,7 +5178,6 @@ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
iommus = <&apps_smmu 0x40 0>;
power-domains = <&gcc USB30_PRIM_GDSC>;
- required-opps = <&rpmhpd_opp_nom>;
snps,hird-threshold = /bits/ 8 <0x0>;
snps,usb2-gadget-lpm-disable;
@@ -5296,38 +5273,26 @@ iris_opp_table: opp-table {
opp-196000000 {
opp-hz = /bits/ 64 <196000000>;
- required-opps = <&rpmhpd_opp_low_svs_d1>,
- <&rpmhpd_opp_low_svs_d1>;
};
opp-300000000 {
opp-hz = /bits/ 64 <300000000>;
- required-opps = <&rpmhpd_opp_svs>,
- <&rpmhpd_opp_low_svs>;
};
opp-380000000 {
opp-hz = /bits/ 64 <380000000>;
- required-opps = <&rpmhpd_opp_svs_l1>,
- <&rpmhpd_opp_svs>;
};
opp-435000000 {
opp-hz = /bits/ 64 <435000000>;
- required-opps = <&rpmhpd_opp_svs_l1>,
- <&rpmhpd_opp_svs_l1>;
};
opp-480000000 {
opp-hz = /bits/ 64 <480000000>;
- required-opps = <&rpmhpd_opp_svs_l1>,
- <&rpmhpd_opp_nom>;
};
opp-533333334 {
opp-hz = /bits/ 64 <533333334>;
- required-opps = <&rpmhpd_opp_svs_l1>,
- <&rpmhpd_opp_turbo>;
};
};
};
@@ -5745,22 +5710,18 @@ mdp_opp_table: opp-table {
opp-200000000 {
opp-hz = /bits/ 64 <200000000>;
- required-opps = <&rpmhpd_opp_low_svs>;
};
opp-325000000 {
opp-hz = /bits/ 64 <325000000>;
- required-opps = <&rpmhpd_opp_svs>;
};
opp-375000000 {
opp-hz = /bits/ 64 <375000000>;
- required-opps = <&rpmhpd_opp_svs_l1>;
};
opp-514000000 {
opp-hz = /bits/ 64 <514000000>;
- required-opps = <&rpmhpd_opp_nom>;
};
};
};
@@ -5827,17 +5788,14 @@ mdss_dsi_opp_table: opp-table {
opp-187500000 {
opp-hz = /bits/ 64 <187500000>;
- required-opps = <&rpmhpd_opp_low_svs>;
};
opp-300000000 {
opp-hz = /bits/ 64 <300000000>;
- required-opps = <&rpmhpd_opp_svs>;
};
opp-358000000 {
opp-hz = /bits/ 64 <358000000>;
- required-opps = <&rpmhpd_opp_svs_l1>;
};
};
};
@@ -5991,22 +5949,18 @@ dp_opp_table: opp-table {
opp-162000000 {
opp-hz = /bits/ 64 <162000000>;
- required-opps = <&rpmhpd_opp_low_svs_d1>;
};
opp-270000000 {
opp-hz = /bits/ 64 <270000000>;
- required-opps = <&rpmhpd_opp_low_svs>;
};
opp-540000000 {
opp-hz = /bits/ 64 <540000000>;
- required-opps = <&rpmhpd_opp_svs_l1>;
};
opp-810000000 {
opp-hz = /bits/ 64 <810000000>;
- required-opps = <&rpmhpd_opp_nom>;
};
};
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH DNM RFC 16/16] pmdomain: qcom: rpmhpd: Stop forwarding MMCX pstate to CX
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (14 preceding siblings ...)
2026-10-14 12:19 ` [PATCH DNM RFC 15/16] arm64: dts: qcom: sm8650: Drop required-opps Konrad Dybcio
@ 2026-10-14 12:19 ` Konrad Dybcio
2026-10-14 12:37 ` sashiko-bot
2026-10-14 14:37 ` [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Dmitry Baryshkov
16 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 12:19 UTC (permalink / raw)
To: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das
Cc: linux-clk, linux-kernel, linux-pm, driver-core, linux-arm-msm,
devicetree, Konrad Dybcio
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
MMCX needs CX to be on, but it does not need CX's performance state to
follow its own. The GenPD parent link currently forwards that state, so
raising MMCX can also raise CX when no CX consumer needs the higher level.
Keep the parent relationship for power ordering, but suppress its
performance-state propagation. Apply the same policy to MMCX_AO and
CX_AO, leaving other RPMhPD parent relationships unchanged.
Assisted-by: LLM
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
Because of the current PM topologies depending on this behavior, this
may not be merged without filling out clock DVFS data for all platforms
first. Additionally, the MX coupling is more nuanced before SM8450.
---
drivers/pmdomain/qcom/rpmhpd.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c
index fa8fed5bd3e5..4d9463b4d5df 100644
--- a/drivers/pmdomain/qcom/rpmhpd.c
+++ b/drivers/pmdomain/qcom/rpmhpd.c
@@ -37,6 +37,7 @@
* @level_count: Number of levels supported by the power domain. max
* being 16 (0 - 15)
* @enabled: true if the power domain is enabled
+ * @parent_flags: configuration flags for the GenPD parent relationship
* @res_name: Resource name used for cmd-db lookup
* @addr: Resource address as looped up using resource name from
* cmd-db
@@ -54,6 +55,7 @@ struct rpmhpd {
u32 level[RPMH_ARC_MAX_LEVELS];
size_t level_count;
bool enabled;
+ unsigned int parent_flags;
const char *res_name;
u32 addr;
bool state_synced;
@@ -154,6 +156,7 @@ static struct rpmhpd mmcx_w_cx_parent = {
.pd = { .name = "mmcx", },
.peer = &mmcx_ao_w_cx_parent,
.parent = &cx.pd,
+ .parent_flags = GENPD_SUBDOMAIN_NO_PERF_STATE_PROPAGATION,
.res_name = "mmcx.lvl",
};
@@ -162,6 +165,7 @@ static struct rpmhpd mmcx_ao_w_cx_parent = {
.active_only = true,
.peer = &mmcx_w_cx_parent,
.parent = &cx_ao.pd,
+ .parent_flags = GENPD_SUBDOMAIN_NO_PERF_STATE_PROPAGATION,
.res_name = "mmcx.lvl",
};
@@ -1223,9 +1227,13 @@ static int rpmhpd_probe(struct platform_device *pdev)
for (i = 0; i < num_pds; i++) {
if (!rpmhpds[i])
continue;
- if (rpmhpds[i]->parent)
- pm_genpd_add_subdomain(rpmhpds[i]->parent,
- &rpmhpds[i]->pd);
+ if (rpmhpds[i]->parent) {
+ ret = pm_genpd_add_subdomain_with_flags(rpmhpds[i]->parent,
+ &rpmhpds[i]->pd,
+ rpmhpds[i]->parent_flags);
+ if (ret)
+ return ret;
+ }
}
return of_genpd_add_provider_onecell(pdev->dev.of_node, data);
--
2.56.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional
2026-10-14 12:19 ` [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional Konrad Dybcio
@ 2026-10-14 12:30 ` sashiko-bot
2026-10-14 14:17 ` Dmitry Baryshkov
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:30 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] clk: qcom: missing PM domain cleanup on gdsc_register() error paths leads to UAF
--
commit c8bac551fa3005340251f161ce7de48e8d323366
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
clk: qcom: gdsc: Make GDSC parenting optional
Allows callers of gdsc_register() to suppress parents inferred from the
controller device and its attached domain list.
> diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c
> index b99d70149af34..2d0803e1eb3b1 100644
> --- a/drivers/clk/qcom/gdsc.c
> +++ b/drivers/clk/qcom/gdsc.c
[ ... ]
> @@ -654,13 +655,17 @@ int gdsc_register(struct gdsc_desc *desc,
>
> /* Add subdomains */
> for (i = 0; i < num; i++) {
> + ret = 0;
> +
> if (!scs[i])
> continue;
> +
> if (scs[i]->parent)
> ret = pm_genpd_add_subdomain(scs[i]->parent, &scs[i]->pd);
> - else if (!IS_ERR_OR_NULL(dev->pm_domain))
> + 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);
> - else if (desc->pd_list)
> + else if (!desc->no_external_parent && desc->pd_list)
> ret = gdsc_add_subdomain_list(desc->pd_list, &scs[i]->pd);
>
> if (ret)
[Severity: High]
Does this error path correctly clean up all initialized PM domains?
If pm_genpd_add_subdomain() fails, the code jumps to err_pm_subdomain_remove:
if (ret)
goto err_pm_subdomain_remove;
...
err_pm_subdomain_remove:
gdsc_pm_subdomain_remove(desc, i);
return ret;
This correctly rolls back the subdomains, but doesn't seem to call
pm_genpd_remove() for the generic PM domains that were successfully
initialized earlier via gdsc_init() -> pm_genpd_init().
Similarly, earlier in gdsc_register(), if gdsc_init() fails, the function
returns immediately:
for (i = 0; i < num; i++) {
if (!scs[i])
continue;
...
ret = gdsc_init(scs[i]);
if (ret)
return ret;
}
And later, if of_genpd_add_provider_onecell() fails:
return of_genpd_add_provider_onecell(dev->of_node, data);
Both of these return paths exit without cleaning up previously initialized
domains.
Since these domains are statically added to the global gpd_list but tied
to devm-allocated structures, could failing the probe (e.g., via
EPROBE_DEFER) leave dangling pointers in the core PM domain list and cause
a use-after-free when traversing gpd_list later?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=11
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 12/16] clk: qcom: gdsc: Link required power domains
2026-10-14 12:19 ` [PATCH RFC 12/16] clk: qcom: gdsc: Link required power domains Konrad Dybcio
@ 2026-10-14 12:32 ` sashiko-bot
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:32 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] clk: qcom: gdsc: Unbalanced hardware state on GenPD unlink leaves GDSCs powered without required rails
- [Low] clk: qcom: gdsc: Redundant wait_for_off logic for non-votable domains
--
commit a3b8e242fc8ba2eae7b378662d9af778b3739733
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
clk: qcom: gdsc: Link required power domains
This commit models enable-only requirements for GDSCs as GenPD subdomain
links without propagating the GDSC performance state to the rail. It allows
GDSCs to specify only the power-domain endpoints they require while powered.
> diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c
> index 2d0803e1eb3b1..73054c600c29a 100644
> --- a/drivers/clk/qcom/gdsc.c
> +++ b/drivers/clk/qcom/gdsc.c
[ ... ]
> @@ -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);
[Severity: Low]
This isn't a bug, but is setting wait_for_off here redundant for non-votable
domains?
Looking at gdsc_toggle_logic(), the wait parameter appears to only affect
the execution path when sc->flags & VOTABLE is true:
static int gdsc_toggle_logic(...) {
...
/* If disabling votable gdscs, don't poll on status */
if ((sc->flags & VOTABLE) && status == GDSC_OFF && !wait) {
udelay(TIMEOUT_US);
return 0;
}
...
ret = gdsc_poll_status(sc, status);
}
Since gdsc_toggle_logic() falls through to gdsc_poll_status() for all
non-votable domains regardless of the wait parameter, does overriding it
here have any effect?
[ ... ]
> @@ -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);
> + }
[Severity: High]
Does this error path leave initialized GDSCs physically powered ON while
their required supply rails are powered OFF?
During initialization, gdsc_get_rail_voters() powers ON the required rails
via pm_runtime_resume_and_get(). Then gdsc_init() turns ON the GDSC
hardware, and gdsc_add_required_subdomains() links the rails as parents,
transferring the power vote to GenPD. Finally, gdsc_put_rail_voters()
drops the explicit pm_runtime vote.
If we take this err_remove_domains path, gdsc_remove_required_subdomains()
removes the GenPD links. This drops the GenPD power vote, causing the
required rails to physically power OFF.
However, the loop then calls pm_genpd_remove() without physically powering
OFF the GDSCs. Will this violate hardware power sequencing constraints by
leaving the GDSCs powered ON without their required rails?
>
> return ret;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=12
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions
2026-10-14 12:19 ` [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions Konrad Dybcio
@ 2026-10-14 12:34 ` sashiko-bot
2026-10-14 13:35 ` Dmitry Baryshkov
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:34 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] opp: permanent transition failure due to strict clock rate bounds check
- [High] opp: permanent fast-path breakage via clk_get_rate mismatch
--
commit 45860eb920070f15eb727913a22f9b1eb403210a
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
OPP: Retry failed default single-clock transitions
A failed OPP transition need not leave the clock at the last successful
OPP's frequency. If hardware changed before the error, the default
single-clock path can subsequently skip a necessary retry because its
cached OPP matches the request. It can also choose voltage and dependency
ordering relative to the wrong operating point.
> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
> index 4ccce7b6afcb1..340911d2971e7 100644
> --- a/drivers/opp/core.c
> +++ b/drivers/opp/core.c
> @@ -1073,11 +1073,11 @@ _opp_config_clk_single(struct device *dev, struct opp_table *opp_table,
> }
>
> ret = clk_set_rate(opp_table->clk, freq);
> + opp_table->current_rate_single_clk = clk_get_rate(opp_table->clk);
> if (ret) {
> + opp_table->config_dirty_single_clk = true;
[Severity: High]
Does this unconditionally replace the requested and rounded frequency with the
hardware readback?
If the clock driver's .recalc_rate (which drives clk_get_rate) differs even
slightly from .determine_rate (which drives clk_round_rate), the readback
will not strictly equal the requested freq.
> dev_err(dev, "%s: failed to set clock rate: %d\n", __func__,
> ret);
> - } else {
> - opp_table->current_rate_single_clk = freq;
> }
>
> return ret;
[ ... ]
> @@ -1297,20 +1319,40 @@ static int _disable_opp_table(struct device *dev, struct opp_table *opp_table)
> static int _set_opp(struct device *dev, struct opp_table *opp_table,
> struct dev_pm_opp *opp, void *clk_data, bool forced)
> {
> + bool default_single = _opp_uses_default_single_clk(opp_table);
> + struct dev_pm_opp *actual_opp __free(put_opp) = NULL;
> struct dev_pm_opp *old_opp;
> int scaling_down, ret;
>
> - if (unlikely(!opp))
> - return _disable_opp_table(dev, opp_table);
> + if (unlikely(!opp)) {
[ ... ]
> /* Find the currently set OPP if we don't know already */
> if (unlikely(!opp_table->current_opp))
> _find_current_opp(dev, opp_table);
>
> old_opp = opp_table->current_opp;
> + if (default_single && opp_table->config_dirty_single_clk) {
> + actual_opp = _find_current_opp_strict(opp_table);
> + if (IS_ERR(actual_opp))
> + return PTR_ERR(actual_opp);
[Severity: High]
Does propagating this error directly cause a permanent transition failure?
If _find_current_opp_strict queries a gated clock (returning 0) or
a frequency that exceeds the highest registered OPP, it returns
ERR_PTR(-ERANGE).
Unlike the original _find_current_opp, this strict lookup lacks a fallback
and the -ERANGE error propagates here directly. This short-circuits the
hardware transition without clearing config_dirty_single_clk, permanently
sticking the OPP table in a dirty state where all future calls to
dev_pm_opp_set_rate instantly fail.
> + old_opp = actual_opp;
> + }
>
> /* Return early if nothing to do */
> - if (!forced && old_opp == opp && opp_table->enabled) {
> + if (!forced && !opp_table->config_dirty_single_clk &&
> + old_opp == opp && opp_table->enabled) {
> dev_dbg_ratelimited(dev, "%s: OPPs are same, nothing to do\n", __func__);
> return 0;
> }
[ ... ]
> @@ -1460,7 +1513,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
> * value of the frequency. In such a case, do not abort but
> * configure the hardware to the desired frequency forcefully.
> */
> - forced = opp_table->current_rate_single_clk != freq;
> + forced = opp_table->config_dirty_single_clk ||
> + opp_table->current_rate_single_clk != freq;
[Severity: High]
If clk_get_rate diverges from freq as mentioned in _opp_config_clk_single,
will this fast-path condition evaluate to true permanently?
This fast-path relies on strict equality between the cached rate and the
newly rounded freq. If they differ, it bypasses the early return in _set_opp
and forces full hardware reconfigurations on every clock change.
> }
>
> return _set_opp(dev, opp_table, opp, &freq, forced);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=3
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 10/16] clk: qcom: clk-alpha-pll: Vote for the PLL calibration frequency
2026-10-14 12:19 ` [PATCH RFC 10/16] clk: qcom: clk-alpha-pll: Vote for the PLL calibration frequency Konrad Dybcio
@ 2026-10-14 12:34 ` sashiko-bot
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:34 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] clk: qcom: dvfs: qcom_clk_dvfs_prepare_rate() unwinds to 0 on failure, dropping votes
- [High] clk: qcom: alpha-pll: disable failure during calibration drops DVFS votes while PLL runs
--
commit 27077f9ef026973ca3f8e723a45854a856554ed2
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Subject: clk: qcom: clk-alpha-pll: Vote for the PLL calibration frequency
This commit updates Lucid EVO PLL initialization to safely handle higher
calibration frequencies. It temporarily raises the DVFS vote to the
calibration rate during preparation and restores the nominal rate once
complete.
> diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c
> index 5dc6b1adc9298..f6e73e2fbe768 100644
> --- a/drivers/clk/qcom/clk-alpha-pll.c
> +++ b/drivers/clk/qcom/clk-alpha-pll.c
[ ... ]
> @@ -2628,12 +2648,9 @@ static int _alpha_pll_lucid_evo_prepare(struct clk_hw *hw, bool reset)
> return -EINVAL;
>
> ret = alpha_pll_lucid_evo_enable(hw);
> - if (ret)
> - return ret;
> -
> - _alpha_pll_lucid_evo_disable(hw, reset);
> + cleanup_ret = alpha_pll_lucid_evo_disable_checked(hw, reset);
>
> - return 0;
> + return ret ?: cleanup_ret;
> }
[Severity: High]
Is it safe to propagate the disable error here if it results in dropping the
DVFS votes while the PLL is still running?
If alpha_pll_lucid_evo_enable() succeeds but alpha_pll_lucid_evo_disable_checked()
fails (e.g., due to a regmap write failure), the error is propagated up to
alpha_pll_lucid_evo_dvfs_prepare().
The error handler there unconditionally calls qcom_clk_dvfs_unprepare() via the
err_unprepare label, resetting all DVFS votes to 0. Because the hardware disable
failed, the PLL is left physically enabled and actively running without the
necessary voltage votes.
[ ... ]
> @@ -2641,21 +2658,69 @@ static void alpha_pll_lucid_evo_disable(struct clk_hw *hw)
> _alpha_pll_lucid_evo_disable(hw, false);
> }
>
> -static int alpha_pll_lucid_evo_prepare(struct clk_hw *hw)
> +/*
> + * Calibration runs at CAL_L * parent_rate, which may exceed the operating rate
> + * covered by the normal DVFS vote. Raise the vote temporarily to keep the
> + * hardware in-spec during that step.
> + */
> +static int alpha_pll_lucid_evo_dvfs_prepare(struct clk_hw *hw, bool reset)
> {
[ ... ]
> + ret = _alpha_pll_lucid_evo_prepare(hw, reset);
> + if (ret)
> + goto err_unprepare;
> +
> + if (cal_rate > rate && qcom_clk_dvfs_prepare_rate(hw, rate))
> + pr_warn("%s: failed to drop calibration DVFS vote\n",
> + clk_hw_get_name(hw));
>
> + return 0;
[Severity: High]
Can ignoring the error from qcom_clk_dvfs_prepare_rate() here leave the unwound
power domains without voltage votes?
When down-voting from cal_rate to rate, if qcom_clk_dvfs_prepare_rate() fails
midway, its unwind path resets the successfully updated domains to 0:
drivers/clk/qcom/clk-dvfs.c:qcom_clk_dvfs_prepare_rate() {
...
unwind:
while (i--) {
unsigned int pd_index = clock->data->pd_indices[i];
qcom_clk_dvfs_replace_vote(&clock->rail_voters[pd_index],
&clock->votes[i], 0);
}
return ret;
}
Since the warning is caught but the error is deliberately ignored (returning 0),
the clock prepares and operates at the target rate but with missing DVFS votes
(vote = 0) on the unwound domains, which risks hardware brownouts or system
crashes.
> +
> +err_unprepare:
> + qcom_clk_dvfs_unprepare(hw);
> return ret;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=10
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 01/16] clk: Propagate rate programming failures
2026-10-14 12:19 ` [PATCH RFC 01/16] clk: Propagate rate programming failures Konrad Dybcio
@ 2026-10-14 12:35 ` sashiko-bot
2026-10-14 13:25 ` Dmitry Baryshkov
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:35 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] clk: Undervoltage on partial rate change failure due to ABORT_RATE_CHANGE
--
commit 5ce89a94f0fcfcda3a335ff0e5689b0f216c3fd4
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
clk: Propagate rate programming failures
This patch stops the clock rate-change walk at the first failure and
propagates the error back to the caller. It attempts to keep the topology,
temporary references, and terminal notifications consistent with the outcome.
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index 4d2cc86bfbeb6..95669775f8cd1 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
[ ... ]
> @@ -2420,13 +2448,20 @@ static struct clk_core *clk_propagate_rate_change(struct clk_core *core,
> unsigned long event)
> {
> struct clk_core *child, *tmp_clk, *fail_clk = NULL;
> + unsigned long old_rate = core->rate;
> + unsigned long new_rate = core->new_rate;
> int ret = NOTIFY_DONE;
>
> - if (core->rate == core->new_rate)
> + if (event != PRE_RATE_CHANGE)
> + old_rate = core->old_rate;
> + if (event == POST_RATE_CHANGE)
> + new_rate = core->rate;
> +
> + if (old_rate == new_rate)
> return NULL;
>
> if (core->notifier_count) {
> - ret = __clk_notify(core, event, core->rate, core->new_rate);
> + ret = __clk_notify(core, event, old_rate, new_rate);
[Severity: High]
When ABORT_RATE_CHANGE is sent, this uses the original old_rate and new_rate
parameters. Will this cause consumers to revert their changes even if the
hardware rate already changed?
[ ... ]
> @@ -2474,53 +2509,83 @@ static void clk_change_rate(struct clk_core *core)
> if (!skip_set_rate && core->ops->set_rate)
> - core->ops->set_rate(core->hw, core->new_rate, best_parent_rate);
> + ret = core->ops->set_rate(core->hw, core->new_rate,
> + best_parent_rate);
[ ... ]
> - clk_change_rate(child);
> + ret = clk_change_rate(child);
> + if (ret)
> + goto runtime_put;
[Severity: High]
If the hardware rate is successfully changed for the parent here, but a
subsequent child failure aborts the walk, does the parent remain at the
new rate while the software aborts?
[ ... ]
> @@ -2613,7 +2686,14 @@ static int clk_core_set_rate_nolock(struct clk_core *core,
> }
>
> /* change the rates */
> - clk_change_rate(top);
> + ret = clk_change_rate(top);
> + if (ret) {
> + __clk_recalc_rates(top, true, 0);
> + clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
> + goto err;
> + }
[Severity: High]
If clk_change_rate() fails downstream after the parent hardware rate was
already increased, does broadcasting ABORT_RATE_CHANGE here result in
undervoltage?
For example, consumers like cpufreq or existing device notifiers might
receive the abort and revert their PRE_RATE_CHANGE voltage increases,
leaving the hardware running at the high new rate with the low old voltage.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation
2026-10-14 12:19 ` [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation Konrad Dybcio
@ 2026-10-14 12:35 ` sashiko-bot
2026-10-14 14:36 ` Dmitry Baryshkov
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:35 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] clk: qcom: Hardware undervolted on actual_rate lookup failure
- [High] clk: qcom: TOCTOU race in qcom_clk_dvfs_cleanup() leads to UAF
- [High] clk: qcom: permanent DVFS domain bricking on failed performance state
- [Low] cleanup: mixed goto-based error handling and __free() in qcom_clk_dvfs_resource_acquire()
--
commit f24ba5feffbb1fedbb7c842c732865ac4c8e3e69
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
clk: qcom: Add clock-owned GenPD DVFS aggregation
Clocks on Qualcomm SoCs need a minimum power rail performance state to
operate at a given frequency. We currently leave this to clock
consumers, so a driver has to know which power domains supply both the
clock and its parents. This ties knowledge of the clock tree to
individual consumers and doesn't protect other users that prepare or
change the same clock without making those power requests.
> diff --git a/drivers/clk/qcom/clk-dvfs.c b/drivers/clk/qcom/clk-dvfs.c
> new file mode 100644
> index 0000000000000..e6490c4dab949
> --- /dev/null
> +++ b/drivers/clk/qcom/clk-dvfs.c
> @@ -0,0 +1,574 @@
[ ... ]
> +static unsigned int
> +qcom_clk_dvfs_aggregate(struct qcom_clk_dvfs_rail_voter *rail_voter)
> +{
> + struct qcom_clk_dvfs_vote *vote;
> + unsigned int level = 0;
> +
> + list_for_each_entry(vote, &rail_voter->votes, node)
> + level = max(level, vote->level);
> +
> + return max(level, rail_voter->safety_floor_level);
> +}
[ ... ]
> +static int qcom_clk_dvfs_apply(struct qcom_clk_dvfs_rail_voter *rail_voter,
> + unsigned int level)
> +{
> + int ret;
> +
> + ret = qcom_clk_dvfs_activate(rail_voter);
> + if (ret)
> + return ret;
> + ret = dev_pm_genpd_set_performance_state(rail_voter->dev, level);
> + if (ret) {
> + rail_voter->safety_floor_level =
> + max(rail_voter->safety_floor_level,
> + max(rail_voter->last_successful_vote_level, level));
> + return ret;
[Severity: High]
Does this code permanently limit the DVFS domain after a failed update?
If dev_pm_genpd_set_performance_state() fails in qcom_clk_dvfs_apply(),
the failed level is incorporated into safety_floor_level.
qcom_clk_dvfs_aggregate() will then always return at least this level for any
future updates, forcing the domain to retry a known-bad state. This could
permanently break frequency scaling for the domain and leak a PM runtime
reference because the domain can never reach level 0 to suspend.
> + }
> +
> + rail_voter->last_successful_vote_level = level;
> + rail_voter->safety_floor_level = 0;
> +
> + return 0;
> +}
[ ... ]
> +int qcom_clk_dvfs_resource_acquire(struct clk_hw *hw,
> + const struct clk_resource_transition *transition,
> + void **cookie)
> +{
> + struct qcom_clk_dvfs_clock *clock = to_clk_regmap(hw)->dvfs;
> + struct qcom_clk_dvfs_transition *dvfs_transition __free(kfree) = NULL;
> + struct qcom_clk_dvfs_vote *votes __free(kfree) = NULL;
> + unsigned int level;
> + size_t i;
> + int ret;
> +
> + if (!clock) {
> + *cookie = NULL;
> + return 0;
> + }
> +
> + dvfs_transition = kzalloc_obj(*dvfs_transition);
> + if (!dvfs_transition)
> + return -ENOMEM;
> +
> + votes = kcalloc(clock->data->num_pd_indices, sizeof(*votes),
> + GFP_KERNEL);
> + if (!votes)
> + return -ENOMEM;
> +
> + dvfs_transition->votes = votes;
> + dvfs_transition->num_votes = clock->data->num_pd_indices;
> + ret = qcom_clk_dvfs_find_level(clock->data, transition->new_rate, &level);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < dvfs_transition->num_votes; i++) {
> + unsigned int pd_index = clock->data->pd_indices[i];
> +
> + ret = qcom_clk_dvfs_add_vote(&clock->rail_voters[pd_index],
> + &dvfs_transition->votes[i], level);
> + if (ret)
> + goto unwind;
> + }
> +
> + dvfs_transition->votes = no_free_ptr(votes);
> + *cookie = no_free_ptr(dvfs_transition);
> +
> + return 0;
> +
> +unwind:
> + while (i--) {
[Severity: Low]
This isn't a bug, but does this function mix goto error handling with __free()?
It appears qcom_clk_dvfs_resource_acquire() uses __free(kfree) for
dvfs_transition and votes while also using a "goto unwind;" path for error
cleanup. The cleanup subsystem guide recommends converting either all resources
to scope-based cleanup or none, rather than mixing both paradigms in the same
function.
> + unsigned int pd_index = clock->data->pd_indices[i];
> +
> + qcom_clk_dvfs_remove_vote(&clock->rail_voters[pd_index],
> + &dvfs_transition->votes[i]);
> + }
> +
> + return ret;
> +}
> +
> +int qcom_clk_dvfs_resource_release(struct clk_hw *hw,
> + const struct clk_resource_transition *transition,
> + void *cookie,
> + enum clk_resource_change_result result)
> +{
> + struct qcom_clk_dvfs_transition *dvfs_transition = cookie;
> + struct qcom_clk_dvfs_clock *clock = to_clk_regmap(hw)->dvfs;
> + int first_error = 0;
> + unsigned int level;
> + int ret;
> +
> + if (!clock)
> + return 0;
> + if (result == CLK_RESOURCE_COMPLETE) {
> + ret = qcom_clk_dvfs_find_level(clock->data, transition->actual_rate,
> + &level);
> + if (ret)
> + first_error = ret;
> + }
> +
> + for (size_t i = 0; i < dvfs_transition->num_votes; i++) {
> + unsigned int pd_index = clock->data->pd_indices[i];
> +
> + if (!clock->votes[i].level)
> + continue;
> +
> + if (result == CLK_RESOURCE_COMPLETE) {
> + ret = first_error;
[Severity: High]
Can this undervolt the hardware on an actual_rate lookup failure?
If qcom_clk_dvfs_find_level() fails to look up the level for the new rate
(setting first_error to a non-zero value), this loop sets ret = first_error.
Because ret is non-zero, it skips qcom_clk_dvfs_replace_vote(), leaving the
permanent vote at the old, lower level.
Then, the temporary high transition vote is removed. Because the hardware
is already running at the higher actual_rate, the resulting lower aggregate
voltage vote could cause an immediate undervolt.
> + } else if (result == CLK_RESOURCE_HW_CHANGED) {
> + level = max(clock->votes[i].level,
> + dvfs_transition->votes[i].level);
> + ret = 0;
> + } else {
> + continue;
> + }
> +
> + if (!ret) {
> + ret = qcom_clk_dvfs_replace_vote(&clock->rail_voters[pd_index],
> + &clock->votes[i], level);
> + }
> + if (ret && !first_error)
> + first_error = ret;
> + }
[ ... ]
> +static void qcom_clk_dvfs_cleanup(void *data)
> +{
> + struct qcom_clk_dvfs *dvfs = data;
> +
> + for (size_t i = 0; i < dvfs->num_clocks; i++)
> + dvfs->clocks[i].rclk->dvfs = NULL;
[Severity: High]
Can this race with consumer unprepare operations?
If a consumer holds a reference and unbinds, devres executes
qcom_clk_dvfs_cleanup(), which nullifies rclk->dvfs without taking the CCF
prepare_lock.
A concurrent clk_unprepare() could read the non-NULL pointer, and if
qcom_clk_dvfs_cleanup() then proceeds to destroy rail_voter->lock and free
memory, qcom_clk_dvfs_unprepare() would cause a use-after-free when it tries
to lock the destroyed mutex in qcom_clk_dvfs_replace_vote().
> +
> + for (size_t i = dvfs->num_rail_voters; i-- > 0;) {
> + struct qcom_clk_dvfs_rail_voter *rail_voter = &dvfs->rail_voters[i];
> +
> + if (rail_voter->active) {
> + dev_pm_genpd_set_performance_state(rail_voter->dev, 0);
> + WRITE_ONCE(rail_voter->must_contribute, false);
> + pm_runtime_put_sync_suspend(rail_voter->dev);
> + }
> + dev_pm_genpd_remove_notifier(rail_voter->dev);
> + device_link_del(rail_voter->link);
> + dev_pm_domain_detach(rail_voter->dev, true);
> + mutex_destroy(&rail_voter->lock);
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=7
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 14/16] clk: qcom: Enable clk-dvfs on SM8650
2026-10-14 12:19 ` [PATCH DNM RFC 14/16] clk: qcom: Enable clk-dvfs on SM8650 Konrad Dybcio
@ 2026-10-14 12:36 ` sashiko-bot
2026-10-14 14:40 ` Dmitry Baryshkov
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:36 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] clk: qcom: Unconditional GDSC required_pds breaks probe on older platforms
--
commit 5e8694e8d0d0380215d061d6b9b0bbabdb94e291
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
One line subject: clk: qcom: Enable clk-dvfs on SM8650
This commit enables clock-dvfs infrastructure for SM8650 by filling in the
power requirements for SM8650 clocks. It also adds SM8650 support to the
shared dispcc, videocc, and camcc drivers.
> diff --git a/drivers/clk/qcom/dispcc-sm8550.c b/drivers/clk/qcom/dispcc-sm8550.c
> --- a/drivers/clk/qcom/dispcc-sm8550.c
> +++ b/drivers/clk/qcom/dispcc-sm8550.c
[ ... ]
> @@ -1629,6 +1778,8 @@ static struct gdsc mdss_gdsc = {
> },
> .pwrsts = PWRSTS_OFF_ON,
> .flags = POLL_CFG_GDSCR | HW_CTRL | RETAIN_FF_ENABLE,
> + .required_pds = disp_cc_sm8650_block_pd_indices,
> + .num_required_pds = ARRAY_SIZE(disp_cc_sm8650_block_pd_indices),
> };
[Severity: High]
Does this unconditional assignment of SM8650-specific power domains to the
mdss_gdsc structure break the probe on older platforms like SM8550, SAR2130P,
and X1E80100?
Since the dispcc-sm8550.c driver is shared between these platforms, the older
platform descriptors (like disp_cc_sm8550_desc) do not set
num_dvfs_rail_voters. As a result, qcom_clk_dvfs_register() won't be called
during qcom_cc_really_probe().
When gdsc_register() attempts to register these shared GDSCs on the older
platforms, it evaluates num_required_pds > 0 and calls
gdsc_get_rail_voters(). This will then unconditionally delegate to
qcom_clk_dvfs_get_rail_voter(), which returns -EINVAL because the DVFS
subsystem was never initialized for them.
Could this cause the entire clock controller probe to fail on those older
platforms?
> diff --git a/drivers/clk/qcom/videocc-sm8550.c b/drivers/clk/qcom/videocc-sm8550.c
> --- a/drivers/clk/qcom/videocc-sm8550.c
> +++ b/drivers/clk/qcom/videocc-sm8550.c
[ ... ]
> @@ -456,6 +514,8 @@ static struct gdsc video_cc_mvs0c_gdsc = {
> },
> .pwrsts = PWRSTS_OFF_ON,
> .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
> + .required_pds = video_cc_sm8650_block_pd_indices,
> + .num_required_pds = ARRAY_SIZE(video_cc_sm8650_block_pd_indices),
> };
[Severity: High]
Could the same probe breakage occur here for the video_cc_mvs0c_gdsc
structure in the shared videocc-sm8550.c driver?
The SM8650-specific requirements are added unconditionally to
video_cc_mvs0c_gdsc, which is used by SM8550 and X1E80100 as well. This
appears to introduce the same risk of failing the clock controller probe on
those platforms.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=14
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 16/16] pmdomain: qcom: rpmhpd: Stop forwarding MMCX pstate to CX
2026-10-14 12:19 ` [PATCH DNM RFC 16/16] pmdomain: qcom: rpmhpd: Stop forwarding MMCX pstate to CX Konrad Dybcio
@ 2026-10-14 12:37 ` sashiko-bot
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:37 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] pmdomain: qcom: missing error cleanup in rpmhpd_probe() corrupts gpd_list
- [High] pmdomain: qcom: missing clk-dvfs data causes system hangs
--
commit e0f5d54b21f4fa98424f55031a683613420b6a75
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
pmdomain: qcom: rpmhpd: Stop forwarding MMCX pstate to CX
This commit modifies the MMCX and MMCX_AO power domains to suppress
performance-state propagation to their CX and CX_AO parent domains. The
parent relationship is kept solely for power ordering.
> diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c
> index fa8fed5bd3e58..4d9463b4d5df0 100644
> --- a/drivers/pmdomain/qcom/rpmhpd.c
> +++ b/drivers/pmdomain/qcom/rpmhpd.c
> @@ -154,6 +156,7 @@ static struct rpmhpd mmcx_w_cx_parent = {
> .pd = { .name = "mmcx", },
> .peer = &mmcx_ao_w_cx_parent,
> .parent = &cx.pd,
> + .parent_flags = GENPD_SUBDOMAIN_NO_PERF_STATE_PROPAGATION,
[Severity: High]
The commit message explicitly warns that this patch must not be merged without
filling out clock DVFS data for all platforms first. It appears platforms
such as SM8550, SM8450, and SM8250 do not yet receive the necessary DVFS data
updates.
Will this cause the CX power domain to lack required performance state votes,
leading to system hangs and hardware lockups when multimedia blocks operate on
these older platforms?
[ ... ]
> @@ -1223,9 +1227,13 @@ static int rpmhpd_probe(struct platform_device *pdev)
> for (i = 0; i < num_pds; i++) {
> if (!rpmhpds[i])
> continue;
> - if (rpmhpds[i]->parent)
> - pm_genpd_add_subdomain(rpmhpds[i]->parent,
> - &rpmhpds[i]->pd);
> + if (rpmhpds[i]->parent) {
> + ret = pm_genpd_add_subdomain_with_flags(rpmhpds[i]->parent,
> + &rpmhpds[i]->pd,
> + rpmhpds[i]->parent_flags);
> + if (ret)
> + return ret;
[Severity: High]
Does returning directly here leave statically allocated PM domains globally
registered in the genpd core?
By the time this loop executes, all PM domains have already been initialized
and added to the global gpd_list by pm_genpd_init() in the preceding loop.
Because the PM domain structures are statically allocated file globals,
returning an error without cleanup leaves their gpd_list_node members in the
global gpd_list.
If probe is later retried, for example via deferred probe, pm_genpd_init()
will be invoked again on these same static structures, potentially leading to
list corruption.
Would an error path that calls pm_genpd_remove() for initialized domains and
pm_genpd_remove_subdomain() for previously added subdomains be needed here?
> + }
> }
>
> return of_genpd_add_provider_onecell(pdev->dev.of_node, data);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=16
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 08/16] clk: qcom: dvfs: Add debugfs
2026-10-14 12:19 ` [PATCH RFC 08/16] clk: qcom: dvfs: Add debugfs Konrad Dybcio
@ 2026-10-14 12:37 ` sashiko-bot
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:37 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] clk: qcom: dvfs: UAF in debugfs due to missing file cleanup on unbind
- [Medium] cleanup: goto-based error handling mixed with __free() cleanup
--
commit 3f9e96971d4a45f08b00bd913e791d8aeb02fc13
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
clk: qcom: dvfs: Add debugfs
It's really useful to be able to analyze the current set of votes and
what holds them. Add debugfs to expose that information.
> diff --git a/drivers/clk/qcom/clk-dvfs.c b/drivers/clk/qcom/clk-dvfs.c
> index e6490c4dab949..4af78eb0c78dd 100644
> --- a/drivers/clk/qcom/clk-dvfs.c
> +++ b/drivers/clk/qcom/clk-dvfs.c
[ ... ]
> @@ -293,6 +333,8 @@ int qcom_clk_dvfs_resource_acquire(struct clk_hw *hw,
> for (i = 0; i < dvfs_transition->num_votes; i++) {
> unsigned int pd_index = clock->data->pd_indices[i];
>
> + dvfs_transition->votes[i].clock_name = clk_hw_get_name(hw);
> + dvfs_transition->votes[i].transition = true;
> ret = qcom_clk_dvfs_add_vote(&clock->rail_voters[pd_index],
> &dvfs_transition->votes[i], level);
> if (ret)
[Severity: Medium]
Is it safe to mix goto-based error handling with scope-based cleanups in this
function?
qcom_clk_dvfs_resource_acquire() declares `dvfs_transition` and `votes` with
`__free(kfree)`, but uses a `goto unwind;` label to perform a manual rollback
loop on error.
The kernel cleanup subsystem guidelines strictly prohibit mixing goto-based
error handling with scope-based `__free()` or `guard()` cleanups within the
same function. This pattern creates confusing ownership semantics and increases
the risk of double-free or resource leak bugs during future refactoring.
Should all resources in the function use either scope-based cleanup or manual
goto-based cleanup, rather than both?
[ ... ]
> @@ -565,7 +609,20 @@ int qcom_clk_dvfs_register(struct device *dev,
>
> dev_set_drvdata(dev, dvfs);
>
> - return devm_add_action_or_reset(dev, qcom_clk_dvfs_cleanup, dvfs);
> + ret = devm_add_action_or_reset(dev, qcom_clk_dvfs_cleanup, dvfs);
> + if (ret)
> + return ret;
> +
> + scoped_guard(mutex, &qcom_clk_dvfs_debugfs_lock) {
> + if (!qcom_clk_dvfs_debugfs_root)
> + qcom_clk_dvfs_debugfs_root = debugfs_create_dir("qcom_clk_dvfs", NULL);
> +
> + debugfs_create_devm_seqfile(dvfs->dev, dev_name(dvfs->dev),
> + qcom_clk_dvfs_debugfs_root,
> + qcom_clk_dvfs_debugfs_show);
> + }
> +
> + return 0;
[Severity: High]
Can this cause a Use-After-Free during device unbind?
The driver creates a debugfs file using debugfs_create_devm_seqfile() and
places it under the global static qcom_clk_dvfs_debugfs_root directory, which
is never removed.
debugfs_create_devm_seqfile() allocates a private entry structure via
devm_kzalloc(), but does not register a devm action to remove the debugfs file
itself. It relies on the removal of the parent directory.
Upon device unbind, devres frees the private entry structure, but the debugfs
file remains accessible because its parent directory is not removed and the
driver's cleanup path (qcom_clk_dvfs_cleanup) does not manually remove it.
A subsequent read of the leaked debugfs file by a user will dereference the
freed entry structure in debugfs_devm_entry_open(), causing a Use-After-Free.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=8
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 15/16] arm64: dts: qcom: sm8650: Drop required-opps
2026-10-14 12:19 ` [PATCH DNM RFC 15/16] arm64: dts: qcom: sm8650: Drop required-opps Konrad Dybcio
@ 2026-10-14 12:39 ` sashiko-bot
2026-10-14 14:23 ` Dmitry Baryshkov
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:39 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] dts: pcie: power domain starvation for Gen3/Gen4
--
commit 467d81619c5b5922cc360aaaf647dd4b6a900f09
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
arm64: dts: qcom: sm8650: Drop required-opps
This commit drops the required-opps properties from various OPP tables in
the SM8650 device tree. It assumes that clock controllers now take care of
their power requirements.
> diff --git a/arch/arm64/boot/dts/qcom/sm8650.dtsi b/arch/arm64/boot/dts/qcom/sm8650.dtsi
> index 0847cfb35954e..47d7be98128e2 100644
> --- a/arch/arm64/boot/dts/qcom/sm8650.dtsi
> +++ b/arch/arm64/boot/dts/qcom/sm8650.dtsi
[ ... ]
> @@ -3749,7 +3745,6 @@ opp-10000000-2 {
> /* 8 GT/s x1 */
> opp-8000000-3 {
> opp-hz = /bits/ 64 <8000000>;
> - required-opps = <&rpmhpd_opp_nom>;
[Severity: High]
Does dropping this required-opps break the static PM domain vote for PCIe
Gen3 and Gen4?
The qcom,pcie driver uses pseudo-frequencies (like 8000000 for Gen3) in the
OPP table to represent link speeds rather than actual clock frequencies. When
the OPP framework processes this, it attempts to set the rate of the first
clock listed for the device (GCC_PCIE_0_AUX_CLK or GCC_PCIE_1_AUX_CLK).
However, in drivers/clk/qcom/gcc-sm8650.c, gcc_pcie_0_aux_clk_src_dvfs is
fixed at 19.2MHz and only maps to LOW_SVS:
drivers/clk/qcom/gcc-sm8650.c:
static const struct qcom_clk_dvfs_data gcc_pcie_0_aux_clk_src_dvfs = {
.pd_indices = (const unsigned int[]) { DT_PMDOMAIN_CX, DT_PMDOMAIN_MX },
.num_pd_indices = 2,
.rates = (const struct qcom_clk_dvfs_rate[]) {
{ 19200000, RPMH_REGULATOR_LEVEL_LOW_SVS },
},
.num_rates = 1,
};
Without the direct power domain vote, the PCIe controller will run at LOW_SVS
voltage instead of the NOM voltage required by Gen3 and higher. Could this
lead to power domain starvation, causing link training failures or bus hangs
when accessing an under-powered PHY on boards booting with a PCIe Gen3 or
Gen4 device?
> opp-peak-kBps = <984500 1>;
> opp-level = <3>;
> };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=15
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 02/16] clk: Acquire resources for clock transitions
2026-10-14 12:19 ` [PATCH RFC 02/16] clk: Acquire resources for clock transitions Konrad Dybcio
@ 2026-10-14 12:40 ` sashiko-bot
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:40 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] clk: ABORT_RATE_CHANGE sent after successful hardware rate transition
- [High] clk: WARN_ON_ONCE(1) in clk_nodrv_ops during clk_unregister
- [High] clk: Missing PM runtime management in resource acquire/release callbacks
- [Medium] clk: permanent resource_fault on clocks lacking acquire ops
--
commit dab9de10ac4ce05ffd78cfafa44b173a82f23842
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
clk: Acquire resources for clock transitions
This commit updates the Common Clock Framework to acquire resources for the
affected tree and parent paths before executing clock transitions. It defers
reconciliation until the tree walk completes to ensure safe fault recovery.
It also modifies parent-change failure paths to retain references explicitly.
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index 95669775f8cd1..46e01d8254e5b 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
[ ... ]
> @@ -2444,52 +2517,270 @@ static struct clk_core *clk_calc_new_rates(struct clk_core *core,
[ ... ]
> +static int
> +clk_resource_release(struct clk_resource_transaction *tx,
> + enum clk_resource_change_result result)
> +{
> + struct clk_resource_acquisition *acquisition, *tmp;
> + int first_error = 0;
> + int ret;
> +
> + list_for_each_entry_reverse(acquisition, &tx->acquisitions, node) {
> + struct clk_core *core = acquisition->core;
> +
> + if (result == CLK_RESOURCE_HW_CHANGED)
> + core->resource_fault = true;
> +
> + acquisition->transition.actual_rate = core->rate;
> + acquisition->transition.actual_parent = core->parent ?
> + core->parent->hw : NULL;
> +
> + ret = core->ops->resource_release(core->hw,
> + &acquisition->transition,
> + acquisition->cookie,
> + result);
[Severity: High]
Does this code call resource_release() without PM runtime management?
clk_resource_acquire_tree() and clk_resource_release() invoke hardware ops on
descendant and ancestor clocks without invoking clk_pm_runtime_get() on them
first.
For descendants, clk_pm_runtime_get(child) is only called later inside
clk_change_rate(), and it is dropped as soon as clk_change_rate() unwinds.
Therefore, resource_release() will execute on children that no longer hold
an active PM runtime reference, potentially leading to unclocked/unpowered
register accesses and system faults.
> + if (ret) {
> + core->resource_fault = true;
> + if (!first_error)
> + first_error = ret;
> + } else if (result == CLK_RESOURCE_COMPLETE) {
> + core->resource_fault = false;
> + }
> + }
[Severity: Medium]
Will clocks lacking resource_acquire() ops get permanently tainted with
resource_fault = true?
When a clock transition fails resource acquisition in a parent path, the
explicitly targeted clock is unconditionally marked with
core->resource_fault = true in clk_core_set_rate_nolock() (e.g. at the
acquire_failed label).
If that target clock doesn't implement ops->resource_acquire(), it isn't
added to the transaction's tx.acquisitions list. Later, if a transition on a
different node succeeds, it iterates over tx.acquisitions to clear the fault
flag. Since the initially failed clock isn't in that list and isn't the new
target, its resource_fault flag stays true permanently, which bypasses
short-circuiting optimizations for it and all its ancestors.
> +
> + list_for_each_entry_safe(acquisition, tmp, &tx->acquisitions, node) {
> + list_del(&acquisition->node);
> + kfree(acquisition);
> + }
> +
> + return first_error;
> +}
[ ... ]
> @@ -2649,8 +2951,14 @@ static unsigned long clk_core_req_round_rate_nolock(struct clk_core *core,
> static int clk_core_set_rate_nolock(struct clk_core *core,
> unsigned long req_rate)
> {
[ ... ]
> /* change the rates */
> - ret = clk_change_rate(top);
> + ret = clk_change_rate(top, resource_managed);
> if (ret) {
> __clk_recalc_rates(top, true, 0);
> - clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
> + clk_finish_rate_notifications(&tx, ABORT_RATE_CHANGE);
> + release_ret = clk_resource_release(&tx,
> + CLK_RESOURCE_HW_CHANGED);
> + if (release_ret)
> + ret = release_ret;
> goto err;
> }
>
> - clk_propagate_rate_change(top, POST_RATE_CHANGE);
> + ret = clk_resource_release(&tx, CLK_RESOURCE_COMPLETE);
> + if (ret) {
> + core->resource_fault = true;
> + clk_finish_rate_notifications(&tx, ABORT_RATE_CHANGE);
> + goto err;
> + }
[Severity: Critical]
Is it intentional to send ABORT_RATE_CHANGE after the hardware rate has already
been successfully changed?
If clk_change_rate() succeeds, the clock is running at the new rate. If
clk_resource_release() subsequently fails, the code branches to send
ABORT_RATE_CHANGE.
Consumers (like voltage regulators or displays) receive this abort notification
and revert their hardware state to match the old clock rate (e.g., dropping
operating voltage). Because the clock hardware actually remains running at the
new, potentially higher rate, this creates a severe inconsistency where a
component operates at a high frequency with an inadequate voltage.
This identical pattern also appears in clk_core_set_parent_nolock().
> + core->resource_fault = false;
> +
> + clk_finish_rate_notifications(&tx, POST_RATE_CHANGE);
>
> core->req_rate = req_rate;
> err:
> + clk_clear_new_child(top);
> clk_pm_runtime_put(core);
>
> return ret;
> +
> +acquire_failed:
> + core->resource_fault = true;
> + release_ret = clk_resource_release(&tx,
> + CLK_RESOURCE_NO_HW_CHANGE);
> + if (release_ret)
> + ret = release_ret;
> + goto err;
[ ... ]
> @@ -4776,14 +5221,25 @@ void clk_unregister(struct clk *clk)
>
> /* Reparent all children to the orphan list. */
> hlist_for_each_entry_safe(child, t, &clk->core->children,
> - child_node)
> + child_node) {
> + if (child->parent_fault) {
> + pr_warn("%s: releasing unresolved parent fault for %s\n",
> + __func__, child->name);
> + clk_release_parent_fault(child);
> + }
> clk_core_set_parent_nolock(child, NULL);
> + }
> }
>
> clk_core_evict_parent_cache(clk->core);
>
> hash_del(&clk->core->hashtable_node);
> hlist_del_init(&clk->core->child_node);
> + if (clk->core->parent_fault) {
> + pr_warn("%s: releasing unresolved parent fault for %s\n",
> + __func__, clk->core->name);
> + clk_release_parent_fault(clk->core);
> + }
[Severity: High]
Does this code trigger a WARN_ON_ONCE(1) due to invoking
clk_release_parent_fault() after operations have been neutralized?
Earlier in clk_unregister(), clk->core->ops is replaced with &clk_nodrv_ops to
neutralize hardware accesses.
When clk_release_parent_fault() is subsequently called here (or in the
child reparenting loop above), it cleans up retained migration references by
calling clk_core_disable_unprepare() on the faulted old or new parent, which
is the clock currently being unregistered.
Because the operations pointer has already been replaced, this call routes to
clk_nodrv_disable_unprepare(), which triggers an unconditional WARN_ON_ONCE(1).
It looks like the fault cleanup needs to occur before the ops pointer is
substituted.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails
2026-10-14 12:19 ` [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails Konrad Dybcio
@ 2026-10-14 12:41 ` sashiko-bot
2026-10-14 14:19 ` Dmitry Baryshkov
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:41 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] dt-bindings: clock: qcom: multiple power-domains exceed schema limits
--
commit 0272a7914d28ecdc80c317abd5c3a4cf467f9ae8
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
arm64: dts: qcom: sm8650: Describe clock controller power rails
The SM8650 clock-controller nodes omit rails needed by their clocks and
PLLs. This commit describes the required power rails for these controllers
to prevent probe failures.
> diff --git a/arch/arm64/boot/dts/qcom/sm8650.dtsi b/arch/arm64/boot/dts/qcom/sm8650.dtsi
> index 86fbb0fd6472d..0847cfb35954e 100644
> --- a/arch/arm64/boot/dts/qcom/sm8650.dtsi
> +++ b/arch/arm64/boot/dts/qcom/sm8650.dtsi
> @@ -2017,8 +2017,9 @@ gcc: clock-controller@100000 {
> <&ufs_mem_phy 1>,
> <&ufs_mem_phy 2>,
> <&usb_dp_qmpphy QMP_USB43DP_USB3_PIPE_CLK>;
> -
> - power-domains = <&rpmhpd RPMHPD_CX>;
> + power-domains = <&rpmhpd RPMHPD_CX>,
> + <&rpmhpd RPMHPD_CX_AO>,
> + <&rpmhpd RPMHPD_MX>;
[Severity: Low]
Does this exceed the dt-binding limits? For instance, qcom,sm8650-gcc.yaml
restricts power-domains to 1 item, but this provides 3. This will cause
make dtbs_check to fail unless the yaml binding is updated.
> @@ -4364,6 +4365,10 @@ gpucc: clock-controller@3d90000 {
> clocks = <&bi_tcxo_div2>,
> <&gcc GCC_GPU_GPLL0_CLK_SRC>,
> <&gcc GCC_GPU_GPLL0_DIV_CLK_SRC>;
> + power-domains = <&rpmhpd RPMHPD_MX>,
> + <&rpmhpd RPMHPD_CX>,
> + <&rpmhpd RPMHPD_MXC>,
> + <&rpmhpd RPMHPD_GFX>;
[Severity: Low]
Will this trigger a validation failure for gpucc? The qcom,sm8450-gpucc.yaml
binding limits power-domains to exactly 2 items, but 4 are being provided
here.
[ ... ]
> @@ -6049,8 +6055,8 @@ dispcc: clock-controller@af00000 {
> <0>, /* dp3 */
> <0>;
>
> - power-domains = <&rpmhpd RPMHPD_MMCX>;
> - required-opps = <&rpmhpd_opp_low_svs>;
> + power-domains = <&rpmhpd RPMHPD_MMCX>,
> + <&rpmhpd RPMHPD_MX>;
[Severity: Low]
Is this compatible with the qcom,sm8550-dispcc.yaml schema? The schema
has a maxItems constraint of 1 for power-domains, but 2 are provided here,
which will result in a dtbs_check error.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=13
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 05/16] pmdomain: core: Support subdomains without pstate propagation
2026-10-14 12:19 ` [PATCH RFC 05/16] pmdomain: core: Support subdomains without pstate propagation Konrad Dybcio
@ 2026-10-14 12:41 ` sashiko-bot
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-10-14 12:41 UTC (permalink / raw)
To: Konrad Dybcio; +Cc: linux-clk, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] pmdomain: missing initial pstate propagation in genpd_add_subdomain()
--
commit 19c8f09b635c74bbdaf740eac823398c603b6fb4
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
pmdomain: core: Support subdomains without pstate propagation
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.
> diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
> index 76ff0d9a229c3..80ea70930cc63 100644
> --- a/drivers/pmdomain/core.c
> +++ b/drivers/pmdomain/core.c
[ ... ]
> @@ -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);
> if (genpd_status_on_unlocked(subdomain))
> genpd_sd_counter_inc(genpd);
[Severity: High]
When the child domain is linked to the parent in genpd_add_subdomain(), its
power state is synced via genpd_sd_counter_inc(). However, if a consumer
device has already probed and set a performance state on the child domain
before it is linked (meaning it already has an active performance_state > 0),
does this code miss evaluating and propagating that initial performance state
to the parent?
If this initial propagation is omitted, the parent might operate at a lower
performance state than required by its subdomains. Could this lead to hardware
malfunctions or system crashes due to under-voltage or under-clocking?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=5
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 01/16] clk: Propagate rate programming failures
2026-10-14 12:19 ` [PATCH RFC 01/16] clk: Propagate rate programming failures Konrad Dybcio
2026-10-14 12:35 ` sashiko-bot
@ 2026-10-14 13:25 ` Dmitry Baryshkov
1 sibling, 0 replies; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 13:25 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree,
Konrad Dybcio
On Wed, Oct 14, 2026 at 02:19:01PM +0200, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> The clock rate-change walk ignores runtime PM, temporary prepare and
> enable, and provider programming failures. clk_set_rate() can therefore
> report success after a provider rejected the operation, while later
> clocks in the affected tree continue to be programmed. Consumers can
> then proceed with a configuration that was never successfully applied.
>
> A failed parent switch must not leave the software topology pointing at
> the rejected source or release the old parent's references. Likewise,
> rate notifiers must not announce success before the whole walk succeeds,
> or lose their ABORT notification when a PRE notifier vetoes the change.
>
> Propagate errors and stop the walk at the first failure. Keep topology,
> temporary references, and terminal notifications consistent with the
> outcome.
>
> Assisted-by: LLM
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> drivers/clk/clk.c | 163 +++++++++++++++++++++++++++++++++++++------------
> drivers/clk/clk_test.c | 16 ++++-
> 2 files changed, 138 insertions(+), 41 deletions(-)
>
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index 4d2cc86bfbeb..95669775f8cd 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
> @@ -84,6 +84,7 @@ struct clk_parent_map {
> * clk_core->rate every time the clock is reparented, and
> * when we're doing the orphan -> !orphan transition.
> * @new_rate: New rate to be set during a rate change operation.
> + * @old_rate: Rate before the current rate change operation.
> * @new_parent: Pointer to new parent during parent change. This is also
> * used when a clk's rate is changed.
> * @new_child: Pointer to new child during reparenting. This is also
> @@ -130,6 +131,7 @@ struct clk_core {
> unsigned long rate;
> unsigned long req_rate;
> unsigned long new_rate;
> + unsigned long old_rate;
> struct clk_core *new_parent;
> struct clk_core *new_child;
> unsigned long flags;
> @@ -2163,11 +2165,14 @@ static void clk_reparent(struct clk_core *core, struct clk_core *new_parent)
> core->parent = new_parent;
> }
>
> -static struct clk_core *__clk_set_parent_before(struct clk_core *core,
> - struct clk_core *parent)
> +static int __clk_set_parent_before(struct clk_core *core,
> + struct clk_core *parent,
> + struct clk_core **old_parent)
Nit: we have an idiom for returning either a value or an error. Is there
a reason why you didn't adopt returning ERR_PTR here?
> {
> unsigned long flags;
> - struct clk_core *old_parent = core->parent;
> + int ret;
> +
> + *old_parent = core->parent;
And that's the reason for asking. A pointer argument is expected to be
in or in/out param.
>
> /*
> * 1. enable parents for CLK_OPS_PARENT_ENABLE clock
[...]
> @@ -2247,10 +2274,10 @@ static int __clk_set_parent(struct clk_core *core, struct clk_core *parent,
> trace_clk_set_parent_complete(core, parent);
>
> if (ret) {
> - flags = clk_enable_lock();
> + unsigned long flags = clk_enable_lock();
> +
> clk_reparent(core, old_parent);
> clk_enable_unlock(flags);
> -
Two unrelated changes?
> __clk_set_parent_after(core, old_parent, parent);
>
> return ret;
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions
2026-10-14 12:19 ` [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions Konrad Dybcio
2026-10-14 12:34 ` sashiko-bot
@ 2026-10-14 13:35 ` Dmitry Baryshkov
2026-10-14 16:14 ` Konrad Dybcio
1 sibling, 1 reply; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 13:35 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree,
Konrad Dybcio
On Wed, Oct 14, 2026 at 02:19:03PM +0200, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> A failed OPP transition need not leave the clock at the last successful
> OPP's frequency. If hardware changed before the error, the default
> single-clock path can subsequently skip a necessary retry because its
What is a necessary retry? Shouldn't it be: try, rollback if it fails?
> cached OPP matches the request. It can also choose voltage and dependency
> ordering relative to the wrong operating point.
>
> Treat an incomplete transition as dirty and use the observed clock rate
> to establish retry direction. Do not trust same-OPP or same-rate
> shortcuts, or allow table disable, until the state has been reconciled.
>
> Limit this to the default single-clock path: custom callbacks can have
> additional state which cannot be recovered from a clock-rate readback.
>
> Assisted-by: LLM
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> drivers/opp/core.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++-------
> drivers/opp/opp.h | 2 ++
> 2 files changed, 65 insertions(+), 9 deletions(-)
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional
2026-10-14 12:19 ` [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional Konrad Dybcio
2026-10-14 12:30 ` sashiko-bot
@ 2026-10-14 14:17 ` Dmitry Baryshkov
2026-10-14 14:20 ` Konrad Dybcio
1 sibling, 1 reply; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 14:17 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree,
Konrad Dybcio
On Wed, Oct 14, 2026 at 02:19:11PM +0200, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> Clock controller power-domain attachments currently become parents of all
> top-level GDSCs exported by that controller. This conflates resources
> needed to access or operate the controller with actual power-collapse
> hierarchy.
>
> Allow callers of gdsc_register() to suppress parents inferred from the
> controller device and its attached domain list.
I think, each GDSC has a parent (in the hardware). If we don't represent
it in practice, we likely need to fix that.
>
> Assisted-by: LLM
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> drivers/clk/qcom/gdsc.c | 13 +++++++++----
> drivers/clk/qcom/gdsc.h | 1 +
> 2 files changed, 10 insertions(+), 4 deletions(-)
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails
2026-10-14 12:19 ` [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails Konrad Dybcio
2026-10-14 12:41 ` sashiko-bot
@ 2026-10-14 14:19 ` Dmitry Baryshkov
2026-10-14 14:20 ` Konrad Dybcio
1 sibling, 1 reply; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 14:19 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree,
Konrad Dybcio
On Wed, Oct 14, 2026 at 02:19:13PM +0200, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> The SM8650 clock-controller nodes omit rails needed by their clocks and
> PLLs. Without those connections, the clock-owned DVFS implementation
> cannot attach its voters to all required domains and controller probe
> can fail before any clocks are registered.
>
> Fixes: d2350377997f ("arm64: dts: qcom: add initial SM8650 dtsi")
> Assisted-by: LLM
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> arch/arm64/boot/dts/qcom/sm8650.dtsi | 16 +++++++++++-----
> 1 file changed, 11 insertions(+), 5 deletions(-)
>
> @@ -6049,8 +6055,8 @@ dispcc: clock-controller@af00000 {
> <0>, /* dp3 */
> <0>;
>
> - power-domains = <&rpmhpd RPMHPD_MMCX>;
> - required-opps = <&rpmhpd_opp_low_svs>;
I think this is neccessary (at least on some platforms) to keep MMCX out
of retention. Even if we can drop it here, I don't think we can drop it
for older platforms.
> + power-domains = <&rpmhpd RPMHPD_MMCX>,
> + <&rpmhpd RPMHPD_MX>;
>
> #clock-cells = <1>;
> #reset-cells = <1>;
>
> --
> 2.56.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails
2026-10-14 14:19 ` Dmitry Baryshkov
@ 2026-10-14 14:20 ` Konrad Dybcio
2026-10-14 14:33 ` Dmitry Baryshkov
0 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 14:20 UTC (permalink / raw)
To: Dmitry Baryshkov, Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree
On 10/14/26 4:19 PM, Dmitry Baryshkov wrote:
> On Wed, Oct 14, 2026 at 02:19:13PM +0200, Konrad Dybcio wrote:
>> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>>
>> The SM8650 clock-controller nodes omit rails needed by their clocks and
>> PLLs. Without those connections, the clock-owned DVFS implementation
>> cannot attach its voters to all required domains and controller probe
>> can fail before any clocks are registered.
>>
>> Fixes: d2350377997f ("arm64: dts: qcom: add initial SM8650 dtsi")
>> Assisted-by: LLM
>> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>> ---
>> arch/arm64/boot/dts/qcom/sm8650.dtsi | 16 +++++++++++-----
>> 1 file changed, 11 insertions(+), 5 deletions(-)
>>
>> @@ -6049,8 +6055,8 @@ dispcc: clock-controller@af00000 {
>> <0>, /* dp3 */
>> <0>;
>>
>> - power-domains = <&rpmhpd RPMHPD_MMCX>;
>> - required-opps = <&rpmhpd_opp_low_svs>;
>
> I think this is neccessary (at least on some platforms) to keep MMCX out
> of retention. Even if we can drop it here, I don't think we can drop it
> for older platforms.
MMCX exists precisely to let you turn it off, the clock patches
retain a way to keep the clock on during the runtime-resume of
the clock controller driver
Konrad
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional
2026-10-14 14:17 ` Dmitry Baryshkov
@ 2026-10-14 14:20 ` Konrad Dybcio
2026-10-14 14:34 ` Dmitry Baryshkov
0 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 14:20 UTC (permalink / raw)
To: Dmitry Baryshkov, Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree
On 10/14/26 4:17 PM, Dmitry Baryshkov wrote:
> On Wed, Oct 14, 2026 at 02:19:11PM +0200, Konrad Dybcio wrote:
>> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>>
>> Clock controller power-domain attachments currently become parents of all
>> top-level GDSCs exported by that controller. This conflates resources
>> needed to access or operate the controller with actual power-collapse
>> hierarchy.
>>
>> Allow callers of gdsc_register() to suppress parents inferred from the
>> controller device and its attached domain list.
>
> I think, each GDSC has a parent (in the hardware). If we don't represent
> it in practice, we likely need to fix that.
The next patch allows setting a precise set of parents instead. Perhaps
this commit message could be improved.
Konrad
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 15/16] arm64: dts: qcom: sm8650: Drop required-opps
2026-10-14 12:19 ` [PATCH DNM RFC 15/16] arm64: dts: qcom: sm8650: Drop required-opps Konrad Dybcio
2026-10-14 12:39 ` sashiko-bot
@ 2026-10-14 14:23 ` Dmitry Baryshkov
2026-10-14 14:33 ` Konrad Dybcio
1 sibling, 1 reply; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 14:23 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree,
Konrad Dybcio
On Wed, Oct 14, 2026 at 02:19:15PM +0200, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> Now that the clock controllers take care of their power requirements,
> drop the required-opps that were only there because they previously
> hadn't.
>
> Assisted-by: LLM
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> DFS clocks (for QUPs) are left out for now, as they need some more
> code changes.
> ---
> arch/arm64/boot/dts/qcom/sm8650.dtsi | 46 ------------------------------------
> 1 file changed, 46 deletions(-)
>
> @@ -5057,22 +5039,18 @@ sdhc2_opp_table: opp-table {
>
> opp-19200000 {
> opp-hz = /bits/ 64 <19200000>;
> - required-opps = <&rpmhpd_opp_min_svs>;
> };
>
> opp-50000000 {
> opp-hz = /bits/ 64 <50000000>;
> - required-opps = <&rpmhpd_opp_low_svs>;
> };
>
> opp-100000000 {
> opp-hz = /bits/ 64 <100000000>;
> - required-opps = <&rpmhpd_opp_svs>;
> };
>
> opp-202000000 {
> opp-hz = /bits/ 64 <202000000>;
> - required-opps = <&rpmhpd_opp_svs_l1>;
> };
> };
> };
Can we then drop these OPP tables, which only have a HZ entry?
> @@ -5200,7 +5178,6 @@ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
> iommus = <&apps_smmu 0x40 0>;
>
> power-domains = <&gcc USB30_PRIM_GDSC>;
> - required-opps = <&rpmhpd_opp_nom>;
How do we ensure that we can access device registers? Is there a clock
(?) which now casts a vote, letting us access USB regs?
>
> snps,hird-threshold = /bits/ 8 <0x0>;
> snps,usb2-gadget-lpm-disable;
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails
2026-10-14 14:20 ` Konrad Dybcio
@ 2026-10-14 14:33 ` Dmitry Baryshkov
2026-10-14 14:36 ` Konrad Dybcio
0 siblings, 1 reply; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 14:33 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Konrad Dybcio, Stephen Boyd, Brian Masney, Jerome Brunet,
Viresh Kumar, Nishanth Menon, Rafael J. Wysocki, Pavel Machek,
Len Brown, Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Ulf Hansson, Bjorn Andersson, Abel Vesa, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das, linux-clk, linux-kernel, linux-pm, driver-core,
linux-arm-msm, devicetree
On Wed, Oct 14, 2026 at 04:20:08PM +0200, Konrad Dybcio wrote:
> On 10/14/26 4:19 PM, Dmitry Baryshkov wrote:
> > On Wed, Oct 14, 2026 at 02:19:13PM +0200, Konrad Dybcio wrote:
> >> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> >>
> >> The SM8650 clock-controller nodes omit rails needed by their clocks and
> >> PLLs. Without those connections, the clock-owned DVFS implementation
> >> cannot attach its voters to all required domains and controller probe
> >> can fail before any clocks are registered.
> >>
> >> Fixes: d2350377997f ("arm64: dts: qcom: add initial SM8650 dtsi")
> >> Assisted-by: LLM
> >> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> >> ---
> >> arch/arm64/boot/dts/qcom/sm8650.dtsi | 16 +++++++++++-----
> >> 1 file changed, 11 insertions(+), 5 deletions(-)
> >>
> >> @@ -6049,8 +6055,8 @@ dispcc: clock-controller@af00000 {
> >> <0>, /* dp3 */
> >> <0>;
> >>
> >> - power-domains = <&rpmhpd RPMHPD_MMCX>;
> >> - required-opps = <&rpmhpd_opp_low_svs>;
> >
> > I think this is neccessary (at least on some platforms) to keep MMCX out
> > of retention. Even if we can drop it here, I don't think we can drop it
> > for older platforms.
>
> MMCX exists precisely to let you turn it off, the clock patches
> retain a way to keep the clock on during the runtime-resume of
> the clock controller driver
Turning off MMCX is possible together with suspending the dispcc. If
dispcc is resumed, MMCX should be at least low_svs. On some platforms
it's handled by the cmd-db, where MMCX has a minimal value of low_svs.
On other platforms MMCX lists 'RET' as a possible value, however it's
not enough for dispcc (or other CCs) to work. In theory, *I think*, it's
exactly what 'GCC_DISP_AHB_CLK' might need to vote, but we don't
represent it.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 15/16] arm64: dts: qcom: sm8650: Drop required-opps
2026-10-14 14:23 ` Dmitry Baryshkov
@ 2026-10-14 14:33 ` Konrad Dybcio
0 siblings, 0 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 14:33 UTC (permalink / raw)
To: Dmitry Baryshkov, Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree
On 10/14/26 4:23 PM, Dmitry Baryshkov wrote:
> On Wed, Oct 14, 2026 at 02:19:15PM +0200, Konrad Dybcio wrote:
>> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>>
>> Now that the clock controllers take care of their power requirements,
>> drop the required-opps that were only there because they previously
>> hadn't.
>>
>> Assisted-by: LLM
>> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>> ---
[...]
> Can we then drop these OPP tables, which only have a HZ entry?
I suppose so. Unless they should also have interconnect values?
(depends on whether these can be inferred from the bus freq for
all protocols)
>
>> @@ -5200,7 +5178,6 @@ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
>> iommus = <&apps_smmu 0x40 0>;
>>
>> power-domains = <&gcc USB30_PRIM_GDSC>;
>> - required-opps = <&rpmhpd_opp_nom>;
>
> How do we ensure that we can access device registers? Is there a clock
> (?) which now casts a vote, letting us access USB regs?
Yes, the device driver turns on the GDSC and enables the clocks
and the clk-dvfs layer ensures all power votes are in place!
Konrad
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional
2026-10-14 14:20 ` Konrad Dybcio
@ 2026-10-14 14:34 ` Dmitry Baryshkov
2026-10-14 14:36 ` Konrad Dybcio
0 siblings, 1 reply; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 14:34 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Konrad Dybcio, Stephen Boyd, Brian Masney, Jerome Brunet,
Viresh Kumar, Nishanth Menon, Rafael J. Wysocki, Pavel Machek,
Len Brown, Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Ulf Hansson, Bjorn Andersson, Abel Vesa, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das, linux-clk, linux-kernel, linux-pm, driver-core,
linux-arm-msm, devicetree
On Wed, Oct 14, 2026 at 04:20:54PM +0200, Konrad Dybcio wrote:
> On 10/14/26 4:17 PM, Dmitry Baryshkov wrote:
> > On Wed, Oct 14, 2026 at 02:19:11PM +0200, Konrad Dybcio wrote:
> >> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> >>
> >> Clock controller power-domain attachments currently become parents of all
> >> top-level GDSCs exported by that controller. This conflates resources
> >> needed to access or operate the controller with actual power-collapse
> >> hierarchy.
> >>
> >> Allow callers of gdsc_register() to suppress parents inferred from the
> >> controller device and its attached domain list.
> >
> > I think, each GDSC has a parent (in the hardware). If we don't represent
> > it in practice, we likely need to fix that.
>
> The next patch allows setting a precise set of parents instead. Perhaps
> this commit message could be improved.
Or the commits needs to be squashed / reverted. I really don't think
that we should have a parent-less GDSC.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation
2026-10-14 12:19 ` [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation Konrad Dybcio
2026-10-14 12:35 ` sashiko-bot
@ 2026-10-14 14:36 ` Dmitry Baryshkov
2026-10-14 15:26 ` Konrad Dybcio
1 sibling, 1 reply; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 14:36 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree,
Konrad Dybcio
On Wed, Oct 14, 2026 at 02:19:07PM +0200, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> Clocks on Qualcomm SoCs need a minimum power rail performance state to
> operate at a given frequency. We currently leave this to clock
> consumers, so a driver has to know which power domains supply both the
> clock and its parents. This ties knowledge of the clock tree to
> individual consumers and doesn't protect other users that prepare or
> change the same clock without making those power requests.
Why would they prepare or change the same clock without making power
requests? If it's a legitimate user of the clock, it should have an OPP
table. If it's not, it should have a separate API to set the clock via a
legitimate user / aggregator.
I see one usecase where this would help though. If the clocks use
different power domains and the corners on one of the domains don't
match voltage on the other domain. E.g. it's not infrequent that the PLL
(powered by MX) requires performance vote change at a different
frequency than the rest of the clock tree.
> We can't simply pass each clock's request to a shared GenPD device
> either. The device has one performance-state vote, so each request
> replaces the previous one. If two clocks need different levels on the
> same rail, lowering one clock's request can leave the other clock
> running without the level it needs.
>
> Move these requirements into the clock provider and aggregate the votes
> per power domain. Keep the highest level needed by prepared clocks and
> in-progress rate or parent changes so that one clock can't withdraw
> another clock's requirement. Use separate virtual GenPD devices for
> these votes and controller register access so their power requirements
> can be managed independently.
The biggest question at this point would be the locking and lock
nesting. What is the expected order of operations? Currently it's
limited to pmdomains, but for some of the (older) platforms clock
supplies are expressd as the normal regulators rather than pmdomains.
>
> Assisted-by: LLM
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> drivers/clk/qcom/Makefile | 1 +
> drivers/clk/qcom/clk-dvfs.c | 574 ++++++++++++++++++++++++++++++++++++++++++
> drivers/clk/qcom/clk-dvfs.h | 40 +++
> drivers/clk/qcom/clk-rcg2.c | 93 ++++++-
> drivers/clk/qcom/clk-regmap.h | 6 +
> drivers/clk/qcom/common.c | 26 +-
> drivers/clk/qcom/common.h | 3 +
> 7 files changed, 725 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
> index 6affb5073f26..b1330047fd58 100644
> --- a/drivers/clk/qcom/Makefile
> +++ b/drivers/clk/qcom/Makefile
> @@ -2,6 +2,7 @@
> obj-$(CONFIG_COMMON_CLK_QCOM) += clk-qcom.o
>
> clk-qcom-y += common.o
> +clk-qcom-y += clk-dvfs.o
> clk-qcom-y += clk-regmap.o
> clk-qcom-y += clk-alpha-pll.o
> clk-qcom-y += clk-pll.o
> diff --git a/drivers/clk/qcom/clk-dvfs.c b/drivers/clk/qcom/clk-dvfs.c
> new file mode 100644
> index 000000000000..e6490c4dab94
> --- /dev/null
> +++ b/drivers/clk/qcom/clk-dvfs.c
> @@ -0,0 +1,574 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/device.h>
> +#include <linux/device/bus.h>
> +#include <linux/err.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/notifier.h>
> +#include <linux/pm_domain.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +
> +#include "clk-dvfs.h"
> +#include "clk-regmap.h"
> +
> +struct qcom_clk_dvfs_vote {
> + struct list_head node;
> + unsigned int level;
> +};
> +
> +struct qcom_clk_dvfs_rail_voter {
> + struct device *dev;
> + struct device_link *link;
> + struct notifier_block nb;
> + /* Serializes the vote list and rail voter state. */
> + struct mutex lock;
> + struct list_head votes;
> + unsigned int last_successful_vote_level;
> + /*
> + * Local conservative floor included in subsequent aggregation after a
> + * failed request.
> + */
> + unsigned int safety_floor_level;
> + bool active;
> + bool must_contribute;
> +};
> +
> +struct qcom_clk_dvfs_clock {
> + struct qcom_clk_dvfs_rail_voter *rail_voters;
> + const struct qcom_clk_dvfs_data *data;
> + struct qcom_clk_dvfs_vote *votes;
> + struct clk_regmap *rclk;
> +};
> +
> +struct qcom_clk_dvfs_transition {
> + struct qcom_clk_dvfs_vote *votes;
> + size_t num_votes;
> +};
> +
> +struct qcom_clk_dvfs {
> + struct device *dev;
> + struct qcom_clk_dvfs_rail_voter *rail_voters;
> + struct qcom_clk_dvfs_clock *clocks;
> + size_t num_rail_voters;
> + size_t num_clocks;
> +};
> +
> +static int qcom_clk_dvfs_find_level(const struct qcom_clk_dvfs_data *data,
> + unsigned long rate, unsigned int *level)
> +{
> + for (size_t i = 0; i < data->num_rates; i++) {
> + /* PLL-derived rates are not Hz-precise. */
> + if (DIV_ROUND_CLOSEST(rate, 1000) <=
> + DIV_ROUND_CLOSEST(data->rates[i].max_rate, 1000)) {
> + *level = data->rates[i].level;
> + return 0;
> + }
> + }
> +
> + return -ERANGE;
> +}
> +
> +static unsigned int
> +qcom_clk_dvfs_aggregate(struct qcom_clk_dvfs_rail_voter *rail_voter)
> +{
> + struct qcom_clk_dvfs_vote *vote;
> + unsigned int level = 0;
> +
> + list_for_each_entry(vote, &rail_voter->votes, node)
> + level = max(level, vote->level);
> +
> + return max(level, rail_voter->safety_floor_level);
I guess you want to avoid having too many voters, but would it be
possible to have one voter per 'signficant' clock and let GenPD itself
aggregate all the votes?
> +}
> +
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails
2026-10-14 14:33 ` Dmitry Baryshkov
@ 2026-10-14 14:36 ` Konrad Dybcio
2026-10-14 15:01 ` Dmitry Baryshkov
0 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 14:36 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Konrad Dybcio, Stephen Boyd, Brian Masney, Jerome Brunet,
Viresh Kumar, Nishanth Menon, Rafael J. Wysocki, Pavel Machek,
Len Brown, Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Ulf Hansson, Bjorn Andersson, Abel Vesa, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das, linux-clk, linux-kernel, linux-pm, driver-core,
linux-arm-msm, devicetree
On 10/14/26 4:33 PM, Dmitry Baryshkov wrote:
> On Wed, Oct 14, 2026 at 04:20:08PM +0200, Konrad Dybcio wrote:
>> On 10/14/26 4:19 PM, Dmitry Baryshkov wrote:
>>> On Wed, Oct 14, 2026 at 02:19:13PM +0200, Konrad Dybcio wrote:
>>>> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>>>>
>>>> The SM8650 clock-controller nodes omit rails needed by their clocks and
>>>> PLLs. Without those connections, the clock-owned DVFS implementation
>>>> cannot attach its voters to all required domains and controller probe
>>>> can fail before any clocks are registered.
>>>>
>>>> Fixes: d2350377997f ("arm64: dts: qcom: add initial SM8650 dtsi")
>>>> Assisted-by: LLM
>>>> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>>>> ---
>>>> arch/arm64/boot/dts/qcom/sm8650.dtsi | 16 +++++++++++-----
>>>> 1 file changed, 11 insertions(+), 5 deletions(-)
>>>>
>>>> @@ -6049,8 +6055,8 @@ dispcc: clock-controller@af00000 {
>>>> <0>, /* dp3 */
>>>> <0>;
>>>>
>>>> - power-domains = <&rpmhpd RPMHPD_MMCX>;
>>>> - required-opps = <&rpmhpd_opp_low_svs>;
>>>
>>> I think this is neccessary (at least on some platforms) to keep MMCX out
>>> of retention. Even if we can drop it here, I don't think we can drop it
>>> for older platforms.
>>
>> MMCX exists precisely to let you turn it off, the clock patches
>> retain a way to keep the clock on during the runtime-resume of
>> the clock controller driver
>
> Turning off MMCX is possible together with suspending the dispcc. If
> dispcc is resumed, MMCX should be at least low_svs. On some platforms
> it's handled by the cmd-db, where MMCX has a minimal value of low_svs.
> On other platforms MMCX lists 'RET' as a possible value, however it's
> not enough for dispcc (or other CCs) to work. In theory, *I think*, it's
> exactly what 'GCC_DISP_AHB_CLK' might need to vote, but we don't
> represent it.
Retention isn't a ""real"" state the OS should vote for, as the HW
downstream of the rails generally needs some flavor of LOW_SVS to
function, or at least I don't know of any such cases.
See also
7178817f1904 ("pmdomain: qcom: rpmhpd: Skip retention by default")
Konrad
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional
2026-10-14 14:34 ` Dmitry Baryshkov
@ 2026-10-14 14:36 ` Konrad Dybcio
0 siblings, 0 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 14:36 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Konrad Dybcio, Stephen Boyd, Brian Masney, Jerome Brunet,
Viresh Kumar, Nishanth Menon, Rafael J. Wysocki, Pavel Machek,
Len Brown, Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Ulf Hansson, Bjorn Andersson, Abel Vesa, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das, linux-clk, linux-kernel, linux-pm, driver-core,
linux-arm-msm, devicetree
On 10/14/26 4:34 PM, Dmitry Baryshkov wrote:
> On Wed, Oct 14, 2026 at 04:20:54PM +0200, Konrad Dybcio wrote:
>> On 10/14/26 4:17 PM, Dmitry Baryshkov wrote:
>>> On Wed, Oct 14, 2026 at 02:19:11PM +0200, Konrad Dybcio wrote:
>>>> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>>>>
>>>> Clock controller power-domain attachments currently become parents of all
>>>> top-level GDSCs exported by that controller. This conflates resources
>>>> needed to access or operate the controller with actual power-collapse
>>>> hierarchy.
>>>>
>>>> Allow callers of gdsc_register() to suppress parents inferred from the
>>>> controller device and its attached domain list.
>>>
>>> I think, each GDSC has a parent (in the hardware). If we don't represent
>>> it in practice, we likely need to fix that.
>>
>> The next patch allows setting a precise set of parents instead. Perhaps
>> this commit message could be improved.
>
> Or the commits needs to be squashed / reverted. I really don't think
> that we should have a parent-less GDSC.
FWIW this commit alone is a NOP.
Konrad
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
` (15 preceding siblings ...)
2026-10-14 12:19 ` [PATCH DNM RFC 16/16] pmdomain: qcom: rpmhpd: Stop forwarding MMCX pstate to CX Konrad Dybcio
@ 2026-10-14 14:37 ` Dmitry Baryshkov
2026-10-14 16:19 ` Konrad Dybcio
16 siblings, 1 reply; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 14:37 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree,
Konrad Dybcio
On Wed, Oct 14, 2026 at 02:19:00PM +0200, Konrad Dybcio wrote:
> On Qualcomm SoCs, both the PLLs and other types of clocks alike require
> specific voltage rail levels to operate in-spec, but through the
> separation and decentralization of clock and voltage votes, these
> requirements are often not met and are never explicitly stated.
> This results in clocks being undervolted, or at the very least a state
> we're unable to reason about.
>
> The devices consuming these clocks very rarely have their own
> requirements in that regard. (1)
>
> This series moves the frequency-power_domain-perf_state mappings inside
> the clock drivers, ensuring a single, trusted source of truth is
> established and that the silicon is no longer driven at out-of-spec
> conditions.
>
> To illustrate the nuances involved, here's a couple examples of hidden
> dependencies, as found on SM8650:
> * video_cc_mvs0_clk requires LOW_SVS to run at 900 MHz, but the
> PLL it sources the signal from requires SVS (higher corner) to
> produce that frequency. Similarly, to produce 1140 MHz, the clock
> requires SVS, but the PLL then requires SVS_L1.
> Analogous story for mvs1_clk.
Is PLL a part of the the same domain or a different domain?
> * gpu_cc_gmu_clk requires MX to be at SVS to output 625 MHz, the PLL
> requires SVS_L1 to produce that frequency.
> * A number of camera clocks follow the same pattern.
> * Additionally, some clocks like cam_cc_cci_{0,1,2}_clk require
> MMCX to run at LOW_SVS, but the PLL has the additional requirement
> of keeping MXC (a wholly different rail) at SVS_L1.
>
> There's a broad agreement that a consumer should only have to care
> about the clock it consumes, and that configuring its parents is the
> responsibility of the CCF. Requiring the consumers to care about the
> above cases (as is the expectation today) is a clear violation of
> that contract.
I think that the conclusion was that the consumers should care about the
full set of supplies they need, which includes all transient and leaf
supplies.
> A clock controller may have on the order of hundreds of clocks, each
> one with its own frequency-power map, and potentially all of them
> could be similarly affected.
You have described several cases, where PLLs stand out, while the rest
of the clocks are typically more or less in line. Would it make sense
instead to solve the same issue in a slightly different way:
clock-controller@abcdef {
compatible = "qcom,sm8888-clockcc";
// required to access registers of the controller
power-domains = <&rpmhpd RPMHPD_MMCCX>;
required-opps = <&rpmhpd_opp_svs>;
pll0 {
power-domains = <&rpmhpd RPMHPD_MMMX>;
operating-points = <&trivian_opp_table>;
trivian_opp_table: opp-table {
compatible = "operating-points-v2";
opp-100000 {
opp-hz = /bits/ 64 <100000>;
required-opps = <&rpmhpd_opp_super_low_svs>;
};
opp-500000 {
opp-hz = /bits/ 64 <500000>;
required-opps = <&rpmhpd_opp_turbo>;
};
};
};
pll1 {
/* same PLL type, same table */
power-domains = <&rpmhpd RPMHPD_MMMX>;
operating-points = <&trivian_opp_table>;
};
pll2 {
power-domains = <&rpmhpd RPMHPD_MMMX>;
operating-points = <&completian_opp_table>;
/* other PLL type */
completian_opp_table: opp-table {
compatible = "operating-points-v2";
opp-123456 {
opp-hz = /bits/ 64 <123456>;
required-opps = <&rpmhpd_opp_super_low_svs>;
};
opp-333333 {
opp-hz = /bits/ 64 <333333>;
required-opps = <&rpmhpd_opp_nominal>;
};
opp-888888 {
opp-hz = /bits/ 64 <888888>;
required-opps = <&rpmhpd_opp_lower_turbo>;
};
};
};
};
The clock controller driver would then create a per-PLL device, bind it
with the OPP table, etc. The consumers would still vote on the resources
necessary to run the leaf clock, while the clock controller would care
about the PLLs.
You can guess, I don't like the idea of having voting tables in the
clock driver...
> Recently, some drivers (Iris, CAMSS) have attempted to circumvent these
> problems by holding a direct reference to the power rails and managing
> the votes manually, through placing direct votes on each rail via an
> OPP table. That however has a number of drawbacks:
> * Because more than just one clock needs to be scaled at runtime, the
> OPP table either has to contain NUM_CLOCKS * NUM_RATES to handle
> that (which blows up pretty quickly), assume the worst case (and
> waste power), or ignore that problem (and undervolt the clock
> controller), instead of just being able to call set_rate() on each
> clock as needed
If the clocks are independent, it's a different question. I don't think
we have a way to describe that we need OPP table a for clock CLK_A and
table b for CLK_B.
If we need to scale several clocks in a lock, the problem is manageable.
We need 2x entries (at least for the typical cases), because we need to
have one entry for each MMCX level and for each MX level.
> * The data ends up being dispersed across various consumers, even
> though it logically belongs to the clock controller, making it
> absolutely unauditable
Yes... See the proposal above.
> * It's horrendously error-prone because of the number of clocks
> involved, the depth of the clock tree and the fact that at the end
> of the day, one still has to consult the clock controller
> documentation
One needs to consult it anyway, more or less.
>
> The approach proposed here also happens to more naturally fit into the
> process of how an SoC is brought up - clock controllers are always
> the very first pieces that need to be complete before other IP blocks
> can be brought up. And because of (1), enclosing the frequency-corner
> data within the clock drivers takes a weight off of everyone else's
> shoulders.
>
> On a related topic, the GDSCs are modeled as children of *all* power
> domains consumed by a clock controller at once. GDSCs are on/off switches
> with some fancy logic attached to them and may need some (or none) rails
> enabled to power on or off while meeting the hardware timing
> requirements. Voting on them was never quite right in the first place
> and is once again only a side-effect of the clocks' power requirements
> not being handled centrally.
>
> This series removes the vote propagation and the overly broad parent-
> child links, resulting in dropping unnecessary/incorrect votes that are
> now encapsulated by the clk-dvfs infrastructure. Big emphasis on (1)
> once again.
>
> The implementation is as follows:
>
> Each clock describes the rail levels it needs at each frequency. The
> new clk-dvfs helper combines these requirements and submits the
> resulting votes through GenPD and RPMHPD. Votes remain held while a
> clock is prepared, and rate or parent changes reserve the new
> requirements before any clocks are reprogrammed. This also requires CCF
> to report programming failures and OPP to retry failed transitions
> instead of relying on stale cached state.
>
> Consumer: select rate, prepare clock
> |
> v
> +------------------------ CCF ----------------------+
> | Walk the clock tree and reserve resources |
> | before programming rates or parents |
> +--------------------------------------------------+
> |
> v
> +------------------------------------------------------------------+
> | |
> | GPLL9 ------> SDCC2 RCG -------> SDCC2 apps clock |
> | | | |
> | | | Other GCC clocks |
> | v v | |
> | +------------+ +------------+ v |
> | | PLL's own | | RCG's own | Per-clock requirements |
> | | rate table | | rate table | | |
> | +-----+------+ +-----+------+ | |
> | | | | |
> | | CX | CX + MX | |
RCG doesn't (typically) care about MX. It is not split between multiple
domains, is it?
> | v v v |
> | +--------------------------------------------------------+ |
> | | Clock-owned DVFS | |
> | | | |
> | | Prepared votes + temporary transition votes | |
This can quickly become a lock dependency (and lockdep) hell. Consider
two consumers setting the same clock concurrently (heh, nice design, I
know). For each of the consumers we must make sure that there is no
AB-BA voting update, which means that the whole sequence of
pre-set-rate vote, clk_set_rate() and post-set-rate vote should happen
under a single lock. However under the lock we vote for genpds, which
might trigger other transitions or, in the worst case, wake up the
domain.
> | | | | |
> | | Maximum requirement per rail | |
> | +-----------+--------------+--------------+--------------+ |
> | | | | |
> | CX voter CX_AO voter MX voter |
> +----------------+-------------+-------------+---------------------+
> | | |
> v v v
> +----------------------- GenPD ----------------------+
> | Aggregate GCC votes with other consumers |
> +---------------------------------------------------+
> |
> v
> +-----------+
> | RPMHPD |
> +-----------+
>
> Straightening out all of the above lets us tackle another problem
> with the upstream description of the power grid - the dependency
> between various rails. Most notably:
> * CX does NOT need to follow the performance state of MMCX
I think this depends on the RPMh translating votes or not. Is it really
fine to run MMCX at turbo if CX is at low_svs?
> * MX/MXA/MXC and CX are co-dependent only on some platforms (in coarse
> terms, SM8350 and before)
>
> However, the main part of series needs to land first to ensure that
> dropping those relationships doesn't remove some wrongful votes the
> system relied on to stay functional. Resolving that will save power and
> finally ensure total stability as far as the providers are concerned.
>
> SM8650 is used as an example in this series, but the conversion will
> have to be done for all Qualcomm platforms. That will follow soon
> after the patches are merged. DFS clocks are left out of this RFC to
> reduce noise (the remaining diff is not particularly large but also
> requires boilerplate changes to bus drivers).
>
> dt-bindings changes are not included since it's an RFC.
>
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> Konrad Dybcio (16):
> clk: Propagate rate programming failures
> clk: Acquire resources for clock transitions
> OPP: Retry failed default single-clock transitions
> pmdomain: core: Support named indexed virtual attachments
> pmdomain: core: Support subdomains without pstate propagation
> pmdomain: core: Support indexed power-domain attachment lists
> clk: qcom: Add clock-owned GenPD DVFS aggregation
> clk: qcom: dvfs: Add debugfs
> clk: qcom: clk-alpha-pll: Add DVFS (un)prepare
> clk: qcom: clk-alpha-pll: Vote for the PLL calibration frequency
> clk: qcom: gdsc: Make GDSC parenting optional
> clk: qcom: gdsc: Link required power domains
> [DNM] arm64: dts: qcom: sm8650: Describe clock controller power rails
> [DNM] clk: qcom: Enable clk-dvfs on SM8650
> [DNM] arm64: dts: qcom: sm8650: Drop required-opps
> [DNM] pmdomain: qcom: rpmhpd: Stop forwarding MMCX pstate to CX
>
> arch/arm64/boot/dts/qcom/sm8650.dtsi | 62 +--
> drivers/base/power/common.c | 24 +-
> drivers/clk/Makefile | 1 +
> drivers/clk/clk.c | 793 +++++++++++++++++++++++-----
> drivers/clk/clk_resource_test.c | 376 +++++++++++++
> drivers/clk/clk_test.c | 194 ++++++-
> drivers/clk/qcom/Makefile | 1 +
> drivers/clk/qcom/camcc-sm8650.c | 282 ++++++++++
> drivers/clk/qcom/clk-alpha-pll.c | 317 ++++++++++-
> drivers/clk/qcom/clk-dvfs.c | 635 ++++++++++++++++++++++
> drivers/clk/qcom/clk-dvfs.h | 41 ++
> drivers/clk/qcom/clk-rcg2.c | 93 +++-
> drivers/clk/qcom/clk-regmap.h | 6 +
> drivers/clk/qcom/common.c | 28 +-
> drivers/clk/qcom/common.h | 3 +
> drivers/clk/qcom/dispcc-sm8550.c | 328 ++++++++++--
> drivers/clk/qcom/gcc-sm8650.c | 261 +++++++++
> drivers/clk/qcom/gdsc.c | 154 +++++-
> drivers/clk/qcom/gdsc.h | 7 +
> drivers/clk/qcom/gpucc-sm8650.c | 101 +++-
> drivers/clk/qcom/videocc-sm8550.c | 135 ++++-
> drivers/opp/core.c | 72 ++-
> drivers/opp/opp.h | 2 +
> drivers/pmdomain/core.c | 60 ++-
> drivers/pmdomain/qcom/rpmhpd.c | 14 +-
> include/dt-bindings/clock/qcom,sm8650-gcc.h | 2 +
> include/linux/clk-provider.h | 39 ++
> include/linux/pm_domain.h | 33 +-
> 28 files changed, 3734 insertions(+), 330 deletions(-)
> ---
> base-commit: 4eb2586bd40db27d77cc5b724ca9386ed0c71d53
> change-id: 20261001-topic-qc_clk_dvfs-ef907f059611
>
> Best regards,
> --
> Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 14/16] clk: qcom: Enable clk-dvfs on SM8650
2026-10-14 12:19 ` [PATCH DNM RFC 14/16] clk: qcom: Enable clk-dvfs on SM8650 Konrad Dybcio
2026-10-14 12:36 ` sashiko-bot
@ 2026-10-14 14:40 ` Dmitry Baryshkov
2026-10-14 15:22 ` Konrad Dybcio
1 sibling, 1 reply; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 14:40 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree,
Konrad Dybcio
On Wed, Oct 14, 2026 at 02:19:14PM +0200, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> Make use of the clock-dvfs infrastructure and fill in the power
> requirements for SM8650's clocks to ensure the hardware can operate in
> accordance with the specification.
>
> Assisted-by: LLM
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> This commit is a DNM since some of the affected drivers are shared
> between multiple platforms, this will be properly reworked if we agree
> on merging the clk-DVFS infra.
> ---
> drivers/clk/qcom/camcc-sm8650.c | 282 ++++++++++++++++++++++++
> drivers/clk/qcom/dispcc-sm8550.c | 328 ++++++++++++++++++++++++----
> drivers/clk/qcom/gcc-sm8650.c | 261 ++++++++++++++++++++++
> drivers/clk/qcom/gpucc-sm8650.c | 101 ++++++++-
> drivers/clk/qcom/videocc-sm8550.c | 135 ++++++++++--
> include/dt-bindings/clock/qcom,sm8650-gcc.h | 2 +
> 6 files changed, 1041 insertions(+), 68 deletions(-)
>
> diff --git a/drivers/clk/qcom/camcc-sm8650.c b/drivers/clk/qcom/camcc-sm8650.c
> index 3aad816ed233..188649368564 100644
> --- a/drivers/clk/qcom/camcc-sm8650.c
> +++ b/drivers/clk/qcom/camcc-sm8650.c
> @@ -6,12 +6,15 @@
> #include <linux/clk-provider.h>
> #include <linux/module.h>
> #include <linux/platform_device.h>
> +#include <linux/pm.h>
> #include <linux/regmap.h>
>
> #include <dt-bindings/clock/qcom,sm8650-camcc.h>
> +#include <dt-bindings/power/qcom,rpmhpd.h>
I've commented to the cover letter. For me it feels like the layering
violation.
>
> #include "clk-alpha-pll.h"
> #include "clk-branch.h"
> +#include "clk-dvfs.h"
> #include "clk-rcg.h"
> #include "clk-regmap.h"
> #include "common.h"
> @@ -46,6 +49,110 @@ enum {
> P_SLEEP_CLK,
> };
>
> +/*
> + * Downstream uses CRM/CESTA for PLL3-7/10 and the CAMNOC AXI RT, CPHY RX,
> + * CSID, IFE0-2 and SFE0-2 RCGs. Until upstream supports that path, manage
> + * their rail requirements through clock-owned DVFS.
> + */
> +enum {
> + DT_PMDOMAIN_MMCX,
> + DT_PMDOMAIN_MXC,
> + DT_PMDOMAIN_MX,
> +};
> +
> +static const struct qcom_clk_dvfs_data cam_cc_pll0_dvfs = {
> + .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MXC },
> + .num_pd_indices = 1,
> + .rates = (const struct qcom_clk_dvfs_rate[]) {
> + { 615000000, RPMH_REGULATOR_LEVEL_LOW_SVS_D1 },
> + { 1100000000, RPMH_REGULATOR_LEVEL_SVS },
> + { 1600000000, RPMH_REGULATOR_LEVEL_SVS_L1 },
> + { 2000000000, RPMH_REGULATOR_LEVEL_NOM },
> + { 2100000000, RPMH_REGULATOR_LEVEL_TURBO_L1 },
> + },
> + .num_rates = 5,
> +};
> +
> +static const struct qcom_clk_dvfs_data cam_cc_camnoc_axi_rt_clk_src_dvfs = {
> + .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX, DT_PMDOMAIN_MXC },
> + .num_pd_indices = 2,
> + .rates = (const struct qcom_clk_dvfs_rate[]) {
> + { 300000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
> + { 400000000, RPMH_REGULATOR_LEVEL_SVS },
And does it actually have the same vote on both MMCX and MXC? I'd need
to check the clock tree.
> + },
> + .num_rates = 2,
> +};
> +
> +static const struct qcom_clk_dvfs_data cam_cc_cci_0_clk_src_dvfs = {
> + .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX },
> + .num_pd_indices = 1,
> + .rates = (const struct qcom_clk_dvfs_rate[]) {
> + { 37500000, RPMH_REGULATOR_LEVEL_LOW_SVS },
> + },
> + .num_rates = 1,
> +};
> +
> +static const struct qcom_clk_dvfs_data cam_cc_cphy_rx_clk_src_dvfs = {
> + .pd_indices = (const unsigned int[]) {
> + DT_PMDOMAIN_MMCX, DT_PMDOMAIN_MX, DT_PMDOMAIN_MXC,
Three domains... If we want to have three votes, we really need to have
three votes in the .rates array. But then it starts to feel like an OPP
table.
> + },
> + .num_pd_indices = 3,
> + .rates = (const struct qcom_clk_dvfs_rate[]) {
> + { 400000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
> + { 480000000, RPMH_REGULATOR_LEVEL_SVS },
> + },
> + .num_rates = 2,
> +};
> +
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails
2026-10-14 14:36 ` Konrad Dybcio
@ 2026-10-14 15:01 ` Dmitry Baryshkov
2026-10-14 15:23 ` Konrad Dybcio
0 siblings, 1 reply; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 15:01 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Konrad Dybcio, Stephen Boyd, Brian Masney, Jerome Brunet,
Viresh Kumar, Nishanth Menon, Rafael J. Wysocki, Pavel Machek,
Len Brown, Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Ulf Hansson, Bjorn Andersson, Abel Vesa, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das, linux-clk, linux-kernel, linux-pm, driver-core,
linux-arm-msm, devicetree
On Wed, Oct 14, 2026 at 04:36:11PM +0200, Konrad Dybcio wrote:
> On 10/14/26 4:33 PM, Dmitry Baryshkov wrote:
> > On Wed, Oct 14, 2026 at 04:20:08PM +0200, Konrad Dybcio wrote:
> >> On 10/14/26 4:19 PM, Dmitry Baryshkov wrote:
> >>> On Wed, Oct 14, 2026 at 02:19:13PM +0200, Konrad Dybcio wrote:
> >>>> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> >>>>
> >>>> The SM8650 clock-controller nodes omit rails needed by their clocks and
> >>>> PLLs. Without those connections, the clock-owned DVFS implementation
> >>>> cannot attach its voters to all required domains and controller probe
> >>>> can fail before any clocks are registered.
> >>>>
> >>>> Fixes: d2350377997f ("arm64: dts: qcom: add initial SM8650 dtsi")
> >>>> Assisted-by: LLM
> >>>> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> >>>> ---
> >>>> arch/arm64/boot/dts/qcom/sm8650.dtsi | 16 +++++++++++-----
> >>>> 1 file changed, 11 insertions(+), 5 deletions(-)
> >>>>
> >>>> @@ -6049,8 +6055,8 @@ dispcc: clock-controller@af00000 {
> >>>> <0>, /* dp3 */
> >>>> <0>;
> >>>>
> >>>> - power-domains = <&rpmhpd RPMHPD_MMCX>;
> >>>> - required-opps = <&rpmhpd_opp_low_svs>;
> >>>
> >>> I think this is neccessary (at least on some platforms) to keep MMCX out
> >>> of retention. Even if we can drop it here, I don't think we can drop it
> >>> for older platforms.
> >>
> >> MMCX exists precisely to let you turn it off, the clock patches
> >> retain a way to keep the clock on during the runtime-resume of
> >> the clock controller driver
> >
> > Turning off MMCX is possible together with suspending the dispcc. If
> > dispcc is resumed, MMCX should be at least low_svs. On some platforms
> > it's handled by the cmd-db, where MMCX has a minimal value of low_svs.
> > On other platforms MMCX lists 'RET' as a possible value, however it's
> > not enough for dispcc (or other CCs) to work. In theory, *I think*, it's
> > exactly what 'GCC_DISP_AHB_CLK' might need to vote, but we don't
> > represent it.
>
> Retention isn't a ""real"" state the OS should vote for, as the HW
> downstream of the rails generally needs some flavor of LOW_SVS to
> function, or at least I don't know of any such cases.
>
> See also
>
> 7178817f1904 ("pmdomain: qcom: rpmhpd: Skip retention by default")
Ack, I missed it. The price of doing OE instead of kernel for several
months :-D
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 14/16] clk: qcom: Enable clk-dvfs on SM8650
2026-10-14 14:40 ` Dmitry Baryshkov
@ 2026-10-14 15:22 ` Konrad Dybcio
0 siblings, 0 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 15:22 UTC (permalink / raw)
To: Dmitry Baryshkov, Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree
On 10/14/26 4:40 PM, Dmitry Baryshkov wrote:
> On Wed, Oct 14, 2026 at 02:19:14PM +0200, Konrad Dybcio wrote:
>> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>>
>> Make use of the clock-dvfs infrastructure and fill in the power
>> requirements for SM8650's clocks to ensure the hardware can operate in
>> accordance with the specification.
[...]
>> #include <dt-bindings/clock/qcom,sm8650-camcc.h>
>> +#include <dt-bindings/power/qcom,rpmhpd.h>
>
> I've commented to the cover letter. For me it feels like the layering
> violation.
I understand. I still think it's okay given this is a driver only
for a specific SoC.
[...]
>> +static const struct qcom_clk_dvfs_data cam_cc_camnoc_axi_rt_clk_src_dvfs = {
>> + .pd_indices = (const unsigned int[]) { DT_PMDOMAIN_MMCX, DT_PMDOMAIN_MXC },
>> + .num_pd_indices = 2,
>> + .rates = (const struct qcom_clk_dvfs_rate[]) {
>> + { 300000000, RPMH_REGULATOR_LEVEL_LOW_SVS },
>> + { 400000000, RPMH_REGULATOR_LEVEL_SVS },
>
> And does it actually have the same vote on both MMCX and MXC? I'd need
> to check the clock tree.
In this case the RCG needs VDD_MM and its direct child branch
needs MXC for the flipflops.
[...]
>> +static const struct qcom_clk_dvfs_data cam_cc_cphy_rx_clk_src_dvfs = {
>> + .pd_indices = (const unsigned int[]) {
>> + DT_PMDOMAIN_MMCX, DT_PMDOMAIN_MX, DT_PMDOMAIN_MXC,
>
> Three domains... If we want to have three votes, we really need to have
> three votes in the .rates array. But then it starts to feel like an OPP
> table.
In this case, yes, three domains, same RPMH level for this clock alone.
Konrad
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails
2026-10-14 15:01 ` Dmitry Baryshkov
@ 2026-10-14 15:23 ` Konrad Dybcio
0 siblings, 0 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 15:23 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Konrad Dybcio, Stephen Boyd, Brian Masney, Jerome Brunet,
Viresh Kumar, Nishanth Menon, Rafael J. Wysocki, Pavel Machek,
Len Brown, Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Ulf Hansson, Bjorn Andersson, Abel Vesa, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das, linux-clk, linux-kernel, linux-pm, driver-core,
linux-arm-msm, devicetree
On 10/14/26 5:01 PM, Dmitry Baryshkov wrote:
> On Wed, Oct 14, 2026 at 04:36:11PM +0200, Konrad Dybcio wrote:
>> On 10/14/26 4:33 PM, Dmitry Baryshkov wrote:
>>> On Wed, Oct 14, 2026 at 04:20:08PM +0200, Konrad Dybcio wrote:
>>>> On 10/14/26 4:19 PM, Dmitry Baryshkov wrote:
>>>>> On Wed, Oct 14, 2026 at 02:19:13PM +0200, Konrad Dybcio wrote:
[...]
> Ack, I missed it. The price of doing OE instead of kernel for several
> months :-D
We really need you back!
Konrad
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation
2026-10-14 14:36 ` Dmitry Baryshkov
@ 2026-10-14 15:26 ` Konrad Dybcio
2026-10-14 16:30 ` Dmitry Baryshkov
0 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 15:26 UTC (permalink / raw)
To: Dmitry Baryshkov, Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree
On 10/14/26 4:36 PM, Dmitry Baryshkov wrote:
> On Wed, Oct 14, 2026 at 02:19:07PM +0200, Konrad Dybcio wrote:
>> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>>
>> Clocks on Qualcomm SoCs need a minimum power rail performance state to
>> operate at a given frequency. We currently leave this to clock
>> consumers, so a driver has to know which power domains supply both the
>> clock and its parents. This ties knowledge of the clock tree to
>> individual consumers and doesn't protect other users that prepare or
>> change the same clock without making those power requests.
>
> Why would they prepare or change the same clock without making power
> requests? If it's a legitimate user of the clock, it should have an OPP
> table. If it's not, it should have a separate API to set the clock via a
> legitimate user / aggregator.
That's the entire problem that motivated this series. The consumer
doesn't know what it needs on Qualcomm platforms. The power
requirements are that of the clock controller's, not of that device's.
> I see one usecase where this would help though. If the clocks use
> different power domains and the corners on one of the domains don't
> match voltage on the other domain. E.g. it's not infrequent that the PLL
> (powered by MX) requires performance vote change at a different
> frequency than the rest of the clock tree.
Correct
[...]
>> Move these requirements into the clock provider and aggregate the votes
>> per power domain. Keep the highest level needed by prepared clocks and
>> in-progress rate or parent changes so that one clock can't withdraw
>> another clock's requirement. Use separate virtual GenPD devices for
>> these votes and controller register access so their power requirements
>> can be managed independently.
>
> The biggest question at this point would be the locking and lock
> nesting. What is the expected order of operations? Currently it's
> limited to pmdomains, but for some of the (older) platforms clock
> supplies are expressd as the normal regulators rather than pmdomains.
The Qualcomm case is easy - RPMH is *always* reachable, so the
pmdomain provider needs no clocks itself. For the other cases,
"patches welcome".
[...]
>> +static unsigned int
>> +qcom_clk_dvfs_aggregate(struct qcom_clk_dvfs_rail_voter *rail_voter)
>> +{
>> + struct qcom_clk_dvfs_vote *vote;
>> + unsigned int level = 0;
>> +
>> + list_for_each_entry(vote, &rail_voter->votes, node)
>> + level = max(level, vote->level);
>> +
>> + return max(level, rail_voter->safety_floor_level);
>
> I guess you want to avoid having too many voters, but would it be
> possible to have one voter per 'signficant' clock and let GenPD itself
> aggregate all the votes?
What does "significant" mean here?
Konrad
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions
2026-10-14 13:35 ` Dmitry Baryshkov
@ 2026-10-14 16:14 ` Konrad Dybcio
2026-10-14 16:32 ` Dmitry Baryshkov
0 siblings, 1 reply; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 16:14 UTC (permalink / raw)
To: Dmitry Baryshkov, Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree
On 10/14/26 3:35 PM, Dmitry Baryshkov wrote:
> On Wed, Oct 14, 2026 at 02:19:03PM +0200, Konrad Dybcio wrote:
>> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>>
>> A failed OPP transition need not leave the clock at the last successful
>> OPP's frequency. If hardware changed before the error, the default
>> single-clock path can subsequently skip a necessary retry because its
>
> What is a necessary retry? Shouldn't it be: try, rollback if it fails?
The failure scenario is:
opp-table {
opp-400000000 {
// low
};
opp-1000000000 {
// high
};
}';
1. The device is running at Low. OPP caches 400 MHz and the Low OPP.
2. A driver requests High.
- OPP raises the voltage to 0.95 V.
- clk_set_rate(..., 1 GHz) changes the clock, but returns an error
- Assume clk_get_rate() can report the resulting 1 GHz rate.
3. The transition returns an error, so current_opp still points to Low.
4. The caller tries to recover by requesting Low again.
Perhaps I should have included that in the commit message.
Konrad
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks
2026-10-14 14:37 ` [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Dmitry Baryshkov
@ 2026-10-14 16:19 ` Konrad Dybcio
0 siblings, 0 replies; 53+ messages in thread
From: Konrad Dybcio @ 2026-10-14 16:19 UTC (permalink / raw)
To: Dmitry Baryshkov, Konrad Dybcio
Cc: Stephen Boyd, Brian Masney, Jerome Brunet, Viresh Kumar,
Nishanth Menon, Rafael J. Wysocki, Pavel Machek, Len Brown,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold, Ulf Hansson,
Bjorn Andersson, Abel Vesa, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Mike Tipton, Taniya Das, linux-clk,
linux-kernel, linux-pm, driver-core, linux-arm-msm, devicetree
On 10/14/26 4:37 PM, Dmitry Baryshkov wrote:
> On Wed, Oct 14, 2026 at 02:19:00PM +0200, Konrad Dybcio wrote:
>> On Qualcomm SoCs, both the PLLs and other types of clocks alike require
>> specific voltage rail levels to operate in-spec, but through the
>> separation and decentralization of clock and voltage votes, these
>> requirements are often not met and are never explicitly stated.
>> This results in clocks being undervolted, or at the very least a state
>> we're unable to reason about.
[...]
>> To illustrate the nuances involved, here's a couple examples of hidden
>> dependencies, as found on SM8650:
>> * video_cc_mvs0_clk requires LOW_SVS to run at 900 MHz, but the
>> PLL it sources the signal from requires SVS (higher corner) to
>> produce that frequency. Similarly, to produce 1140 MHz, the clock
>> requires SVS, but the PLL then requires SVS_L1.
>> Analogous story for mvs1_clk.
>
> Is PLL a part of the the same domain or a different domain?
The same.
>> * gpu_cc_gmu_clk requires MX to be at SVS to output 625 MHz, the PLL
>> requires SVS_L1 to produce that frequency.
>> * A number of camera clocks follow the same pattern.
>> * Additionally, some clocks like cam_cc_cci_{0,1,2}_clk require
>> MMCX to run at LOW_SVS, but the PLL has the additional requirement
>> of keeping MXC (a wholly different rail) at SVS_L1.
>>
>> There's a broad agreement that a consumer should only have to care
>> about the clock it consumes, and that configuring its parents is the
>> responsibility of the CCF. Requiring the consumers to care about the
>> above cases (as is the expectation today) is a clear violation of
>> that contract.
>
> I think that the conclusion was that the consumers should care about the
> full set of supplies they need, which includes all transient and leaf
> supplies.
Right, and my argument is that it's impractical and effectively
impossible to enforce and audit.
>> A clock controller may have on the order of hundreds of clocks, each
>> one with its own frequency-power map, and potentially all of them
>> could be similarly affected.
>
> You have described several cases, where PLLs stand out, while the rest
> of the clocks are typically more or less in line. Would it make sense
> instead to solve the same issue in a slightly different way:
[...]
> The clock controller driver would then create a per-PLL device, bind it
> with the OPP table, etc. The consumers would still vote on the resources
> necessary to run the leaf clock, while the clock controller would care
> about the PLLs.
>
> You can guess, I don't like the idea of having voting tables in the
> clock driver...
I don't think the PLLs are any special here. It just happened that
the special cases involved them. If we had OPP tables for PLLs, we'd
probably desire to keep *all* of the data in the DT (so also for all
the other clocks), and i don't want 50% of the DT to become clock data.
As you can guess, I don't like boardfiles in general too, but I think
containing that in the driver is the right choice here.
>> Recently, some drivers (Iris, CAMSS) have attempted to circumvent these
>> problems by holding a direct reference to the power rails and managing
>> the votes manually, through placing direct votes on each rail via an
>> OPP table. That however has a number of drawbacks:
>> * Because more than just one clock needs to be scaled at runtime, the
>> OPP table either has to contain NUM_CLOCKS * NUM_RATES to handle
>> that (which blows up pretty quickly), assume the worst case (and
>> waste power), or ignore that problem (and undervolt the clock
>> controller), instead of just being able to call set_rate() on each
>> clock as needed
>
> If the clocks are independent, it's a different question. I don't think
> we have a way to describe that we need OPP table a for clock CLK_A and
> table b for CLK_B.
Yes, they are! The AHB/AXI clocks for example (at the very least for
camera).
> If we need to scale several clocks in a lock, the problem is manageable.
> We need 2x entries (at least for the typical cases), because we need to
> have one entry for each MMCX level and for each MX level.
That's already solved today, and as you said that's just a different
requirement.
>> * The data ends up being dispersed across various consumers, even
>> though it logically belongs to the clock controller, making it
>> absolutely unauditable
>
> Yes... See the proposal above.
>
>> * It's horrendously error-prone because of the number of clocks
>> involved, the depth of the clock tree and the fact that at the end
>> of the day, one still has to consult the clock controller
>> documentation
>
> One needs to consult it anyway, more or less.
Yes, but provided you have the data, with this patchset, you can look
at the known single source and relay its contents to the known single
place (the clock driver), letting the computer do the path traversal
and max() along the way.
[...]
>> +------------------------------------------------------------------+
>> | |
>> | GPLL9 ------> SDCC2 RCG -------> SDCC2 apps clock |
>> | | | |
>> | | | Other GCC clocks |
>> | v v | |
>> | +------------+ +------------+ v |
>> | | PLL's own | | RCG's own | Per-clock requirements |
>> | | rate table | | rate table | | |
>> | +-----+------+ +-----+------+ | |
>> | | | | |
>> | | CX | CX + MX | |
>
> RCG doesn't (typically) care about MX.
That's unfortunately not a universal truth.
> It is not split between multiple
> domains, is it?
I think the most common case is "RCG needs CX and its direct child
branch needs MX at the same level" and the generator ends up treating
that as "RCG needs CX and MX" (which is totally fair). But again, RCGs
sourced from MX is also a valid config.
>
>> | v v v |
>> | +--------------------------------------------------------+ |
>> | | Clock-owned DVFS | |
>> | | | |
>> | | Prepared votes + temporary transition votes | |
>
> This can quickly become a lock dependency (and lockdep) hell. Consider
> two consumers setting the same clock concurrently (heh, nice design, I
> know). For each of the consumers we must make sure that there is no
> AB-BA voting update, which means that the whole sequence of
> pre-set-rate vote, clk_set_rate() and post-set-rate vote should happen
> under a single lock. However under the lock we vote for genpds, which
> might trigger other transitions or, in the worst case, wake up the
> domain.
Lockdep didn't complain on SM8650 and Glymur. I'm assuming the only case
where it actually matters is that if the power-domain provider is
suspended and needs a clock to turn on before accessing it. But that'
not a problem on Qualcomm platforms and that case would have bigger
issues anyway..
[...]
>> Straightening out all of the above lets us tackle another problem
>> with the upstream description of the power grid - the dependency
>> between various rails. Most notably:
>> * CX does NOT need to follow the performance state of MMCX
>
> I think this depends on the RPMh translating votes or not. Is it really
> fine to run MMCX at turbo if CX is at low_svs?
Yes, that's actually the reason MMCX exists - so that the multimedia
power is wholly separate from the main rail.
Konrad
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation
2026-10-14 15:26 ` Konrad Dybcio
@ 2026-10-14 16:30 ` Dmitry Baryshkov
0 siblings, 0 replies; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 16:30 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Konrad Dybcio, Stephen Boyd, Brian Masney, Jerome Brunet,
Viresh Kumar, Nishanth Menon, Rafael J. Wysocki, Pavel Machek,
Len Brown, Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Ulf Hansson, Bjorn Andersson, Abel Vesa, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das, linux-clk, linux-kernel, linux-pm, driver-core,
linux-arm-msm, devicetree
On Wed, Oct 14, 2026 at 05:26:00PM +0200, Konrad Dybcio wrote:
> On 10/14/26 4:36 PM, Dmitry Baryshkov wrote:
> > On Wed, Oct 14, 2026 at 02:19:07PM +0200, Konrad Dybcio wrote:
> >> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> >>
> >> Clocks on Qualcomm SoCs need a minimum power rail performance state to
> >> operate at a given frequency. We currently leave this to clock
> >> consumers, so a driver has to know which power domains supply both the
> >> clock and its parents. This ties knowledge of the clock tree to
> >> individual consumers and doesn't protect other users that prepare or
> >> change the same clock without making those power requests.
> >
> > Why would they prepare or change the same clock without making power
> > requests? If it's a legitimate user of the clock, it should have an OPP
> > table. If it's not, it should have a separate API to set the clock via a
> > legitimate user / aggregator.
>
> That's the entire problem that motivated this series. The consumer
> doesn't know what it needs on Qualcomm platforms. The power
> requirements are that of the clock controller's, not of that device's.
I'd still say that the consumers know that they need a certain
frequency, which demands a certain voltage / power / etc.
>
> > I see one usecase where this would help though. If the clocks use
> > different power domains and the corners on one of the domains don't
> > match voltage on the other domain. E.g. it's not infrequent that the PLL
> > (powered by MX) requires performance vote change at a different
> > frequency than the rest of the clock tree.
>
> Correct
>
> [...]
>
> >> Move these requirements into the clock provider and aggregate the votes
> >> per power domain. Keep the highest level needed by prepared clocks and
> >> in-progress rate or parent changes so that one clock can't withdraw
> >> another clock's requirement. Use separate virtual GenPD devices for
> >> these votes and controller register access so their power requirements
> >> can be managed independently.
> >
> > The biggest question at this point would be the locking and lock
> > nesting. What is the expected order of operations? Currently it's
> > limited to pmdomains, but for some of the (older) platforms clock
> > supplies are expressd as the normal regulators rather than pmdomains.
>
> The Qualcomm case is easy - RPMH is *always* reachable, so the
> pmdomain provider needs no clocks itself. For the other cases,
> "patches welcome".
Ok. There is a promise that I2C PMICs will never ever be involved in the
clock / power tables.
>
> [...]
>
> >> +static unsigned int
> >> +qcom_clk_dvfs_aggregate(struct qcom_clk_dvfs_rail_voter *rail_voter)
> >> +{
> >> + struct qcom_clk_dvfs_vote *vote;
> >> + unsigned int level = 0;
> >> +
> >> + list_for_each_entry(vote, &rail_voter->votes, node)
> >> + level = max(level, vote->level);
> >> +
> >> + return max(level, rail_voter->safety_floor_level);
> >
> > I guess you want to avoid having too many voters, but would it be
> > possible to have one voter per 'signficant' clock and let GenPD itself
> > aggregate all the votes?
>
> What does "significant" mean here?
I'd define it as "using a different power domain or requiring a
different corner".
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions
2026-10-14 16:14 ` Konrad Dybcio
@ 2026-10-14 16:32 ` Dmitry Baryshkov
0 siblings, 0 replies; 53+ messages in thread
From: Dmitry Baryshkov @ 2026-10-14 16:32 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Konrad Dybcio, Stephen Boyd, Brian Masney, Jerome Brunet,
Viresh Kumar, Nishanth Menon, Rafael J. Wysocki, Pavel Machek,
Len Brown, Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Ulf Hansson, Bjorn Andersson, Abel Vesa, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Mike Tipton,
Taniya Das, linux-clk, linux-kernel, linux-pm, driver-core,
linux-arm-msm, devicetree
On Wed, Oct 14, 2026 at 06:14:43PM +0200, Konrad Dybcio wrote:
> On 10/14/26 3:35 PM, Dmitry Baryshkov wrote:
> > On Wed, Oct 14, 2026 at 02:19:03PM +0200, Konrad Dybcio wrote:
> >> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> >>
> >> A failed OPP transition need not leave the clock at the last successful
> >> OPP's frequency. If hardware changed before the error, the default
> >> single-clock path can subsequently skip a necessary retry because its
> >
> > What is a necessary retry? Shouldn't it be: try, rollback if it fails?
>
> The failure scenario is:
>
> opp-table {
> opp-400000000 {
> // low
> };
>
> opp-1000000000 {
> // high
> };
> }';
>
> 1. The device is running at Low. OPP caches 400 MHz and the Low OPP.
> 2. A driver requests High.
> - OPP raises the voltage to 0.95 V.
> - clk_set_rate(..., 1 GHz) changes the clock, but returns an error
I thought that your previous patches actively target this. I.e. if there
is an error, CCF should retract the changes for as much as it can
(assuming that there is no extra error on the reverting of the clocks).
> - Assume clk_get_rate() can report the resulting 1 GHz rate.
> 3. The transition returns an error, so current_opp still points to Low.
> 4. The caller tries to recover by requesting Low again.
>
> Perhaps I should have included that in the commit message.
I think, if something changes the state, but returns an error, it's more
or less... an error. Or a bug.
>
> Konrad
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 53+ messages in thread
end of thread, other threads:[~2026-10-14 16:33 UTC | newest]
Thread overview: 53+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 01/16] clk: Propagate rate programming failures Konrad Dybcio
2026-10-14 12:35 ` sashiko-bot
2026-10-14 13:25 ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH RFC 02/16] clk: Acquire resources for clock transitions Konrad Dybcio
2026-10-14 12:40 ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions Konrad Dybcio
2026-10-14 12:34 ` sashiko-bot
2026-10-14 13:35 ` Dmitry Baryshkov
2026-10-14 16:14 ` Konrad Dybcio
2026-10-14 16:32 ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH RFC 04/16] pmdomain: core: Support named indexed virtual attachments Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 05/16] pmdomain: core: Support subdomains without pstate propagation Konrad Dybcio
2026-10-14 12:41 ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 06/16] pmdomain: core: Support indexed power-domain attachment lists Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation Konrad Dybcio
2026-10-14 12:35 ` sashiko-bot
2026-10-14 14:36 ` Dmitry Baryshkov
2026-10-14 15:26 ` Konrad Dybcio
2026-10-14 16:30 ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH RFC 08/16] clk: qcom: dvfs: Add debugfs Konrad Dybcio
2026-10-14 12:37 ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 09/16] clk: qcom: clk-alpha-pll: Add DVFS (un)prepare Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 10/16] clk: qcom: clk-alpha-pll: Vote for the PLL calibration frequency Konrad Dybcio
2026-10-14 12:34 ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional Konrad Dybcio
2026-10-14 12:30 ` sashiko-bot
2026-10-14 14:17 ` Dmitry Baryshkov
2026-10-14 14:20 ` Konrad Dybcio
2026-10-14 14:34 ` Dmitry Baryshkov
2026-10-14 14:36 ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 12/16] clk: qcom: gdsc: Link required power domains Konrad Dybcio
2026-10-14 12:32 ` sashiko-bot
2026-10-14 12:19 ` [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails Konrad Dybcio
2026-10-14 12:41 ` sashiko-bot
2026-10-14 14:19 ` Dmitry Baryshkov
2026-10-14 14:20 ` Konrad Dybcio
2026-10-14 14:33 ` Dmitry Baryshkov
2026-10-14 14:36 ` Konrad Dybcio
2026-10-14 15:01 ` Dmitry Baryshkov
2026-10-14 15:23 ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH DNM RFC 14/16] clk: qcom: Enable clk-dvfs on SM8650 Konrad Dybcio
2026-10-14 12:36 ` sashiko-bot
2026-10-14 14:40 ` Dmitry Baryshkov
2026-10-14 15:22 ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH DNM RFC 15/16] arm64: dts: qcom: sm8650: Drop required-opps Konrad Dybcio
2026-10-14 12:39 ` sashiko-bot
2026-10-14 14:23 ` Dmitry Baryshkov
2026-10-14 14:33 ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH DNM RFC 16/16] pmdomain: qcom: rpmhpd: Stop forwarding MMCX pstate to CX Konrad Dybcio
2026-10-14 12:37 ` sashiko-bot
2026-10-14 14:37 ` [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Dmitry Baryshkov
2026-10-14 16:19 ` Konrad Dybcio
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox