Re: [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation
From: Dmitry Baryshkov
Date: Wed Oct 14 2026 - 10:42:01 EST
On Wed, Oct 14, 2026 at 02:19:07PM +0200, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@xxxxxxxxxxxxxxxx>
>
> 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@xxxxxxxxxxxxxxxx>
> ---
> 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