aw_ccu.c revision 302408
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/aw_ccu.c 301082 2016-05-31 21:58:09Z jmcneill $ 27 */ 28 29/* 30 * Allwinner oscillator clock 31 */ 32 33#include <sys/cdefs.h> 34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/aw_ccu.c 301082 2016-05-31 21:58:09Z jmcneill $"); 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/fdt/simplebus.h> 45#include <dev/fdt/fdt_common.h> 46 47#include <dev/ofw/ofw_bus.h> 48#include <dev/ofw/ofw_bus_subr.h> 49 50#include <dev/extres/clk/clk.h> 51 52#include "clkdev_if.h" 53 54#define CCU_BASE 0x01c20000 55#define CCU_SIZE 0x400 56 57#define PRCM_BASE 0x01f01400 58#define PRCM_SIZE 0x200 59 60#define SYSCTRL_BASE 0x01c00000 61#define SYSCTRL_SIZE 0x34 62 63struct aw_ccu_softc { 64 struct simplebus_softc sc; 65 bus_space_tag_t bst; 66 bus_space_handle_t ccu_bsh; 67 bus_space_handle_t prcm_bsh; 68 bus_space_handle_t sysctrl_bsh; 69 struct mtx mtx; 70 int flags; 71}; 72 73#define CLOCK_CCU (1 << 0) 74#define CLOCK_PRCM (1 << 1) 75#define CLOCK_SYSCTRL (1 << 2) 76 77static struct ofw_compat_data compat_data[] = { 78 { "allwinner,sun4i-a10", CLOCK_CCU }, 79 { "allwinner,sun7i-a20", CLOCK_CCU }, 80 { "allwinner,sun6i-a31", CLOCK_CCU }, 81 { "allwinner,sun6i-a31s", CLOCK_CCU }, 82 { "allwinner,sun8i-a83t", CLOCK_CCU|CLOCK_PRCM|CLOCK_SYSCTRL }, 83 { "allwinner,sun8i-h3", CLOCK_CCU }, 84 { NULL, 0 } 85}; 86 87static int 88aw_ccu_check_addr(struct aw_ccu_softc *sc, bus_addr_t addr, 89 bus_space_handle_t *pbsh, bus_size_t *poff) 90{ 91 if (addr >= CCU_BASE && addr < (CCU_BASE + CCU_SIZE) && 92 (sc->flags & CLOCK_CCU) != 0) { 93 *poff = addr - CCU_BASE; 94 *pbsh = sc->ccu_bsh; 95 return (0); 96 } 97 if (addr >= PRCM_BASE && addr < (PRCM_BASE + PRCM_SIZE) && 98 (sc->flags & CLOCK_PRCM) != 0) { 99 *poff = addr - PRCM_BASE; 100 *pbsh = sc->prcm_bsh; 101 return (0); 102 } 103 if (addr >= SYSCTRL_BASE && addr < (SYSCTRL_BASE + SYSCTRL_SIZE) && 104 (sc->flags & CLOCK_SYSCTRL) != 0) { 105 *poff = addr - SYSCTRL_BASE; 106 *pbsh = sc->sysctrl_bsh; 107 return (0); 108 } 109 return (EINVAL); 110} 111 112static int 113aw_ccu_write_4(device_t dev, bus_addr_t addr, uint32_t val) 114{ 115 struct aw_ccu_softc *sc; 116 bus_space_handle_t bsh; 117 bus_size_t reg; 118 119 sc = device_get_softc(dev); 120 121 if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) 122 return (EINVAL); 123 124 mtx_assert(&sc->mtx, MA_OWNED); 125 bus_space_write_4(sc->bst, bsh, reg, val); 126 127 return (0); 128} 129 130static int 131aw_ccu_read_4(device_t dev, bus_addr_t addr, uint32_t *val) 132{ 133 struct aw_ccu_softc *sc; 134 bus_space_handle_t bsh; 135 bus_size_t reg; 136 137 sc = device_get_softc(dev); 138 139 if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) 140 return (EINVAL); 141 142 mtx_assert(&sc->mtx, MA_OWNED); 143 *val = bus_space_read_4(sc->bst, bsh, reg); 144 145 return (0); 146} 147 148static int 149aw_ccu_modify_4(device_t dev, bus_addr_t addr, uint32_t clr, uint32_t set) 150{ 151 struct aw_ccu_softc *sc; 152 bus_space_handle_t bsh; 153 bus_size_t reg; 154 uint32_t val; 155 156 sc = device_get_softc(dev); 157 158 if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) 159 return (EINVAL); 160 161 mtx_assert(&sc->mtx, MA_OWNED); 162 val = bus_space_read_4(sc->bst, bsh, reg); 163 val &= ~clr; 164 val |= set; 165 bus_space_write_4(sc->bst, bsh, reg, val); 166 167 return (0); 168} 169 170static void 171aw_ccu_device_lock(device_t dev) 172{ 173 struct aw_ccu_softc *sc; 174 175 sc = device_get_softc(dev); 176 mtx_lock(&sc->mtx); 177} 178 179static void 180aw_ccu_device_unlock(device_t dev) 181{ 182 struct aw_ccu_softc *sc; 183 184 sc = device_get_softc(dev); 185 mtx_unlock(&sc->mtx); 186} 187 188static const struct ofw_compat_data * 189aw_ccu_search_compatible(void) 190{ 191 const struct ofw_compat_data *compat; 192 phandle_t root; 193 194 root = OF_finddevice("/"); 195 for (compat = compat_data; compat->ocd_str != NULL; compat++) 196 if (fdt_is_compatible(root, compat->ocd_str)) 197 break; 198 199 return (compat); 200} 201 202static int 203aw_ccu_probe(device_t dev) 204{ 205 const char *name; 206 207 name = ofw_bus_get_name(dev); 208 209 if (name == NULL || strcmp(name, "clocks") != 0) 210 return (ENXIO); 211 212 if (aw_ccu_search_compatible()->ocd_data == 0) 213 return (ENXIO); 214 215 device_set_desc(dev, "Allwinner Clock Control Unit"); 216 return (BUS_PROBE_SPECIFIC); 217} 218 219static int 220aw_ccu_attach(device_t dev) 221{ 222 struct aw_ccu_softc *sc; 223 phandle_t node, child; 224 device_t cdev; 225 int error; 226 227 sc = device_get_softc(dev); 228 node = ofw_bus_get_node(dev); 229 230 simplebus_init(dev, node); 231 232 sc->flags = aw_ccu_search_compatible()->ocd_data; 233 234 /* 235 * Map registers. The DT doesn't have a "reg" property 236 * for the /clocks node and child nodes have conflicting "reg" 237 * properties. 238 */ 239 sc->bst = bus_get_bus_tag(dev); 240 if (sc->flags & CLOCK_CCU) { 241 error = bus_space_map(sc->bst, CCU_BASE, CCU_SIZE, 0, 242 &sc->ccu_bsh); 243 if (error != 0) { 244 device_printf(dev, "couldn't map CCU: %d\n", error); 245 return (error); 246 } 247 } 248 if (sc->flags & CLOCK_PRCM) { 249 error = bus_space_map(sc->bst, PRCM_BASE, PRCM_SIZE, 0, 250 &sc->prcm_bsh); 251 if (error != 0) { 252 device_printf(dev, "couldn't map PRCM: %d\n", error); 253 return (error); 254 } 255 } 256 if (sc->flags & CLOCK_SYSCTRL) { 257 error = bus_space_map(sc->bst, SYSCTRL_BASE, SYSCTRL_SIZE, 0, 258 &sc->sysctrl_bsh); 259 if (error != 0) { 260 device_printf(dev, "couldn't map SYSCTRL: %d\n", error); 261 return (error); 262 } 263 } 264 265 mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); 266 267 /* Attach child devices */ 268 for (child = OF_child(node); child > 0; child = OF_peer(child)) { 269 cdev = simplebus_add_device(dev, child, 0, NULL, -1, NULL); 270 if (cdev != NULL) 271 device_probe_and_attach(cdev); 272 } 273 274 return (bus_generic_attach(dev)); 275} 276 277static device_method_t aw_ccu_methods[] = { 278 /* Device interface */ 279 DEVMETHOD(device_probe, aw_ccu_probe), 280 DEVMETHOD(device_attach, aw_ccu_attach), 281 282 /* clkdev interface */ 283 DEVMETHOD(clkdev_write_4, aw_ccu_write_4), 284 DEVMETHOD(clkdev_read_4, aw_ccu_read_4), 285 DEVMETHOD(clkdev_modify_4, aw_ccu_modify_4), 286 DEVMETHOD(clkdev_device_lock, aw_ccu_device_lock), 287 DEVMETHOD(clkdev_device_unlock, aw_ccu_device_unlock), 288 289 DEVMETHOD_END 290}; 291 292DEFINE_CLASS_1(aw_ccu, aw_ccu_driver, aw_ccu_methods, 293 sizeof(struct aw_ccu_softc), simplebus_driver); 294 295static devclass_t aw_ccu_devclass; 296 297EARLY_DRIVER_MODULE(aw_ccu, simplebus, aw_ccu_driver, aw_ccu_devclass, 298 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); 299 300MODULE_VERSION(aw_ccu, 1); 301