#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <machine/intr.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/ofw_misc.h>
#include <dev/ofw/fdt.h>
#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))
struct mvclock_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
struct clock_device sc_cd;
};
int mvclock_match(struct device *, void *, void *);
void mvclock_attach(struct device *, struct device *, void *);
const struct cfattach mvclock_ca = {
sizeof (struct mvclock_softc), mvclock_match, mvclock_attach
};
struct cfdriver mvclock_cd = {
NULL, "mvclock", DV_DULL
};
uint32_t ap806_get_frequency(void *, uint32_t *);
uint32_t cp110_get_frequency(void *, uint32_t *);
void cp110_enable(void *, uint32_t *, int);
void a3700_periph_nb_enable(void *, uint32_t *, int);
uint32_t a3700_periph_nb_get_frequency(void *, uint32_t *);
void a3700_periph_sb_enable(void *, uint32_t *, int);
uint32_t a3700_periph_sb_get_frequency(void *, uint32_t *);
uint32_t a3700_tbg_get_frequency(void *, uint32_t *);
int
mvclock_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
int node = faa->fa_node;
if (OF_is_compatible(node, "marvell,ap806-clock") ||
OF_is_compatible(node, "marvell,ap807-clock") ||
OF_is_compatible(node, "marvell,cp110-clock") ||
OF_is_compatible(node, "marvell,armada-3700-periph-clock-nb") ||
OF_is_compatible(node, "marvell,armada-3700-periph-clock-sb") ||
OF_is_compatible(node, "marvell,armada-3700-tbg-clock") ||
OF_is_compatible(node, "marvell,armada-3700-xtal-clock"))
return 10;
return 0;
}
void
mvclock_attach(struct device *parent, struct device *self, void *aux)
{
struct mvclock_softc *sc = (struct mvclock_softc *)self;
struct fdt_attach_args *faa = aux;
int node = faa->fa_node;
if (faa->fa_nreg > 0) {
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("\n");
sc->sc_cd.cd_node = node;
sc->sc_cd.cd_cookie = sc;
if (OF_is_compatible(node, "marvell,ap806-clock") ||
OF_is_compatible(node, "marvell,ap807-clock")) {
sc->sc_cd.cd_get_frequency = ap806_get_frequency;
} else if (OF_is_compatible(node, "marvell,cp110-clock")) {
sc->sc_cd.cd_get_frequency = cp110_get_frequency;
sc->sc_cd.cd_enable = cp110_enable;
} else if (OF_is_compatible(node, "marvell,armada-3700-periph-clock-nb")) {
sc->sc_cd.cd_enable = a3700_periph_nb_enable;
sc->sc_cd.cd_get_frequency = a3700_periph_nb_get_frequency;
} else if (OF_is_compatible(node, "marvell,armada-3700-periph-clock-sb")) {
sc->sc_cd.cd_enable = a3700_periph_sb_enable;
sc->sc_cd.cd_get_frequency = a3700_periph_sb_get_frequency;
} else if (OF_is_compatible(node, "marvell,armada-3700-tbg-clock")) {
sc->sc_cd.cd_get_frequency = a3700_tbg_get_frequency;
}
clock_register(&sc->sc_cd);
}
#define AP806_CORE_FIXED 2
#define AP806_CORE_MSS 3
#define AP806_CORE_SDIO 4
uint32_t
ap806_get_frequency(void *cookie, uint32_t *cells)
{
uint32_t idx = cells[0];
switch (idx) {
case AP806_CORE_FIXED:
return 1200000000;
case AP806_CORE_MSS:
return 200000000;
case AP806_CORE_SDIO:
return 400000000;
default:
break;
}
printf("%s: 0x%08x\n", __func__, idx);
return 0;
}
#define CP110_PM_CLOCK_GATING_CTRL 0x220
#define CP110_CORE_APLL 0
#define CP110_CORE_PPV2 1
#define CP110_CORE_X2CORE 2
#define CP110_CORE_CORE 3
#define CP110_CORE_SDIO 5
#define CP110_GATE_PPV2 3
#define CP110_GATE_SDIO 4
#define CP110_GATE_SLOW_IO 21
uint32_t
cp110_get_frequency(void *cookie, uint32_t *cells)
{
struct mvclock_softc *sc = cookie;
uint32_t mod = cells[0];
uint32_t idx = cells[1];
uint32_t parent[2] = { 0, 0 };
if (mod == 0) {
switch (idx) {
case CP110_CORE_APLL:
return 1000000000;
case CP110_CORE_PPV2:
return 333333333;
case CP110_CORE_X2CORE:
return 500000000;
case CP110_CORE_CORE:
return 250000000;
case CP110_CORE_SDIO:
return 400000000;
default:
break;
}
}
if (mod == 1) {
switch (idx) {
case CP110_GATE_PPV2:
parent[1] = CP110_CORE_PPV2;
break;
case CP110_GATE_SDIO:
parent[1] = CP110_CORE_SDIO;
break;
case CP110_GATE_SLOW_IO:
parent[1] = CP110_CORE_X2CORE;
break;
default:
break;
}
if (parent[1] != 0)
return cp110_get_frequency(sc, parent);
}
printf("%s: 0x%08x 0x%08x\n", __func__, mod, idx);
return 0;
}
void
cp110_enable(void *cookie, uint32_t *cells, int on)
{
struct mvclock_softc *sc = cookie;
uint32_t mod = cells[0];
uint32_t idx = cells[1];
if (mod == 1 && idx < 32) {
struct regmap *rm;
uint32_t reg;
rm = regmap_bynode(OF_parent(sc->sc_cd.cd_node));
if (rm == NULL) {
printf("%s: can't enable clock 0x%08x 0x%08x\n",
sc->sc_dev.dv_xname, mod, idx);
return;
}
reg = regmap_read_4(rm, CP110_PM_CLOCK_GATING_CTRL);
if (on)
reg |= (1U << idx);
else
reg &= ~(1U << idx);
regmap_write_4(rm, CP110_PM_CLOCK_GATING_CTRL, reg);
return;
}
printf("%s: 0x%08x 0x%08x\n", __func__, mod, idx);
}
#define PERIPH_NB_MMC 0x00
#define PERIPH_NB_SATA 0x01
#define PERIPH_NB_SEC_AT 0x02
#define PERIPH_NB_SEC_DAP 0x03
#define PERIPH_NB_TSECM 0x04
#define PERIPH_NB_SETM_TMX 0x05
#define PERIPH_NB_AVS 0x06
#define PERIPH_NB_SQF 0x07
#define PERIPH_NB_I2C2 0x09
#define PERIPH_NB_I2C1 0x0a
#define PERIPH_NB_DDR_PHY 0x0b
#define PERIPH_NB_DDR_FCLK 0x0c
#define PERIPH_NB_TRACE 0x0d
#define PERIPH_NB_COUNTER 0x0e
#define PERIPH_NB_EIO97 0x0f
#define PERIPH_NB_CPU 0x10
#define PERIPH_SB_GBE_50 0x00
#define PERIPH_SB_GBE_CORE 0x01
#define PERIPH_SB_GBE_125 0x02
#define PERIPH_SB_GBE1_50 0x03
#define PERIPH_SB_GBE0_50 0x04
#define PERIPH_SB_GBE1_125 0x05
#define PERIPH_SB_GBE0_125 0x06
#define PERIPH_SB_GBE1_CORE 0x07
#define PERIPH_SB_GBE0_CORE 0x08
#define PERIPH_SB_GBE_BM 0x09
#define PERIPH_SB_SDIO 0x0a
#define PERIPH_SB_USB32_USB2_SYS 0x0b
#define PERIPH_SB_USB32_SS_SYS 0x0c
#define PERIPH_SB_PCIE 0x0d
#define PERIPH_TBG_SEL 0x0
#define PERIPH_TBG_SEL_MASK 0x3
#define PERIPH_DIV_SEL0 0x4
#define PERIPH_DIV_SEL1 0x8
#define PERIPH_DIV_SEL2 0xc
#define PERIPH_DIV_SEL_MASK 0x7
#define PERIPH_CLK_SEL 0x10
#define PERIPH_CLK_DIS 0x14
void a3700_periph_enable(struct mvclock_softc *, uint32_t, int);
uint32_t a3700_periph_tbg_get_frequency(struct mvclock_softc *, uint32_t);
uint32_t a3700_periph_get_div(struct mvclock_softc *, uint32_t, uint32_t);
uint32_t a3700_periph_get_double_div(struct mvclock_softc *, uint32_t,
uint32_t, uint32_t);
void
a3700_periph_nb_enable(void *cookie, uint32_t *cells, int on)
{
struct mvclock_softc *sc = cookie;
uint32_t idx = cells[0];
switch (idx) {
case PERIPH_NB_MMC:
return a3700_periph_enable(sc, 2, on);
case PERIPH_NB_SQF:
return a3700_periph_enable(sc, 12, on);
case PERIPH_NB_I2C2:
return a3700_periph_enable(sc, 16, on);
case PERIPH_NB_I2C1:
return a3700_periph_enable(sc, 17, on);
default:
break;
}
printf("%s: 0x%08x\n", __func__, idx);
}
uint32_t
a3700_periph_nb_get_frequency(void *cookie, uint32_t *cells)
{
struct mvclock_softc *sc = cookie;
uint32_t idx = cells[0];
uint32_t freq;
switch (idx) {
case PERIPH_NB_MMC:
freq = a3700_periph_tbg_get_frequency(sc, 0);
freq /= a3700_periph_get_double_div(sc,
PERIPH_DIV_SEL2, 16, 13);
return freq;
case PERIPH_NB_SQF:
freq = a3700_periph_tbg_get_frequency(sc, 12);
freq /= a3700_periph_get_double_div(sc,
PERIPH_DIV_SEL1, 27, 24);
return freq;
case PERIPH_NB_CPU:
freq = a3700_periph_tbg_get_frequency(sc, 22);
freq /= a3700_periph_get_div(sc, PERIPH_DIV_SEL0, 28);
return freq;
default:
break;
}
printf("%s: 0x%08x\n", __func__, idx);
return 0;
}
void
a3700_periph_sb_enable(void *cookie, uint32_t *cells, int on)
{
struct mvclock_softc *sc = cookie;
uint32_t idx = cells[0];
switch (idx) {
case PERIPH_SB_GBE1_CORE:
return a3700_periph_enable(sc, 4, on);
case PERIPH_SB_GBE0_CORE:
return a3700_periph_enable(sc, 5, on);
case PERIPH_SB_USB32_USB2_SYS:
return a3700_periph_enable(sc, 16, on);
case PERIPH_SB_USB32_SS_SYS:
return a3700_periph_enable(sc, 17, on);
default:
break;
}
printf("%s: 0x%08x\n", __func__, idx);
}
uint32_t
a3700_periph_sb_get_frequency(void *cookie, uint32_t *cells)
{
struct mvclock_softc *sc = cookie;
uint32_t idx = cells[0];
uint32_t freq;
switch (idx) {
case PERIPH_SB_GBE_CORE:
freq = a3700_periph_tbg_get_frequency(sc, 8);
freq /= a3700_periph_get_double_div(sc,
PERIPH_DIV_SEL1, 18, 21);
return freq;
case PERIPH_SB_GBE1_CORE:
idx = PERIPH_SB_GBE_CORE;
freq = a3700_periph_sb_get_frequency(sc, &idx);
freq /= a3700_periph_get_div(sc, PERIPH_DIV_SEL1, 13) + 1;
return freq;
case PERIPH_SB_GBE0_CORE:
idx = PERIPH_SB_GBE_CORE;
freq = a3700_periph_sb_get_frequency(sc, &idx);
freq /= a3700_periph_get_div(sc, PERIPH_DIV_SEL1, 14) + 1;
return freq;
default:
break;
}
printf("%s: 0x%08x\n", __func__, idx);
return 0;
}
void
a3700_periph_enable(struct mvclock_softc *sc, uint32_t idx, int on)
{
uint32_t reg;
reg = HREAD4(sc, PERIPH_CLK_DIS);
reg &= ~(1 << idx);
if (!on)
reg |= (1 << idx);
HWRITE4(sc, PERIPH_CLK_DIS, reg);
}
uint32_t
a3700_periph_tbg_get_frequency(struct mvclock_softc *sc, uint32_t idx)
{
uint32_t reg;
reg = HREAD4(sc, PERIPH_TBG_SEL);
reg >>= idx;
reg &= PERIPH_TBG_SEL_MASK;
return clock_get_frequency_idx(sc->sc_cd.cd_node, reg);
}
uint32_t
a3700_periph_get_div(struct mvclock_softc *sc, uint32_t off, uint32_t idx)
{
uint32_t reg = HREAD4(sc, off);
return ((reg >> idx) & PERIPH_DIV_SEL_MASK);
}
uint32_t
a3700_periph_get_double_div(struct mvclock_softc *sc, uint32_t off,
uint32_t idx0, uint32_t idx1)
{
uint32_t reg = HREAD4(sc, off);
return ((reg >> idx0) & PERIPH_DIV_SEL_MASK) *
((reg >> idx1) & PERIPH_DIV_SEL_MASK);
}
#define TBG_A_P 0
#define TBG_B_P 1
#define TBG_A_S 2
#define TBG_B_S 3
#define TBG_CTRL0 0x4
#define TBG_A_FBDIV_SHIFT 2
#define TBG_B_FBDIV_SHIFT 18
#define TBG_CTRL1 0x8
#define TBG_A_VCODIV_SE_SHIFT 0
#define TBG_B_VCODIV_SE_SHIFT 16
#define TBG_CTRL7 0x20
#define TBG_A_REFDIV_SHIFT 0
#define TBG_B_REFDIV_SHIFT 16
#define TBG_CTRL8 0x30
#define TBG_A_VCODIV_DIFF_SHIFT 1
#define TBG_B_VCODIV_DIFF_SHIFT 17
#define TBG_DIV_MASK 0x1ff
uint32_t
a3700_tbg_get_frequency(void *cookie, uint32_t *cells)
{
struct mvclock_softc *sc = cookie;
uint32_t idx = cells[0];
uint64_t mult, div, freq;
uint32_t reg, vcodiv;
switch (idx) {
case TBG_A_P:
vcodiv = HREAD4(sc, TBG_CTRL8);
vcodiv >>= TBG_A_VCODIV_DIFF_SHIFT;
vcodiv &= TBG_DIV_MASK;
break;
case TBG_B_P:
vcodiv = HREAD4(sc, TBG_CTRL8);
vcodiv >>= TBG_B_VCODIV_DIFF_SHIFT;
vcodiv &= TBG_DIV_MASK;
break;
case TBG_A_S:
vcodiv = HREAD4(sc, TBG_CTRL1);
vcodiv >>= TBG_A_VCODIV_SE_SHIFT;
vcodiv &= TBG_DIV_MASK;
break;
case TBG_B_S:
vcodiv = HREAD4(sc, TBG_CTRL1);
vcodiv >>= TBG_B_VCODIV_SE_SHIFT;
vcodiv &= TBG_DIV_MASK;
break;
default:
printf("%s: 0x%08x\n", __func__, idx);
return 0;
}
reg = HREAD4(sc, TBG_CTRL0);
if (idx == TBG_A_P || idx == TBG_A_S)
reg >>= TBG_A_FBDIV_SHIFT;
else
reg >>= TBG_B_FBDIV_SHIFT;
reg &= TBG_DIV_MASK;
mult = reg << 2;
reg = HREAD4(sc, TBG_CTRL7);
if (idx == TBG_A_P || idx == TBG_A_S)
reg >>= TBG_A_REFDIV_SHIFT;
else
reg >>= TBG_B_REFDIV_SHIFT;
reg &= TBG_DIV_MASK;
div = reg;
if (div == 0)
div = 1;
div *= 1 << vcodiv;
freq = clock_get_frequency(sc->sc_cd.cd_node, NULL);
return (freq * mult) / div;
}