[PATCH 4/5] clk: qcom: clk-rcg2: calculate timeout based on clock frequency

From: Xilin Wu

Date: Mon Apr 06 2026 - 11:56:54 EST


RCGs with extremely low rates (tens of Hz to low kHz) take much longer
to update than the fixed 500 us timeout allows. A 1 kHz clock needs at
least 3 ms (3 cycles) for the configuration handshake.

Instead of increasing the timeout to a huge fixed value for all clocks,
dynamically compute the required timeout based on both the old and new
clock rates, accounting for 3 cycles at each rate.

Based on a downstream patch by Mike Tipton:
https://git.codelinaro.org/clo/la/kernel/qcom/-/commit/aa899c2d1fa31e247f04810f125ac9c60927c901

Fixes: bcd61c0f535a ("clk: qcom: Add support for root clock generators (RCGs)")
Signed-off-by: Mike Tipton <quic_mdtipton@xxxxxxxxxxx>
Signed-off-by: Xilin Wu <sophon@xxxxxxxxx>
---
drivers/clk/qcom/clk-rcg.h | 2 ++
drivers/clk/qcom/clk-rcg2.c | 27 +++++++++++++++++++++++++--
2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/qcom/clk-rcg.h b/drivers/clk/qcom/clk-rcg.h
index 4fbdf4880d03..12c1ce0f774c 100644
--- a/drivers/clk/qcom/clk-rcg.h
+++ b/drivers/clk/qcom/clk-rcg.h
@@ -159,6 +159,7 @@ extern const struct clk_ops clk_dyn_rcg_ops;
* @clkr: regmap clock handle
* @cfg_off: defines the cfg register offset from the CMD_RCGR + CFG_REG
* @parked_cfg: cached value of the CFG register for parked RCGs
+ * @configured_freq: last configured frequency, used for timeout calculation
* @hw_clk_ctrl: whether to enable hardware clock control
*/
struct clk_rcg2 {
@@ -174,6 +175,7 @@ struct clk_rcg2 {
struct clk_regmap clkr;
u8 cfg_off;
u32 parked_cfg;
+ unsigned long configured_freq;
bool hw_clk_ctrl;
};

diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c
index 0e8f0473897e..732f34629ea2 100644
--- a/drivers/clk/qcom/clk-rcg2.c
+++ b/drivers/clk/qcom/clk-rcg2.c
@@ -111,9 +111,27 @@ static u8 clk_rcg2_get_parent(struct clk_hw *hw)
return __clk_rcg2_get_parent(hw, cfg);
}

+static int get_update_timeout(const struct clk_rcg2 *rcg)
+{
+ int timeout = 0;
+ unsigned long current_freq;
+
+ /*
+ * The time it takes an RCG to update is roughly 3 clock cycles of the
+ * old and new clock rates.
+ */
+ current_freq = clk_hw_get_rate(&rcg->clkr.hw);
+ if (current_freq)
+ timeout += 3 * (USEC_PER_SEC / current_freq);
+ if (rcg->configured_freq)
+ timeout += 3 * (USEC_PER_SEC / rcg->configured_freq);
+
+ return max(timeout, 500);
+}
+
static int update_config(struct clk_rcg2 *rcg)
{
- int count, ret;
+ int timeout, count, ret;
u32 cmd;
struct clk_hw *hw = &rcg->clkr.hw;
const char *name = clk_hw_get_name(hw);
@@ -123,8 +141,10 @@ static int update_config(struct clk_rcg2 *rcg)
if (ret)
return ret;

+ timeout = get_update_timeout(rcg);
+
/* Wait for update to take effect */
- for (count = 500; count > 0; count--) {
+ for (count = timeout; count > 0; count--) {
ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CMD_REG, &cmd);
if (ret)
return ret;
@@ -602,6 +622,8 @@ static int clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f)
if (ret)
return ret;

+ rcg->configured_freq = f->freq;
+
return update_config(rcg);
}

@@ -689,6 +711,7 @@ static int clk_rcg2_set_gp_rate(struct clk_hw *hw, unsigned long rate,

clk_rcg2_calc_mnd(parent_rate, rate, f, mnd_max, hid_max / 2);
convert_to_reg_val(f);
+ rcg->configured_freq = rate;
ret = clk_rcg2_configure_gp(rcg, f);

return ret;

--
2.53.0