MFC 264052, 264057, 264065, 264094, 264103, 264120

Actually save the mpcore clock frequency retrieved from fdt data.

  imx6..
  - Don't call sdhci_init_slot() until after handling the FDT properties
    related to detecting card presence.
  - Flag several sysctl variables as tunables.
  - Rework the cpu frequency management code for imx6 to add "operating
    points" and min/max frequency controls.

  generic timer...
  - Setup both secure and non-secure timer IRQs.
    We don't know our ARM security state, so one of them will operate.
  - Don't set frequency, since it's unpossible in non-secure state.
    Only rely on DTS clock-frequency value or get clock from timer.
This commit is contained in:
ian 2014-05-17 20:52:10 +00:00
parent e111203de4
commit 941a79bd10
6 changed files with 219 additions and 80 deletions

View File

@ -73,16 +73,23 @@ __FBSDID("$FreeBSD$");
#define GT_CNTKCTL_PL0VCTEN (1 << 1) /* PL0 CNTVCT and CNTFRQ access */
#define GT_CNTKCTL_PL0PCTEN (1 << 0) /* PL0 CNTPCT and CNTFRQ access */
#define GT_CNTPSIRQ 29
struct arm_tmr_softc {
struct resource *irq_res;
struct resource *res[4];
void *ihl[4];
uint32_t clkfreq;
struct eventtimer et;
};
static struct arm_tmr_softc *arm_tmr_sc = NULL;
static struct resource_spec timer_spec[] = {
{ SYS_RES_IRQ, 0, RF_ACTIVE }, /* Secure */
{ SYS_RES_IRQ, 1, RF_ACTIVE }, /* Non-secure */
{ SYS_RES_IRQ, 2, RF_ACTIVE }, /* Virt */
{ SYS_RES_IRQ, 3, RF_ACTIVE }, /* Hyp */
{ -1, 0 }
};
static timecounter_get_t arm_tmr_get_timecount;
static struct timecounter arm_tmr_timecount = {
@ -261,9 +268,8 @@ arm_tmr_attach(device_t dev)
struct arm_tmr_softc *sc;
phandle_t node;
pcell_t clock;
void *ihl;
int rid;
int error;
int i;
sc = device_get_softc(dev);
if (arm_tmr_sc)
@ -272,29 +278,37 @@ arm_tmr_attach(device_t dev)
/* Get the base clock frequency */
node = ofw_bus_get_node(dev);
error = OF_getprop(node, "clock-frequency", &clock, sizeof(clock));
if (error <= 0) {
device_printf(dev, "missing clock-frequency "
"attribute in FDT\n");
if (error > 0) {
sc->clkfreq = fdt32_to_cpu(clock);
}
if (sc->clkfreq == 0) {
/* Try to get clock frequency from timer */
sc->clkfreq = get_freq();
}
if (sc->clkfreq == 0) {
device_printf(dev, "No clock frequency specified\n");
return (ENXIO);
}
sc->clkfreq = fdt32_to_cpu(clock);
rid = 0;
sc->irq_res = bus_alloc_resource(dev, SYS_RES_IRQ, &rid,
GT_CNTPSIRQ, GT_CNTPSIRQ,
1, RF_SHAREABLE | RF_ACTIVE);
if (bus_alloc_resources(dev, timer_spec, sc->res)) {
device_printf(dev, "could not allocate resources\n");
return (ENXIO);
};
arm_tmr_sc = sc;
/* Setup and enable the timer */
if (bus_setup_intr(dev, sc->irq_res, INTR_TYPE_CLK, arm_tmr_intr,
NULL, sc, &ihl) != 0) {
bus_release_resource(dev, SYS_RES_IRQ, rid, sc->irq_res);
device_printf(dev, "Unable to setup the CLK irq handler.\n");
return (ENXIO);
/* Setup secure and non-secure IRQs handler */
for (i = 0; i < 2; i++) {
error = bus_setup_intr(dev, sc->res[i], INTR_TYPE_CLK,
arm_tmr_intr, NULL, sc, &sc->ihl[i]);
if (error) {
device_printf(dev, "Unable to alloc int resource.\n");
return (ENXIO);
}
}
set_freq(sc->clkfreq);
disable_user_access();
arm_tmr_timecount.tc_frequency = sc->clkfreq;

View File

@ -301,6 +301,7 @@ arm_tmr_attach(device_t dev)
"attribute in FDT\n");
return (ENXIO);
}
sc->clkfreq = clock;
}
}

