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 struct ti_dpll_clknode_sc *sc; 80 81 sc = clknode_get_softc(clk); 82 83 clknode_init_parent_idx(clk, 0); 84 return (0); 85 } 86 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) 90 { 91 uint32_t max; 92 93 if (factor->flags & TI_CLK_FACTOR_FIXED) 94 max = factor->value; 95 else { 96 max = (1 << factor->width); 97 } 98 99 return (max); 100 } 101 102 static inline uint32_t 103 ti_clk_factor_get_min(struct ti_clk_factor *factor) 104 { 105 uint32_t min; 106 107 if (factor->flags & TI_CLK_FACTOR_FIXED) 108 min = factor->value; 109 else if (factor->flags & TI_CLK_FACTOR_ZERO_BASED) 110 min = 0; 111 else if (factor->flags & TI_CLK_FACTOR_MIN_VALUE) 112 min = factor->min_value; 113 else 114 min = 1; 115 116 return (min); 117 } 118 119 static uint64_t 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) 122 { 123 uint64_t cur, best; 124 uint32_t n, p, max_n, max_p, min_n, min_p; 125 126 *factor_n = *factor_p = 0; 127 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); 132 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)) { 137 best = cur; 138 *factor_n = n; 139 *factor_p = p; 140 } 141 142 n++; 143 } 144 p++; 145 } 146 147 return (best); 148 } 149 150 static inline uint32_t 151 ti_clk_get_factor(uint32_t val, struct ti_clk_factor *factor) 152 { 153 uint32_t factor_val; 154 155 if (factor->flags & TI_CLK_FACTOR_FIXED) 156 return (factor->value); 157 158 factor_val = (val & factor->mask) >> factor->shift; 159 if (!(factor->flags & TI_CLK_FACTOR_ZERO_BASED)) 160 factor_val += 1; 161 162 return (factor_val); 163 } 164 165 static inline uint32_t 166 ti_clk_factor_get_value(struct ti_clk_factor *factor, uint32_t raw) 167 { 168 uint32_t val; 169 170 if (factor->flags & TI_CLK_FACTOR_FIXED) 171 return (factor->value); 172 173 if (factor->flags & TI_CLK_FACTOR_ZERO_BASED) 174 val = raw; 175 else if (factor->flags & TI_CLK_FACTOR_MAX_VALUE && 176 raw > factor->max_value) 177 val = factor->max_value; 178 else 179 val = raw - 1; 180 181 return (val); 182 } 183 184 static int 185 ti_dpll_clk_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout, 186 int flags, int *stop) 187 { 188 struct ti_dpll_clknode_sc *sc; 189 uint64_t cur, best; 190 uint32_t val, n, p, best_n, best_p, timeout; 191 192 sc = clknode_get_softc(clk); 193 194 best = cur = 0; 195 196 best = ti_dpll_clk_find_best(sc, fparent, fout, 197 &best_n, &best_p); 198 199 if ((flags & CLK_SET_DRYRUN) != 0) { 200 *fout = best; 201 *stop = 1; 202 return (0); 203 } 204 205 if ((best < *fout) && 206 (flags == CLK_SET_ROUND_DOWN)) { 207 *stop = 1; 208 return (ERANGE); 209 } 210 if ((best > *fout) && 211 (flags == CLK_SET_ROUND_UP)) { 212 *stop = 1; 213 return (ERANGE); 214 } 215 216 DEVICE_LOCK(clk); 217 /* 1 switch PLL to bypass mode */ 218 WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_MN_BYPASS_MODE); 219 220 /* 2 Ensure PLL is in bypass */ 221 timeout = 10000; 222 do { 223 DELAY(10); 224 READ4(clk, sc->ti_idlest_offset, &val); 225 } while (!(val & ST_MN_BYPASS_MASK) && timeout--); 226 227 if (timeout == 0) { 228 DEVICE_UNLOCK(clk); 229 return (ERANGE); // FIXME: Better return value? 230 } 231 232 /* 3 Set DPLL_MULT & DPLL_DIV bits */ 233 READ4(clk, sc->ti_clksel_offset, &val); 234 235 n = ti_clk_factor_get_value(&sc->n, best_n); 236 p = ti_clk_factor_get_value(&sc->p, best_p); 237 val &= ~sc->n.mask; 238 val &= ~sc->p.mask; 239 val |= n << sc->n.shift; 240 val |= p << sc->p.shift; 241 242 WRITE4(clk, sc->ti_clksel_offset, val); 243 244 /* 4. configure M2, M4, M5 and M6 */ 245 /* 246 * FIXME: According to documentation M2/M4/M5/M6 can be set "later" 247 * See note in TRM 8.1.6.7.1 248 */ 249 250 /* 5 Switch over to lock mode */ 251 WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_LOCK_MODE); 252 253 /* 6 Ensure PLL is locked */ 254 timeout = 10000; 255 do { 256 DELAY(10); 257 READ4(clk, sc->ti_idlest_offset, &val); 258 } while (!(val & ST_DPLL_CLK_MASK) && timeout--); 259 260 DEVICE_UNLOCK(clk); 261 if (timeout == 0) { 262 return (ERANGE); // FIXME: Better return value? 263 } 264 265 *fout = best; 266 *stop = 1; 267 268 return (0); 269 } 270 271 static int 272 ti_dpll_clk_recalc(struct clknode *clk, uint64_t *freq) 273 { 274 struct ti_dpll_clknode_sc *sc; 275 uint32_t val, n, p; 276 277 sc = clknode_get_softc(clk); 278 279 DEVICE_LOCK(clk); 280 READ4(clk, sc->ti_clksel_offset, &val); 281 DEVICE_UNLOCK(clk); 282 283 n = ti_clk_get_factor(val, &sc->n); 284 p = ti_clk_get_factor(val, &sc->p); 285 286 *freq = *freq * n / p; 287 288 return (0); 289 } 290 291 static clknode_method_t ti_dpll_clknode_methods[] = { 292 /* Device interface */ 293 CLKNODEMETHOD(clknode_init, ti_dpll_clk_init), 294 CLKNODEMETHOD(clknode_recalc_freq, ti_dpll_clk_recalc), 295 CLKNODEMETHOD(clknode_set_freq, ti_dpll_clk_set_freq), 296 CLKNODEMETHOD_END 297 }; 298 299 DEFINE_CLASS_1(ti_dpll_clknode, ti_dpll_clknode_class, ti_dpll_clknode_methods, 300 sizeof(struct ti_dpll_clknode_sc), clknode_class); 301 302 int 303 ti_clknode_dpll_register(struct clkdom *clkdom, struct ti_clk_dpll_def *clkdef) 304 { 305 struct clknode *clk; 306 struct ti_dpll_clknode_sc *sc; 307 308 clk = clknode_create(clkdom, &ti_dpll_clknode_class, &clkdef->clkdef); 309 if (clk == NULL) 310 return (1); 311 312 sc = clknode_get_softc(clk); 313 314 sc->ti_clkmode_offset = clkdef->ti_clkmode_offset; 315 sc->ti_clkmode_flags = clkdef->ti_clkmode_flags; 316 sc->ti_idlest_offset = clkdef->ti_idlest_offset; 317 sc->ti_clksel_offset = clkdef->ti_clksel_offset; 318 319 sc->n.shift = clkdef->ti_clksel_mult.shift; 320 sc->n.mask = clkdef->ti_clksel_mult.mask; 321 sc->n.width = clkdef->ti_clksel_mult.width; 322 sc->n.value = clkdef->ti_clksel_mult.value; 323 sc->n.min_value = clkdef->ti_clksel_mult.min_value; 324 sc->n.max_value = clkdef->ti_clksel_mult.max_value; 325 sc->n.flags = clkdef->ti_clksel_mult.flags; 326 327 sc->p.shift = clkdef->ti_clksel_div.shift; 328 sc->p.mask = clkdef->ti_clksel_div.mask; 329 sc->p.width = clkdef->ti_clksel_div.width; 330 sc->p.value = clkdef->ti_clksel_div.value; 331 sc->p.min_value = clkdef->ti_clksel_div.min_value; 332 sc->p.max_value = clkdef->ti_clksel_div.max_value; 333 sc->p.flags = clkdef->ti_clksel_div.flags; 334 335 sc->ti_autoidle_offset = clkdef->ti_autoidle_offset; 336 337 clknode_register(clkdom, clk); 338 339 return (0); 340 } 341