2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 Emmanuel Vadot <manu@freebsd.org>
6 * Copyright (c) 2020 Oskar Holmlund <oskar.holmlund@ohdata.se>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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.
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
29 * based on sys/arm/allwinner/clkng/aw_clk_np.c
34 #include <sys/cdefs.h>
35 __FBSDID("$FreeBSD$");
37 #include <sys/param.h>
38 #include <sys/systm.h>
41 #include <dev/extres/clk/clk.h>
43 #include <arm/ti/clk/ti_clk_dpll.h>
45 #include "clkdev_if.h"
48 * clknode for clocks matching the formula :
54 struct ti_dpll_clknode_sc {
55 uint32_t ti_clkmode_offset; /* control */
56 uint8_t ti_clkmode_flags;
58 uint32_t ti_idlest_offset;
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 */
64 uint32_t ti_autoidle_offset;
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))
77 ti_dpll_clk_init(struct clknode *clk, device_t dev)
79 struct ti_dpll_clknode_sc *sc;
81 sc = clknode_get_softc(clk);
83 clknode_init_parent_idx(clk, 0);
87 /* helper to keep aw_clk_np_find_best "intact" */
88 static inline uint32_t
89 ti_clk_factor_get_max(struct ti_clk_factor *factor)
93 if (factor->flags & TI_CLK_FACTOR_FIXED)
96 max = (1 << factor->width);
102 static inline uint32_t
103 ti_clk_factor_get_min(struct ti_clk_factor *factor)
107 if (factor->flags & TI_CLK_FACTOR_FIXED)
109 else if (factor->flags & TI_CLK_FACTOR_ZERO_BASED)
111 else if (factor->flags & TI_CLK_FACTOR_MIN_VALUE)
112 min = factor->min_value;
120 ti_dpll_clk_find_best(struct ti_dpll_clknode_sc *sc, uint64_t fparent,
121 uint64_t *fout, uint32_t *factor_n, uint32_t *factor_p)
124 uint32_t n, p, max_n, max_p, min_n, min_p;
126 *factor_n = *factor_p = 0;
128 max_n = ti_clk_factor_get_max(&sc->n);
129 max_p = ti_clk_factor_get_max(&sc->p);
130 min_n = ti_clk_factor_get_min(&sc->n);
131 min_p = ti_clk_factor_get_min(&sc->p);
133 for (p = min_p; p <= max_p; ) {
134 for (n = min_n; n <= max_n; ) {
135 cur = fparent * n / p;
136 if (abs(*fout - cur) < abs(*fout - best)) {
150 static inline uint32_t
151 ti_clk_get_factor(uint32_t val, struct ti_clk_factor *factor)
155 if (factor->flags & TI_CLK_FACTOR_FIXED)
156 return (factor->value);
158 factor_val = (val & factor->mask) >> factor->shift;
159 if (!(factor->flags & TI_CLK_FACTOR_ZERO_BASED))
165 static inline uint32_t
166 ti_clk_factor_get_value(struct ti_clk_factor *factor, uint32_t raw)
170 if (factor->flags & TI_CLK_FACTOR_FIXED)
171 return (factor->value);
173 if (factor->flags & TI_CLK_FACTOR_ZERO_BASED)
175 else if (factor->flags & TI_CLK_FACTOR_MAX_VALUE &&
176 raw > factor->max_value)
177 val = factor->max_value;
186 ti_dpll_clk_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout,
187 int flags, int *stop)
189 struct ti_dpll_clknode_sc *sc;
191 uint32_t val, n, p, best_n, best_p, timeout;
193 sc = clknode_get_softc(clk);
197 best = ti_dpll_clk_find_best(sc, fparent, fout,
200 if ((flags & CLK_SET_DRYRUN) != 0) {
206 if ((best < *fout) &&
207 (flags == CLK_SET_ROUND_DOWN)) {
211 if ((best > *fout) &&
212 (flags == CLK_SET_ROUND_UP)) {
218 /* 1 switch PLL to bypass mode */
219 WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_MN_BYPASS_MODE);
221 /* 2 Ensure PLL is in bypass */
225 READ4(clk, sc->ti_idlest_offset, &val);
226 } while (!(val & ST_MN_BYPASS_MASK) && timeout--);
230 return (ERANGE); // FIXME: Better return value?
233 /* 3 Set DPLL_MULT & DPLL_DIV bits */
234 READ4(clk, sc->ti_clksel_offset, &val);
236 n = ti_clk_factor_get_value(&sc->n, best_n);
237 p = ti_clk_factor_get_value(&sc->p, best_p);
240 val |= n << sc->n.shift;
241 val |= p << sc->p.shift;
243 WRITE4(clk, sc->ti_clksel_offset, val);
245 /* 4. configure M2, M4, M5 and M6 */
247 * FIXME: According to documentation M2/M4/M5/M6 can be set "later"
248 * See note in TRM 8.1.6.7.1
251 /* 5 Switch over to lock mode */
252 WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_LOCK_MODE);
254 /* 6 Ensure PLL is locked */
258 READ4(clk, sc->ti_idlest_offset, &val);
259 } while (!(val & ST_DPLL_CLK_MASK) && timeout--);
263 return (ERANGE); // FIXME: Better return value?
273 ti_dpll_clk_recalc(struct clknode *clk, uint64_t *freq)
275 struct ti_dpll_clknode_sc *sc;
278 sc = clknode_get_softc(clk);
281 READ4(clk, sc->ti_clksel_offset, &val);
284 n = ti_clk_get_factor(val, &sc->n);
285 p = ti_clk_get_factor(val, &sc->p);
287 *freq = *freq * n / p;
292 static clknode_method_t ti_dpll_clknode_methods[] = {
293 /* Device interface */
294 CLKNODEMETHOD(clknode_init, ti_dpll_clk_init),
295 CLKNODEMETHOD(clknode_recalc_freq, ti_dpll_clk_recalc),
296 CLKNODEMETHOD(clknode_set_freq, ti_dpll_clk_set_freq),
300 DEFINE_CLASS_1(ti_dpll_clknode, ti_dpll_clknode_class, ti_dpll_clknode_methods,
301 sizeof(struct ti_dpll_clknode_sc), clknode_class);
304 ti_clknode_dpll_register(struct clkdom *clkdom, struct ti_clk_dpll_def *clkdef)
307 struct ti_dpll_clknode_sc *sc;
309 clk = clknode_create(clkdom, &ti_dpll_clknode_class, &clkdef->clkdef);
313 sc = clknode_get_softc(clk);
315 sc->ti_clkmode_offset = clkdef->ti_clkmode_offset;
316 sc->ti_clkmode_flags = clkdef->ti_clkmode_flags;
317 sc->ti_idlest_offset = clkdef->ti_idlest_offset;
318 sc->ti_clksel_offset = clkdef->ti_clksel_offset;
320 sc->n.shift = clkdef->ti_clksel_mult.shift;
321 sc->n.mask = clkdef->ti_clksel_mult.mask;
322 sc->n.width = clkdef->ti_clksel_mult.width;
323 sc->n.value = clkdef->ti_clksel_mult.value;
324 sc->n.min_value = clkdef->ti_clksel_mult.min_value;
325 sc->n.max_value = clkdef->ti_clksel_mult.max_value;
326 sc->n.flags = clkdef->ti_clksel_mult.flags;
328 sc->p.shift = clkdef->ti_clksel_div.shift;
329 sc->p.mask = clkdef->ti_clksel_div.mask;
330 sc->p.width = clkdef->ti_clksel_div.width;
331 sc->p.value = clkdef->ti_clksel_div.value;
332 sc->p.min_value = clkdef->ti_clksel_div.min_value;
333 sc->p.max_value = clkdef->ti_clksel_div.max_value;
334 sc->p.flags = clkdef->ti_clksel_div.flags;
336 sc->ti_autoidle_offset = clkdef->ti_autoidle_offset;
338 clknode_register(clkdom, clk);