aw_cpusclk.c revision 308324
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_cpusclk.c 308324 2016-11-05 04:17:32Z mmel $ 27 */ 28 29/* 30 * Allwinner CPUS clock 31 */ 32 33#include <sys/cdefs.h> 34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_cpusclk.c 308324 2016-11-05 04:17:32Z mmel $"); 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.h> 49 50#include "clkdev_if.h" 51 52#define A80_CPUS_CLK_SRC_SEL (0x3 << 16) 53#define A80_CPUS_CLK_SRC_SEL_SHIFT 16 54#define A80_CPUS_CLK_SRC_SEL_X32KI 0 55#define A80_CPUS_CLK_SRC_SEL_OSC24M 1 56#define A80_CPUS_CLK_SRC_SEL_PLL_PERIPH 2 57#define A80_CPUS_CLK_SRC_SEL_PLL_AUDIO 3 58#define A80_CPUS_POST_DIV (0x1f << 8) 59#define A80_CPUS_POST_DIV_SHIFT 8 60#define A80_CPUS_CLK_RATIO (0x3 << 4) 61#define A80_CPUS_CLK_RATIO_SHIFT 4 62 63#define A83T_CPUS_CLK_SRC_SEL (0x3 << 16) 64#define A83T_CPUS_CLK_SRC_SEL_SHIFT 16 65#define A83T_CPUS_CLK_SRC_SEL_X32KI 0 66#define A83T_CPUS_CLK_SRC_SEL_OSC24M 1 67#define A83T_CPUS_CLK_SRC_SEL_PLL_PERIPH 2 68#define A83T_CPUS_CLK_SRC_SEL_INTERNAL_OSC 3 69#define A83T_CPUS_POST_DIV (0x1f << 8) 70#define A83T_CPUS_POST_DIV_SHIFT 8 71#define A83T_CPUS_CLK_RATIO (0x3 << 4) 72#define A83T_CPUS_CLK_RATIO_SHIFT 4 73 74enum aw_cpusclk_type { 75 AW_A80_CPUS = 1, 76 AW_A83T_CPUS, 77}; 78 79static struct ofw_compat_data compat_data[] = { 80 { "allwinner,sun9i-a80-cpus-clk", AW_A80_CPUS }, 81 { "allwinner,sun8i-a83t-cpus-clk", AW_A83T_CPUS }, 82 { NULL, 0 } 83}; 84 85struct aw_cpusclk_sc { 86 device_t clkdev; 87 bus_addr_t reg; 88 enum aw_cpusclk_type type; 89}; 90 91#define CPUSCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) 92#define CPUSCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) 93#define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) 94#define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) 95 96static int 97aw_cpusclk_init(struct clknode *clk, device_t dev) 98{ 99 struct aw_cpusclk_sc *sc; 100 uint32_t val, mask, shift, index; 101 102 sc = clknode_get_softc(clk); 103 104 switch (sc->type) { 105 case AW_A80_CPUS: 106 mask = A80_CPUS_CLK_SRC_SEL; 107 shift = A80_CPUS_CLK_SRC_SEL_SHIFT; 108 break; 109 case AW_A83T_CPUS: 110 mask = A83T_CPUS_CLK_SRC_SEL; 111 shift = A83T_CPUS_CLK_SRC_SEL_SHIFT; 112 break; 113 default: 114 return (ENXIO); 115 } 116 117 DEVICE_LOCK(sc); 118 CPUSCLK_READ(sc, &val); 119 DEVICE_UNLOCK(sc); 120 index = (val & mask) >> shift; 121 122 clknode_init_parent_idx(clk, index); 123 return (0); 124} 125 126static int 127aw_cpusclk_recalc_freq(struct clknode *clk, uint64_t *freq) 128{ 129 struct aw_cpusclk_sc *sc; 130 uint32_t val, src_sel, post_div, clk_ratio; 131 132 sc = clknode_get_softc(clk); 133 134 DEVICE_LOCK(sc); 135 CPUSCLK_READ(sc, &val); 136 DEVICE_UNLOCK(sc); 137 138 switch (sc->type) { 139 case AW_A80_CPUS: 140 src_sel = (val & A80_CPUS_CLK_SRC_SEL) >> 141 A80_CPUS_CLK_SRC_SEL_SHIFT; 142 post_div = ((val & A80_CPUS_POST_DIV) >> 143 A80_CPUS_POST_DIV_SHIFT) + 1; 144 clk_ratio = ((val & A80_CPUS_CLK_RATIO) >> 145 A80_CPUS_CLK_RATIO_SHIFT) + 1; 146 if (src_sel == A80_CPUS_CLK_SRC_SEL_PLL_PERIPH) 147 *freq = *freq / post_div / clk_ratio; 148 else 149 *freq = *freq / clk_ratio; 150 break; 151 case AW_A83T_CPUS: 152 src_sel = (val & A83T_CPUS_CLK_SRC_SEL) >> 153 A83T_CPUS_CLK_SRC_SEL_SHIFT; 154 post_div = ((val & A83T_CPUS_POST_DIV) >> 155 A83T_CPUS_POST_DIV_SHIFT) + 1; 156 clk_ratio = 1 << ((val & A83T_CPUS_CLK_RATIO) >> 157 A83T_CPUS_CLK_RATIO_SHIFT); 158 if (src_sel == A83T_CPUS_CLK_SRC_SEL_PLL_PERIPH) 159 *freq = *freq / post_div / clk_ratio; 160 else 161 *freq = *freq / clk_ratio; 162 break; 163 default: 164 return (EINVAL); 165 } 166 167 return (0); 168} 169 170static int 171aw_cpusclk_set_mux(struct clknode *clk, int index) 172{ 173 struct aw_cpusclk_sc *sc; 174 uint32_t mask, shift, val; 175 176 sc = clknode_get_softc(clk); 177 178 switch (sc->type) { 179 case AW_A80_CPUS: 180 mask = A80_CPUS_CLK_SRC_SEL; 181 shift = A80_CPUS_CLK_SRC_SEL_SHIFT; 182 break; 183 case AW_A83T_CPUS: 184 mask = A83T_CPUS_CLK_SRC_SEL; 185 shift = A83T_CPUS_CLK_SRC_SEL_SHIFT; 186 break; 187 default: 188 return (ENXIO); 189 } 190 191 DEVICE_LOCK(sc); 192 CPUSCLK_READ(sc, &val); 193 val &= ~mask; 194 val |= (index << shift); 195 CPUSCLK_WRITE(sc, val); 196 DEVICE_UNLOCK(sc); 197 198 return (0); 199} 200 201static clknode_method_t aw_cpusclk_clknode_methods[] = { 202 /* Device interface */ 203 CLKNODEMETHOD(clknode_init, aw_cpusclk_init), 204 CLKNODEMETHOD(clknode_recalc_freq, aw_cpusclk_recalc_freq), 205 CLKNODEMETHOD(clknode_set_mux, aw_cpusclk_set_mux), 206 CLKNODEMETHOD_END 207}; 208DEFINE_CLASS_1(aw_cpusclk_clknode, aw_cpusclk_clknode_class, 209 aw_cpusclk_clknode_methods, sizeof(struct aw_cpusclk_sc), clknode_class); 210 211static int 212aw_cpusclk_probe(device_t dev) 213{ 214 if (!ofw_bus_status_okay(dev)) 215 return (ENXIO); 216 217 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) 218 return (ENXIO); 219 220 device_set_desc(dev, "Allwinner CPUS Clock"); 221 return (BUS_PROBE_DEFAULT); 222} 223 224static int 225aw_cpusclk_attach(device_t dev) 226{ 227 struct clknode_init_def def; 228 struct aw_cpusclk_sc *sc; 229 struct clkdom *clkdom; 230 struct clknode *clk; 231 clk_t clk_parent; 232 bus_addr_t paddr; 233 bus_size_t psize; 234 phandle_t node; 235 int error, ncells, i; 236 237 node = ofw_bus_get_node(dev); 238 239 if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { 240 device_printf(dev, "cannot parse 'reg' property\n"); 241 return (ENXIO); 242 } 243 244 error = ofw_bus_parse_xref_list_get_length(node, "clocks", 245 "#clock-cells", &ncells); 246 if (error != 0) { 247 device_printf(dev, "cannot get clock count\n"); 248 return (error); 249 } 250 251 clkdom = clkdom_create(dev); 252 253 memset(&def, 0, sizeof(def)); 254 def.id = 1; 255 def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, 256 M_WAITOK); 257 for (i = 0; i < ncells; i++) { 258 error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); 259 if (error != 0) { 260 device_printf(dev, "cannot get clock %d\n", i); 261 goto fail; 262 } 263 def.parent_names[i] = clk_get_name(clk_parent); 264 clk_release(clk_parent); 265 } 266 def.parent_cnt = ncells; 267 268 error = clk_parse_ofw_clk_name(dev, node, &def.name); 269 if (error != 0) { 270 device_printf(dev, "cannot parse clock name\n"); 271 error = ENXIO; 272 goto fail; 273 } 274 275 clk = clknode_create(clkdom, &aw_cpusclk_clknode_class, &def); 276 if (clk == NULL) { 277 device_printf(dev, "cannot create clknode\n"); 278 error = ENXIO; 279 goto fail; 280 } 281 sc = clknode_get_softc(clk); 282 sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; 283 sc->reg = paddr; 284 sc->clkdev = device_get_parent(dev); 285 286 clknode_register(clkdom, clk); 287 288 if (clkdom_finit(clkdom) != 0) { 289 device_printf(dev, "cannot finalize clkdom initialization\n"); 290 error = ENXIO; 291 goto fail; 292 } 293 294 if (bootverbose) 295 clkdom_dump(clkdom); 296 297 return (0); 298 299fail: 300 return (error); 301} 302 303static device_method_t aw_cpusclk_methods[] = { 304 /* Device interface */ 305 DEVMETHOD(device_probe, aw_cpusclk_probe), 306 DEVMETHOD(device_attach, aw_cpusclk_attach), 307 308 DEVMETHOD_END 309}; 310 311static driver_t aw_cpusclk_driver = { 312 "aw_cpusclk", 313 aw_cpusclk_methods, 314 0 315}; 316 317static devclass_t aw_cpusclk_devclass; 318 319EARLY_DRIVER_MODULE(aw_cpusclk, simplebus, aw_cpusclk_driver, 320 aw_cpusclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); 321