View File

@ -57,6 +57,7 @@ __FBSDID("$FreeBSD$");
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/kernel.h>
#include <sys/limits.h>
#include <sys/sysctl.h>
#include <sys/module.h>
#include <sys/bus.h>
@ -74,6 +75,8 @@ __FBSDID("$FreeBSD$");
#include <arm/freescale/imx/imx6_anatopreg.h>
#include <arm/freescale/imx/imx6_anatopvar.h>
static SYSCTL_NODE(_hw, OID_AUTO, imx6, CTLFLAG_RW, NULL, "i.MX6 container");
static struct resource_spec imx6_anatop_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE },
{ SYS_RES_IRQ, 0, RF_ACTIVE },
@ -85,14 +88,15 @@ static struct resource_spec imx6_anatop_spec[] = {
struct imx6_anatop_softc {
device_t dev;
struct resource *res[2];
uint32_t cpu_curhz;
uint32_t cpu_curmhz;
uint32_t cpu_curmv;
uint32_t cpu_minhz;
uint32_t cpu_minmhz;
uint32_t cpu_minmv;
uint32_t cpu_maxhz;
uint32_t cpu_maxmhz;
uint32_t cpu_maxmv;
uint32_t refosc_hz;
uint32_t cpu_maxmhz_hw;
boolean_t cpu_overclock_enable;
uint32_t refosc_mhz;
void *temp_intrhand;
uint32_t temp_high_val;
uint32_t temp_high_cnt;
@ -108,16 +112,27 @@ struct imx6_anatop_softc {
static struct imx6_anatop_softc *imx6_anatop_sc;
/*
* Tables of CPU max frequencies and corresponding voltages. This is indexed by
* the max frequency value (0-3) from the ocotp CFG3 register.
* Table of "operating points".
* These are combinations of frequency and voltage blessed by Freescale.
*/
static uint32_t imx6_cpu_maxhz_tab[] = {
792000000, 852000000, 996000000, 1200000000
};
static uint32_t imx6_cpu_millivolt_tab[] = {
1150, 1225, 1225, 1275
static struct oppt {
uint32_t mhz;
uint32_t mv;
} imx6_oppt_table[] = {
/* { 396, 925}, XXX: need functional ccm code for this speed */
{ 792, 1150},
{ 852, 1225},
{ 996, 1225},
{1200, 1275},
};
/*
* Table of CPU max frequencies. This is used to translate the max frequency
* value (0-3) from the ocotp CFG3 register into a mhz value that can be looked
* up in the operating points table.
*/
static uint32_t imx6_ocotp_mhz_tab[] = {792, 852, 996, 1200};
#define TZ_ZEROC 2732 /* deci-Kelvin <-> deci-Celcius offset. */
uint32_t
@ -193,49 +208,58 @@ vdd_set(struct imx6_anatop_softc *sc, int mv)
imx6_anatop_write_4(IMX6_ANALOG_PMU_REG_CORE, pmureg);
DELAY(delay);
sc->cpu_curmv = newtarg * 25 + 700;
device_printf(sc->dev, "voltage set to %u\n", sc->cpu_curmv);
}
static inline uint32_t
cpufreq_hz_from_div(struct imx6_anatop_softc *sc, uint32_t div)
cpufreq_mhz_from_div(struct imx6_anatop_softc *sc, uint32_t div)
{
return (sc->refosc_hz * (div / 2));
return (sc->refosc_mhz * (div / 2));
}
static inline uint32_t
cpufreq_hz_to_div(struct imx6_anatop_softc *sc, uint32_t cpu_hz)
cpufreq_mhz_to_div(struct imx6_anatop_softc *sc, uint32_t cpu_mhz)
{
return (cpu_hz / (sc->refosc_hz / 2));
return (cpu_mhz / (sc->refosc_mhz / 2));
}
static inline uint32_t
cpufreq_actual_hz(struct imx6_anatop_softc *sc, uint32_t cpu_hz)
cpufreq_actual_mhz(struct imx6_anatop_softc *sc, uint32_t cpu_mhz)
{
return (cpufreq_hz_from_div(sc, cpufreq_hz_to_div(sc, cpu_hz)));
return (cpufreq_mhz_from_div(sc, cpufreq_mhz_to_div(sc, cpu_mhz)));
}
static struct oppt *
cpufreq_nearest_oppt(struct imx6_anatop_softc *sc, uint32_t cpu_newmhz)
{
int d, diff, i, nearest;
if (cpu_newmhz > sc->cpu_maxmhz_hw && !sc->cpu_overclock_enable)
cpu_newmhz = sc->cpu_maxmhz_hw;
diff = INT_MAX;
nearest = 0;
for (i = 0; i < nitems(imx6_oppt_table); ++i) {
d = abs((int)cpu_newmhz - (int)imx6_oppt_table[i].mhz);
if (diff > d) {
diff = d;
nearest = i;
}
}
return (&imx6_oppt_table[nearest]);
}
static void
cpufreq_set_clock(struct imx6_anatop_softc * sc, uint32_t cpu_newhz)
cpufreq_set_clock(struct imx6_anatop_softc * sc, struct oppt *op)
{
uint32_t div, timeout, wrk32;
const uint32_t mindiv = 54;
const uint32_t maxdiv = 108;
uint32_t timeout, wrk32;
/*
* Clip the requested frequency to the configured max, then clip the
* resulting divisor to the documented min/max values.
*/
cpu_newhz = min(cpu_newhz, sc->cpu_maxhz);
div = cpufreq_hz_to_div(sc, cpu_newhz);
if (div < mindiv)
div = mindiv;
else if (div > maxdiv)
div = maxdiv;
sc->cpu_curhz = cpufreq_hz_from_div(sc, div);
sc->cpu_curmhz = sc->cpu_curhz / 1000000;
/* If increasing the frequency, we must first increase the voltage. */
if (op->mhz > sc->cpu_curmhz) {
vdd_set(sc, op->mv);
}
/*
* I can't find a documented procedure for changing the ARM PLL divisor,
@ -244,7 +268,7 @@ cpufreq_set_clock(struct imx6_anatop_softc * sc, uint32_t cpu_newhz)
* - Set the PLL into bypass mode; cpu should now be running at 24mhz.
* - Change the divisor.
* - Wait for the LOCK bit to come on; it takes ~50 loop iterations.
* - Turn off bypass mode; cpu should now be running at cpu_newhz.
* - Turn off bypass mode; cpu should now be running at the new speed.
*/
imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM_CLR,
IMX6_ANALOG_CCM_PLL_ARM_CLK_SRC_MASK);
@ -253,7 +277,7 @@ cpufreq_set_clock(struct imx6_anatop_softc * sc, uint32_t cpu_newhz)
wrk32 = imx6_anatop_read_4(IMX6_ANALOG_CCM_PLL_ARM);
wrk32 &= ~IMX6_ANALOG_CCM_PLL_ARM_DIV_MASK;
wrk32 |= div;
wrk32 |= cpufreq_mhz_to_div(sc, op->mhz);
imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM, wrk32);
timeout = 10000;
@ -265,26 +289,114 @@ cpufreq_set_clock(struct imx6_anatop_softc * sc, uint32_t cpu_newhz)
imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM_CLR,
IMX6_ANALOG_CCM_PLL_ARM_BYPASS);
arm_tmr_change_frequency(sc->cpu_curhz / 2);
/* If lowering the frequency, it is now safe to lower the voltage. */
if (op->mhz < sc->cpu_curmhz)
vdd_set(sc, op->mv);
sc->cpu_curmhz = op->mhz;
/* Tell the mpcore timer that its frequency has changed. */
arm_tmr_change_frequency(
cpufreq_actual_mhz(sc, sc->cpu_curmhz) * 1000000 / 2);
}
static int
cpufreq_sysctl_minmhz(SYSCTL_HANDLER_ARGS)
{
struct imx6_anatop_softc *sc;
struct oppt * op;
uint32_t temp;
int err;
sc = arg1;
temp = sc->cpu_minmhz;
err = sysctl_handle_int(oidp, &temp, 0, req);
if (err != 0 || req->newptr == NULL)
return (err);
op = cpufreq_nearest_oppt(sc, temp);
if (op->mhz > sc->cpu_maxmhz)
return (ERANGE);
else if (op->mhz == sc->cpu_minmhz)
return (0);
/*
* Value changed, update softc. If the new min is higher than the
* current speed, raise the current speed to match.
*/
sc->cpu_minmhz = op->mhz;
if (sc->cpu_minmhz > sc->cpu_curmhz) {
cpufreq_set_clock(sc, op);
}
return (err);
}
static int
cpufreq_sysctl_maxmhz(SYSCTL_HANDLER_ARGS)
{
struct imx6_anatop_softc *sc;
struct oppt * op;
uint32_t temp;
int err;
sc = arg1;
temp = sc->cpu_maxmhz;
err = sysctl_handle_int(oidp, &temp, 0, req);
if (err != 0 || req->newptr == NULL)
return (err);
op = cpufreq_nearest_oppt(sc, temp);
if (op->mhz < sc->cpu_minmhz)
return (ERANGE);
else if (op->mhz == sc->cpu_maxmhz)
return (0);
/*
* Value changed, update softc and hardware. The hardware update is
* unconditional. We always try to run at max speed, so any change of
* the max means we need to change the current speed too, regardless of
* whether it is higher or lower than the old max.
*/
sc->cpu_maxmhz = op->mhz;
cpufreq_set_clock(sc, op);
return (err);
}
static void
cpufreq_initialize(struct imx6_anatop_softc *sc)
{
uint32_t cfg3speed;
struct sysctl_ctx_list *ctx;
struct oppt * op;
ctx = device_get_sysctl_ctx(sc->dev);
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
SYSCTL_ADD_INT(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx6),
OID_AUTO, "cpu_mhz", CTLFLAG_RD, &sc->cpu_curmhz, 0,
"CPU frequency in MHz");
"CPU frequency");
SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx6),
OID_AUTO, "cpu_minmhz", CTLTYPE_INT | CTLFLAG_RWTUN, sc, 0,
cpufreq_sysctl_minmhz, "IU", "Minimum CPU frequency");
SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx6),
OID_AUTO, "cpu_maxmhz", CTLTYPE_INT | CTLFLAG_RWTUN, sc, 0,
cpufreq_sysctl_maxmhz, "IU", "Maximum CPU frequency");
SYSCTL_ADD_INT(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx6),
OID_AUTO, "cpu_maxmhz_hw", CTLFLAG_RD, &sc->cpu_maxmhz_hw, 0,
"Maximum CPU frequency allowed by hardware");
SYSCTL_ADD_INT(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx6),
OID_AUTO, "cpu_overclock_enable", CTLFLAG_RWTUN,
&sc->cpu_overclock_enable, 0,
"Allow setting CPU frequency higher than cpu_maxmhz_hw");
/*
* XXX 24mhz shouldn't be hard-coded, should get this from imx6_ccm
* (even though in the real world it will always be 24mhz). Oh wait a
* sec, I never wrote imx6_ccm.
*/
sc->refosc_hz = 24000000;
sc->refosc_mhz = 24;
/*
* Get the maximum speed this cpu can be set to. The values in the
@ -294,14 +406,25 @@ cpufreq_initialize(struct imx6_anatop_softc *sc)
* - 2b'10: 996000000Hz;
* - 2b'01: 852000000Hz; -- i.MX6Q Only, exclusive with 996MHz.
* - 2b'00: 792000000Hz;
* The default hardware max speed can be overridden by a tunable.
*/
cfg3speed = (fsl_ocotp_read_4(FSL_OCOTP_CFG3) &
FSL_OCOTP_CFG3_SPEED_MASK) >> FSL_OCOTP_CFG3_SPEED_SHIFT;
sc->cpu_maxmhz_hw = imx6_ocotp_mhz_tab[cfg3speed];
sc->cpu_maxmhz = sc->cpu_maxmhz_hw;
sc->cpu_minhz = cpufreq_actual_hz(sc, imx6_cpu_maxhz_tab[0]);
sc->cpu_minmv = imx6_cpu_millivolt_tab[0];
sc->cpu_maxhz = cpufreq_actual_hz(sc, imx6_cpu_maxhz_tab[cfg3speed]);
sc->cpu_maxmv = imx6_cpu_millivolt_tab[cfg3speed];
TUNABLE_INT_FETCH("hw.imx6.cpu_overclock_enable",
&sc->cpu_overclock_enable);
TUNABLE_INT_FETCH("hw.imx6.cpu_minmhz", &sc->cpu_minmhz);
op = cpufreq_nearest_oppt(sc, sc->cpu_minmhz);
sc->cpu_minmhz = op->mhz;
sc->cpu_minmv = op->mv;
TUNABLE_INT_FETCH("hw.imx6.cpu_maxmhz", &sc->cpu_maxmhz);
op = cpufreq_nearest_oppt(sc, sc->cpu_maxmhz);
sc->cpu_maxmhz = op->mhz;
sc->cpu_maxmv = op->mv;
/*
* Set the CPU to maximum speed.
@ -311,9 +434,7 @@ cpufreq_initialize(struct imx6_anatop_softc *sc)
* basically assumes that a single core can't overheat before interrupts
* are enabled; empirical testing shows that to be a safe assumption.
*/
vdd_set(sc, sc->cpu_maxmv);
cpufreq_set_clock(sc, sc->cpu_maxhz);
device_printf(sc->dev, "CPU frequency %uMHz\n", sc->cpu_curmhz);
cpufreq_set_clock(sc, op);
}
static inline uint32_t
@ -391,9 +512,8 @@ static void
tempmon_gofast(struct imx6_anatop_softc *sc)
{
if (sc->cpu_curhz < sc->cpu_maxhz) {
vdd_set(sc, sc->cpu_maxmv);
cpufreq_set_clock(sc, sc->cpu_maxhz);
if (sc->cpu_curmhz < sc->cpu_maxmhz) {
cpufreq_set_clock(sc, cpufreq_nearest_oppt(sc, sc->cpu_maxmhz));
}
}
@ -401,9 +521,8 @@ static void
tempmon_goslow(struct imx6_anatop_softc *sc)
{
if (sc->cpu_curhz > sc->cpu_minhz) {
cpufreq_set_clock(sc, sc->cpu_minhz);
vdd_set(sc, sc->cpu_minmv);
if (sc->cpu_curmhz > sc->cpu_minmhz) {
cpufreq_set_clock(sc, cpufreq_nearest_oppt(sc, sc->cpu_minmhz));
}
}
@ -542,6 +661,10 @@ imx6_anatop_attach(device_t dev)
cpufreq_initialize(sc);
initialize_tempmon(sc);
if (bootverbose) {
device_printf(sc->dev, "CPU %uMHz @ %umV\n", sc->cpu_curmhz,
sc->cpu_curmv);
}
err = 0;
out:
@ -575,7 +698,7 @@ imx6_get_cpu_clock()
div = imx6_anatop_read_4(IMX6_ANALOG_CCM_PLL_ARM) &
IMX6_ANALOG_CCM_PLL_ARM_DIV_MASK;
return (cpufreq_hz_from_div(imx6_anatop_sc, div));
return (cpufreq_mhz_from_div(imx6_anatop_sc, div));
}
static device_method_t imx6_anatop_methods[] = {

View File

@ -730,9 +730,6 @@ imx_sdhci_attach(device_t dev)
sc->baseclk_hz = imx51_get_clock(IMX51CLK_PERCLK_ROOT);
}
sdhci_init_slot(dev, &sc->slot, 0);
callout_init(&sc->r1bfix_callout, true);
/*
* If the slot is flagged with the non-removable property, set our flag
* to always force the SDHCI_CARD_PRESENT bit on.
@ -752,6 +749,9 @@ imx_sdhci_attach(device_t dev)
sc->force_card_present = true;
}
callout_init(&sc->r1bfix_callout, true);
sdhci_init_slot(dev, &sc->slot, 0);
bus_generic_probe(dev);
bus_generic_attach(dev);

View File

@ -138,7 +138,6 @@ struct ckb_softc {
};
/* prototypes */
static void ckb_set_leds(struct ckb_softc *, uint8_t);
static int ckb_set_typematic(keyboard_t *, int);
static uint32_t ckb_read_char(keyboard_t *, int);
static void ckb_clear_state(keyboard_t *);

View File

@ -81,6 +81,8 @@
generic_timer {
compatible = "arm,armv7-timer";
clock-frequency = <24000000>;
interrupts = < 29 30 27 26 >;
interrupt-parent = <&GIC>;
};
pwm {