[PATCH v2 4/5] clk: renesas: rzv2h-cpg: Extract PLL calculation math into a library
From: Prabhakar
Date: Tue Jun 09 2026 - 07:12:31 EST
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>
Move the common PLL and divider parameter calculation logic from the
core rzv2h-cpg driver into a standalone library file.
Introduce the CLK_RZV2H_CPG_LIB Kconfig configuration symbol and create
rzv2h-cpg-lib.c to house rzv2h_cpg_get_pll_pars() and
rzv2h_cpg_get_pll_divs_pars().
Keep rzv2h_get_pll_pars() and rzv2h_get_pll_divs_pars() in the original
driver as wrappers that call into the new library helper endpoints.
These wrappers are maintained for this cycle because they are actively
referenced by the DSI driver; they will be safely removed in a subsequent
cycle once the DSI driver is updated to use the new APIs from the library,
preventing cross-subsystem build breakages.
This restructuring allows other Renesas SoC clock drivers, such as the
upcoming RZ/T2H and RZ/N2H platforms that utilize similar LCDC clock
divider mathematical logic, to share the iterative calculation helper
infrastructure without duplication.
Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>
---
v1->v2:
- New patch
---
drivers/clk/renesas/Kconfig | 4 +
drivers/clk/renesas/Makefile | 1 +
drivers/clk/renesas/rzv2h-cpg-lib.c | 216 ++++++++++++++++++++++++++++
drivers/clk/renesas/rzv2h-cpg.c | 186 +-----------------------
include/linux/clk/renesas.h | 23 +++
5 files changed, 250 insertions(+), 180 deletions(-)
create mode 100644 drivers/clk/renesas/rzv2h-cpg-lib.c
diff --git a/drivers/clk/renesas/Kconfig b/drivers/clk/renesas/Kconfig
index 0203ecbb3882..7659550b8566 100644
--- a/drivers/clk/renesas/Kconfig
+++ b/drivers/clk/renesas/Kconfig
@@ -260,8 +260,12 @@ config CLK_RZG2L
config CLK_RZV2H
bool "RZ/{G3E,V2H(P)} family clock support" if COMPILE_TEST
+ select CLK_RZV2H_CPG_LIB
select RESET_CONTROLLER
+config CLK_RZV2H_CPG_LIB
+ bool "RZV2H CPG library functions" if COMPILE_TEST
+
config CLK_RENESAS_VBATTB
tristate "Renesas VBATTB clock controller"
depends on ARCH_RZG2L || COMPILE_TEST
diff --git a/drivers/clk/renesas/Makefile b/drivers/clk/renesas/Makefile
index bd2bed91ab29..ac790e56034b 100644
--- a/drivers/clk/renesas/Makefile
+++ b/drivers/clk/renesas/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_CLK_RCAR_GEN3_CPG) += rcar-gen3-cpg.o
obj-$(CONFIG_CLK_RCAR_GEN4_CPG) += rcar-gen4-cpg.o
obj-$(CONFIG_CLK_RCAR_USB2_CLOCK_SEL) += rcar-usb2-clock-sel.o
obj-$(CONFIG_CLK_RZG2L) += rzg2l-cpg.o
+obj-$(CONFIG_CLK_RZV2H_CPG_LIB) += rzv2h-cpg-lib.o
obj-$(CONFIG_CLK_RZV2H) += rzv2h-cpg.o
# Generic
diff --git a/drivers/clk/renesas/rzv2h-cpg-lib.c b/drivers/clk/renesas/rzv2h-cpg-lib.c
new file mode 100644
index 000000000000..98e789df5d7f
--- /dev/null
+++ b/drivers/clk/renesas/rzv2h-cpg-lib.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RZV2H CPG Library. This library provides common functions to calculate
+ * PLL parameters for the RZV2H SoC.
+ *
+ * Copyright (C) 2026 Renesas Electronics Corp.
+ *
+ */
+
+#include <linux/clk/renesas.h>
+#include <linux/math.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+/**
+ * rzv2h_cpg_get_pll_pars - Finds the best combination of PLL parameters
+ * for a given frequency.
+ *
+ * @limits: Pointer to the structure containing the limits for the PLL parameters
+ * @pars: Pointer to the structure where the best calculated PLL parameters values
+ * will be stored
+ * @freq_millihz: Target output frequency in millihertz
+ *
+ * This function calculates the best set of PLL parameters (M, K, P, S) to achieve
+ * the desired frequency.
+ * There is no direct formula to calculate the PLL parameters, as it's an open
+ * system of equations, therefore this function uses an iterative approach to
+ * determine the best solution. The best solution is one that minimizes the error
+ * (desired frequency - actual frequency).
+ *
+ * Return: true if a valid set of parameters values is found, false otherwise.
+ */
+bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits,
+ struct rzv2h_pll_pars *pars, u64 freq_millihz)
+{
+ unsigned long input_fref = limits->input_fref ?: (24 * MEGA);
+ u64 fout_min_millihz = mul_u32_u32(limits->fout.min, MILLI);
+ u64 fout_max_millihz = mul_u32_u32(limits->fout.max, MILLI);
+ struct rzv2h_pll_pars p, best;
+
+ if (freq_millihz > fout_max_millihz ||
+ freq_millihz < fout_min_millihz)
+ return false;
+
+ /* Initialize best error to maximum possible value */
+ best.error_millihz = S64_MAX;
+
+ for (p.p = limits->p.min; p.p <= limits->p.max; p.p++) {
+ u32 fref = input_fref / p.p;
+ u16 divider;
+
+ for (divider = 1 << limits->s.min, p.s = limits->s.min;
+ p.s <= limits->s.max; p.s++, divider <<= 1) {
+ for (p.m = limits->m.min; p.m <= limits->m.max; p.m++) {
+ u64 output_m, output_k_range;
+ s64 pll_k, output_k;
+ u64 fvco, output;
+
+ /*
+ * The frequency generated by the PLL + divider
+ * is calculated as follows:
+ *
+ * With:
+ * Freq = Ffout = Ffvco / 2^(pll_s)
+ * Ffvco = (pll_m + (pll_k / 65536)) * Ffref
+ * Ffref = 24MHz / pll_p
+ *
+ * Freq can also be rewritten as:
+ * Freq = Ffvco / 2^(pll_s)
+ * = ((pll_m + (pll_k / 65536)) * Ffref) / 2^(pll_s)
+ * = (pll_m * Ffref) / 2^(pll_s) + ((pll_k / 65536) * Ffref) / 2^(pll_s)
+ * = output_m + output_k
+ *
+ * Every parameter has been determined at this
+ * point, but pll_k.
+ *
+ * Considering that:
+ * limits->k.min <= pll_k <= limits->k.max
+ * Then:
+ * -0.5 <= (pll_k / 65536) < 0.5
+ * Therefore:
+ * -Ffref / (2 * 2^(pll_s)) <= output_k < Ffref / (2 * 2^(pll_s))
+ */
+
+ /* Compute output M component (in mHz) */
+ output_m = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(p.m, fref) * MILLI,
+ divider);
+ /* Compute range for output K (in mHz) */
+ output_k_range = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(fref, MILLI),
+ 2 * divider);
+ /*
+ * No point in continuing if we can't achieve
+ * the desired frequency
+ */
+ if (freq_millihz < (output_m - output_k_range) ||
+ freq_millihz >= (output_m + output_k_range)) {
+ continue;
+ }
+
+ /*
+ * Compute the K component
+ *
+ * Since:
+ * Freq = output_m + output_k
+ * Then:
+ * output_k = Freq - output_m
+ * = ((pll_k / 65536) * Ffref) / 2^(pll_s)
+ * Therefore:
+ * pll_k = (output_k * 65536 * 2^(pll_s)) / Ffref
+ */
+ output_k = freq_millihz - output_m;
+ pll_k = div_s64(output_k * 65536ULL * divider,
+ fref);
+ pll_k = DIV_S64_ROUND_CLOSEST(pll_k, MILLI);
+
+ /* Validate K value within allowed limits */
+ if (pll_k < limits->k.min ||
+ pll_k > limits->k.max)
+ continue;
+
+ p.k = pll_k;
+
+ /* Compute (Ffvco * 65536) */
+ fvco = mul_u32_u32(p.m * 65536 + p.k, fref);
+ if (fvco < mul_u32_u32(limits->fvco.min, 65536) ||
+ fvco > mul_u32_u32(limits->fvco.max, 65536))
+ continue;
+
+ /* PLL_M component of (output * 65536 * PLL_P) */
+ output = mul_u32_u32(p.m * 65536, input_fref);
+ /* PLL_K component of (output * 65536 * PLL_P) */
+ output += p.k * input_fref;
+ /* Make it in mHz */
+ output *= MILLI;
+ output = DIV_U64_ROUND_CLOSEST(output, 65536 * p.p * divider);
+
+ /* Check output frequency against limits */
+ if (output < fout_min_millihz ||
+ output > fout_max_millihz)
+ continue;
+
+ p.error_millihz = freq_millihz - output;
+ p.freq_millihz = output;
+
+ /* If an exact match is found, return immediately */
+ if (p.error_millihz == 0) {
+ *pars = p;
+ return true;
+ }
+
+ /* Update best match if error is smaller */
+ if (abs(best.error_millihz) > abs(p.error_millihz))
+ best = p;
+ }
+ }
+ }
+
+ /* If no valid parameters were found, return false */
+ if (best.error_millihz == S64_MAX)
+ return false;
+
+ *pars = best;
+ return true;
+}
+EXPORT_SYMBOL_NS_GPL(rzv2h_cpg_get_pll_pars, "RZV2H_CPG");
+
+/*
+ * rzv2h_cpg_get_pll_divs_pars - Finds the best combination of PLL parameters
+ * and divider value for a given frequency.
+ *
+ * @limits: Pointer to the structure containing the limits for the PLL parameters
+ * @pars: Pointer to the structure where the best calculated PLL parameters and
+ * divider values will be stored
+ * @table: Pointer to the array of valid divider values
+ * @table_size: Size of the divider values array
+ * @freq_millihz: Target output frequency in millihertz
+ *
+ * This function calculates the best set of PLL parameters (M, K, P, S) and divider
+ * value to achieve the desired frequency. See rzv2h_cpg_get_pll_pars() for more
+ * details on how the PLL parameters are calculated.
+ *
+ * freq_millihz is the desired frequency generated by the PLL followed by a
+ * a gear.
+ */
+bool rzv2h_cpg_get_pll_divs_pars(const struct rzv2h_pll_limits *limits,
+ struct rzv2h_pll_div_pars *pars,
+ const u8 *table, u8 table_size, u64 freq_millihz)
+{
+ struct rzv2h_pll_div_pars p, best;
+
+ best.div.error_millihz = S64_MAX;
+ p.div.error_millihz = S64_MAX;
+ for (unsigned int i = 0; i < table_size; i++) {
+ if (!rzv2h_cpg_get_pll_pars(limits, &p.pll, freq_millihz * table[i]))
+ continue;
+
+ p.div.divider_value = table[i];
+ p.div.freq_millihz = DIV_U64_ROUND_CLOSEST(p.pll.freq_millihz, table[i]);
+ p.div.error_millihz = freq_millihz - p.div.freq_millihz;
+
+ if (p.div.error_millihz == 0) {
+ *pars = p;
+ return true;
+ }
+
+ if (abs(best.div.error_millihz) > abs(p.div.error_millihz))
+ best = p;
+ }
+
+ if (best.div.error_millihz == S64_MAX)
+ return false;
+
+ *pars = best;
+ return true;
+}
+EXPORT_SYMBOL_NS_GPL(rzv2h_cpg_get_pll_divs_pars, "RZV2H_CPG");
diff --git a/drivers/clk/renesas/rzv2h-cpg.c b/drivers/clk/renesas/rzv2h-cpg.c
index fff89f2bdc0b..c2f018e27521 100644
--- a/drivers/clk/renesas/rzv2h-cpg.c
+++ b/drivers/clk/renesas/rzv2h-cpg.c
@@ -224,151 +224,13 @@ struct rzv2h_plldsi_div_clk {
* rzv2h_get_pll_pars - Finds the best combination of PLL parameters
* for a given frequency.
*
- * @limits: Pointer to the structure containing the limits for the PLL parameters
- * @pars: Pointer to the structure where the best calculated PLL parameters values
- * will be stored
- * @freq_millihz: Target output frequency in millihertz
- *
- * This function calculates the best set of PLL parameters (M, K, P, S) to achieve
- * the desired frequency.
- * There is no direct formula to calculate the PLL parameters, as it's an open
- * system of equations, therefore this function uses an iterative approach to
- * determine the best solution. The best solution is one that minimizes the error
- * (desired frequency - actual frequency).
- *
- * Return: true if a valid set of parameters values is found, false otherwise.
+ * Refer to rzv2h_cpg_get_pll_pars() for more details on how the PLL
+ * parameters are calculated.
*/
bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits,
struct rzv2h_pll_pars *pars, u64 freq_millihz)
{
- unsigned long input_fref = limits->input_fref ?: (24 * MEGA);
- u64 fout_min_millihz = mul_u32_u32(limits->fout.min, MILLI);
- u64 fout_max_millihz = mul_u32_u32(limits->fout.max, MILLI);
- struct rzv2h_pll_pars p, best;
-
- if (freq_millihz > fout_max_millihz ||
- freq_millihz < fout_min_millihz)
- return false;
-
- /* Initialize best error to maximum possible value */
- best.error_millihz = S64_MAX;
-
- for (p.p = limits->p.min; p.p <= limits->p.max; p.p++) {
- u32 fref = input_fref / p.p;
- u16 divider;
-
- for (divider = 1 << limits->s.min, p.s = limits->s.min;
- p.s <= limits->s.max; p.s++, divider <<= 1) {
- for (p.m = limits->m.min; p.m <= limits->m.max; p.m++) {
- u64 output_m, output_k_range;
- s64 pll_k, output_k;
- u64 fvco, output;
-
- /*
- * The frequency generated by the PLL + divider
- * is calculated as follows:
- *
- * With:
- * Freq = Ffout = Ffvco / 2^(pll_s)
- * Ffvco = (pll_m + (pll_k / 65536)) * Ffref
- * Ffref = 24MHz / pll_p
- *
- * Freq can also be rewritten as:
- * Freq = Ffvco / 2^(pll_s)
- * = ((pll_m + (pll_k / 65536)) * Ffref) / 2^(pll_s)
- * = (pll_m * Ffref) / 2^(pll_s) + ((pll_k / 65536) * Ffref) / 2^(pll_s)
- * = output_m + output_k
- *
- * Every parameter has been determined at this
- * point, but pll_k.
- *
- * Considering that:
- * limits->k.min <= pll_k <= limits->k.max
- * Then:
- * -0.5 <= (pll_k / 65536) < 0.5
- * Therefore:
- * -Ffref / (2 * 2^(pll_s)) <= output_k < Ffref / (2 * 2^(pll_s))
- */
-
- /* Compute output M component (in mHz) */
- output_m = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(p.m, fref) * MILLI,
- divider);
- /* Compute range for output K (in mHz) */
- output_k_range = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(fref, MILLI),
- 2 * divider);
- /*
- * No point in continuing if we can't achieve
- * the desired frequency
- */
- if (freq_millihz < (output_m - output_k_range) ||
- freq_millihz >= (output_m + output_k_range)) {
- continue;
- }
-
- /*
- * Compute the K component
- *
- * Since:
- * Freq = output_m + output_k
- * Then:
- * output_k = Freq - output_m
- * = ((pll_k / 65536) * Ffref) / 2^(pll_s)
- * Therefore:
- * pll_k = (output_k * 65536 * 2^(pll_s)) / Ffref
- */
- output_k = freq_millihz - output_m;
- pll_k = div_s64(output_k * 65536ULL * divider,
- fref);
- pll_k = DIV_S64_ROUND_CLOSEST(pll_k, MILLI);
-
- /* Validate K value within allowed limits */
- if (pll_k < limits->k.min ||
- pll_k > limits->k.max)
- continue;
-
- p.k = pll_k;
-
- /* Compute (Ffvco * 65536) */
- fvco = mul_u32_u32(p.m * 65536 + p.k, fref);
- if (fvco < mul_u32_u32(limits->fvco.min, 65536) ||
- fvco > mul_u32_u32(limits->fvco.max, 65536))
- continue;
-
- /* PLL_M component of (output * 65536 * PLL_P) */
- output = mul_u32_u32(p.m * 65536, input_fref);
- /* PLL_K component of (output * 65536 * PLL_P) */
- output += p.k * input_fref;
- /* Make it in mHz */
- output *= MILLI;
- output = DIV_U64_ROUND_CLOSEST(output, 65536 * p.p * divider);
-
- /* Check output frequency against limits */
- if (output < fout_min_millihz ||
- output > fout_max_millihz)
- continue;
-
- p.error_millihz = freq_millihz - output;
- p.freq_millihz = output;
-
- /* If an exact match is found, return immediately */
- if (p.error_millihz == 0) {
- *pars = p;
- return true;
- }
-
- /* Update best match if error is smaller */
- if (abs(best.error_millihz) > abs(p.error_millihz))
- best = p;
- }
- }
- }
-
- /* If no valid parameters were found, return false */
- if (best.error_millihz == S64_MAX)
- return false;
-
- *pars = best;
- return true;
+ return rzv2h_cpg_get_pll_pars(limits, pars, freq_millihz);
}
EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_pars, "RZV2H_CPG");
@@ -376,50 +238,14 @@ EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_pars, "RZV2H_CPG");
* rzv2h_get_pll_divs_pars - Finds the best combination of PLL parameters
* and divider value for a given frequency.
*
- * @limits: Pointer to the structure containing the limits for the PLL parameters
- * @pars: Pointer to the structure where the best calculated PLL parameters and
- * divider values will be stored
- * @table: Pointer to the array of valid divider values
- * @table_size: Size of the divider values array
- * @freq_millihz: Target output frequency in millihertz
- *
- * This function calculates the best set of PLL parameters (M, K, P, S) and divider
- * value to achieve the desired frequency. See rzv2h_get_pll_pars() for more details
- * on how the PLL parameters are calculated.
- *
- * freq_millihz is the desired frequency generated by the PLL followed by a
- * a gear.
+ * Refer to rzv2h_cpg_get_pll_divs_pars() for more details on how the PLL
+ * parameters and divider value are calculated.
*/
bool rzv2h_get_pll_divs_pars(const struct rzv2h_pll_limits *limits,
struct rzv2h_pll_div_pars *pars,
const u8 *table, u8 table_size, u64 freq_millihz)
{
- struct rzv2h_pll_div_pars p, best;
-
- best.div.error_millihz = S64_MAX;
- p.div.error_millihz = S64_MAX;
- for (unsigned int i = 0; i < table_size; i++) {
- if (!rzv2h_get_pll_pars(limits, &p.pll, freq_millihz * table[i]))
- continue;
-
- p.div.divider_value = table[i];
- p.div.freq_millihz = DIV_U64_ROUND_CLOSEST(p.pll.freq_millihz, table[i]);
- p.div.error_millihz = freq_millihz - p.div.freq_millihz;
-
- if (p.div.error_millihz == 0) {
- *pars = p;
- return true;
- }
-
- if (abs(best.div.error_millihz) > abs(p.div.error_millihz))
- best = p;
- }
-
- if (best.div.error_millihz == S64_MAX)
- return false;
-
- *pars = best;
- return true;
+ return rzv2h_cpg_get_pll_divs_pars(limits, pars, table, table_size, freq_millihz);
}
EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_divs_pars, "RZV2H_CPG");
diff --git a/include/linux/clk/renesas.h b/include/linux/clk/renesas.h
index 2aeff01150c3..98d9ab38e027 100644
--- a/include/linux/clk/renesas.h
+++ b/include/linux/clk/renesas.h
@@ -213,4 +213,27 @@ static inline bool rzv2h_get_pll_divs_pars(const struct rzv2h_pll_limits *limits
}
#endif
+#ifdef CONFIG_CLK_RZV2H_CPG_LIB
+bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits,
+ struct rzv2h_pll_pars *pars, u64 freq_millihz);
+
+bool rzv2h_cpg_get_pll_divs_pars(const struct rzv2h_pll_limits *limits,
+ struct rzv2h_pll_div_pars *pars,
+ const u8 *table, u8 table_size, u64 freq_millihz);
+#else
+static inline bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits,
+ struct rzv2h_pll_pars *pars,
+ u64 freq_millihz)
+{
+ return false;
+}
+
+static inline bool rzv2h_cpg_get_pll_divs_pars(const struct rzv2h_pll_limits *limits,
+ struct rzv2h_pll_div_pars *pars,
+ const u8 *table, u8 table_size,
+ u64 freq_millihz)
+{
+ return false;
+}
+#endif
#endif
--
2.54.0