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 185 static int 186 ti_dpll_clk_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout, 187 int flags, int *stop) 188 { 189 struct ti_dpll_clknode_sc *sc; 190 uint64_t cur, best; 191 uint32_t val, n, p, best_n, best_p, timeout; 192 193 sc = clknode_get_softc(clk); 194 195 best = cur = 0; 196 197 best = ti_dpll_clk_find_best(sc, fparent, fout, 198 &best_n, &best_p); 199 200 if ((flags & CLK_SET_DRYRUN) != 0) { 201 *fout = best; 202 *stop = 1; 203 return (0); 204 } 205 206 if ((best < *fout) && 207 (flags == CLK_SET_ROUND_DOWN)) { 208 *stop = 1; 209 return (ERANGE); 210 } 211 if ((best > *fout) && 212 (flags == CLK_SET_ROUND_UP)) { 213 *stop = 1; 214 return (ERANGE); 215 } 216 217 DEVICE_LOCK(clk); 218 /* 1 switch PLL to bypass mode */ 219 WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_MN_BYPASS_MODE); 220 221 /* 2 Ensure PLL is in bypass */ 222 timeout = 10000; 223 do { 224 DELAY(10); 225 READ4(clk, sc->ti_idlest_offset, &val); 226 } while (!(val & ST_MN_BYPASS_MASK) && timeout--); 227 228 if (timeout == 0) { 229 DEVICE_UNLOCK(clk); 230 return (ERANGE); // FIXME: Better return value? 231 } 232 233 /* 3 Set DPLL_MULT & DPLL_DIV bits */ 234 READ4(clk, sc->ti_clksel_offset, &val); 235 236 n = ti_clk_factor_get_value(&sc->n, best_n); 237 p = ti_clk_factor_get_value(&sc->p, best_p); 238 val &= ~sc->n.mask; 239 val &= ~sc->p.mask; 240 val |= n << sc->n.shift; 241 val |= p << sc->p.shift; 242 243 WRITE4(clk, sc->ti_clksel_offset, val); 244 245 /* 4. configure M2, M4, M5 and M6 */ 246 /* 247 * FIXME: According to documentation M2/M4/M5/M6 can be set "later" 248 * See note in TRM 8.1.6.7.1 249 */ 250 251 /* 5 Switch over to lock mode */ 252 WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_LOCK_MODE); 253 254 /* 6 Ensure PLL is locked */ 255 timeout = 10000; 256 do { 257 DELAY(10); 258 READ4(clk, sc->ti_idlest_offset, &val); 259 } while (!(val & ST_DPLL_CLK_MASK) && timeout--); 260 261 DEVICE_UNLOCK(clk); 262 if (timeout == 0) { 263 return (ERANGE); // FIXME: Better return value? 264 } 265 266 *fout = best; 267 *stop = 1; 268 269 return (0); 270 } 271 272 static int 273 ti_dpll_clk_recalc(struct clknode *clk, uint64_t *freq) 274 { 275 struct ti_dpll_clknode_sc *sc; 276 uint32_t val, n, p; 277 278 sc = clknode_get_softc(clk); 279 280 DEVICE_LOCK(clk); 281 READ4(clk, sc->ti_clksel_offset, &val); 282 DEVICE_UNLOCK(clk); 283 284 n = ti_clk_get_factor(val, &sc->n); 285 p = ti_clk_get_factor(val, &sc->p); 286 287 *freq = *freq * n / p; 288 289 return (0); 290 } 291 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), 297 CLKNODEMETHOD_END 298 }; 299 300 DEFINE_CLASS_1(ti_dpll_clknode, ti_dpll_clknode_class, ti_dpll_clknode_methods, 301 sizeof(struct ti_dpll_clknode_sc), clknode_class); 302 303 int 304 ti_clknode_dpll_register(struct clkdom *clkdom, struct ti_clk_dpll_def *clkdef) 305 { 306 struct clknode *clk; 307 struct ti_dpll_clknode_sc *sc; 308 309 clk = clknode_create(clkdom, &ti_dpll_clknode_class, &clkdef->clkdef); 310 if (clk == NULL) 311 return (1); 312 313 sc = clknode_get_softc(clk); 314 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; 319 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; 327 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; 335 336 sc->ti_autoidle_offset = clkdef->ti_autoidle_offset; 337 338 clknode_register(clkdom, clk); 339 340 return (0); 341 } 342