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