#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))
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);
}
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);
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);
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[] = {
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);
}