#include <sys/param.h>
#include <sys/systm.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/fdt.h>
#define RP1_PLL_SYS_CORE 0
#define RP1_PLL_AUDIO_CORE 1
#define RP1_PLL_SYS 3
#define RP1_PLL_AUDIO 4
#define RP1_PLL_SYS_PRI_PH 6
#define RP1_PLL_AUDIO_PRI_PH 8
#define RP1_PLL_SYS_SEC 9
#define RP1_PLL_AUDIO_SEC 10
#define RP1_CLK_SYS 12
#define RP1_CLK_SLOW_SYS 13
#define RP1_CLK_ETH 16
#define RP1_CLK_PWM0 17
#define RP1_CLK_PWM1 18
#define RP1_CLK_ETH_TSU 29
#define RP1_CLK_SDIO_TIMER 31
#define RP1_CLK_SDIO_ALT_SRC 32
#define RP1_CLK_GP0 33
#define RP1_CLK_GP1 34
#define RP1_CLK_GP2 35
#define RP1_CLK_GP3 36
#define RP1_CLK_GP4 37
#define RP1_CLK_GP5 38
#define RP1_CLK_XOSC 1023
#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))
#define HSET4(sc, reg, bits) \
HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
#define HCLR4(sc, reg, bits) \
HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
#define GPCLK_OE_CTRL 0x0000
#define CLK_SYS_CTRL 0x0014
#define CLK_SYS_DIV_INT 0x0018
#define CLK_SYS_SEL 0x0020
#define CLK_SLOW_SYS_CTRL 0x0024
#define CLK_SLOW_SYS_DIV_INT 0x0028
#define CLK_SLOW_SYS_SEL 0x0030
#define CLK_ETH_CTRL 0x0064
#define CLK_ETH_DIV_INT 0x0068
#define CLK_ETH_SEL 0x0070
#define CLK_PWM0_CTRL 0x0074
#define CLK_PWM0_DIV_INT 0x0078
#define CLK_PWM0_DIV_FRAC 0x007c
#define CLK_PWM0_SEL 0x0080
#define CLK_PWM1_CTRL 0x0084
#define CLK_PWM1_DIV_INT 0x0088
#define CLK_PWM1_DIV_FRAC 0x008c
#define CLK_PWM1_SEL 0x0090
#define CLK_ETH_TSU_CTRL 0x0134
#define CLK_ETH_TSU_DIV_INT 0x0138
#define CLK_ETH_TSU_SEL 0x0140
#define CLK_SDIO_TIMER_CTRL 0x0154
#define CLK_SDIO_TIMER_DIV_INT 0x0158
#define CLK_SDIO_TIMER_SEL 0x0160
#define CLK_SDIO_ALT_SRC_CTRL 0x0164
#define CLK_SDIO_ALT_SRC_DIV_INT 0x0168
#define CLK_SDIO_ALT_SRC_SEL 0x0170
#define CLK_CTRL_ENABLE (1 << 11)
#define CLK_CTRL_AUXSRC_MASK (0x1f << 5)
#define CLK_CTRL_AUXSRC_SHIFT 5
#define CLK_CTRL_SRC_MASK (0x1f << 0)
#define CLK_CTRL_SRC_SHIFT 0
#define CLK_CTRL_SRC_AUX (0x01 << 0)
#define PLL_SYS_CS 0x8000
#define PLL_SYS_PWR 0x8004
#define PLL_SYS_FBDIV_INT 0x8008
#define PLL_SYS_FBDIV_FRAC 0x800c
#define PLL_SYS_PRIM 0x8010
#define PLL_SYS_SEC 0x8014
#define PLL_AUDIO_CS 0xc000
#define PLL_AUDIO_PWR 0xc004
#define PLL_AUDIO_FBDIV_INT 0xc008
#define PLL_AUDIO_FBDIV_FRAC 0xc00c
#define PLL_AUDIO_PRIM 0xc010
#define PLL_AUDIO_SEC 0xc014
#define PLL_CS_REFDIV_SHIFT 0
#define PLL_PWR_DSMPD (1 << 2)
#define PLL_PRIM_DIV1_MASK (0x7 << 16)
#define PLL_PRIM_DIV1_SHIFT 16
#define PLL_PRIM_DIV2_MASK (0x7 << 12)
#define PLL_PRIM_DIV2_SHIFT 12
#define PLL_SEC_RST (1 << 16)
#define PLL_SEC_DIV_MASK (0x1f << 8)
#define PLL_SEC_DIV_SHIFT 8
struct rpiclock {
uint16_t idx;
uint16_t ctrl_reg;
uint16_t div_int_reg;
uint16_t sel_reg;
uint16_t parents[3];
};
const struct rpiclock rpiclocks[] = {
{
RP1_CLK_SYS, CLK_SYS_CTRL, CLK_SYS_DIV_INT, CLK_SYS_SEL,
{ RP1_CLK_XOSC, 0, RP1_PLL_SYS }
},
{
RP1_CLK_SLOW_SYS, CLK_SLOW_SYS_CTRL, CLK_SLOW_SYS_DIV_INT,
CLK_SLOW_SYS_SEL,
{ RP1_CLK_XOSC }
},
{
RP1_CLK_ETH, CLK_ETH_CTRL, CLK_ETH_DIV_INT, 0,
{ RP1_PLL_SYS_SEC, RP1_PLL_SYS }
},
{
RP1_CLK_PWM0, CLK_PWM0_CTRL, CLK_PWM0_DIV_INT, 0,
{ 0, 0, RP1_CLK_XOSC }
},
{
RP1_CLK_PWM1, CLK_PWM1_CTRL, CLK_PWM1_DIV_INT, 0,
{ 0, 0, RP1_CLK_XOSC }
},
{
RP1_CLK_ETH_TSU, CLK_ETH_TSU_CTRL, CLK_ETH_TSU_DIV_INT, 0,
{ RP1_CLK_XOSC }
},
{
RP1_CLK_SDIO_TIMER, CLK_SDIO_TIMER_CTRL,
CLK_SDIO_TIMER_DIV_INT, 0,
{ RP1_CLK_XOSC }
},
{
RP1_CLK_SDIO_ALT_SRC, CLK_SDIO_ALT_SRC_CTRL,
CLK_SDIO_ALT_SRC_DIV_INT, 0,
{ RP1_PLL_SYS }
},
};
struct rpiclock_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
uint32_t sc_xosc_freq;
struct clock_device sc_cd;
};
int rpiclock_match(struct device *, void *, void *);
void rpiclock_attach(struct device *, struct device *, void *);
const struct cfattach rpiclock_ca = {
sizeof(struct rpiclock_softc), rpiclock_match, rpiclock_attach
};
struct cfdriver rpiclock_cd = {
NULL, "rpiclock", DV_DULL
};
uint32_t rpiclock_get_frequency(void *, uint32_t *);
int rpiclock_set_frequency(void *, uint32_t *, uint32_t);
void rpiclock_enable(void *, uint32_t *, int);
int
rpiclock_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "raspberrypi,rp1-clocks");
}
void
rpiclock_attach(struct device *parent, struct device *self, void *aux)
{
struct rpiclock_softc *sc = (struct rpiclock_softc *)self;
struct fdt_attach_args *faa = aux;
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;
}
sc->sc_xosc_freq = clock_get_frequency(faa->fa_node, NULL);
if (sc->sc_xosc_freq == 0) {
printf(": no clock\n");
return;
}
printf("\n");
sc->sc_cd.cd_node = faa->fa_node;
sc->sc_cd.cd_cookie = sc;
sc->sc_cd.cd_get_frequency = rpiclock_get_frequency;
sc->sc_cd.cd_set_frequency = rpiclock_set_frequency;
sc->sc_cd.cd_enable = rpiclock_enable;
clock_register(&sc->sc_cd);
clock_set_assigned(faa->fa_node);
}
const struct rpiclock *
rpiclock_lookup(uint32_t idx)
{
int i;
for (i = 0; i < nitems(rpiclocks); i++) {
if (rpiclocks[i].idx == idx)
return &rpiclocks[i];
}
return NULL;
}
#define DIV_ROUND(_n, _d) (((_n) + ((_d) / 2)) / (_d))
uint32_t
rpiclock_get_pll_core_frequency(struct rpiclock_softc *sc, bus_addr_t base)
{
uint64_t fbdiv_int, fbdiv_frac;
uint64_t freq = sc->sc_xosc_freq;
fbdiv_int = HREAD4(sc, base + PLL_SYS_FBDIV_INT - PLL_SYS_CS);
fbdiv_frac = HREAD4(sc,base + PLL_SYS_FBDIV_FRAC - PLL_SYS_CS);
freq = (freq * ((fbdiv_int << 24) + fbdiv_frac) + (1 << 23)) >> 24;
return freq;
}
int
rpiclock_set_pll_core_frequency(struct rpiclock_softc *sc, bus_addr_t base,
uint32_t freq)
{
uint32_t parent_freq = sc->sc_xosc_freq;
uint32_t fbdiv_int, fbdiv_frac;
uint64_t div;
if (parent_freq > (freq / 16))
return -1;
HWRITE4(sc, base + PLL_SYS_FBDIV_INT - PLL_SYS_CS, 0);
HWRITE4(sc, base + PLL_SYS_FBDIV_FRAC - PLL_SYS_CS, 0);
div = DIV_ROUND((uint64_t)freq << 32, parent_freq);
div += (1 << (32 - 24 - 1));
fbdiv_int = div >> 32;
fbdiv_frac = (div >> (32 - 24)) & 0xffffff;
if (fbdiv_frac != 0) {
HCLR4(sc, base + PLL_SYS_FBDIV_INT - PLL_SYS_PWR,
PLL_PWR_DSMPD);
} else {
HSET4(sc, base + PLL_SYS_FBDIV_INT - PLL_SYS_PWR,
PLL_PWR_DSMPD);
}
HWRITE4(sc, base + PLL_SYS_FBDIV_INT - PLL_SYS_CS, fbdiv_int);
HWRITE4(sc, base + PLL_SYS_FBDIV_FRAC - PLL_SYS_CS, fbdiv_frac);
HSET4(sc, base, 1 << PLL_CS_REFDIV_SHIFT);
return 0;
}
uint32_t
rpiclock_get_pll_frequency(struct rpiclock_softc *sc, bus_addr_t base)
{
uint64_t freq = rpiclock_get_pll_core_frequency(sc, base);
uint32_t prim, div1, div2;
prim = HREAD4(sc, base + PLL_SYS_PRIM - PLL_SYS_CS);
div1 = (prim & PLL_PRIM_DIV1_MASK) >> PLL_PRIM_DIV1_SHIFT;
div2 = (prim & PLL_PRIM_DIV2_MASK) >> PLL_PRIM_DIV2_SHIFT;
if (div1 == 0 || div2 == 0)
return 0;
return DIV_ROUND(freq, div1 * div2);
}
int
rpiclock_set_pll_frequency(struct rpiclock_softc *sc, bus_addr_t base,
uint32_t freq)
{
uint64_t parent_freq = rpiclock_get_pll_core_frequency(sc, base);
uint32_t best_div1, best_div2, best_freq;
uint32_t f, div1, div2;
uint32_t prim;
best_div1 = best_div2 = 7;
best_freq = DIV_ROUND(parent_freq, best_div1 * best_div2);
for (div1 = 1; div1 <= 7; div1++) {
for (div2 = 1; div2 <= div1; div2++) {
f = DIV_ROUND(parent_freq, div1 * div2);
if (f == freq) {
best_div1 = div1;
best_div2 = div2;
goto found;
}
if ((best_freq > freq && f < best_freq) ||
(f > best_freq && f <= freq)) {
best_div1 = div1;
best_div2 = div2;
best_freq = f;
}
}
}
found:
prim = HREAD4(sc, base + PLL_SYS_PRIM - PLL_SYS_CS);
prim &= ~(PLL_PRIM_DIV1_MASK | PLL_PRIM_DIV2_MASK);
prim |= (best_div1 << PLL_PRIM_DIV1_SHIFT);
prim |= (best_div2 << PLL_PRIM_DIV2_SHIFT);
HWRITE4(sc, base + PLL_SYS_PRIM - PLL_SYS_CS, prim);
return 0;
}
uint32_t
rpiclock_get_pll_sec_frequency(struct rpiclock_softc *sc, bus_addr_t base)
{
uint64_t freq = rpiclock_get_pll_core_frequency(sc, base);
uint32_t sec, div;
sec = HREAD4(sc, base + PLL_SYS_SEC - PLL_SYS_CS);
div = (sec & PLL_SEC_DIV_MASK) >> PLL_SEC_DIV_SHIFT;
if (div == 0)
return 0;
return freq / div;
}
int
rpiclock_set_pll_sec_frequency(struct rpiclock_softc *sc, bus_addr_t base,
uint32_t freq)
{
uint64_t parent_freq = rpiclock_get_pll_core_frequency(sc, base);
uint32_t sec, div;
int s;
div = DIV_ROUND(parent_freq, freq);
div = MAX(div, 8);
div = MIN(div, 19);
sec = HREAD4(sc, base + PLL_SYS_SEC - PLL_SYS_CS);
sec &= ~PLL_SEC_DIV_MASK;
sec |= (div << PLL_SEC_DIV_SHIFT);
s = splhigh();
HWRITE4(sc, base + PLL_SYS_SEC - PLL_SYS_CS, sec | PLL_SEC_RST);
delay((10 * div * 1000000) / parent_freq);
HWRITE4(sc, base + PLL_SYS_SEC - PLL_SYS_CS, sec);
splx(s);
return 0;
}
uint32_t
rpiclock_freq(struct rpiclock_softc *sc, const struct rpiclock *clk,
uint32_t mux, uint32_t freq)
{
uint32_t parent_freq, div;
uint32_t idx = clk->parents[mux];
parent_freq = rpiclock_get_frequency(sc, &idx);
div = (parent_freq + freq - 1) / freq;
if (div == 0)
return 0;
return parent_freq / div;
}
uint32_t
rpiclock_get_frequency(void *cookie, uint32_t *cells)
{
struct rpiclock_softc *sc = cookie;
const struct rpiclock *clk;
uint32_t idx = cells[0];
uint32_t ctrl, sel, div;
uint32_t parent, freq;
switch (idx) {
case RP1_PLL_SYS_CORE:
return rpiclock_get_pll_core_frequency(sc, PLL_SYS_CS);
case RP1_PLL_AUDIO_CORE:
return rpiclock_get_pll_core_frequency(sc, PLL_AUDIO_CS);
case RP1_PLL_SYS:
return rpiclock_get_pll_frequency(sc, PLL_SYS_CS);
case RP1_PLL_AUDIO:
return rpiclock_get_pll_frequency(sc, PLL_AUDIO_CS);
case RP1_PLL_SYS_SEC:
return rpiclock_get_pll_sec_frequency(sc, PLL_SYS_CS);
case RP1_PLL_AUDIO_SEC:
return rpiclock_get_pll_sec_frequency(sc, PLL_AUDIO_CS);
case RP1_PLL_SYS_PRI_PH:
return rpiclock_get_pll_frequency(sc, PLL_SYS_CS) / 2;
case RP1_CLK_XOSC:
return sc->sc_xosc_freq;
}
clk = rpiclock_lookup(idx);
if (clk == NULL) {
printf("%s(%s, %u)\n", __func__, sc->sc_dev.dv_xname, idx);
return 0;
}
if (clk->sel_reg) {
sel = HREAD4(sc, clk->sel_reg);
parent = ffs(sel) - 1;
} else {
ctrl = HREAD4(sc, clk->ctrl_reg);
parent = (ctrl & CLK_CTRL_AUXSRC_MASK) >>
CLK_CTRL_AUXSRC_SHIFT;
}
if (parent >= nitems(clk->parents))
return 0;
parent = clk->parents[parent];
if (parent == 0)
return 0;
freq = rpiclock_get_frequency(sc, &parent);
div = HREAD4(sc, clk->div_int_reg);
if (div == 0)
return 0;
return freq / div;
}
int
rpiclock_set_frequency(void *cookie, uint32_t *cells, uint32_t freq)
{
struct rpiclock_softc *sc = cookie;
const struct rpiclock *clk;
uint32_t idx = cells[0];
uint32_t parent, parent_freq;
uint32_t best_freq, best_mux, f;
uint32_t ctrl, div, sel;
int i;
best_freq = rpiclock_get_frequency(sc, cells);
if (best_freq == freq)
return 0;
switch (idx) {
case RP1_PLL_SYS_CORE:
return rpiclock_set_pll_core_frequency(sc, PLL_SYS_CS, freq);
case RP1_PLL_AUDIO_CORE:
return rpiclock_set_pll_core_frequency(sc, PLL_AUDIO_CS, freq);
case RP1_PLL_SYS:
return rpiclock_set_pll_frequency(sc, PLL_SYS_CS, freq);
case RP1_PLL_AUDIO:
return rpiclock_set_pll_frequency(sc, PLL_AUDIO_CS, freq);
case RP1_PLL_SYS_SEC:
return rpiclock_set_pll_sec_frequency(sc, PLL_SYS_CS, freq);
case RP1_PLL_AUDIO_SEC:
return rpiclock_set_pll_sec_frequency(sc, PLL_AUDIO_CS, freq);
}
clk = rpiclock_lookup(idx);
if (clk == NULL) {
printf("%s(%s, %u, %u)\n", __func__, sc->sc_dev.dv_xname,
idx, freq);
return -1;
}
if (clk->sel_reg) {
sel = HREAD4(sc, clk->sel_reg);
best_mux = ffs(sel) - 1;
} else {
ctrl = HREAD4(sc, clk->ctrl_reg);
best_mux = (ctrl & CLK_CTRL_AUXSRC_MASK) >>
CLK_CTRL_AUXSRC_SHIFT;
}
for (i = 0; i < nitems(clk->parents); i++) {
if (clk->parents[i] == 0)
continue;
f = rpiclock_freq(sc, clk, i, freq);
if ((best_freq > freq && f < best_freq) ||
(f > best_freq && f <= freq)) {
best_freq = f;
best_mux = i;
}
}
ctrl = HREAD4(sc, clk->ctrl_reg);
if (clk->sel_reg) {
ctrl &= ~CLK_CTRL_SRC_MASK;
ctrl |= (best_mux << CLK_CTRL_SRC_SHIFT);
} else {
ctrl &= ~CLK_CTRL_AUXSRC_MASK;
ctrl |= (best_mux << CLK_CTRL_AUXSRC_SHIFT);
ctrl &= ~CLK_CTRL_SRC_MASK;
ctrl |= CLK_CTRL_SRC_AUX;
}
HWRITE4(sc, clk->ctrl_reg, ctrl);
parent = clk->parents[best_mux];
parent_freq = rpiclock_get_frequency(sc, &parent);
div = (parent_freq + freq - 1) / freq;
HWRITE4(sc, clk->div_int_reg, div);
return 0;
}
void
rpiclock_enable(void *cookie, uint32_t *cells, int on)
{
struct rpiclock_softc *sc = cookie;
const struct rpiclock *clk;
uint32_t idx = cells[0];
clk = rpiclock_lookup(idx);
if (clk == NULL) {
printf("%s(%s, %u)\n", __func__, sc->sc_dev.dv_xname, idx);
return;
}
if (on)
HSET4(sc, clk->ctrl_reg, CLK_CTRL_ENABLE);
else
HCLR4(sc, clk->ctrl_reg, CLK_CTRL_ENABLE);
}