aw_apbclk.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_apbclk.c 308324 2016-11-05 04:17:32Z mmel $ 27 */ 28 29/* 30 * Allwinner APB clock 31 */ 32 33#include <sys/cdefs.h> 34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_apbclk.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 A10_APB0_CLK_RATIO (0x3 << 8) 53#define A10_APB0_CLK_RATIO_SHIFT 8 54#define A10_APB1_CLK_SRC_SEL (0x3 << 24) 55#define A10_APB1_CLK_SRC_SEL_SHIFT 24 56#define A10_APB1_CLK_SRC_SEL_MAX 0x3 57#define A10_APB1_CLK_RAT_N (0x3 << 16) 58#define A10_APB1_CLK_RAT_N_SHIFT 16 59#define A10_APB1_CLK_RAT_M (0x1f << 0) 60#define A10_APB1_CLK_RAT_M_SHIFT 0 61#define A23_APB0_CLK_RATIO (0x3 << 0) 62#define A23_APB0_CLK_RATIO_SHIFT 0 63#define A83T_APB1_CLK_RATIO (0x3 << 8) 64#define A83T_APB1_CLK_RATIO_SHIFT 8 65 66enum aw_apbclk_type { 67 AW_A10_APB0 = 1, 68 AW_A10_APB1, 69 AW_A23_APB0, 70 AW_A83T_APB1, 71}; 72 73static struct ofw_compat_data compat_data[] = { 74 { "allwinner,sun4i-a10-apb0-clk", AW_A10_APB0 }, 75 { "allwinner,sun4i-a10-apb1-clk", AW_A10_APB1 }, 76 { "allwinner,sun8i-a23-apb0-clk", AW_A23_APB0 }, 77 { "allwinner,sun8i-a83t-apb1-clk", AW_A83T_APB1 }, 78 { NULL, 0 } 79}; 80 81struct aw_apbclk_sc { 82 device_t clkdev; 83 bus_addr_t reg; 84 enum aw_apbclk_type type; 85}; 86 87#define APBCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) 88#define APBCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) 89#define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) 90#define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) 91 92static int 93aw_apbclk_init(struct clknode *clk, device_t dev) 94{ 95 struct aw_apbclk_sc *sc; 96 uint32_t val, index; 97 98 sc = clknode_get_softc(clk); 99 100 switch (sc->type) { 101 case AW_A10_APB0: 102 case AW_A23_APB0: 103 case AW_A83T_APB1: 104 index = 0; 105 break; 106 case AW_A10_APB1: 107 DEVICE_LOCK(sc); 108 APBCLK_READ(sc, &val); 109 DEVICE_UNLOCK(sc); 110 index = (val & A10_APB1_CLK_SRC_SEL) >> 111 A10_APB1_CLK_SRC_SEL_SHIFT; 112 break; 113 default: 114 return (ENXIO); 115 } 116 117 clknode_init_parent_idx(clk, index); 118 return (0); 119} 120 121static int 122aw_apbclk_recalc_freq(struct clknode *clk, uint64_t *freq) 123{ 124 struct aw_apbclk_sc *sc; 125 uint32_t val, div, m, n; 126 127 sc = clknode_get_softc(clk); 128 129 DEVICE_LOCK(sc); 130 APBCLK_READ(sc, &val); 131 DEVICE_UNLOCK(sc); 132 133 switch (sc->type) { 134 case AW_A10_APB0: 135 div = 1 << ((val & A10_APB0_CLK_RATIO) >> 136 A10_APB0_CLK_RATIO_SHIFT); 137 if (div == 1) 138 div = 2; 139 *freq = *freq / div; 140 break; 141 case AW_A10_APB1: 142 n = 1 << ((val & A10_APB1_CLK_RAT_N) >> 143 A10_APB1_CLK_RAT_N_SHIFT); 144 m = ((val & A10_APB1_CLK_RAT_N) >> 145 A10_APB1_CLK_RAT_M_SHIFT) + 1; 146 *freq = *freq / n / m; 147 break; 148 case AW_A23_APB0: 149 div = 1 << ((val & A23_APB0_CLK_RATIO) >> 150 A23_APB0_CLK_RATIO_SHIFT); 151 *freq = *freq / div; 152 break; 153 case AW_A83T_APB1: 154 div = ((val & A83T_APB1_CLK_RATIO) >> 155 A83T_APB1_CLK_RATIO_SHIFT) + 1; 156 *freq = *freq / div; 157 break; 158 default: 159 return (ENXIO); 160 } 161 162 return (0); 163} 164 165static int 166aw_apbclk_set_mux(struct clknode *clk, int index) 167{ 168 struct aw_apbclk_sc *sc; 169 uint32_t val; 170 171 sc = clknode_get_softc(clk); 172 173 if (sc->type != AW_A10_APB1) 174 return (ENXIO); 175 176 if (index < 0 || index > A10_APB1_CLK_SRC_SEL_MAX) 177 return (ERANGE); 178 179 DEVICE_LOCK(sc); 180 APBCLK_READ(sc, &val); 181 val &= ~A10_APB1_CLK_SRC_SEL; 182 val |= (index << A10_APB1_CLK_SRC_SEL_SHIFT); 183 APBCLK_WRITE(sc, val); 184 DEVICE_UNLOCK(sc); 185 186 return (0); 187} 188 189static clknode_method_t aw_apbclk_clknode_methods[] = { 190 /* Device interface */ 191 CLKNODEMETHOD(clknode_init, aw_apbclk_init), 192 CLKNODEMETHOD(clknode_recalc_freq, aw_apbclk_recalc_freq), 193 CLKNODEMETHOD(clknode_set_mux, aw_apbclk_set_mux), 194 CLKNODEMETHOD_END 195}; 196DEFINE_CLASS_1(aw_apbclk_clknode, aw_apbclk_clknode_class, 197 aw_apbclk_clknode_methods, sizeof(struct aw_apbclk_sc), clknode_class); 198 199static int 200aw_apbclk_probe(device_t dev) 201{ 202 if (!ofw_bus_status_okay(dev)) 203 return (ENXIO); 204 205 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) 206 return (ENXIO); 207 208 device_set_desc(dev, "Allwinner APB Clock"); 209 return (BUS_PROBE_DEFAULT); 210} 211 212static int 213aw_apbclk_attach(device_t dev) 214{ 215 struct clknode_init_def def; 216 struct aw_apbclk_sc *sc; 217 struct clkdom *clkdom; 218 struct clknode *clk; 219 clk_t clk_parent; 220 bus_addr_t paddr; 221 bus_size_t psize; 222 phandle_t node; 223 int error, ncells, i; 224 225 node = ofw_bus_get_node(dev); 226 227 if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { 228 device_printf(dev, "cannot parse 'reg' property\n"); 229 return (ENXIO); 230 } 231 232 error = ofw_bus_parse_xref_list_get_length(node, "clocks", 233 "#clock-cells", &ncells); 234 if (error != 0) { 235 device_printf(dev, "cannot get clock count\n"); 236 return (error); 237 } 238 239 clkdom = clkdom_create(dev); 240 241 memset(&def, 0, sizeof(def)); 242 error = clk_parse_ofw_clk_name(dev, node, &def.name); 243 if (error != 0) { 244 device_printf(dev, "cannot parse clock name\n"); 245 error = ENXIO; 246 goto fail; 247 } 248 def.id = 1; 249 def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); 250 for (i = 0; i < ncells; i++) { 251 error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); 252 if (error != 0) { 253 device_printf(dev, "cannot get clock %d\n", i); 254 goto fail; 255 } 256 def.parent_names[i] = clk_get_name(clk_parent); 257 clk_release(clk_parent); 258 } 259 def.parent_cnt = ncells; 260 261 clk = clknode_create(clkdom, &aw_apbclk_clknode_class, &def); 262 if (clk == NULL) { 263 device_printf(dev, "cannot create clknode\n"); 264 error = ENXIO; 265 goto fail; 266 } 267 268 sc = clknode_get_softc(clk); 269 sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; 270 sc->reg = paddr; 271 sc->clkdev = device_get_parent(dev); 272 273 clknode_register(clkdom, clk); 274 275 if (clkdom_finit(clkdom) != 0) { 276 device_printf(dev, "cannot finalize clkdom initialization\n"); 277 error = ENXIO; 278 goto fail; 279 } 280 281 if (bootverbose) 282 clkdom_dump(clkdom); 283 284 return (0); 285 286fail: 287 return (error); 288} 289 290static device_method_t aw_apbclk_methods[] = { 291 /* Device interface */ 292 DEVMETHOD(device_probe, aw_apbclk_probe), 293 DEVMETHOD(device_attach, aw_apbclk_attach), 294 295 DEVMETHOD_END 296}; 297 298static driver_t aw_apbclk_driver = { 299 "aw_apbclk", 300 aw_apbclk_methods, 301 0 302}; 303 304static devclass_t aw_apbclk_devclass; 305 306EARLY_DRIVER_MODULE(aw_apbclk, simplebus, aw_apbclk_driver, 307 aw_apbclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); 308