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