#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/limits.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <dev/bhnd/bhnd.h>
#include <dev/bhnd/bhndb/bhndb_pcireg.h>
#include <dev/bhnd/cores/chipc/chipc.h>
#include <dev/bhnd/cores/chipc/chipcreg.h>
#include <dev/bhnd/cores/pmu/bhnd_pmuvar.h>
#include <dev/bhnd/cores/pmu/bhnd_pmureg.h>
#include "bhnd_chipc_if.h"
#include "bhnd_pwrctl_private.h"
static uint32_t bhnd_pwrctl_factor6(uint32_t x);
static uint32_t
bhnd_pwrctl_factor6(uint32_t x)
{
switch (x) {
case CHIPC_F6_2:
return (2);
case CHIPC_F6_3:
return (3);
case CHIPC_F6_4:
return (4);
case CHIPC_F6_5:
return (5);
case CHIPC_F6_6:
return (6);
case CHIPC_F6_7:
return (7);
default:
return (0);
}
}
bus_size_t
bhnd_pwrctl_si_clkreg_m(const struct bhnd_chipid *cid,
uint8_t pll_type, uint32_t *fixed_hz)
{
switch (pll_type) {
case CHIPC_PLL_TYPE6:
return (CHIPC_CLKC_M3);
case CHIPC_PLL_TYPE3:
return (CHIPC_CLKC_M2);
default:
return (CHIPC_CLKC_SB);
}
}
uint32_t
bhnd_pwrctl_si_clock_rate(const struct bhnd_chipid *cid,
uint32_t pll_type, uint32_t n, uint32_t m)
{
uint32_t rate;
KASSERT(bhnd_pwrctl_si_clkreg_m(cid, pll_type, NULL) != 0,
("can't compute clock rate on fixed clock"));
rate = bhnd_pwrctl_clock_rate(pll_type, n, m);
if (pll_type == CHIPC_PLL_TYPE3)
rate /= 2;
return (rate);
}
bus_size_t
bhnd_pwrctl_cpu_clkreg_m(const struct bhnd_chipid *cid,
uint8_t pll_type, uint32_t *fixed_hz)
{
switch (pll_type) {
case CHIPC_PLL_TYPE2:
case CHIPC_PLL_TYPE4:
case CHIPC_PLL_TYPE6:
case CHIPC_PLL_TYPE7:
return (CHIPC_CLKC_M3);
case CHIPC_PLL_TYPE5:
if (fixed_hz != NULL)
*fixed_hz = 200 * 1000 * 1000;
return (0);
case CHIPC_PLL_TYPE3:
if (cid->chip_id == BHND_CHIPID_BCM5365) {
if (fixed_hz != NULL)
*fixed_hz = 200 * 1000 * 1000;
return (0);
}
return (CHIPC_CLKC_M2);
default:
return (CHIPC_CLKC_SB);
}
}
uint32_t
bhnd_pwrctl_cpu_clock_rate(const struct bhnd_chipid *cid,
uint32_t pll_type, uint32_t n, uint32_t m)
{
KASSERT(bhnd_pwrctl_cpu_clkreg_m(cid, pll_type, NULL) != 0,
("can't compute clock rate on fixed clock"));
return (bhnd_pwrctl_clock_rate(pll_type, n, m));
}
uint32_t
bhnd_pwrctl_clock_rate(uint32_t pll_type, uint32_t n, uint32_t m)
{
uint32_t clk_base;
uint32_t n1, n2, clock, m1, m2, m3, mc;
n1 = CHIPC_GET_BITS(n, CHIPC_CN_N1);
n2 = CHIPC_GET_BITS(n, CHIPC_CN_N2);
switch (pll_type) {
case CHIPC_PLL_TYPE1:
case CHIPC_PLL_TYPE3:
case CHIPC_PLL_TYPE4:
case CHIPC_PLL_TYPE7:
n1 = bhnd_pwrctl_factor6(n1);
n2 += CHIPC_F5_BIAS;
break;
case CHIPC_PLL_TYPE2:
n1 += CHIPC_T2_BIAS;
n2 += CHIPC_T2_BIAS;
KASSERT(n1 >= 2 && n1 <= 7, ("invalid n1 value"));
KASSERT(n2 >= 5 && n2 <= 23, ("invalid n2 value"));
break;
case CHIPC_PLL_TYPE5:
return (100000000);
case CHIPC_PLL_TYPE6:
if (m & CHIPC_T6_MMASK)
return (CHIPC_T6_M1);
else
return (CHIPC_T6_M0);
default:
printf("unsupported PLL type %u\n", pll_type);
return (0);
}
if (pll_type == CHIPC_PLL_TYPE3 || pll_type == CHIPC_PLL_TYPE7) {
clk_base = CHIPC_CLOCK_BASE2;
} else {
clk_base = CHIPC_CLOCK_BASE1;
}
clock = clk_base * n1 * n2;
if (clock == 0)
return (0);
m1 = CHIPC_GET_BITS(m, CHIPC_M1);
m2 = CHIPC_GET_BITS(m, CHIPC_M2);
m3 = CHIPC_GET_BITS(m, CHIPC_M3);
mc = CHIPC_GET_BITS(m, CHIPC_MC);
switch (pll_type) {
case CHIPC_PLL_TYPE1:
case CHIPC_PLL_TYPE3:
case CHIPC_PLL_TYPE4:
case CHIPC_PLL_TYPE7:
m1 = bhnd_pwrctl_factor6(m1);
if (pll_type == CHIPC_PLL_TYPE1 || pll_type == CHIPC_PLL_TYPE3)
m2 += CHIPC_F5_BIAS;
else
m2 = bhnd_pwrctl_factor6(m2);
m3 = bhnd_pwrctl_factor6(m3);
switch (mc) {
case CHIPC_MC_BYPASS:
return (clock);
case CHIPC_MC_M1:
return (clock / m1);
case CHIPC_MC_M1M2:
return (clock / (m1 * m2));
case CHIPC_MC_M1M2M3:
return (clock / (m1 * m2 * m3));
case CHIPC_MC_M1M3:
return (clock / (m1 * m3));
default:
printf("unsupported pwrctl mc %#x\n", mc);
return (0);
}
case CHIPC_PLL_TYPE2:
m1 += CHIPC_T2_BIAS;
m2 += CHIPC_T2M2_BIAS;
m3 += CHIPC_T2_BIAS;
KASSERT(m1 >= 2 && m1 <= 7, ("invalid m1 value"));
KASSERT(m2 >= 3 && m2 <= 10, ("invalid m2 value"));
KASSERT(m3 >= 2 && m3 <= 7, ("invalid m3 value"));
if ((mc & CHIPC_T2MC_M1BYP) == 0)
clock /= m1;
if ((mc & CHIPC_T2MC_M2BYP) == 0)
clock /= m2;
if ((mc & CHIPC_T2MC_M3BYP) == 0)
clock /= m3;
return (clock);
default:
panic("unhandled PLL type %u\n", pll_type);
}
}
uint32_t
bhnd_pwrctl_getclk_speed(struct bhnd_pwrctl_softc *sc)
{
const struct bhnd_chipid *cid;
struct chipc_caps *ccaps;
bus_size_t creg;
uint32_t n, m;
uint32_t rate;
PWRCTL_LOCK_ASSERT(sc, MA_OWNED);
cid = bhnd_get_chipid(sc->chipc_dev);
ccaps = BHND_CHIPC_GET_CAPS(sc->chipc_dev);
n = bhnd_bus_read_4(sc->res, CHIPC_CLKC_N);
creg = bhnd_pwrctl_si_clkreg_m(cid, ccaps->pll_type, &rate);
if (creg == 0)
return (rate);
m = bhnd_bus_read_4(sc->res, creg);
return (bhnd_pwrctl_si_clock_rate(cid, ccaps->pll_type, n, m));
}
static bhnd_clksrc
bhnd_pwrctl_slowclk_src(struct bhnd_pwrctl_softc *sc)
{
uint32_t clkreg;
uint32_t clksrc;
if (PWRCTL_QUIRK(sc, PCICLK_CTL)) {
return (bhnd_pwrctl_hostb_get_clksrc(sc->chipc_dev,
BHND_CLOCK_ILP));
} else if (PWRCTL_QUIRK(sc, SLOWCLK_CTL)) {
clkreg = bhnd_bus_read_4(sc->res, CHIPC_PLL_SLOWCLK_CTL);
clksrc = clkreg & CHIPC_SCC_SS_MASK;
} else {
clksrc = CHIPC_SCC_SS_XTAL;
}
switch (clksrc) {
case CHIPC_SCC_SS_PCI:
return (BHND_CLKSRC_PCI);
case CHIPC_SCC_SS_LPO:
return (BHND_CLKSRC_LPO);
case CHIPC_SCC_SS_XTAL:
return (BHND_CLKSRC_XTAL);
default:
return (BHND_CLKSRC_UNKNOWN);
}
}
static uint32_t
bhnd_pwrctl_slowclk_freq(struct bhnd_pwrctl_softc *sc, bool max_freq)
{
bhnd_clksrc slowclk;
uint32_t div;
uint32_t hz;
slowclk = bhnd_pwrctl_slowclk_src(sc);
if (PWRCTL_QUIRK(sc, PCICLK_CTL)) {
if (slowclk == BHND_CLKSRC_PCI)
div = 64;
else
div = 32;
} else if (PWRCTL_QUIRK(sc, SLOWCLK_CTL)) {
div = bhnd_bus_read_4(sc->res, CHIPC_PLL_SLOWCLK_CTL);
div = CHIPC_GET_BITS(div, CHIPC_SCC_CD);
div = 4 * (div + 1);
} else if (PWRCTL_QUIRK(sc, INSTACLK_CTL)) {
if (max_freq) {
div = 1;
} else {
div = bhnd_bus_read_4(sc->res, CHIPC_SYS_CLK_CTL);
div = CHIPC_GET_BITS(div, CHIPC_SYCC_CD);
div = 4 * (div + 1);
}
} else {
device_printf(sc->dev, "unknown device type\n");
return (0);
}
switch (slowclk) {
case BHND_CLKSRC_LPO:
hz = max_freq ? CHIPC_LPOMAXFREQ : CHIPC_LPOMINFREQ;
break;
case BHND_CLKSRC_XTAL:
hz = max_freq ? CHIPC_XTALMAXFREQ : CHIPC_XTALMINFREQ;
break;
case BHND_CLKSRC_PCI:
hz = max_freq ? CHIPC_PCIMAXFREQ : CHIPC_PCIMINFREQ;
break;
default:
device_printf(sc->dev, "unknown slowclk source %#x\n", slowclk);
return (0);
}
return (hz / div);
}
int
bhnd_pwrctl_init(struct bhnd_pwrctl_softc *sc)
{
uint32_t clkctl;
uint32_t pll_delay, slowclk, slowmaxfreq;
uint32_t pll_on_delay, fref_sel_delay;
int error;
pll_delay = CHIPC_PLL_DELAY;
if (PWRCTL_QUIRK(sc, INSTACLK_CTL)) {
clkctl = (CHIPC_ILP_DIV_1MHZ << CHIPC_SYCC_CD_SHIFT);
clkctl &= CHIPC_SYCC_CD_MASK;
bhnd_bus_write_4(sc->res, CHIPC_SYS_CLK_CTL, clkctl);
}
slowclk = bhnd_pwrctl_slowclk_src(sc);
if (slowclk != CHIPC_SCC_SS_XTAL)
pll_delay += CHIPC_XTAL_ON_DELAY;
if (PWRCTL_QUIRK(sc, INSTACLK_CTL))
slowmaxfreq = bhnd_pwrctl_slowclk_freq(sc, false);
else
slowmaxfreq = bhnd_pwrctl_slowclk_freq(sc, true);
pll_on_delay = ((slowmaxfreq * pll_delay) + 999999) / 1000000;
fref_sel_delay = ((slowmaxfreq * CHIPC_FREF_DELAY) + 999999) / 1000000;
bhnd_bus_write_4(sc->res, CHIPC_PLL_ON_DELAY, pll_on_delay);
bhnd_bus_write_4(sc->res, CHIPC_PLL_FREFSEL_DELAY, fref_sel_delay);
if (PWRCTL_QUIRK(sc, FORCE_HT)) {
if ((error = bhnd_pwrctl_setclk(sc, BHND_CLOCK_HT)))
return (error);
}
return (0);
}
u_int
bhnd_pwrctl_fast_pwrup_delay(struct bhnd_pwrctl_softc *sc)
{
u_int pll_on_delay, slowminfreq;
u_int fpdelay;
fpdelay = 0;
slowminfreq = bhnd_pwrctl_slowclk_freq(sc, false);
pll_on_delay = bhnd_bus_read_4(sc->res, CHIPC_PLL_ON_DELAY) + 2;
pll_on_delay *= 1000000;
pll_on_delay += (slowminfreq - 1);
fpdelay = pll_on_delay / slowminfreq;
return (fpdelay);
}
int
bhnd_pwrctl_setclk(struct bhnd_pwrctl_softc *sc, bhnd_clock clock)
{
uint32_t scc;
PWRCTL_LOCK_ASSERT(sc, MA_OWNED);
if (PWRCTL_QUIRK(sc, FIXED_CLK))
return (ENODEV);
if (bhnd_get_hwrev(sc->chipc_dev) == 10)
return (ENODEV);
if (PWRCTL_QUIRK(sc, SLOWCLK_CTL))
scc = bhnd_bus_read_4(sc->res, CHIPC_PLL_SLOWCLK_CTL);
else
scc = bhnd_bus_read_4(sc->res, CHIPC_SYS_CLK_CTL);
switch (clock) {
case BHND_CLOCK_HT:
if (PWRCTL_QUIRK(sc, SLOWCLK_CTL)) {
scc &= ~(CHIPC_SCC_XC | CHIPC_SCC_FS | CHIPC_SCC_IP);
scc |= CHIPC_SCC_IP;
bhnd_pwrctl_hostb_ungate_clock(sc->chipc_dev,
BHND_CLOCK_HT);
} else if (PWRCTL_QUIRK(sc, INSTACLK_CTL)) {
scc |= CHIPC_SYCC_HR;
} else {
return (ENODEV);
}
if (PWRCTL_QUIRK(sc, SLOWCLK_CTL))
bhnd_bus_write_4(sc->res, CHIPC_PLL_SLOWCLK_CTL, scc);
else
bhnd_bus_write_4(sc->res, CHIPC_SYS_CLK_CTL, scc);
DELAY(CHIPC_PLL_DELAY);
break;
case BHND_CLOCK_DYN:
if (PWRCTL_QUIRK(sc, SLOWCLK_CTL)) {
scc &= ~(CHIPC_SCC_FS | CHIPC_SCC_IP | CHIPC_SCC_XC);
if ((scc & CHIPC_SCC_SS_MASK) != CHIPC_SCC_SS_XTAL)
scc |= CHIPC_SCC_XC;
bhnd_bus_write_4(sc->res, CHIPC_PLL_SLOWCLK_CTL, scc);
if (scc & CHIPC_SCC_XC) {
bhnd_pwrctl_hostb_gate_clock(sc->chipc_dev,
BHND_CLOCK_HT);
}
} else if (PWRCTL_QUIRK(sc, INSTACLK_CTL)) {
scc &= ~CHIPC_SYCC_HR;
bhnd_bus_write_4(sc->res, CHIPC_SYS_CLK_CTL, scc);
} else {
return (ENODEV);
}
break;
default:
return (ENODEV);
}
return (0);
}