diff options
Diffstat (limited to 'sys/dev/clk/starfive/jh7110_clk.c')
-rw-r--r-- | sys/dev/clk/starfive/jh7110_clk.c | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/sys/dev/clk/starfive/jh7110_clk.c b/sys/dev/clk/starfive/jh7110_clk.c new file mode 100644 index 000000000000..adb5707b3f64 --- /dev/null +++ b/sys/dev/clk/starfive/jh7110_clk.c @@ -0,0 +1,277 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019 Emmanuel Vadot <manu@freebsd.org> + * Copyright (c) 2022 Mitchell Horne <mhorne@FreeBSD.org> + * Copyright (c) 2024 Jari Sihvola <jsihv@gmx.com> + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/mutex.h> +#include <sys/rman.h> + +#include <machine/bus.h> +#include <machine/intr.h> +#include <machine/resource.h> + +#include <dev/clk/clk.h> +#include <dev/hwreset/hwreset.h> + +#include <dt-bindings/clock/starfive,jh7110-crg.h> + +#include <dev/clk/starfive/jh7110_clk.h> + +#include "clkdev_if.h" +#include "hwreset_if.h" + +#define JH7110_DIV_MASK 0xffffff +#define JH7110_MUX_SHIFT 24 +#define JH7110_MUX_MASK 0x3f000000 +#define JH7110_ENABLE_SHIFT 31 + +#define REG_SIZE 4 + +struct jh7110_clk_sc { + uint32_t offset; + uint32_t flags; + uint64_t d_max; + int id; +}; + +#define DIV_ROUND_CLOSEST(n, d) (((n) + (d) / 2) / (d)) + +#define READ4(_sc, _off) \ + bus_read_4(_sc->mem_res, _off) +#define WRITE4(_sc, _off, _val) \ + bus_write_4(_sc->mem_res, _off, _val) + +#define DEVICE_LOCK(_clk) \ + CLKDEV_DEVICE_LOCK(clknode_get_device(_clk)) +#define DEVICE_UNLOCK(_clk) \ + CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk)) + +/* Reset functions */ + +int +jh7110_reset_assert(device_t dev, intptr_t id, bool assert) +{ + struct jh7110_clkgen_softc *sc; + uint32_t regvalue, offset, bitmask = 1UL << id % 32; + + sc = device_get_softc(dev); + offset = sc->reset_selector_offset + id / 32 * 4; + + mtx_lock(&sc->mtx); + + regvalue = READ4(sc, offset); + + if (assert) + regvalue |= bitmask; + else + regvalue &= ~bitmask; + WRITE4(sc, offset, regvalue); + + mtx_unlock(&sc->mtx); + + return (0); +} + +int +jh7110_reset_is_asserted(device_t dev, intptr_t id, bool *reset) +{ + struct jh7110_clkgen_softc *sc; + uint32_t regvalue, offset, bitmask; + + sc = device_get_softc(dev); + offset = sc->reset_status_offset + id / 32 * 4; + + mtx_lock(&sc->mtx); + + regvalue = READ4(sc, offset); + bitmask = 1UL << id % 32; + + mtx_unlock(&sc->mtx); + + *reset = (regvalue & bitmask) == 0; + + return (0); +} + +/* Clock functions */ + +static int +jh7110_clk_init(struct clknode *clk, device_t dev) +{ + struct jh7110_clkgen_softc *sc; + struct jh7110_clk_sc *sc_clk; + uint32_t reg; + int idx = 0; + + sc = device_get_softc(clknode_get_device(clk)); + sc_clk = clknode_get_softc(clk); + + if (sc_clk->flags & JH7110_CLK_HAS_MUX) { + DEVICE_LOCK(clk); + reg = READ4(sc, sc_clk->offset); + DEVICE_UNLOCK(clk); + idx = (reg & JH7110_MUX_MASK) >> JH7110_MUX_SHIFT; + } + + clknode_init_parent_idx(clk, idx); + + return (0); +} + +static int +jh7110_clk_set_gate(struct clknode *clk, bool enable) +{ + struct jh7110_clkgen_softc *sc; + struct jh7110_clk_sc *sc_clk; + uint32_t reg; + + sc = device_get_softc(clknode_get_device(clk)); + sc_clk = clknode_get_softc(clk); + + if ((sc_clk->flags & JH7110_CLK_HAS_GATE) == 0) + return (0); + + DEVICE_LOCK(clk); + + reg = READ4(sc, sc_clk->offset); + if (enable) + reg |= (1 << JH7110_ENABLE_SHIFT); + else + reg &= ~(1 << JH7110_ENABLE_SHIFT); + WRITE4(sc, sc_clk->offset, reg); + + DEVICE_UNLOCK(clk); + + return (0); +} + +static int +jh7110_clk_set_mux(struct clknode *clk, int idx) +{ + struct jh7110_clkgen_softc *sc; + struct jh7110_clk_sc *sc_clk; + uint32_t reg; + + sc = device_get_softc(clknode_get_device(clk)); + sc_clk = clknode_get_softc(clk); + + if ((sc_clk->flags & JH7110_CLK_HAS_MUX) == 0) + return (ENXIO); + + /* Checking index size */ + if ((idx & (JH7110_MUX_MASK >> JH7110_MUX_SHIFT)) != idx) + return (EINVAL); + + DEVICE_LOCK(clk); + + reg = READ4(sc, sc_clk->offset) & ~JH7110_MUX_MASK; + reg |= idx << JH7110_MUX_SHIFT; + WRITE4(sc, sc_clk->offset, reg); + + DEVICE_UNLOCK(clk); + + return (0); +} + +static int +jh7110_clk_recalc_freq(struct clknode *clk, uint64_t *freq) +{ + struct jh7110_clkgen_softc *sc; + struct jh7110_clk_sc *sc_clk; + uint32_t divisor; + + sc = device_get_softc(clknode_get_device(clk)); + sc_clk = clknode_get_softc(clk); + + /* Returning error here causes panic */ + if ((sc_clk->flags & JH7110_CLK_HAS_DIV) == 0) + return (0); + + DEVICE_LOCK(clk); + + divisor = READ4(sc, sc_clk->offset) & JH7110_DIV_MASK; + + DEVICE_UNLOCK(clk); + + if (divisor) + *freq = *freq / divisor; + else + *freq = 0; + + return (0); +} + +static int +jh7110_clk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, + int flags, int *done) +{ + struct jh7110_clkgen_softc *sc; + struct jh7110_clk_sc *sc_clk; + uint32_t divisor; + + sc = device_get_softc(clknode_get_device(clk)); + sc_clk = clknode_get_softc(clk); + + if ((sc_clk->flags & JH7110_CLK_HAS_DIV) == 0) + return (0); + + divisor = MIN(MAX(DIV_ROUND_CLOSEST(fin, *fout), 1UL), sc_clk->d_max); + + if (flags & CLK_SET_DRYRUN) + goto done; + + DEVICE_LOCK(clk); + + divisor |= READ4(sc, sc_clk->offset) & ~JH7110_DIV_MASK; + WRITE4(sc, sc_clk->offset, divisor); + + DEVICE_UNLOCK(clk); + +done: + *fout = divisor; + *done = 1; + + return (0); +} + +static clknode_method_t jh7110_clknode_methods[] = { + /* Device interface */ + CLKNODEMETHOD(clknode_init, jh7110_clk_init), + CLKNODEMETHOD(clknode_set_gate, jh7110_clk_set_gate), + CLKNODEMETHOD(clknode_set_mux, jh7110_clk_set_mux), + CLKNODEMETHOD(clknode_recalc_freq, jh7110_clk_recalc_freq), + CLKNODEMETHOD(clknode_set_freq, jh7110_clk_set_freq), + CLKNODEMETHOD_END +}; + +DEFINE_CLASS_1(jh7110_clknode, jh7110_clknode_class, jh7110_clknode_methods, + sizeof(struct jh7110_clk_sc), clknode_class); + +int +jh7110_clk_register(struct clkdom *clkdom, const struct jh7110_clk_def *clkdef) +{ + struct clknode *clk; + struct jh7110_clk_sc *sc; + + clk = clknode_create(clkdom, &jh7110_clknode_class, &clkdef->clkdef); + if (clk == NULL) + return (-1); + + sc = clknode_get_softc(clk); + + sc->offset = clkdef->clkdef.id * REG_SIZE; + + sc->flags = clkdef->flags; + sc->id = clkdef->clkdef.id; + sc->d_max = clkdef->d_max; + + clknode_register(clkdom, clk); + + return (0); +} |