clk: renesas: rzv2h-cpg: Extract PLL calculation helpers into shared library

Move the RZ/V2H PLL and divider parameter calculation helpers from
rzv2h-cpg.c into a new reusable library.

Introduce the CLK_RZV2H_CPG_LIB Kconfig symbol and add
rzv2h-cpg-lib.c to host the PLL parameter search algorithms currently
implemented by rzv2h_get_pll_pars() and rzv2h_get_pll_divs_pars().
Export the helpers as rzv2h_cpg_get_pll_pars() and
rzv2h_cpg_get_pll_divs_pars() for use by other drivers.

Update the public clock header to expose the new interfaces and provide
compatibility aliases for the existing helper names, avoiding build
breakage for current users while allowing future conversions to the new
API.

This prepares for reuse of the PLL and divider calculation logic by
other Renesas clock drivers, including upcoming RZ/T2H and RZ/N2H CPG
support, without duplicating the implementation.

Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Link: https://patch.msgid.link/20260618181949.3036280-5-prabhakar.mahadev-lad.rj@bp.renesas.com
Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
This commit is contained in:
Lad Prabhakar
2026-06-30 11:55:37 +02:00
committed by Geert Uytterhoeven
parent 4f4230ff5d
commit 73c360100d
5 changed files with 238 additions and 216 deletions
+4
View File
@@ -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
+1
View File
@@ -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
+217
View File
@@ -0,0 +1,217 @@
// 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/export.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");
-203
View File
@@ -220,209 +220,6 @@ struct rzv2h_plldsi_div_clk {
#define RZV2H_MAX_DIV_TABLES (16)
/**
* 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.
*/
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;
}
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.
*/
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;
}
EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_divs_pars, "RZV2H_CPG");
/**
* struct rzv2h_plldsi_mux_clk - PLL DSI MUX clock
*
+16 -13
View File
@@ -189,28 +189,31 @@ struct rzv2h_pll_div_pars {
.k = { .min = -32768, .max = 32767 }, \
} \
#ifdef CONFIG_CLK_RZV2H
bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits,
struct rzv2h_pll_pars *pars, u64 freq_millihz);
#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_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);
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_get_pll_pars(const struct rzv2h_pll_limits *limits,
struct rzv2h_pll_pars *pars,
u64 freq_millihz)
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_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)
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
#define rzv2h_get_pll_pars rzv2h_cpg_get_pll_pars
#define rzv2h_get_pll_divs_pars rzv2h_cpg_get_pll_divs_pars
#endif