1/*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2019 Axiado Corporation 5 * All rights reserved. 6 * 7 * This software was developed in part by Kristof Provost under contract for 8 * Axiado Corporation. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32#include <sys/cdefs.h> 33__FBSDID("$FreeBSD$"); 34 35#include <sys/param.h> 36#include <sys/systm.h> 37#include <sys/bus.h> 38#include <sys/kernel.h> 39#include <sys/lock.h> 40#include <sys/module.h> 41#include <sys/mutex.h> 42#include <sys/rman.h> 43 44#include <machine/bus.h> 45#include <machine/cpu.h> 46 47#include <dev/extres/clk/clk.h> 48#include <dev/extres/clk/clk_fixed.h> 49 50#include <dev/ofw/ofw_bus.h> 51#include <dev/ofw/ofw_bus_subr.h> 52#include <dev/ofw/openfirm.h> 53 54#include <dt-bindings/clock/sifive-fu540-prci.h> 55 56static struct ofw_compat_data compat_data[] = { 57 { "sifive,aloeprci0", 1 }, 58 { "sifive,ux00prci0", 1 }, 59 { "sifive,fu540-c000-prci", 1 }, 60 { NULL, 0 }, 61}; 62 63static struct resource_spec prci_spec[] = { 64 { SYS_RES_MEMORY, 0, RF_ACTIVE }, 65 RESOURCE_SPEC_END 66}; 67 68struct prci_softc { 69 device_t dev; 70 71 struct mtx mtx; 72 73 struct clkdom *clkdom; 74 struct resource *res; 75 bus_space_tag_t bst; 76 bus_space_handle_t bsh; 77}; 78 79struct prci_clk_pll_sc { 80 struct prci_softc *parent_sc; 81 uint32_t reg; 82}; 83 84#define PRCI_LOCK(sc) mtx_lock(&(sc)->mtx) 85#define PRCI_UNLOCK(sc) mtx_unlock(&(sc)->mtx) 86#define PRCI_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED); 87#define PRCI_ASSERT_UNLOCKED(sc) mtx_assert(&(sc)->mtx, MA_NOTOWNED); 88 89#define PRCI_COREPLL_CFG0 0x4 90#define PRCI_DDRPLL_CFG0 0xC 91#define PRCI_GEMGXLPLL_CFG0 0x1C 92 93#define PRCI_PLL_DIVR_MASK 0x3f 94#define PRCI_PLL_DIVR_SHIFT 0 95#define PRCI_PLL_DIVF_MASK 0x7fc0 96#define PRCI_PLL_DIVF_SHIFT 6 97#define PRCI_PLL_DIVQ_MASK 0x38000 98#define PRCI_PLL_DIVQ_SHIFT 15 99 100#define PRCI_READ(_sc, _reg) \ 101 bus_space_read_4((_sc)->bst, (_sc)->bsh, (_reg)) 102 103struct prci_pll_def { 104 uint32_t id; 105 const char *name; 106 uint32_t reg; 107}; 108 109#define PLL(_id, _name, _base) \ 110{ \ 111 .id = (_id), \ 112 .name = (_name), \ 113 .reg = (_base), \ 114} 115 116/* PLL Clocks */ 117struct prci_pll_def pll_clks[] = { 118 PLL(PRCI_CLK_COREPLL, "coreclk", PRCI_COREPLL_CFG0), 119 PLL(PRCI_CLK_DDRPLL, "ddrclk", PRCI_DDRPLL_CFG0), 120 PLL(PRCI_CLK_GEMGXLPLL, "gemgxclk", PRCI_GEMGXLPLL_CFG0), 121}; 122 123/* Fixed divisor clock TLCLK. */ 124struct clk_fixed_def tlclk_def = { 125 .clkdef.id = PRCI_CLK_TLCLK, 126 .clkdef.name = "prci_tlclk", 127 .clkdef.parent_names = (const char *[]){"coreclk"}, 128 .clkdef.parent_cnt = 1, 129 .clkdef.flags = CLK_NODE_STATIC_STRINGS, 130 .mult = 1, 131 .div = 2, 132}; 133 134static int 135prci_clk_pll_init(struct clknode *clk, device_t dev) 136{ 137 138 clknode_init_parent_idx(clk, 0); 139 140 return (0); 141} 142 143static int 144prci_clk_pll_recalc(struct clknode *clk, uint64_t *freq) 145{ 146 struct prci_clk_pll_sc *sc; 147 struct clknode *parent_clk; 148 uint32_t val; 149 uint64_t refclk, divf, divq, divr; 150 int err; 151 152 KASSERT(freq != NULL, ("freq cannot be NULL")); 153 154 sc = clknode_get_softc(clk); 155 156 PRCI_LOCK(sc->parent_sc); 157 158 /* Get refclock frequency. */ 159 parent_clk = clknode_get_parent(clk); 160 err = clknode_get_freq(parent_clk, &refclk); 161 if (err) { 162 device_printf(sc->parent_sc->dev, 163 "Failed to get refclk frequency\n"); 164 PRCI_UNLOCK(sc->parent_sc); 165 return (err); 166 } 167 168 /* Calculate the PLL output */ 169 val = PRCI_READ(sc->parent_sc, sc->reg); 170 171 divf = (val & PRCI_PLL_DIVF_MASK) >> PRCI_PLL_DIVF_SHIFT; 172 divq = (val & PRCI_PLL_DIVQ_MASK) >> PRCI_PLL_DIVQ_SHIFT; 173 divr = (val & PRCI_PLL_DIVR_MASK) >> PRCI_PLL_DIVR_SHIFT; 174 175 *freq = refclk / (divr + 1) * (2 * (divf + 1)) / (1 << divq); 176 177 PRCI_UNLOCK(sc->parent_sc); 178 179 return (0); 180} 181 182static clknode_method_t prci_clk_pll_clknode_methods[] = { 183 CLKNODEMETHOD(clknode_init, prci_clk_pll_init), 184 CLKNODEMETHOD(clknode_recalc_freq, prci_clk_pll_recalc), 185 CLKNODEMETHOD_END 186}; 187 188DEFINE_CLASS_1(prci_clk_pll_clknode, prci_clk_pll_clknode_class, 189 prci_clk_pll_clknode_methods, sizeof(struct prci_clk_pll_sc), 190 clknode_class); 191 192static int 193prci_probe(device_t dev) 194{ 195 196 if (!ofw_bus_status_okay(dev)) 197 return (ENXIO); 198 199 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) 200 return (ENXIO); 201 202 device_set_desc(dev, "SiFive FU540 Power Reset Clocking Interrupt"); 203 204 return (BUS_PROBE_DEFAULT); 205} 206 207static void 208prci_pll_register(struct prci_softc *parent_sc, struct clknode_init_def *clkdef, 209 uint32_t reg) 210{ 211 struct clknode *clk; 212 struct prci_clk_pll_sc *sc; 213 214 clk = clknode_create(parent_sc->clkdom, &prci_clk_pll_clknode_class, 215 clkdef); 216 if (clk == NULL) 217 panic("Failed to create clknode"); 218 219 sc = clknode_get_softc(clk); 220 sc->parent_sc = parent_sc; 221 sc->reg = reg; 222 223 clknode_register(parent_sc->clkdom, clk); 224} 225 226static int 227prci_attach(device_t dev) 228{ 229 struct clknode_init_def clkdef; 230 struct prci_softc *sc; 231 clk_t clk_parent; 232 phandle_t node; 233 int i, ncells, error; 234 235 sc = device_get_softc(dev); 236 sc->dev = dev; 237 238 mtx_init(&sc->mtx, device_get_nameunit(sc->dev), NULL, MTX_DEF); 239 240 error = bus_alloc_resources(dev, prci_spec, &sc->res); 241 if (error) { 242 device_printf(dev, "Couldn't allocate resources\n"); 243 goto fail; 244 } 245 sc->bst = rman_get_bustag(sc->res); 246 sc->bsh = rman_get_bushandle(sc->res); 247 248 node = ofw_bus_get_node(dev); 249 error = ofw_bus_parse_xref_list_get_length(node, "clocks", 250 "#clock-cells", &ncells); 251 if (error != 0 || ncells < 1) { 252 device_printf(dev, "couldn't find parent clock\n"); 253 goto fail; 254 } 255 256 bzero(&clkdef, sizeof(clkdef)); 257 clkdef.parent_names = mallocarray(ncells, sizeof(char *), M_OFWPROP, 258 M_WAITOK); 259 for (i = 0; i < ncells; i++) { 260 error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); 261 if (error != 0) { 262 device_printf(dev, "cannot get clock %d\n", error); 263 goto fail1; 264 } 265 clkdef.parent_names[i] = clk_get_name(clk_parent); 266 if (bootverbose) 267 device_printf(dev, "clk parent: %s\n", 268 clkdef.parent_names[i]); 269 clk_release(clk_parent); 270 } 271 clkdef.parent_cnt = ncells; 272 273 sc->clkdom = clkdom_create(dev); 274 if (sc->clkdom == NULL) { 275 device_printf(dev, "Couldn't create clock domain\n"); 276 goto fail; 277 } 278 279 /* We can't free a clkdom, so from now on we cannot fail. */ 280 for (i = 0; i < nitems(pll_clks); i++) { 281 clkdef.id = pll_clks[i].id; 282 clkdef.name = pll_clks[i].name; 283 prci_pll_register(sc, &clkdef, pll_clks[i].reg); 284 } 285 286 /* 287 * Register the fixed clock "tlclk". 288 * 289 * If an older device tree is being used, tlclk may appear as its own 290 * entity in the device tree, under soc/tlclk. If this is the case it 291 * will be registered automatically by the fixed_clk driver, and the 292 * version we register here will be an unreferenced duplicate. 293 */ 294 clknode_fixed_register(sc->clkdom, &tlclk_def); 295 296 error = clkdom_finit(sc->clkdom); 297 if (error) 298 panic("Couldn't finalise clock domain"); 299 300 return (0); 301 302fail1: 303 free(clkdef.parent_names, M_OFWPROP); 304 305fail: 306 bus_release_resources(dev, prci_spec, &sc->res); 307 mtx_destroy(&sc->mtx); 308 return (error); 309} 310 311static device_method_t prci_methods[] = { 312 DEVMETHOD(device_probe, prci_probe), 313 DEVMETHOD(device_attach, prci_attach), 314 315 DEVMETHOD_END 316}; 317 318static driver_t prci_driver = { 319 "fu540prci", 320 prci_methods, 321 sizeof(struct prci_softc) 322}; 323 324static devclass_t prci_devclass; 325 326EARLY_DRIVER_MODULE(fu540prci, simplebus, prci_driver, prci_devclass, 0, 0, 327 BUS_PASS_BUS); 328