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