Commit | Line | Data |
---|---|---|
7b9487a9 PW |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (C) 2018-2019 SiFive, Inc. | |
4 | * Wesley Terpstra | |
5 | * Paul Walmsley | |
6 | * | |
7 | * This library supports configuration parsing and reprogramming of | |
8 | * the CLN28HPC variant of the Analog Bits Wide Range PLL. The | |
9 | * intention is for this library to be reusable for any device that | |
10 | * integrates this PLL; thus the register structure and programming | |
11 | * details are expected to be provided by a separate IP block driver. | |
12 | * | |
13 | * The bulk of this code is primarily useful for clock configurations | |
14 | * that must operate at arbitrary rates, as opposed to clock configurations | |
15 | * that are restricted by software or manufacturer guidance to a small, | |
16 | * pre-determined set of performance points. | |
17 | * | |
18 | * References: | |
19 | * - Analog Bits "Wide Range PLL Datasheet", version 2015.10.01 | |
20 | * - SiFive FU540-C000 Manual v1p0, Chapter 7 "Clocking and Reset" | |
21 | * https://static.dev.sifive.com/FU540-C000-v1.0.pdf | |
22 | */ | |
23 | ||
24 | #include <linux/bug.h> | |
25 | #include <linux/err.h> | |
f39650de | 26 | #include <linux/limits.h> |
7b9487a9 PW |
27 | #include <linux/log2.h> |
28 | #include <linux/math64.h> | |
f39650de AS |
29 | #include <linux/math.h> |
30 | #include <linux/minmax.h> | |
ee58d6a1 | 31 | #include <linux/module.h> |
f39650de | 32 | |
7b9487a9 PW |
33 | #include <linux/clk/analogbits-wrpll-cln28hpc.h> |
34 | ||
35 | /* MIN_INPUT_FREQ: minimum input clock frequency, in Hz (Fref_min) */ | |
36 | #define MIN_INPUT_FREQ 7000000 | |
37 | ||
38 | /* MAX_INPUT_FREQ: maximum input clock frequency, in Hz (Fref_max) */ | |
39 | #define MAX_INPUT_FREQ 600000000 | |
40 | ||
41 | /* MIN_POST_DIVIDE_REF_FREQ: minimum post-divider reference frequency, in Hz */ | |
42 | #define MIN_POST_DIVR_FREQ 7000000 | |
43 | ||
44 | /* MAX_POST_DIVIDE_REF_FREQ: maximum post-divider reference frequency, in Hz */ | |
45 | #define MAX_POST_DIVR_FREQ 200000000 | |
46 | ||
47 | /* MIN_VCO_FREQ: minimum VCO frequency, in Hz (Fvco_min) */ | |
48 | #define MIN_VCO_FREQ 2400000000UL | |
49 | ||
50 | /* MAX_VCO_FREQ: maximum VCO frequency, in Hz (Fvco_max) */ | |
51 | #define MAX_VCO_FREQ 4800000000ULL | |
52 | ||
53 | /* MAX_DIVQ_DIVISOR: maximum output divisor. Selected by DIVQ = 6 */ | |
54 | #define MAX_DIVQ_DIVISOR 64 | |
55 | ||
56 | /* MAX_DIVR_DIVISOR: maximum reference divisor. Selected by DIVR = 63 */ | |
57 | #define MAX_DIVR_DIVISOR 64 | |
58 | ||
59 | /* MAX_LOCK_US: maximum PLL lock time, in microseconds (tLOCK_max) */ | |
60 | #define MAX_LOCK_US 70 | |
61 | ||
62 | /* | |
63 | * ROUND_SHIFT: number of bits to shift to avoid precision loss in the rounding | |
64 | * algorithm | |
65 | */ | |
66 | #define ROUND_SHIFT 20 | |
67 | ||
68 | /* | |
69 | * Private functions | |
70 | */ | |
71 | ||
72 | /** | |
73 | * __wrpll_calc_filter_range() - determine PLL loop filter bandwidth | |
74 | * @post_divr_freq: input clock rate after the R divider | |
75 | * | |
76 | * Select the value to be presented to the PLL RANGE input signals, based | |
77 | * on the input clock frequency after the post-R-divider @post_divr_freq. | |
78 | * This code follows the recommendations in the PLL datasheet for filter | |
79 | * range selection. | |
80 | * | |
81 | * Return: The RANGE value to be presented to the PLL configuration inputs, | |
82 | * or a negative return code upon error. | |
83 | */ | |
84 | static int __wrpll_calc_filter_range(unsigned long post_divr_freq) | |
85 | { | |
86 | if (post_divr_freq < MIN_POST_DIVR_FREQ || | |
87 | post_divr_freq > MAX_POST_DIVR_FREQ) { | |
88 | WARN(1, "%s: post-divider reference freq out of range: %lu", | |
89 | __func__, post_divr_freq); | |
90 | return -ERANGE; | |
91 | } | |
92 | ||
93 | switch (post_divr_freq) { | |
94 | case 0 ... 10999999: | |
95 | return 1; | |
96 | case 11000000 ... 17999999: | |
97 | return 2; | |
98 | case 18000000 ... 29999999: | |
99 | return 3; | |
100 | case 30000000 ... 49999999: | |
101 | return 4; | |
102 | case 50000000 ... 79999999: | |
103 | return 5; | |
104 | case 80000000 ... 129999999: | |
105 | return 6; | |
106 | } | |
107 | ||
108 | return 7; | |
109 | } | |
110 | ||
111 | /** | |
112 | * __wrpll_calc_fbdiv() - return feedback fixed divide value | |
113 | * @c: ptr to a struct wrpll_cfg record to read from | |
114 | * | |
115 | * The internal feedback path includes a fixed by-two divider; the | |
116 | * external feedback path does not. Return the appropriate divider | |
117 | * value (2 or 1) depending on whether internal or external feedback | |
118 | * is enabled. This code doesn't test for invalid configurations | |
119 | * (e.g. both or neither of WRPLL_FLAGS_*_FEEDBACK are set); it relies | |
120 | * on the caller to do so. | |
121 | * | |
122 | * Context: Any context. Caller must protect the memory pointed to by | |
123 | * @c from simultaneous modification. | |
124 | * | |
125 | * Return: 2 if internal feedback is enabled or 1 if external feedback | |
126 | * is enabled. | |
127 | */ | |
128 | static u8 __wrpll_calc_fbdiv(const struct wrpll_cfg *c) | |
129 | { | |
130 | return (c->flags & WRPLL_FLAGS_INT_FEEDBACK_MASK) ? 2 : 1; | |
131 | } | |
132 | ||
133 | /** | |
134 | * __wrpll_calc_divq() - determine DIVQ based on target PLL output clock rate | |
135 | * @target_rate: target PLL output clock rate | |
136 | * @vco_rate: pointer to a u64 to store the computed VCO rate into | |
137 | * | |
138 | * Determine a reasonable value for the PLL Q post-divider, based on the | |
139 | * target output rate @target_rate for the PLL. Along with returning the | |
140 | * computed Q divider value as the return value, this function stores the | |
141 | * desired target VCO rate into the variable pointed to by @vco_rate. | |
142 | * | |
143 | * Context: Any context. Caller must protect the memory pointed to by | |
144 | * @vco_rate from simultaneous access or modification. | |
145 | * | |
146 | * Return: a positive integer DIVQ value to be programmed into the hardware | |
147 | * upon success, or 0 upon error (since 0 is an invalid DIVQ value) | |
148 | */ | |
149 | static u8 __wrpll_calc_divq(u32 target_rate, u64 *vco_rate) | |
150 | { | |
151 | u64 s; | |
152 | u8 divq = 0; | |
153 | ||
154 | if (!vco_rate) { | |
155 | WARN_ON(1); | |
156 | goto wcd_out; | |
157 | } | |
158 | ||
159 | s = div_u64(MAX_VCO_FREQ, target_rate); | |
160 | if (s <= 1) { | |
161 | divq = 1; | |
162 | *vco_rate = MAX_VCO_FREQ; | |
163 | } else if (s > MAX_DIVQ_DIVISOR) { | |
164 | divq = ilog2(MAX_DIVQ_DIVISOR); | |
165 | *vco_rate = MIN_VCO_FREQ; | |
166 | } else { | |
167 | divq = ilog2(s); | |
168 | *vco_rate = (u64)target_rate << divq; | |
169 | } | |
170 | ||
171 | wcd_out: | |
172 | return divq; | |
173 | } | |
174 | ||
175 | /** | |
176 | * __wrpll_update_parent_rate() - update PLL data when parent rate changes | |
177 | * @c: ptr to a struct wrpll_cfg record to write PLL data to | |
178 | * @parent_rate: PLL input refclk rate (pre-R-divider) | |
179 | * | |
180 | * Pre-compute some data used by the PLL configuration algorithm when | |
181 | * the PLL's reference clock rate changes. The intention is to avoid | |
182 | * computation when the parent rate remains constant - expected to be | |
183 | * the common case. | |
184 | * | |
185 | * Returns: 0 upon success or -ERANGE if the reference clock rate is | |
186 | * out of range. | |
187 | */ | |
188 | static int __wrpll_update_parent_rate(struct wrpll_cfg *c, | |
189 | unsigned long parent_rate) | |
190 | { | |
191 | u8 max_r_for_parent; | |
192 | ||
193 | if (parent_rate > MAX_INPUT_FREQ || parent_rate < MIN_POST_DIVR_FREQ) | |
194 | return -ERANGE; | |
195 | ||
196 | c->parent_rate = parent_rate; | |
197 | max_r_for_parent = div_u64(parent_rate, MIN_POST_DIVR_FREQ); | |
198 | c->max_r = min_t(u8, MAX_DIVR_DIVISOR, max_r_for_parent); | |
199 | ||
200 | c->init_r = DIV_ROUND_UP_ULL(parent_rate, MAX_POST_DIVR_FREQ); | |
201 | ||
202 | return 0; | |
203 | } | |
204 | ||
205 | /** | |
06abc753 | 206 | * wrpll_configure_for_rate() - compute PLL configuration for a target rate |
7b9487a9 PW |
207 | * @c: ptr to a struct wrpll_cfg record to write into |
208 | * @target_rate: target PLL output clock rate (post-Q-divider) | |
209 | * @parent_rate: PLL input refclk rate (pre-R-divider) | |
210 | * | |
211 | * Compute the appropriate PLL signal configuration values and store | |
212 | * in PLL context @c. PLL reprogramming is not glitchless, so the | |
213 | * caller should switch any downstream logic to a different clock | |
214 | * source or clock-gate it before presenting these values to the PLL | |
215 | * configuration signals. | |
216 | * | |
217 | * The caller must pass this function a pre-initialized struct | |
218 | * wrpll_cfg record: either initialized to zero (with the | |
219 | * exception of the .name and .flags fields) or read from the PLL. | |
220 | * | |
221 | * Context: Any context. Caller must protect the memory pointed to by @c | |
222 | * from simultaneous access or modification. | |
223 | * | |
224 | * Return: 0 upon success; anything else upon failure. | |
225 | */ | |
226 | int wrpll_configure_for_rate(struct wrpll_cfg *c, u32 target_rate, | |
227 | unsigned long parent_rate) | |
228 | { | |
229 | unsigned long ratio; | |
230 | u64 target_vco_rate, delta, best_delta, f_pre_div, vco, vco_pre; | |
231 | u32 best_f, f, post_divr_freq; | |
232 | u8 fbdiv, divq, best_r, r; | |
233 | int range; | |
234 | ||
235 | if (c->flags == 0) { | |
236 | WARN(1, "%s called with uninitialized PLL config", __func__); | |
237 | return -EINVAL; | |
238 | } | |
239 | ||
240 | /* Initialize rounding data if it hasn't been initialized already */ | |
241 | if (parent_rate != c->parent_rate) { | |
242 | if (__wrpll_update_parent_rate(c, parent_rate)) { | |
243 | pr_err("%s: PLL input rate is out of range\n", | |
244 | __func__); | |
245 | return -ERANGE; | |
246 | } | |
247 | } | |
248 | ||
249 | c->flags &= ~WRPLL_FLAGS_RESET_MASK; | |
250 | ||
251 | /* Put the PLL into bypass if the user requests the parent clock rate */ | |
252 | if (target_rate == parent_rate) { | |
253 | c->flags |= WRPLL_FLAGS_BYPASS_MASK; | |
254 | return 0; | |
255 | } | |
256 | ||
257 | c->flags &= ~WRPLL_FLAGS_BYPASS_MASK; | |
258 | ||
259 | /* Calculate the Q shift and target VCO rate */ | |
260 | divq = __wrpll_calc_divq(target_rate, &target_vco_rate); | |
261 | if (!divq) | |
262 | return -1; | |
263 | c->divq = divq; | |
264 | ||
265 | /* Precalculate the pre-Q divider target ratio */ | |
266 | ratio = div64_u64((target_vco_rate << ROUND_SHIFT), parent_rate); | |
267 | ||
268 | fbdiv = __wrpll_calc_fbdiv(c); | |
269 | best_r = 0; | |
270 | best_f = 0; | |
271 | best_delta = MAX_VCO_FREQ; | |
272 | ||
273 | /* | |
274 | * Consider all values for R which land within | |
275 | * [MIN_POST_DIVR_FREQ, MAX_POST_DIVR_FREQ]; prefer smaller R | |
276 | */ | |
277 | for (r = c->init_r; r <= c->max_r; ++r) { | |
278 | f_pre_div = ratio * r; | |
279 | f = (f_pre_div + (1 << ROUND_SHIFT)) >> ROUND_SHIFT; | |
280 | f >>= (fbdiv - 1); | |
281 | ||
282 | post_divr_freq = div_u64(parent_rate, r); | |
283 | vco_pre = fbdiv * post_divr_freq; | |
284 | vco = vco_pre * f; | |
285 | ||
286 | /* Ensure rounding didn't take us out of range */ | |
287 | if (vco > target_vco_rate) { | |
288 | --f; | |
289 | vco = vco_pre * f; | |
290 | } else if (vco < MIN_VCO_FREQ) { | |
291 | ++f; | |
292 | vco = vco_pre * f; | |
293 | } | |
294 | ||
295 | delta = abs(target_rate - vco); | |
296 | if (delta < best_delta) { | |
297 | best_delta = delta; | |
298 | best_r = r; | |
299 | best_f = f; | |
300 | } | |
301 | } | |
302 | ||
303 | c->divr = best_r - 1; | |
304 | c->divf = best_f - 1; | |
305 | ||
306 | post_divr_freq = div_u64(parent_rate, best_r); | |
307 | ||
308 | /* Pick the best PLL jitter filter */ | |
309 | range = __wrpll_calc_filter_range(post_divr_freq); | |
310 | if (range < 0) | |
311 | return range; | |
312 | c->range = range; | |
313 | ||
314 | return 0; | |
315 | } | |
ee58d6a1 | 316 | EXPORT_SYMBOL_GPL(wrpll_configure_for_rate); |
7b9487a9 PW |
317 | |
318 | /** | |
319 | * wrpll_calc_output_rate() - calculate the PLL's target output rate | |
320 | * @c: ptr to a struct wrpll_cfg record to read from | |
321 | * @parent_rate: PLL refclk rate | |
322 | * | |
323 | * Given a pointer to the PLL's current input configuration @c and the | |
324 | * PLL's input reference clock rate @parent_rate (before the R | |
325 | * pre-divider), calculate the PLL's output clock rate (after the Q | |
326 | * post-divider). | |
327 | * | |
328 | * Context: Any context. Caller must protect the memory pointed to by @c | |
329 | * from simultaneous modification. | |
330 | * | |
331 | * Return: the PLL's output clock rate, in Hz. The return value from | |
332 | * this function is intended to be convenient to pass directly | |
333 | * to the Linux clock framework; thus there is no explicit | |
334 | * error return value. | |
335 | */ | |
336 | unsigned long wrpll_calc_output_rate(const struct wrpll_cfg *c, | |
337 | unsigned long parent_rate) | |
338 | { | |
339 | u8 fbdiv; | |
340 | u64 n; | |
341 | ||
342 | if (c->flags & WRPLL_FLAGS_EXT_FEEDBACK_MASK) { | |
343 | WARN(1, "external feedback mode not yet supported"); | |
344 | return ULONG_MAX; | |
345 | } | |
346 | ||
347 | fbdiv = __wrpll_calc_fbdiv(c); | |
348 | n = parent_rate * fbdiv * (c->divf + 1); | |
349 | n = div_u64(n, c->divr + 1); | |
350 | n >>= c->divq; | |
351 | ||
352 | return n; | |
353 | } | |
ee58d6a1 | 354 | EXPORT_SYMBOL_GPL(wrpll_calc_output_rate); |
7b9487a9 PW |
355 | |
356 | /** | |
357 | * wrpll_calc_max_lock_us() - return the time for the PLL to lock | |
358 | * @c: ptr to a struct wrpll_cfg record to read from | |
359 | * | |
360 | * Return the minimum amount of time (in microseconds) that the caller | |
361 | * must wait after reprogramming the PLL to ensure that it is locked | |
362 | * to the input frequency and stable. This is likely to depend on the DIVR | |
363 | * value; this is under discussion with the manufacturer. | |
364 | * | |
365 | * Return: the minimum amount of time the caller must wait for the PLL | |
366 | * to lock (in microseconds) | |
367 | */ | |
368 | unsigned int wrpll_calc_max_lock_us(const struct wrpll_cfg *c) | |
369 | { | |
370 | return MAX_LOCK_US; | |
371 | } | |
ee58d6a1 SH |
372 | EXPORT_SYMBOL_GPL(wrpll_calc_max_lock_us); |
373 | ||
374 | MODULE_AUTHOR("Paul Walmsley <paul.walmsley@sifive.com>"); | |
375 | MODULE_DESCRIPTION("Analog Bits Wide-Range PLL library"); | |
376 | MODULE_LICENSE("GPL"); |