[PATCH v2 03/12] clk: divider: Introduce CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET flag
Luo Jie <[email protected]> Thu, 06 Aug 2026 23:53:07 -0700
| Newsgroups | org.kernel.vger.linux-clk,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Some hardware dividers derive an even divisor directly from the raw register value, i.e. divisor = 2 * val, with no +1 offset. This differs from CLK_DIVIDER_EVEN_INTEGERS, which is one-based (divisor = 2 * (val + 1)). Add CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET for this variant, updating _get_maxdiv()/_get_div()/_get_val() accordingly. _is_valid_div() is extended to also reject odd divisors, since _get_val()'s `div >> 1` would otherwise silently decode them to the wrong even value (e.g. 3 -> 1 -> 2). To avoid regressing rate requests that previously computed an odd divisor (which used to succeed with a silently wrong rate), round to the nearest even divisor in _div_round_up(), _div_round_closest(), and _next_div(), mirroring how CLK_DIVIDER_POWER_OF_TWO already rounds to the nearest power of two. Also guard divider_ro_determine_rate() against a zero divisor, which can occur from an unprogrammed/reset register, the same way divider_recalc_rate() already does: WARN unless CLK_DIVIDER_ALLOW_ZERO is set, and return -EINVAL. Signed-off-by: Luo Jie <[email protected]> --- drivers/clk/clk-divider.c | 22 ++++++++++++++++++++++ include/linux/clk-provider.h | 3 +++ 2 files changed, 25 insertions(+) diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index b3b485d23ea8..f062d77ee106 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -74,6 +74,8 @@ static unsigned int _get_maxdiv(const struct clk_div_table *table, u8 width, return 1 << clk_div_mask(width); if (flags & CLK_DIVIDER_EVEN_INTEGERS) return 2 * (clk_div_mask(width) + 1); + if (flags & CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET) + return 2 * clk_div_mask(width); if (table) return _get_table_maxdiv(table, width); return clk_div_mask(width) + 1; @@ -101,6 +103,8 @@ static unsigned int _get_div(const struct clk_div_table *table, return val ? val : clk_div_mask(width) + 1; if (flags & CLK_DIVIDER_EVEN_INTEGERS) return 2 * (val + 1); + if (flags & CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET) + return 2 * val; if (table) return _get_table_div(table, val); return val + 1; @@ -128,6 +132,8 @@ static unsigned int _get_val(const struct clk_div_table *table, return (div == clk_div_mask(width) + 1) ? 0 : div; if (flags & CLK_DIVIDER_EVEN_INTEGERS) return (div >> 1) - 1; + if (flags & CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET) + return div >> 1; if (table) return _get_table_val(table, div); return div - 1; @@ -181,6 +187,8 @@ static bool _is_valid_div(const struct clk_div_table *table, unsigned int div, { if (flags & CLK_DIVIDER_POWER_OF_TWO) return is_power_of_2(div); + if (flags & CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET) + return div >= 2 && !(div & 1); if (table) return _is_valid_table_div(table, div); return true; @@ -230,6 +238,8 @@ static int _div_round_up(const struct clk_div_table *table, if (flags & CLK_DIVIDER_POWER_OF_TWO) div = __roundup_pow_of_two(div); + else if (flags & CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET) + div = max(2, (div + 1) & ~1); if (table) div = _round_up_table(table, div); @@ -249,6 +259,9 @@ static int _div_round_closest(const struct clk_div_table *table, if (flags & CLK_DIVIDER_POWER_OF_TWO) { up = __roundup_pow_of_two(up); down = __rounddown_pow_of_two(down); + } else if (flags & CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET) { + up = max(2, (up + 1) & ~1); + down = max(2, down & ~1); } else if (table) { up = _round_up_table(table, up); down = _round_down_table(table, down); @@ -286,6 +299,8 @@ static int _next_div(const struct clk_div_table *table, int div, if (flags & CLK_DIVIDER_POWER_OF_TWO) return __roundup_pow_of_two(div); + if (flags & CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET) + return div + (div & 1); if (table) return _round_up_table(table, div); @@ -372,6 +387,13 @@ int divider_ro_determine_rate(struct clk_hw *hw, struct clk_rate_request *req, div = _get_div(table, val, flags, width); + if (!div) { + WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO), + "%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n", + clk_hw_get_name(hw)); + return -EINVAL; + } + /* Even a read-only clock can propagate a rate change */ if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) { if (!req->best_parent_hw) diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index b01a38fef8cf..368970d6a12a 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -703,6 +703,8 @@ struct clk_div_table { * big endian. * CLK_DIVIDER_EVEN_INTEGERS - clock divisor is 2, 4, 6, 8, 10, etc. * Formula is 2 * (value read from hardware + 1). + * CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET - clock divisor is 2, 4, 6, 8, etc. + * Formula is 2 * (value read from hardware). */ struct clk_divider { struct clk_hw hw; @@ -726,6 +728,7 @@ struct clk_divider { #define CLK_DIVIDER_MAX_AT_ZERO BIT(6) #define CLK_DIVIDER_BIG_ENDIAN BIT(7) #define CLK_DIVIDER_EVEN_INTEGERS BIT(8) +#define CLK_DIVIDER_EVEN_INTEGERS_NO_OFFSET BIT(9) extern const struct clk_ops clk_divider_ops; extern const struct clk_ops clk_divider_ro_ops; -- 2.43.0