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 46#include <dev/ofw/ofw_bus.h> 47#include <dev/ofw/ofw_bus_subr.h> 48 49#include <dev/extres/clk/clk.h> 50 51#include "clkdev_if.h" 52 53#define CCU_BASE 0x01c20000 54#define CCU_SIZE 0x400 55 56struct aw_ccu_softc { 57 struct simplebus_softc sc; 58 bus_space_tag_t bst; 59 bus_space_handle_t bsh; 60 struct mtx mtx; 61 int flags; 62}; 63 64static struct ofw_compat_data compat_data[] = { 65 { "allwinner,sun7i-a20", 1 }, 66 { "allwinner,sun6i-a31", 1 }, 67 { "allwinner,sun6i-a31s", 1 }, 68 { NULL, 0 } 69}; 70 71static int 72aw_ccu_check_addr(struct aw_ccu_softc *sc, bus_addr_t addr, 73 bus_space_handle_t *pbsh, bus_size_t *poff) 74{ 75 if (addr >= CCU_BASE && addr < (CCU_BASE + CCU_SIZE)) { 76 *poff = addr - CCU_BASE; 77 *pbsh = sc->bsh; 78 return (0); 79 } 80 return (EINVAL); 81} 82 83static int 84aw_ccu_write_4(device_t dev, bus_addr_t addr, uint32_t val) 85{ 86 struct aw_ccu_softc *sc; 87 bus_space_handle_t bsh; 88 bus_size_t reg; 89 90 sc = device_get_softc(dev); 91 92 if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) 93 return (EINVAL); 94 95 mtx_assert(&sc->mtx, MA_OWNED); 96 bus_space_write_4(sc->bst, bsh, reg, val); 97 98 return (0); 99} 100 101static int 102aw_ccu_read_4(device_t dev, bus_addr_t addr, uint32_t *val) 103{ 104 struct aw_ccu_softc *sc; 105 bus_space_handle_t bsh; 106 bus_size_t reg; 107 108 sc = device_get_softc(dev); 109 110 if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) 111 return (EINVAL); 112 113 mtx_assert(&sc->mtx, MA_OWNED); 114 *val = bus_space_read_4(sc->bst, bsh, reg); 115 116 return (0); 117} 118 119static int 120aw_ccu_modify_4(device_t dev, bus_addr_t addr, uint32_t clr, uint32_t set) 121{ 122 struct aw_ccu_softc *sc; 123 bus_space_handle_t bsh; 124 bus_size_t reg; 125 uint32_t val; 126 127 sc = device_get_softc(dev); 128 129 if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) 130 return (EINVAL); 131 132 mtx_assert(&sc->mtx, MA_OWNED); 133 val = bus_space_read_4(sc->bst, bsh, reg); 134 val &= ~clr; 135 val |= set; 136 bus_space_write_4(sc->bst, bsh, reg, val); 137 138 return (0); 139} 140 141static void 142aw_ccu_device_lock(device_t dev) 143{ 144 struct aw_ccu_softc *sc; 145 146 sc = device_get_softc(dev); 147 mtx_lock(&sc->mtx); 148} 149 150static void 151aw_ccu_device_unlock(device_t dev) 152{ 153 struct aw_ccu_softc *sc; 154 155 sc = device_get_softc(dev); 156 mtx_unlock(&sc->mtx); 157} 158 159static const struct ofw_compat_data * 160aw_ccu_search_compatible(void) 161{ 162 const struct ofw_compat_data *compat; 163 phandle_t root; 164 165 root = OF_finddevice("/"); 166 for (compat = compat_data; compat->ocd_str != NULL; compat++) 167 if (ofw_bus_node_is_compatible(root, compat->ocd_str)) 168 break; 169 170 return (compat); 171} 172 173static int 174aw_ccu_probe(device_t dev) 175{ 176 const char *name; 177 178 name = ofw_bus_get_name(dev); 179 180 if (name == NULL || strcmp(name, "clocks") != 0) 181 return (ENXIO); 182 183 if (aw_ccu_search_compatible()->ocd_data == 0) 184 return (ENXIO); 185 186 device_set_desc(dev, "Allwinner Clock Control Unit"); 187 return (BUS_PROBE_SPECIFIC); 188} 189 190static int 191aw_ccu_attach(device_t dev) 192{ 193 struct aw_ccu_softc *sc; 194 phandle_t node, child; 195 device_t cdev; 196 int error; 197 198 sc = device_get_softc(dev); 199 node = ofw_bus_get_node(dev); 200 201 simplebus_init(dev, node); 202 203 sc->flags = aw_ccu_search_compatible()->ocd_data; 204 205 /* 206 * Map registers. The DT doesn't have a "reg" property 207 * for the /clocks node and child nodes have conflicting "reg" 208 * properties. 209 */ 210 sc->bst = bus_get_bus_tag(dev); 211 error = bus_space_map(sc->bst, CCU_BASE, CCU_SIZE, 0, 212 &sc->bsh); 213 if (error != 0) { 214 device_printf(dev, "couldn't map CCU: %d\n", error); 215 return (error); 216 } 217 218 mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); 219 220 /* Attach child devices */ 221 for (child = OF_child(node); child > 0; child = OF_peer(child)) { 222 cdev = simplebus_add_device(dev, child, 0, NULL, -1, NULL); 223 if (cdev != NULL) 224 device_probe_and_attach(cdev); 225 } 226 227 return (bus_generic_attach(dev)); 228} 229 230static device_method_t aw_ccu_methods[] = { 231 /* Device interface */ 232 DEVMETHOD(device_probe, aw_ccu_probe), 233 DEVMETHOD(device_attach, aw_ccu_attach), 234 235 /* clkdev interface */ 236 DEVMETHOD(clkdev_write_4, aw_ccu_write_4), 237 DEVMETHOD(clkdev_read_4, aw_ccu_read_4), 238 DEVMETHOD(clkdev_modify_4, aw_ccu_modify_4), 239 DEVMETHOD(clkdev_device_lock, aw_ccu_device_lock), 240 DEVMETHOD(clkdev_device_unlock, aw_ccu_device_unlock), 241 242 DEVMETHOD_END 243}; 244 245DEFINE_CLASS_1(aw_ccu, aw_ccu_driver, aw_ccu_methods, 246 sizeof(struct aw_ccu_softc), simplebus_driver); 247 248static devclass_t aw_ccu_devclass; 249 250EARLY_DRIVER_MODULE(aw_ccu, simplebus, aw_ccu_driver, aw_ccu_devclass, 251 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); 252 253MODULE_VERSION(aw_ccu, 1); 254