[PATCH 2/4] clk: renesas: rzv2h-cpg: Add validation hook to PLL parameter search

From: Prabhakar

Date: Tue Nov 03 2026 - 12:21:32 EST


From: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>

rzv2h_get_pll_pars() returns the candidate with the smallest frequency
error, which can be unusable for callers whose constraints come from
downstream PLLs, such as the MIPI DSI driver.

Add rzv2h_cpg_get_pll_pars_validate(), which takes a callback and its
context. The callback is invoked for every candidate in search order and
the first one it accepts is returned, which is not necessarily the one
with the smallest error. Rejected candidates are never returned, so
false means none was accepted. rzv2h_get_pll_pars() becomes a wrapper
passing a NULL validator, so its behaviour is unchanged.

Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>
Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@xxxxxxxxxxxxxx>
---
drivers/clk/renesas/rzv2h-cpg-lib.c | 61 +++++++++++++++++++++++++----
include/linux/clk/renesas.h | 22 +++++++++++
2 files changed, 75 insertions(+), 8 deletions(-)

diff --git a/drivers/clk/renesas/rzv2h-cpg-lib.c b/drivers/clk/renesas/rzv2h-cpg-lib.c
index 4b59bf60edb0..a49add2f2fc3 100644
--- a/drivers/clk/renesas/rzv2h-cpg-lib.c
+++ b/drivers/clk/renesas/rzv2h-cpg-lib.c
@@ -62,31 +62,47 @@ struct rzv2h_pll_div_pars rzv2h_cpg_get_cached_pll_pars(unsigned int dsi_instanc
}

/**
- * rzv2h_cpg_get_pll_pars - Finds the best combination of PLL parameters
- * for a given frequency.
+ * rzv2h_cpg_get_pll_pars_validate - Finds a combination of PLL parameters
+ * for a given frequency, optionally accepted by a validation callback.
*
* @limits: Pointer to the structure containing the limits for the PLL parameters
- * @pars: Pointer to the structure where the best calculated PLL parameters values
+ * @pars: Pointer to the structure where the calculated PLL parameters values
* will be stored
* @freq_millihz: Target output frequency in millihertz
+ * @validator: Pointer to a structure containing a validation callback and
+ * context, or NULL
*
- * This function calculates the best set of PLL parameters (M, K, P, S) to achieve
+ * This function calculates a 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).
+ * determine the solution.
+ *
+ * When validator is NULL, the function searches for the solution that minimizes
+ * the error (desired frequency - actual frequency) and returns the best match.
+ *
+ * When validator is provided, the callback is invoked for every candidate in
+ * search order and the first one it accepts is returned, even if it is not the
+ * one with the smallest error. Candidates the callback rejects are never
+ * returned, so false is returned when none is accepted. This allows additional
+ * constraints beyond frequency matching to be satisfied (e.g., derived PLL
+ * parameters must also meet certain criteria). The callback runs once per
+ * candidate, so it must be cheap and must not sleep.
*
* 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)
+bool rzv2h_cpg_get_pll_pars_validate(const struct rzv2h_pll_limits *limits,
+ struct rzv2h_pll_pars *pars, u64 freq_millihz,
+ const struct rzv2h_pll_validator *validator)
{
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 (WARN_ON(validator && !validator->validate))
+ return false;
+
if (freq_millihz > fout_max_millihz ||
freq_millihz < fout_min_millihz)
return false;
@@ -191,6 +207,15 @@ bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits,
p.error_millihz = freq_millihz - output;
p.freq_millihz = output;

+ /* Take the first set the caller accepts */
+ if (validator) {
+ if (validator->validate(&p, validator->context)) {
+ *pars = p;
+ return true;
+ }
+ continue;
+ }
+
/* If an exact match is found, return immediately */
if (p.error_millihz == 0) {
*pars = p;
@@ -211,6 +236,26 @@ bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits,
*pars = best;
return true;
}
+EXPORT_SYMBOL_NS_GPL(rzv2h_cpg_get_pll_pars_validate, "RZV2H_CPG");
+
+/**
+ * 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
+ *
+ * See rzv2h_cpg_get_pll_pars_validate() for more details.
+ *
+ * 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)
+{
+ return rzv2h_cpg_get_pll_pars_validate(limits, pars, freq_millihz, NULL);
+}
EXPORT_SYMBOL_NS_GPL(rzv2h_cpg_get_pll_pars, "RZV2H_CPG");

/*
diff --git a/include/linux/clk/renesas.h b/include/linux/clk/renesas.h
index c1582ed9bf69..c846d926020c 100644
--- a/include/linux/clk/renesas.h
+++ b/include/linux/clk/renesas.h
@@ -192,10 +192,25 @@ struct rzv2h_pll_div_pars {
.k = { .min = -32768, .max = 32767 }, \
} \

+/**
+ * struct rzv2h_pll_validator - Callback structure for custom PLL parameter validation
+ *
+ * @validate: Validation function that checks if the computed PLL parameters are acceptable
+ * @context: Opaque pointer to any data needed by the validation function
+ */
+struct rzv2h_pll_validator {
+ bool (*validate)(const struct rzv2h_pll_pars *pars, void *context);
+ void *context;
+};
+
#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_pars_validate(const struct rzv2h_pll_limits *limits,
+ struct rzv2h_pll_pars *pars, u64 freq_millihz,
+ const struct rzv2h_pll_validator *validator);
+
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);
@@ -210,6 +225,13 @@ static inline bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits,
return false;
}

+static inline bool rzv2h_cpg_get_pll_pars_validate(const struct rzv2h_pll_limits *limits,
+ struct rzv2h_pll_pars *pars, u64 freq_millihz,
+ const struct rzv2h_pll_validator *validator)
+{
+ 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,
--
2.56.0