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