1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * RZV2H CPG Library. This library provides common functions to calculate
4 * PLL parameters for the RZV2H SoC.
5 *
6 * Copyright (C) 2026 Renesas Electronics Corp.
7 *
8 */
9
10 #include <linux/clk/renesas.h>
11 #include <linux/export.h>
12 #include <linux/math.h>
13 #include <linux/types.h>
14 #include <linux/units.h>
15
16 /**
17 * rzv2h_cpg_get_pll_pars - Finds the best combination of PLL parameters
18 * for a given frequency.
19 *
20 * @limits: Pointer to the structure containing the limits for the PLL parameters
21 * @pars: Pointer to the structure where the best calculated PLL parameters values
22 * will be stored
23 * @freq_millihz: Target output frequency in millihertz
24 *
25 * This function calculates the best set of PLL parameters (M, K, P, S) to achieve
26 * the desired frequency.
27 * There is no direct formula to calculate the PLL parameters, as it's an open
28 * system of equations, therefore this function uses an iterative approach to
29 * determine the best solution. The best solution is one that minimizes the error
30 * (desired frequency - actual frequency).
31 *
32 * Return: true if a valid set of parameters values is found, false otherwise.
33 */
rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits * limits,struct rzv2h_pll_pars * pars,u64 freq_millihz)34 bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits,
35 struct rzv2h_pll_pars *pars, u64 freq_millihz)
36 {
37 unsigned long input_fref = limits->input_fref ?: (24 * MEGA);
38 u64 fout_min_millihz = mul_u32_u32(limits->fout.min, MILLI);
39 u64 fout_max_millihz = mul_u32_u32(limits->fout.max, MILLI);
40 struct rzv2h_pll_pars p, best;
41
42 if (freq_millihz > fout_max_millihz ||
43 freq_millihz < fout_min_millihz)
44 return false;
45
46 /* Initialize best error to maximum possible value */
47 best.error_millihz = S64_MAX;
48
49 for (p.p = limits->p.min; p.p <= limits->p.max; p.p++) {
50 u32 fref = input_fref / p.p;
51 u16 divider;
52
53 for (divider = 1 << limits->s.min, p.s = limits->s.min;
54 p.s <= limits->s.max; p.s++, divider <<= 1) {
55 for (p.m = limits->m.min; p.m <= limits->m.max; p.m++) {
56 u64 output_m, output_k_range;
57 s64 pll_k, output_k;
58 u64 fvco, output;
59
60 /*
61 * The frequency generated by the PLL + divider
62 * is calculated as follows:
63 *
64 * With:
65 * Freq = Ffout = Ffvco / 2^(pll_s)
66 * Ffvco = (pll_m + (pll_k / 65536)) * Ffref
67 * Ffref = 24MHz / pll_p
68 *
69 * Freq can also be rewritten as:
70 * Freq = Ffvco / 2^(pll_s)
71 * = ((pll_m + (pll_k / 65536)) * Ffref) / 2^(pll_s)
72 * = (pll_m * Ffref) / 2^(pll_s) + ((pll_k / 65536) * Ffref) / 2^(pll_s)
73 * = output_m + output_k
74 *
75 * Every parameter has been determined at this
76 * point, but pll_k.
77 *
78 * Considering that:
79 * limits->k.min <= pll_k <= limits->k.max
80 * Then:
81 * -0.5 <= (pll_k / 65536) < 0.5
82 * Therefore:
83 * -Ffref / (2 * 2^(pll_s)) <= output_k < Ffref / (2 * 2^(pll_s))
84 */
85
86 /* Compute output M component (in mHz) */
87 output_m = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(p.m, fref) * MILLI,
88 divider);
89 /* Compute range for output K (in mHz) */
90 output_k_range = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(fref, MILLI),
91 2 * divider);
92 /*
93 * No point in continuing if we can't achieve
94 * the desired frequency
95 */
96 if (freq_millihz < (output_m - output_k_range) ||
97 freq_millihz >= (output_m + output_k_range)) {
98 continue;
99 }
100
101 /*
102 * Compute the K component
103 *
104 * Since:
105 * Freq = output_m + output_k
106 * Then:
107 * output_k = Freq - output_m
108 * = ((pll_k / 65536) * Ffref) / 2^(pll_s)
109 * Therefore:
110 * pll_k = (output_k * 65536 * 2^(pll_s)) / Ffref
111 */
112 output_k = freq_millihz - output_m;
113 pll_k = div_s64(output_k * 65536ULL * divider,
114 fref);
115 pll_k = DIV_S64_ROUND_CLOSEST(pll_k, MILLI);
116
117 /* Validate K value within allowed limits */
118 if (pll_k < limits->k.min ||
119 pll_k > limits->k.max)
120 continue;
121
122 p.k = pll_k;
123
124 /* Compute (Ffvco * 65536) */
125 fvco = mul_u32_u32(p.m * 65536 + p.k, fref);
126 if (fvco < mul_u32_u32(limits->fvco.min, 65536) ||
127 fvco > mul_u32_u32(limits->fvco.max, 65536))
128 continue;
129
130 /* PLL_M component of (output * 65536 * PLL_P) */
131 output = mul_u32_u32(p.m * 65536, input_fref);
132 /* PLL_K component of (output * 65536 * PLL_P) */
133 output += p.k * input_fref;
134 /* Make it in mHz */
135 output *= MILLI;
136 output = DIV_U64_ROUND_CLOSEST(output, 65536 * p.p * divider);
137
138 /* Check output frequency against limits */
139 if (output < fout_min_millihz ||
140 output > fout_max_millihz)
141 continue;
142
143 p.error_millihz = freq_millihz - output;
144 p.freq_millihz = output;
145
146 /* If an exact match is found, return immediately */
147 if (p.error_millihz == 0) {
148 *pars = p;
149 return true;
150 }
151
152 /* Update best match if error is smaller */
153 if (abs(best.error_millihz) > abs(p.error_millihz))
154 best = p;
155 }
156 }
157 }
158
159 /* If no valid parameters were found, return false */
160 if (best.error_millihz == S64_MAX)
161 return false;
162
163 *pars = best;
164 return true;
165 }
166 EXPORT_SYMBOL_NS_GPL(rzv2h_cpg_get_pll_pars, "RZV2H_CPG");
167
168 /*
169 * rzv2h_cpg_get_pll_divs_pars - Finds the best combination of PLL parameters
170 * and divider value for a given frequency.
171 *
172 * @limits: Pointer to the structure containing the limits for the PLL parameters
173 * @pars: Pointer to the structure where the best calculated PLL parameters and
174 * divider values will be stored
175 * @table: Pointer to the array of valid divider values
176 * @table_size: Size of the divider values array
177 * @freq_millihz: Target output frequency in millihertz
178 *
179 * This function calculates the best set of PLL parameters (M, K, P, S) and divider
180 * value to achieve the desired frequency. See rzv2h_cpg_get_pll_pars() for more
181 * details on how the PLL parameters are calculated.
182 *
183 * freq_millihz is the desired frequency generated by the PLL followed by a
184 * a gear.
185 */
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)186 bool rzv2h_cpg_get_pll_divs_pars(const struct rzv2h_pll_limits *limits,
187 struct rzv2h_pll_div_pars *pars,
188 const u8 *table, u8 table_size, u64 freq_millihz)
189 {
190 struct rzv2h_pll_div_pars p, best;
191
192 best.div.error_millihz = S64_MAX;
193 p.div.error_millihz = S64_MAX;
194 for (unsigned int i = 0; i < table_size; i++) {
195 if (!rzv2h_cpg_get_pll_pars(limits, &p.pll, freq_millihz * table[i]))
196 continue;
197
198 p.div.divider_value = table[i];
199 p.div.freq_millihz = DIV_U64_ROUND_CLOSEST(p.pll.freq_millihz, table[i]);
200 p.div.error_millihz = freq_millihz - p.div.freq_millihz;
201
202 if (p.div.error_millihz == 0) {
203 *pars = p;
204 return true;
205 }
206
207 if (abs(best.div.error_millihz) > abs(p.div.error_millihz))
208 best = p;
209 }
210
211 if (best.div.error_millihz == S64_MAX)
212 return false;
213
214 *pars = best;
215 return true;
216 }
217 EXPORT_SYMBOL_NS_GPL(rzv2h_cpg_get_pll_divs_pars, "RZV2H_CPG");
218