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