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