/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Emmanuel Vadot * Copyright (c) 2022 Mitchell Horne * Copyright (c) 2024 Jari Sihvola */ #include #include #include #include #include #include #include #include #include #include #include #include #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); }