// 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");