1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
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
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/bus.h>
35
36 #include <dev/clk/clk.h>
37
38 #include <arm/ti/clk/ti_clk_dpll.h>
39
40 #include "clkdev_if.h"
41
42 /*
43 * clknode for clocks matching the formula :
44 *
45 * clk = clkin * n / p
46 *
47 */
48
49 struct ti_dpll_clknode_sc {
50 uint32_t ti_clkmode_offset; /* control */
51 uint8_t ti_clkmode_flags;
52
53 uint32_t ti_idlest_offset;
54
55 uint32_t ti_clksel_offset; /* mult-div1 */
56 struct ti_clk_factor n; /* ti_clksel_mult */
57 struct ti_clk_factor p; /* ti_clksel_div */
58
59 uint32_t ti_autoidle_offset;
60 };
61
62 #define WRITE4(_clk, off, val) \
63 CLKDEV_WRITE_4(clknode_get_device(_clk), off, val)
64 #define READ4(_clk, off, val) \
65 CLKDEV_READ_4(clknode_get_device(_clk), off, val)
66 #define DEVICE_LOCK(_clk) \
67 CLKDEV_DEVICE_LOCK(clknode_get_device(_clk))
68 #define DEVICE_UNLOCK(_clk) \
69 CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk))
70
71 static int
ti_dpll_clk_init(struct clknode * clk,device_t dev)72 ti_dpll_clk_init(struct clknode *clk, device_t dev)
73 {
74 clknode_init_parent_idx(clk, 0);
75 return (0);
76 }
77
78 /* helper to keep aw_clk_np_find_best "intact" */
79 static inline uint32_t
ti_clk_factor_get_max(struct ti_clk_factor * factor)80 ti_clk_factor_get_max(struct ti_clk_factor *factor)
81 {
82 uint32_t max;
83
84 if (factor->flags & TI_CLK_FACTOR_FIXED)
85 max = factor->value;
86 else {
87 max = (1 << factor->width);
88 }
89
90 return (max);
91 }
92
93 static inline uint32_t
ti_clk_factor_get_min(struct ti_clk_factor * factor)94 ti_clk_factor_get_min(struct ti_clk_factor *factor)
95 {
96 uint32_t min;
97
98 if (factor->flags & TI_CLK_FACTOR_FIXED)
99 min = factor->value;
100 else if (factor->flags & TI_CLK_FACTOR_ZERO_BASED)
101 min = 0;
102 else if (factor->flags & TI_CLK_FACTOR_MIN_VALUE)
103 min = factor->min_value;
104 else
105 min = 1;
106
107 return (min);
108 }
109
110 static uint64_t
ti_dpll_clk_find_best(struct ti_dpll_clknode_sc * sc,uint64_t fparent,uint64_t * fout,uint32_t * factor_n,uint32_t * factor_p)111 ti_dpll_clk_find_best(struct ti_dpll_clknode_sc *sc, uint64_t fparent,
112 uint64_t *fout, uint32_t *factor_n, uint32_t *factor_p)
113 {
114 uint64_t cur, best;
115 uint32_t n, p, max_n, max_p, min_n, min_p;
116
117 *factor_n = *factor_p = 0;
118
119 max_n = ti_clk_factor_get_max(&sc->n);
120 max_p = ti_clk_factor_get_max(&sc->p);
121 min_n = ti_clk_factor_get_min(&sc->n);
122 min_p = ti_clk_factor_get_min(&sc->p);
123
124 for (p = min_p; p <= max_p; ) {
125 for (n = min_n; n <= max_n; ) {
126 cur = fparent * n / p;
127 if (abs(*fout - cur) < abs(*fout - best)) {
128 best = cur;
129 *factor_n = n;
130 *factor_p = p;
131 }
132
133 n++;
134 }
135 p++;
136 }
137
138 return (best);
139 }
140
141 static inline uint32_t
ti_clk_get_factor(uint32_t val,struct ti_clk_factor * factor)142 ti_clk_get_factor(uint32_t val, struct ti_clk_factor *factor)
143 {
144 uint32_t factor_val;
145
146 if (factor->flags & TI_CLK_FACTOR_FIXED)
147 return (factor->value);
148
149 factor_val = (val & factor->mask) >> factor->shift;
150 if (!(factor->flags & TI_CLK_FACTOR_ZERO_BASED))
151 factor_val += 1;
152
153 return (factor_val);
154 }
155
156 static inline uint32_t
ti_clk_factor_get_value(struct ti_clk_factor * factor,uint32_t raw)157 ti_clk_factor_get_value(struct ti_clk_factor *factor, uint32_t raw)
158 {
159 uint32_t val;
160
161 if (factor->flags & TI_CLK_FACTOR_FIXED)
162 return (factor->value);
163
164 if (factor->flags & TI_CLK_FACTOR_ZERO_BASED)
165 val = raw;
166 else if (factor->flags & TI_CLK_FACTOR_MAX_VALUE &&
167 raw > factor->max_value)
168 val = factor->max_value;
169 else
170 val = raw - 1;
171
172 return (val);
173 }
174
175 static int
ti_dpll_clk_set_freq(struct clknode * clk,uint64_t fparent,uint64_t * fout,int flags,int * stop)176 ti_dpll_clk_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout,
177 int flags, int *stop)
178 {
179 struct ti_dpll_clknode_sc *sc;
180 uint64_t cur, best;
181 uint32_t val, n, p, best_n, best_p, timeout;
182
183 sc = clknode_get_softc(clk);
184
185 best = cur = 0;
186
187 best = ti_dpll_clk_find_best(sc, fparent, fout,
188 &best_n, &best_p);
189
190 if ((flags & CLK_SET_DRYRUN) != 0) {
191 *fout = best;
192 *stop = 1;
193 return (0);
194 }
195
196 if ((best < *fout) &&
197 (flags == CLK_SET_ROUND_DOWN)) {
198 *stop = 1;
199 return (ERANGE);
200 }
201 if ((best > *fout) &&
202 (flags == CLK_SET_ROUND_UP)) {
203 *stop = 1;
204 return (ERANGE);
205 }
206
207 DEVICE_LOCK(clk);
208 /* 1 switch PLL to bypass mode */
209 WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_MN_BYPASS_MODE);
210
211 /* 2 Ensure PLL is in bypass */
212 timeout = 10000;
213 do {
214 DELAY(10);
215 READ4(clk, sc->ti_idlest_offset, &val);
216 } while (!(val & ST_MN_BYPASS_MASK) && timeout--);
217
218 if (timeout == 0) {
219 DEVICE_UNLOCK(clk);
220 return (ERANGE); // FIXME: Better return value?
221 }
222
223 /* 3 Set DPLL_MULT & DPLL_DIV bits */
224 READ4(clk, sc->ti_clksel_offset, &val);
225
226 n = ti_clk_factor_get_value(&sc->n, best_n);
227 p = ti_clk_factor_get_value(&sc->p, best_p);
228 val &= ~sc->n.mask;
229 val &= ~sc->p.mask;
230 val |= n << sc->n.shift;
231 val |= p << sc->p.shift;
232
233 WRITE4(clk, sc->ti_clksel_offset, val);
234
235 /* 4. configure M2, M4, M5 and M6 */
236 /*
237 * FIXME: According to documentation M2/M4/M5/M6 can be set "later"
238 * See note in TRM 8.1.6.7.1
239 */
240
241 /* 5 Switch over to lock mode */
242 WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_LOCK_MODE);
243
244 /* 6 Ensure PLL is locked */
245 timeout = 10000;
246 do {
247 DELAY(10);
248 READ4(clk, sc->ti_idlest_offset, &val);
249 } while (!(val & ST_DPLL_CLK_MASK) && timeout--);
250
251 DEVICE_UNLOCK(clk);
252 if (timeout == 0) {
253 return (ERANGE); // FIXME: Better return value?
254 }
255
256 *fout = best;
257 *stop = 1;
258
259 return (0);
260 }
261
262 static int
ti_dpll_clk_recalc(struct clknode * clk,uint64_t * freq)263 ti_dpll_clk_recalc(struct clknode *clk, uint64_t *freq)
264 {
265 struct ti_dpll_clknode_sc *sc;
266 uint32_t val, n, p;
267
268 sc = clknode_get_softc(clk);
269
270 DEVICE_LOCK(clk);
271 READ4(clk, sc->ti_clksel_offset, &val);
272 DEVICE_UNLOCK(clk);
273
274 n = ti_clk_get_factor(val, &sc->n);
275 p = ti_clk_get_factor(val, &sc->p);
276
277 *freq = *freq * n / p;
278
279 return (0);
280 }
281
282 static clknode_method_t ti_dpll_clknode_methods[] = {
283 /* Device interface */
284 CLKNODEMETHOD(clknode_init, ti_dpll_clk_init),
285 CLKNODEMETHOD(clknode_recalc_freq, ti_dpll_clk_recalc),
286 CLKNODEMETHOD(clknode_set_freq, ti_dpll_clk_set_freq),
287 CLKNODEMETHOD_END
288 };
289
290 DEFINE_CLASS_1(ti_dpll_clknode, ti_dpll_clknode_class, ti_dpll_clknode_methods,
291 sizeof(struct ti_dpll_clknode_sc), clknode_class);
292
293 int
ti_clknode_dpll_register(struct clkdom * clkdom,struct ti_clk_dpll_def * clkdef)294 ti_clknode_dpll_register(struct clkdom *clkdom, struct ti_clk_dpll_def *clkdef)
295 {
296 struct clknode *clk;
297 struct ti_dpll_clknode_sc *sc;
298
299 clk = clknode_create(clkdom, &ti_dpll_clknode_class, &clkdef->clkdef);
300 if (clk == NULL)
301 return (1);
302
303 sc = clknode_get_softc(clk);
304
305 sc->ti_clkmode_offset = clkdef->ti_clkmode_offset;
306 sc->ti_clkmode_flags = clkdef->ti_clkmode_flags;
307 sc->ti_idlest_offset = clkdef->ti_idlest_offset;
308 sc->ti_clksel_offset = clkdef->ti_clksel_offset;
309
310 sc->n.shift = clkdef->ti_clksel_mult.shift;
311 sc->n.mask = clkdef->ti_clksel_mult.mask;
312 sc->n.width = clkdef->ti_clksel_mult.width;
313 sc->n.value = clkdef->ti_clksel_mult.value;
314 sc->n.min_value = clkdef->ti_clksel_mult.min_value;
315 sc->n.max_value = clkdef->ti_clksel_mult.max_value;
316 sc->n.flags = clkdef->ti_clksel_mult.flags;
317
318 sc->p.shift = clkdef->ti_clksel_div.shift;
319 sc->p.mask = clkdef->ti_clksel_div.mask;
320 sc->p.width = clkdef->ti_clksel_div.width;
321 sc->p.value = clkdef->ti_clksel_div.value;
322 sc->p.min_value = clkdef->ti_clksel_div.min_value;
323 sc->p.max_value = clkdef->ti_clksel_div.max_value;
324 sc->p.flags = clkdef->ti_clksel_div.flags;
325
326 sc->ti_autoidle_offset = clkdef->ti_autoidle_offset;
327
328 clknode_register(clkdom, clk);
329
330 return (0);
331 }
332