1/*- 2 * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 21 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD: stable/11/sys/arm/allwinner/clk/aw_modclk.c 309767 2016-12-09 20:52:48Z manu $ 27 */ 28 29/* 30 * Allwinner module clocks 31 */ 32 33#include <sys/cdefs.h> 34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_modclk.c 309767 2016-12-09 20:52:48Z manu $"); 35 36#include <sys/param.h> 37#include <sys/systm.h> 38#include <sys/bus.h> 39#include <sys/rman.h> 40#include <sys/kernel.h> 41#include <sys/module.h> 42#include <machine/bus.h> 43 44#include <dev/ofw/ofw_bus.h> 45#include <dev/ofw/ofw_bus_subr.h> 46#include <dev/ofw/ofw_subr.h> 47 48#include <dev/extres/clk/clk_mux.h> 49#include <dev/extres/clk/clk_gate.h> 50 51#include "clkdev_if.h" 52 53#define SCLK_GATING (1 << 31) 54#define CLK_SRC_SEL (0x3 << 24) 55#define CLK_SRC_SEL_SHIFT 24 56#define CLK_RATIO_N (0x3 << 16) 57#define CLK_RATIO_N_SHIFT 16 58#define CLK_RATIO_N_MAX 0x3 59#define CLK_RATIO_M (0x1f << 0) 60#define CLK_RATIO_M_SHIFT 0 61#define CLK_RATIO_M_MAX 0x1f 62 63static struct ofw_compat_data compat_data[] = { 64 { "allwinner,sun4i-a10-mod0-clk", 1 }, 65 { NULL, 0 } 66}; 67 68struct aw_modclk_sc { 69 device_t clkdev; 70 bus_addr_t reg; 71 u_int parent_cnt; 72}; 73 74#define MODCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) 75#define MODCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) 76#define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) 77#define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) 78 79static int 80aw_modclk_init(struct clknode *clk, device_t dev) 81{ 82 struct aw_modclk_sc *sc; 83 uint32_t val, index; 84 85 sc = clknode_get_softc(clk); 86 87 DEVICE_LOCK(sc); 88 MODCLK_READ(sc, &val); 89 DEVICE_UNLOCK(sc); 90 91 index = (val & CLK_SRC_SEL) >> CLK_SRC_SEL_SHIFT; 92 93 clknode_init_parent_idx(clk, index); 94 return (0); 95} 96 97static int 98aw_modclk_set_mux(struct clknode *clk, int index) 99{ 100 struct aw_modclk_sc *sc; 101 uint32_t val; 102 103 sc = clknode_get_softc(clk); 104 105 if (index < 0 || index >= sc->parent_cnt) 106 return (ERANGE); 107 108 DEVICE_LOCK(sc); 109 MODCLK_READ(sc, &val); 110 val &= ~CLK_SRC_SEL; 111 val |= (index << CLK_SRC_SEL_SHIFT); 112 MODCLK_WRITE(sc, val); 113 DEVICE_UNLOCK(sc); 114 115 return (0); 116} 117 118static int 119aw_modclk_set_gate(struct clknode *clk, bool enable) 120{ 121 struct aw_modclk_sc *sc; 122 uint32_t val; 123 124 sc = clknode_get_softc(clk); 125 126 DEVICE_LOCK(sc); 127 MODCLK_READ(sc, &val); 128 if (enable) 129 val |= SCLK_GATING; 130 else 131 val &= ~SCLK_GATING; 132 MODCLK_WRITE(sc, val); 133 DEVICE_UNLOCK(sc); 134 135 return (0); 136} 137 138static int 139aw_modclk_recalc_freq(struct clknode *clk, uint64_t *freq) 140{ 141 struct aw_modclk_sc *sc; 142 uint32_t val, m, n; 143 144 sc = clknode_get_softc(clk); 145 146 DEVICE_LOCK(sc); 147 MODCLK_READ(sc, &val); 148 DEVICE_UNLOCK(sc); 149 150 n = 1 << ((val & CLK_RATIO_N) >> CLK_RATIO_N_SHIFT); 151 m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1; 152 153 *freq = *freq / n / m; 154 155 return (0); 156} 157 158static int 159aw_modclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, 160 int flags, int *stop) 161{ 162 struct aw_modclk_sc *sc; 163 uint32_t val, m, n, src, best_m, best_n, best_src; 164 uint64_t cur_freq; 165 int64_t best_diff, cur_diff; 166 int error; 167 168 sc = clknode_get_softc(clk); 169 best_n = best_m = 0; 170 best_diff = (int64_t)*fout; 171 best_src = 0; 172 173 for (src = 0; src < sc->parent_cnt; src++) { 174 error = clknode_set_parent_by_idx(clk, src); 175 if (error != 0) 176 continue; 177 error = clknode_get_freq(clknode_get_parent(clk), &fin); 178 if (error != 0) 179 continue; 180 181 for (n = 0; n <= CLK_RATIO_N_MAX; n++) 182 for (m = 0; m <= CLK_RATIO_M_MAX; m++) { 183 cur_freq = fin / (1 << n) / (m + 1); 184 cur_diff = (int64_t)*fout - cur_freq; 185 if (cur_diff >= 0 && cur_diff < best_diff) { 186 best_src = src; 187 best_diff = cur_diff; 188 best_m = m; 189 best_n = n; 190 } 191 } 192 } 193 194 if (best_diff == (int64_t)*fout) 195 return (ERANGE); 196 197 error = clknode_set_parent_by_idx(clk, best_src); 198 if (error != 0) 199 return (error); 200 error = clknode_get_freq(clknode_get_parent(clk), &fin); 201 if (error != 0) 202 return (error); 203 204 DEVICE_LOCK(sc); 205 MODCLK_READ(sc, &val); 206 val &= ~(CLK_RATIO_N | CLK_RATIO_M); 207 val |= (best_n << CLK_RATIO_N_SHIFT); 208 val |= (best_m << CLK_RATIO_M_SHIFT); 209 MODCLK_WRITE(sc, val); 210 DEVICE_UNLOCK(sc); 211 212 *fout = fin / (1 << best_n) / (best_m + 1); 213 *stop = 1; 214 215 return (0); 216} 217 218static clknode_method_t aw_modclk_clknode_methods[] = { 219 /* Device interface */ 220 CLKNODEMETHOD(clknode_init, aw_modclk_init), 221 CLKNODEMETHOD(clknode_set_gate, aw_modclk_set_gate), 222 CLKNODEMETHOD(clknode_set_mux, aw_modclk_set_mux), 223 CLKNODEMETHOD(clknode_recalc_freq, aw_modclk_recalc_freq), 224 CLKNODEMETHOD(clknode_set_freq, aw_modclk_set_freq), 225 CLKNODEMETHOD_END 226}; 227DEFINE_CLASS_1(aw_modclk_clknode, aw_modclk_clknode_class, 228 aw_modclk_clknode_methods, sizeof(struct aw_modclk_sc), clknode_class); 229 230static int 231aw_modclk_probe(device_t dev) 232{ 233 if (!ofw_bus_status_okay(dev)) 234 return (ENXIO); 235 236 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) 237 return (ENXIO); 238 239 device_set_desc(dev, "Allwinner Module Clock"); 240 return (BUS_PROBE_DEFAULT); 241} 242 243static int 244aw_modclk_attach(device_t dev) 245{ 246 struct clknode_init_def def; 247 struct aw_modclk_sc *sc; 248 struct clkdom *clkdom; 249 struct clknode *clk; 250 clk_t clk_parent; 251 bus_addr_t paddr; 252 bus_size_t psize; 253 phandle_t node; 254 int error, ncells, i; 255 256 node = ofw_bus_get_node(dev); 257 258 if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { 259 device_printf(dev, "cannot parse 'reg' property\n"); 260 return (ENXIO); 261 } 262 263 error = ofw_bus_parse_xref_list_get_length(node, "clocks", 264 "#clock-cells", &ncells); 265 if (error != 0) { 266 device_printf(dev, "cannot get clock count\n"); 267 return (error); 268 } 269 270 clkdom = clkdom_create(dev); 271 272 memset(&def, 0, sizeof(def)); 273 error = clk_parse_ofw_clk_name(dev, node, &def.name); 274 if (error != 0) { 275 device_printf(dev, "cannot parse clock name\n"); 276 error = ENXIO; 277 goto fail; 278 } 279 def.id = 1; 280 def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); 281 for (i = 0; i < ncells; i++) { 282 error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); 283 if (error != 0) { 284 device_printf(dev, "cannot get clock %d\n", i); 285 goto fail; 286 } 287 def.parent_names[i] = clk_get_name(clk_parent); 288 clk_release(clk_parent); 289 } 290 def.parent_cnt = ncells; 291 292 clk = clknode_create(clkdom, &aw_modclk_clknode_class, &def); 293 if (clk == NULL) { 294 device_printf(dev, "cannot create clknode\n"); 295 error = ENXIO; 296 goto fail; 297 } 298 299 sc = clknode_get_softc(clk); 300 sc->reg = paddr; 301 sc->clkdev = device_get_parent(dev); 302 sc->parent_cnt = def.parent_cnt; 303 304 clknode_register(clkdom, clk); 305 306 if (clkdom_finit(clkdom) != 0) { 307 device_printf(dev, "cannot finalize clkdom initialization\n"); 308 error = ENXIO; 309 goto fail; 310 } 311 312 if (bootverbose) 313 clkdom_dump(clkdom); 314 315 return (0); 316 317fail: 318 return (error); 319} 320 321static device_method_t aw_modclk_methods[] = { 322 /* Device interface */ 323 DEVMETHOD(device_probe, aw_modclk_probe), 324 DEVMETHOD(device_attach, aw_modclk_attach), 325 326 DEVMETHOD_END 327}; 328 329static driver_t aw_modclk_driver = { 330 "aw_modclk", 331 aw_modclk_methods, 332 0 333}; 334 335static devclass_t aw_modclk_devclass; 336 337EARLY_DRIVER_MODULE(aw_modclk, simplebus, aw_modclk_driver, 338 aw_modclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); 339