root/sys/dev/clk/starfive/jh7110_clk.c
/*-
 * 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);
}