xref: /freebsd/sys/arm/ti/clk/ti_clk_dpll.c (revision 3c4ba5f55438f7afd4f4b0b56f88f2bb505fd6a6)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019 Emmanuel Vadot <manu@freebsd.org>
5  *
6  * Copyright (c) 2020 Oskar Holmlund <oskar.holmlund@ohdata.se>
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * based on sys/arm/allwinner/clkng/aw_clk_np.c
30  *
31  * $FreeBSD$
32  */
33 
34 #include <sys/cdefs.h>
35 __FBSDID("$FreeBSD$");
36 
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/bus.h>
40 
41 #include <dev/extres/clk/clk.h>
42 
43 #include <arm/ti/clk/ti_clk_dpll.h>
44 
45 #include "clkdev_if.h"
46 
47 /*
48  * clknode for clocks matching the formula :
49  *
50  * clk = clkin * n / p
51  *
52  */
53 
54 struct ti_dpll_clknode_sc {
55 	uint32_t		ti_clkmode_offset; /* control */
56 	uint8_t			ti_clkmode_flags;
57 
58 	uint32_t		ti_idlest_offset;
59 
60 	uint32_t		ti_clksel_offset; /* mult-div1 */
61 	struct ti_clk_factor	n; /* ti_clksel_mult */
62 	struct ti_clk_factor	p; /* ti_clksel_div */
63 
64 	uint32_t		ti_autoidle_offset;
65 };
66 
67 #define	WRITE4(_clk, off, val)						\
68 	CLKDEV_WRITE_4(clknode_get_device(_clk), off, val)
69 #define	READ4(_clk, off, val)						\
70 	CLKDEV_READ_4(clknode_get_device(_clk), off, val)
71 #define	DEVICE_LOCK(_clk)						\
72 	CLKDEV_DEVICE_LOCK(clknode_get_device(_clk))
73 #define	DEVICE_UNLOCK(_clk)						\
74 	CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk))
75 
76 static int
77 ti_dpll_clk_init(struct clknode *clk, device_t dev)
78 {
79 	clknode_init_parent_idx(clk, 0);
80 	return (0);
81 }
82 
83 /* helper to keep aw_clk_np_find_best "intact" */
84 static inline uint32_t
85 ti_clk_factor_get_max(struct ti_clk_factor *factor)
86 {
87 	uint32_t max;
88 
89 	if (factor->flags & TI_CLK_FACTOR_FIXED)
90 		max = factor->value;
91 	else {
92 		max = (1 << factor->width);
93 	}
94 
95 	return (max);
96 }
97 
98 static inline uint32_t
99 ti_clk_factor_get_min(struct ti_clk_factor *factor)
100 {
101 	uint32_t min;
102 
103 	if (factor->flags & TI_CLK_FACTOR_FIXED)
104 		min = factor->value;
105 	else if (factor->flags & TI_CLK_FACTOR_ZERO_BASED)
106 		min = 0;
107 	else if (factor->flags & TI_CLK_FACTOR_MIN_VALUE)
108 		min = factor->min_value;
109 	else
110 		min = 1;
111 
112 	return (min);
113 }
114 
115 static uint64_t
116 ti_dpll_clk_find_best(struct ti_dpll_clknode_sc *sc, uint64_t fparent,
117 	uint64_t *fout, uint32_t *factor_n, uint32_t *factor_p)
118 {
119 	uint64_t cur, best;
120 	uint32_t n, p, max_n, max_p, min_n, min_p;
121 
122 	*factor_n = *factor_p = 0;
123 
124 	max_n = ti_clk_factor_get_max(&sc->n);
125 	max_p = ti_clk_factor_get_max(&sc->p);
126 	min_n = ti_clk_factor_get_min(&sc->n);
127 	min_p = ti_clk_factor_get_min(&sc->p);
128 
129 	for (p = min_p; p <= max_p; ) {
130 		for (n = min_n; n <= max_n; ) {
131 			cur = fparent * n / p;
132 			if (abs(*fout - cur) < abs(*fout - best)) {
133 				best = cur;
134 				*factor_n = n;
135 				*factor_p = p;
136 			}
137 
138 			n++;
139 		}
140 		p++;
141 	}
142 
143 	return (best);
144 }
145 
146 static inline uint32_t
147 ti_clk_get_factor(uint32_t val, struct ti_clk_factor *factor)
148 {
149 	uint32_t factor_val;
150 
151 	if (factor->flags & TI_CLK_FACTOR_FIXED)
152 		return (factor->value);
153 
154 	factor_val = (val & factor->mask) >> factor->shift;
155 	if (!(factor->flags & TI_CLK_FACTOR_ZERO_BASED))
156 		factor_val += 1;
157 
158 	return (factor_val);
159 }
160 
161 static inline uint32_t
162 ti_clk_factor_get_value(struct ti_clk_factor *factor, uint32_t raw)
163 {
164 	uint32_t val;
165 
166 	if (factor->flags & TI_CLK_FACTOR_FIXED)
167 		return (factor->value);
168 
169 	if (factor->flags & TI_CLK_FACTOR_ZERO_BASED)
170 		val = raw;
171 	else if (factor->flags & TI_CLK_FACTOR_MAX_VALUE &&
172 	    raw > factor->max_value)
173 		val = factor->max_value;
174 	else
175 		val = raw - 1;
176 
177 	return (val);
178 }
179 
180 static int
181 ti_dpll_clk_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout,
182     int flags, int *stop)
183 {
184 	struct ti_dpll_clknode_sc *sc;
185 	uint64_t cur, best;
186 	uint32_t val, n, p, best_n, best_p, timeout;
187 
188 	sc = clknode_get_softc(clk);
189 
190 	best = cur = 0;
191 
192 	best = ti_dpll_clk_find_best(sc, fparent, fout,
193 	    &best_n, &best_p);
194 
195 	if ((flags & CLK_SET_DRYRUN) != 0) {
196 		*fout = best;
197 		*stop = 1;
198 		return (0);
199 	}
200 
201 	if ((best < *fout) &&
202 	  (flags == CLK_SET_ROUND_DOWN)) {
203 		*stop = 1;
204 		return (ERANGE);
205 	}
206 	if ((best > *fout) &&
207 	  (flags == CLK_SET_ROUND_UP)) {
208 		*stop = 1;
209 		return (ERANGE);
210 	}
211 
212 	DEVICE_LOCK(clk);
213 	/* 1 switch PLL to bypass mode */
214 	WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_MN_BYPASS_MODE);
215 
216 	/* 2 Ensure PLL is in bypass */
217 	timeout = 10000;
218 	do {
219 		DELAY(10);
220 		READ4(clk, sc->ti_idlest_offset, &val);
221 	} while (!(val & ST_MN_BYPASS_MASK) && timeout--);
222 
223 	if (timeout == 0) {
224 		DEVICE_UNLOCK(clk);
225 		return (ERANGE); // FIXME: Better return value?
226 	}
227 
228 	/* 3 Set DPLL_MULT & DPLL_DIV bits */
229 	READ4(clk, sc->ti_clksel_offset, &val);
230 
231 	n = ti_clk_factor_get_value(&sc->n, best_n);
232 	p = ti_clk_factor_get_value(&sc->p, best_p);
233 	val &= ~sc->n.mask;
234 	val &= ~sc->p.mask;
235 	val |= n << sc->n.shift;
236 	val |= p << sc->p.shift;
237 
238 	WRITE4(clk, sc->ti_clksel_offset, val);
239 
240 	/* 4. configure M2, M4, M5 and M6 */
241 	/*
242 	 * FIXME: According to documentation M2/M4/M5/M6 can be set "later"
243 	 * See note in TRM 8.1.6.7.1
244 	 */
245 
246 	/* 5 Switch over to lock mode */
247 	WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_LOCK_MODE);
248 
249 	/* 6 Ensure PLL is locked */
250 	timeout = 10000;
251 	do {
252 		DELAY(10);
253 		READ4(clk, sc->ti_idlest_offset, &val);
254 	} while (!(val & ST_DPLL_CLK_MASK) && timeout--);
255 
256 	DEVICE_UNLOCK(clk);
257 	if (timeout == 0) {
258 		return (ERANGE); // FIXME: Better return value?
259 	}
260 
261 	*fout = best;
262 	*stop = 1;
263 
264 	return (0);
265 }
266 
267 static int
268 ti_dpll_clk_recalc(struct clknode *clk, uint64_t *freq)
269 {
270 	struct ti_dpll_clknode_sc *sc;
271 	uint32_t val, n, p;
272 
273 	sc = clknode_get_softc(clk);
274 
275 	DEVICE_LOCK(clk);
276 	READ4(clk, sc->ti_clksel_offset, &val);
277 	DEVICE_UNLOCK(clk);
278 
279 	n = ti_clk_get_factor(val, &sc->n);
280 	p = ti_clk_get_factor(val, &sc->p);
281 
282 	*freq = *freq * n / p;
283 
284 	return (0);
285 }
286 
287 static clknode_method_t ti_dpll_clknode_methods[] = {
288 	/* Device interface */
289 	CLKNODEMETHOD(clknode_init,		ti_dpll_clk_init),
290 	CLKNODEMETHOD(clknode_recalc_freq,	ti_dpll_clk_recalc),
291 	CLKNODEMETHOD(clknode_set_freq,		ti_dpll_clk_set_freq),
292 	CLKNODEMETHOD_END
293 };
294 
295 DEFINE_CLASS_1(ti_dpll_clknode, ti_dpll_clknode_class, ti_dpll_clknode_methods,
296 	sizeof(struct ti_dpll_clknode_sc), clknode_class);
297 
298 int
299 ti_clknode_dpll_register(struct clkdom *clkdom, struct ti_clk_dpll_def *clkdef)
300 {
301 	struct clknode *clk;
302 	struct ti_dpll_clknode_sc *sc;
303 
304 	clk = clknode_create(clkdom, &ti_dpll_clknode_class, &clkdef->clkdef);
305 	if (clk == NULL)
306 		return (1);
307 
308 	sc = clknode_get_softc(clk);
309 
310 	sc->ti_clkmode_offset = clkdef->ti_clkmode_offset;
311 	sc->ti_clkmode_flags = clkdef->ti_clkmode_flags;
312 	sc->ti_idlest_offset = clkdef->ti_idlest_offset;
313 	sc->ti_clksel_offset = clkdef->ti_clksel_offset;
314 
315 	sc->n.shift = clkdef->ti_clksel_mult.shift;
316 	sc->n.mask = clkdef->ti_clksel_mult.mask;
317 	sc->n.width = clkdef->ti_clksel_mult.width;
318 	sc->n.value = clkdef->ti_clksel_mult.value;
319 	sc->n.min_value = clkdef->ti_clksel_mult.min_value;
320 	sc->n.max_value = clkdef->ti_clksel_mult.max_value;
321 	sc->n.flags = clkdef->ti_clksel_mult.flags;
322 
323 	sc->p.shift = clkdef->ti_clksel_div.shift;
324 	sc->p.mask = clkdef->ti_clksel_div.mask;
325 	sc->p.width = clkdef->ti_clksel_div.width;
326 	sc->p.value = clkdef->ti_clksel_div.value;
327 	sc->p.min_value = clkdef->ti_clksel_div.min_value;
328 	sc->p.max_value = clkdef->ti_clksel_div.max_value;
329 	sc->p.flags = clkdef->ti_clksel_div.flags;
330 
331 	sc->ti_autoidle_offset = clkdef->ti_autoidle_offset;
332 
333 	clknode_register(clkdom, clk);
334 
335 	return (0);
336 }
337