1/*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2019 Emmanuel Vadot <manu@freebsd.org> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 22 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 * 27 * $FreeBSD$ 28 */ 29 30#include <sys/cdefs.h> 31__FBSDID("$FreeBSD$"); 32 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/allwinner/clkng/aw_clk.h> 40#include <arm/allwinner/clkng/aw_clk_nmm.h> 41 42#include "clkdev_if.h" 43 44/* 45 * clknode for clocks matching the formula : 46 * 47 * clk = clkin * n / m0 / m1 48 * 49 */ 50 51struct aw_clk_nmm_sc { 52 uint32_t offset; 53 54 struct aw_clk_factor n; 55 struct aw_clk_factor m0; 56 struct aw_clk_factor m1; 57 58 uint32_t gate_shift; 59 uint32_t lock_shift; 60 uint32_t lock_retries; 61 62 uint32_t flags; 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 74static int 75aw_clk_nmm_init(struct clknode *clk, device_t dev) 76{ 77 struct aw_clk_nmm_sc *sc; 78 79 sc = clknode_get_softc(clk); 80 81 clknode_init_parent_idx(clk, 0); 82 return (0); 83} 84 85static int 86aw_clk_nmm_set_gate(struct clknode *clk, bool enable) 87{ 88 struct aw_clk_nmm_sc *sc; 89 uint32_t val; 90 91 sc = clknode_get_softc(clk); 92 93 if ((sc->flags & AW_CLK_HAS_GATE) == 0) 94 return (0); 95 96 DEVICE_LOCK(clk); 97 READ4(clk, sc->offset, &val); 98 if (enable) 99 val |= (1 << sc->gate_shift); 100 else 101 val &= ~(1 << sc->gate_shift); 102 WRITE4(clk, sc->offset, val); 103 DEVICE_UNLOCK(clk); 104 105 return (0); 106} 107 108static uint64_t 109aw_clk_nmm_find_best(struct aw_clk_nmm_sc *sc, uint64_t fparent, uint64_t *fout, 110 uint32_t *factor_n, uint32_t *factor_m0, uint32_t *factor_m1) 111{ 112 uint64_t cur, best; 113 uint32_t n, m0, m1; 114 uint32_t max_n, max_m0, max_m1; 115 uint32_t min_n, min_m0, min_m1; 116 117 *factor_n = *factor_m0 = *factor_m1 = 0; 118 119 max_n = aw_clk_factor_get_max(&sc->n); 120 min_n = aw_clk_factor_get_min(&sc->n); 121 max_m0 = aw_clk_factor_get_max(&sc->m0); 122 min_m0 = aw_clk_factor_get_min(&sc->m0); 123 max_m1 = aw_clk_factor_get_max(&sc->m1); 124 min_m1 = aw_clk_factor_get_min(&sc->m1); 125 126 for (m0 = min_m0; m0 <= max_m0; ) { 127 for (m1 = min_m1; m1 <= max_m1; ) { 128 for (n = min_n; n <= max_n; ) { 129 cur = fparent * n / m0 / m1; 130 if (abs(*fout - cur) < abs(*fout - best)) { 131 best = cur; 132 *factor_n = n; 133 *factor_m0 = m0; 134 *factor_m1 = m1; 135 } 136 n++; 137 } 138 m1++; 139 } 140 m0++; 141 } 142 143 return (best); 144} 145 146static int 147aw_clk_nmm_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout, 148 int flags, int *stop) 149{ 150 struct aw_clk_nmm_sc *sc; 151 uint64_t cur, best; 152 uint32_t val, n, m0, m1, best_n, best_m0, best_m1; 153 int retry; 154 155 sc = clknode_get_softc(clk); 156 157 best = cur = 0; 158 159 best = aw_clk_nmm_find_best(sc, fparent, fout, 160 &best_n, &best_m0, &best_m1); 161 162 if ((flags & CLK_SET_DRYRUN) != 0) { 163 *fout = best; 164 *stop = 1; 165 return (0); 166 } 167 168 if ((best < *fout) && 169 ((flags & CLK_SET_ROUND_DOWN) == 0)) { 170 *stop = 1; 171 return (ERANGE); 172 } 173 if ((best > *fout) && 174 ((flags & CLK_SET_ROUND_UP) == 0)) { 175 *stop = 1; 176 return (ERANGE); 177 } 178 179 DEVICE_LOCK(clk); 180 READ4(clk, sc->offset, &val); 181 182 n = aw_clk_factor_get_value(&sc->n, best_n); 183 m0 = aw_clk_factor_get_value(&sc->m0, best_m0); 184 m1 = aw_clk_factor_get_value(&sc->m1, best_m1); 185 val &= ~sc->n.mask; 186 val &= ~sc->m0.mask; 187 val &= ~sc->m1.mask; 188 val |= n << sc->n.shift; 189 val |= m0 << sc->m0.shift; 190 val |= m1 << sc->m1.shift; 191 192 WRITE4(clk, sc->offset, val); 193 DEVICE_UNLOCK(clk); 194 195 if ((sc->flags & AW_CLK_HAS_LOCK) != 0) { 196 for (retry = 0; retry < sc->lock_retries; retry++) { 197 READ4(clk, sc->offset, &val); 198 if ((val & (1 << sc->lock_shift)) != 0) 199 break; 200 DELAY(1000); 201 } 202 } 203 204 *fout = best; 205 *stop = 1; 206 207 return (0); 208} 209 210static int 211aw_clk_nmm_recalc(struct clknode *clk, uint64_t *freq) 212{ 213 struct aw_clk_nmm_sc *sc; 214 uint32_t val, n, m0, m1; 215 216 sc = clknode_get_softc(clk); 217 218 DEVICE_LOCK(clk); 219 READ4(clk, sc->offset, &val); 220 DEVICE_UNLOCK(clk); 221 222 n = aw_clk_get_factor(val, &sc->n); 223 m0 = aw_clk_get_factor(val, &sc->m0); 224 m1 = aw_clk_get_factor(val, &sc->m1); 225 226 *freq = *freq * n / m0 / m1; 227 228 return (0); 229} 230 231static clknode_method_t aw_nmm_clknode_methods[] = { 232 /* Device interface */ 233 CLKNODEMETHOD(clknode_init, aw_clk_nmm_init), 234 CLKNODEMETHOD(clknode_set_gate, aw_clk_nmm_set_gate), 235 CLKNODEMETHOD(clknode_recalc_freq, aw_clk_nmm_recalc), 236 CLKNODEMETHOD(clknode_set_freq, aw_clk_nmm_set_freq), 237 CLKNODEMETHOD_END 238}; 239 240DEFINE_CLASS_1(aw_nmm_clknode, aw_nmm_clknode_class, aw_nmm_clknode_methods, 241 sizeof(struct aw_clk_nmm_sc), clknode_class); 242 243int 244aw_clk_nmm_register(struct clkdom *clkdom, struct aw_clk_nmm_def *clkdef) 245{ 246 struct clknode *clk; 247 struct aw_clk_nmm_sc *sc; 248 249 clk = clknode_create(clkdom, &aw_nmm_clknode_class, &clkdef->clkdef); 250 if (clk == NULL) 251 return (1); 252 253 sc = clknode_get_softc(clk); 254 255 sc->offset = clkdef->offset; 256 257 sc->n.shift = clkdef->n.shift; 258 sc->n.width = clkdef->n.width; 259 sc->n.mask = ((1 << sc->n.width) - 1) << sc->n.shift; 260 sc->n.value = clkdef->n.value; 261 sc->n.flags = clkdef->n.flags; 262 263 sc->m0.shift = clkdef->m0.shift; 264 sc->m0.width = clkdef->m0.width; 265 sc->m0.mask = ((1 << sc->m0.width) - 1) << sc->m0.shift; 266 sc->m0.value = clkdef->m0.value; 267 sc->m0.flags = clkdef->m0.flags; 268 269 sc->m1.shift = clkdef->m1.shift; 270 sc->m1.width = clkdef->m1.width; 271 sc->m1.mask = ((1 << sc->m1.width) - 1) << sc->m1.shift; 272 sc->m1.value = clkdef->m1.value; 273 sc->m1.flags = clkdef->m1.flags; 274 275 sc->gate_shift = clkdef->gate_shift; 276 277 sc->lock_shift = clkdef->lock_shift; 278 sc->lock_retries = clkdef->lock_retries; 279 280 sc->flags = clkdef->flags; 281 282 clknode_register(clkdom, clk); 283 284 return (0); 285} 286