#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/timeout.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/fdt.h>
#define RNG_CTRL 0x0008
#define RNG_CTRL_START (1 << 8)
#define RNG_TRNG_CTRL 0x0200
#define RNG_TRNG_CTRL_OSC_ENABLE (1 << 16)
#define RNG_TRNG_CTRL_SAMPLE_PERIOD(x) (x)
#define RNG_DATA0 0x0204
#define TRNG_RST_CTL 0x0004
#define TRNG_RST_CTL_SW_RNG_RESET (0x1U << 1)
#define TRNG_CTL 0x0400
#define TRNG_CTL_RNG_START (0x1U << 0)
#define TRNG_CTL_RNG_ENABLE (0x1U << 1)
#define TRNG_CTL_RING_SEL_MASK (0x3U << 2)
#define TRNG_CTL_RING_SEL_SLOWEST (0x0U << 2)
#define TRNG_CTL_RING_SEL_SLOW (0x1U << 2)
#define TRNG_CTL_RING_SEL_FAST (0x2U << 2)
#define TRNG_CTL_RING_SEL_FASTEST (0x3U << 2)
#define TRNG_CTL_RNG_LEN_MASK (0x3U << 4)
#define TRNG_CTL_RNG_LEN_64BIT (0x0U << 4)
#define TRNG_CTL_RNG_LEN_128BIT (0x1U << 4)
#define TRNG_CTL_RNG_LEN_192BIT (0x2U << 4)
#define TRNG_CTL_RNG_LEN_256BIT (0x3U << 4)
#define TRNG_SAMPLE_CNT 0x0404
#define TRNG_DOUT_BASE 0x0410
#define TRNG_V1_CTRL 0x0000
#define TRNG_V1_CTRL_NOP 0x00
#define TRNG_V1_CTRL_RAND 0x01
#define TRNG_V1_STAT 0x0004
#define TRNG_V1_STAT_SEEDED (1U << 9)
#define TRNG_V1_STAT_GENERATING (1U << 30)
#define TRNG_V1_STAT_RESEEDING (1U << 31)
#define TRNG_V1_MODE 0x0008
#define TRNG_V1_MODE_256_BIT (1U << 3)
#define TRNG_V1_ISTAT 0x0014
#define TRNG_V1_ISTAT_RAND_RDY (1U << 0)
#define TRNG_V1_RAND0 0x0020
#define TRNG_V1_AUTO_RQSTS 0x0060
#define HREAD4(sc, reg) \
(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
#define HWRITE4(sc, reg, val) \
bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
struct rkrng_v;
struct rkrng_softc {
struct device sc_dev;
const struct rkrng_v *sc_v;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
struct timeout sc_to;
int sc_started;
};
struct rkrng_v {
unsigned int version;
int (*init)(struct rkrng_softc *sc);
void (*start)(struct rkrng_softc *sc);
int (*starting)(struct rkrng_softc *sc);
void (*stop)(struct rkrng_softc *sc);
bus_size_t dout;
};
int rkrng_match(struct device *, void *, void *);
void rkrng_attach(struct device *, struct device *, void *);
const struct cfattach rkrng_ca = {
sizeof (struct rkrng_softc), rkrng_match, rkrng_attach
};
struct cfdriver rkrng_cd = {
NULL, "rkrng", DV_DULL
};
void rkrng_rnd(void *);
void rkrng_v1_start(struct rkrng_softc *);
int rkrng_v1_starting(struct rkrng_softc *);
void rkrng_v1_stop(struct rkrng_softc *);
static const struct rkrng_v rkrnv_v1 = {
.version = 1,
.start = rkrng_v1_start,
.starting = rkrng_v1_starting,
.stop = rkrng_v1_stop,
.dout = RNG_DATA0,
};
int rkrng_v2_init(struct rkrng_softc *);
void rkrng_v2_start(struct rkrng_softc *);
int rkrng_v2_starting(struct rkrng_softc *);
void rkrng_v2_stop(struct rkrng_softc *);
static const struct rkrng_v rkrnv_v2 = {
.version = 2,
.start = rkrng_v2_start,
.starting = rkrng_v2_starting,
.stop = rkrng_v2_stop,
.dout = TRNG_DOUT_BASE,
};
int rkrng_rk3588_init(struct rkrng_softc *);
void rkrng_rk3588_start(struct rkrng_softc *);
int rkrng_rk3588_starting(struct rkrng_softc *);
void rkrng_rk3588_stop(struct rkrng_softc *);
static const struct rkrng_v rkrnv_rk3588 = {
.version = 3,
.init = rkrng_rk3588_init,
.start = rkrng_rk3588_start,
.starting = rkrng_rk3588_starting,
.stop = rkrng_rk3588_stop,
.dout = TRNG_V1_RAND0,
};
int
rkrng_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "rockchip,cryptov1-rng") ||
OF_is_compatible(faa->fa_node, "rockchip,rk3288-crypto") ||
OF_is_compatible(faa->fa_node, "rockchip,rk3328-crypto") ||
OF_is_compatible(faa->fa_node, "rockchip,rk3399-crypto") ||
OF_is_compatible(faa->fa_node, "rockchip,cryptov2-rng") ||
OF_is_compatible(faa->fa_node, "rockchip,rk3568-rng") ||
OF_is_compatible(faa->fa_node, "rockchip,rk3588-rng");
}
void
rkrng_attach(struct device *parent, struct device *self, void *aux)
{
struct rkrng_softc *sc = (struct rkrng_softc *)self;
struct fdt_attach_args *faa = aux;
if (OF_is_compatible(faa->fa_node, "rockchip,cryptov1-rng") ||
OF_is_compatible(faa->fa_node, "rockchip,rk3288-crypto") ||
OF_is_compatible(faa->fa_node, "rockchip,rk3328-crypto") ||
OF_is_compatible(faa->fa_node, "rockchip,rk3399-crypto"))
sc->sc_v = &rkrnv_v1;
else if (OF_is_compatible(faa->fa_node, "rockchip,cryptov2-rng") ||
OF_is_compatible(faa->fa_node, "rockchip,rk3568-rng"))
sc->sc_v = &rkrnv_v2;
else if (OF_is_compatible(faa->fa_node, "rockchip,rk3588-rng"))
sc->sc_v = &rkrnv_rk3588;
else {
printf(": unhandled version\n");
return;
}
if (faa->fa_nreg < 1) {
printf(": no registers\n");
return;
}
sc->sc_iot = faa->fa_iot;
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
printf(": can't map registers\n");
return;
}
printf(": ver %u\n", sc->sc_v->version);
clock_set_assigned(faa->fa_node);
clock_enable_all(faa->fa_node);
if (sc->sc_v->init && sc->sc_v->init(sc)) {
printf("%s: initialization failed\n", sc->sc_dev.dv_xname);
return;
}
timeout_set(&sc->sc_to, rkrng_rnd, sc);
rkrng_rnd(sc);
}
void
rkrng_v1_start(struct rkrng_softc *sc)
{
HWRITE4(sc, RNG_TRNG_CTRL, RNG_TRNG_CTRL_OSC_ENABLE |
RNG_TRNG_CTRL_SAMPLE_PERIOD(100));
HWRITE4(sc, RNG_CTRL, (RNG_CTRL_START << 16) | RNG_CTRL_START);
}
int
rkrng_v1_starting(struct rkrng_softc *sc)
{
return (HREAD4(sc, RNG_CTRL) & RNG_CTRL_START);
}
void
rkrng_v1_stop(struct rkrng_softc *sc)
{
HWRITE4(sc, RNG_CTRL, (RNG_CTRL_START << 16) | 0);
}
void
rkrng_v2_start(struct rkrng_softc *sc)
{
uint32_t ctl_m = TRNG_CTL_RNG_START | TRNG_CTL_RNG_ENABLE |
TRNG_CTL_RING_SEL_MASK | TRNG_CTL_RNG_LEN_MASK;
uint32_t ctl_v = TRNG_CTL_RNG_START | TRNG_CTL_RNG_ENABLE |
TRNG_CTL_RING_SEL_SLOW | TRNG_CTL_RNG_LEN_256BIT;
HWRITE4(sc, TRNG_SAMPLE_CNT, 100);
HWRITE4(sc, TRNG_CTL, (ctl_m << 16) | ctl_v);
}
int
rkrng_v2_starting(struct rkrng_softc *sc)
{
return (HREAD4(sc, TRNG_CTL) & TRNG_CTL_RNG_START);
}
void
rkrng_v2_stop(struct rkrng_softc *sc)
{
uint32_t ctl_m = TRNG_CTL_RNG_START | TRNG_CTL_RNG_ENABLE;
HWRITE4(sc, TRNG_CTL, (ctl_m << 16) | 0);
}
int
rkrng_rk3588_init(struct rkrng_softc *sc)
{
uint32_t stat;
uint32_t mask;
int timo;
mask = TRNG_V1_STAT_SEEDED;
mask |= TRNG_V1_STAT_GENERATING | TRNG_V1_STAT_RESEEDING;
for (timo = 100; timo > 0; timo--) {
stat = HREAD4(sc, TRNG_V1_STAT);
if ((stat & mask) == TRNG_V1_STAT_SEEDED)
break;
delay(100);
}
if (timo == 0)
return ETIMEDOUT;
HWRITE4(sc, TRNG_V1_ISTAT, HREAD4(sc, TRNG_V1_ISTAT));
HWRITE4(sc, TRNG_V1_AUTO_RQSTS, 1000);
return 0;
}
void
rkrng_rk3588_start(struct rkrng_softc *sc)
{
HWRITE4(sc, TRNG_V1_ISTAT, HREAD4(sc, TRNG_V1_ISTAT));
HWRITE4(sc, TRNG_V1_MODE, TRNG_V1_MODE_256_BIT);
HWRITE4(sc, TRNG_V1_CTRL, TRNG_V1_CTRL_RAND);
}
int
rkrng_rk3588_starting(struct rkrng_softc *sc)
{
return ((HREAD4(sc, TRNG_V1_ISTAT) & TRNG_V1_ISTAT_RAND_RDY) == 0);
}
void
rkrng_rk3588_stop(struct rkrng_softc *sc)
{
HWRITE4(sc, TRNG_V1_ISTAT, HREAD4(sc, TRNG_V1_ISTAT));
HWRITE4(sc, TRNG_V1_CTRL, TRNG_V1_CTRL_NOP);
}
void
rkrng_rnd(void *arg)
{
struct rkrng_softc *sc = arg;
bus_size_t off;
if (!sc->sc_started) {
sc->sc_v->start(sc);
sc->sc_started = 1;
timeout_add_usec(&sc->sc_to, 100);
return;
}
if (sc->sc_v->starting(sc)) {
timeout_add_usec(&sc->sc_to, 100);
return;
}
for (off = 0; off < 32; off += 4)
enqueue_randomness(HREAD4(sc, sc->sc_v->dout + off));
sc->sc_v->stop(sc);
sc->sc_started = 0;
timeout_add_sec(&sc->sc_to, 1);
}