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 * $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