xref: /linux/drivers/clk/renesas/rzv2h-cpg-lib.c (revision 502d45774af09f1c681c754c4b7cdfb5d7f72fd9)
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