cpu/allwinner/a10: Add helper to configure CPU clock
Change-Id: I5a3bb3220aeefdd6822a7dbecf210ff77095dad6 Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com> Reviewed-on: http://review.coreboot.org/4685 Tested-by: build bot (Jenkins) Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
This commit is contained in:
parent
594ef81326
commit
601b5b5302
|
@ -8,6 +8,10 @@
|
|||
#include "clock.h"
|
||||
|
||||
#include <arch/io.h>
|
||||
#include <console/console.h>
|
||||
#include <delay.h>
|
||||
#include <lib.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static struct a10_ccm *const ccm = (void *)A1X_CCM_BASE;
|
||||
|
||||
|
@ -121,3 +125,154 @@ void a1x_gate_dram_clock_output(void)
|
|||
{
|
||||
clrbits_le32(&ccm->dram_clk_cfg, DRAM_CTRL_DCLK_OUT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Linker doesn't garbage collect and the function below adds about half
|
||||
* kilobyte to the bootblock, and log2_ceil is not available in the bootblock.
|
||||
*/
|
||||
#ifndef __BOOT_BLOCK__
|
||||
|
||||
#define PLL1_CFG(N, K, M, P_EXP) \
|
||||
((1 << 31 | 0 << 30 | 8 << 26 | 0 << 25 | 16 << 20 | 2 << 13) | \
|
||||
(P_EXP) << 16 | (N) << 8 | \
|
||||
(K - 1) << 4 | 0 << 3 | 0 << 2 | (M -1) << 0)
|
||||
|
||||
static const struct {
|
||||
u32 pll1_cfg;
|
||||
u16 freq_mhz;
|
||||
} pll1_table[] = {
|
||||
/* PLL1 output = (24MHz * N * K) / (M * P) */
|
||||
{ PLL1_CFG(16, 1, 1, 0), 384 },
|
||||
{ PLL1_CFG(16, 2, 1, 0), 768 },
|
||||
{ PLL1_CFG(20, 2, 1, 0), 960 },
|
||||
{ PLL1_CFG(21, 2, 1, 0), 1008 },
|
||||
{ PLL1_CFG(22, 2, 1, 0), 1056 },
|
||||
{ PLL1_CFG(23, 2, 1, 0), 1104 },
|
||||
{ PLL1_CFG(24, 2, 1, 0), 1152 },
|
||||
{ PLL1_CFG(25, 2, 1, 0), 1200 },
|
||||
{ PLL1_CFG(26, 2, 1, 0), 1248 },
|
||||
{ PLL1_CFG(27, 2, 1, 0), 1296 },
|
||||
{ PLL1_CFG(28, 2, 1, 0), 1344 },
|
||||
{ PLL1_CFG(29, 2, 1, 0), 1392 },
|
||||
{ PLL1_CFG(30, 2, 1, 0), 1440 },
|
||||
{ PLL1_CFG(31, 2, 1, 0), 1488 },
|
||||
{ PLL1_CFG(20, 4, 1, 0), 1944 },
|
||||
};
|
||||
|
||||
static inline u32 div_ceil(u32 a, u32 b)
|
||||
{
|
||||
return (a + b - 1) / b;
|
||||
}
|
||||
|
||||
static void cpu_clk_src_switch(u32 clksel_bits)
|
||||
{
|
||||
u32 reg32;
|
||||
|
||||
reg32 = read32(&ccm->cpu_ahb_apb0_cfg);
|
||||
reg32 &= ~CPU_CLK_SRC_MASK;
|
||||
reg32 |= clksel_bits & CPU_CLK_SRC_MASK;
|
||||
write32(reg32, &ccm->cpu_ahb_apb0_cfg);
|
||||
}
|
||||
|
||||
static void change_sys_divisors(u8 axi, u8 ahb_exp, u8 apb0_exp)
|
||||
{
|
||||
u32 reg32;
|
||||
|
||||
reg32 = read32(&ccm->cpu_ahb_apb0_cfg);
|
||||
/* Not a typo: We want to keep only the CLK_SRC bits */
|
||||
reg32 &= CPU_CLK_SRC_MASK;
|
||||
reg32 |= ((axi - 1) << 0) & AXI_DIV_MASK;
|
||||
reg32 |= (ahb_exp << 4) & AHB_DIV_MASK;
|
||||
reg32 |= (apb0_exp << 8) & APB0_DIV_MASK;
|
||||
write32(reg32, &ccm->cpu_ahb_apb0_cfg);
|
||||
}
|
||||
|
||||
static void spin_delay(u32 loops)
|
||||
{
|
||||
volatile u32 x = loops;
|
||||
while (x--);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Configure the CPU clock and PLL1
|
||||
*
|
||||
* To run at full speed, the CPU uses PLL1 as the clock source. AXI, AHB, and
|
||||
* APB0 are derived from the CPU clock, and need to be kept within certain
|
||||
* limits. This function configures PLL1 as close as possible to the desired
|
||||
* frequency, based on a set of known working configurations for PLL1. It then
|
||||
* calculates and applies the appropriate divisors for the AXI/AHB/APB0 clocks,
|
||||
* before finally switching the CPU to run from the new clock.
|
||||
* No further configuration of the CPU clock or divisors is needed. after
|
||||
* calling this function.
|
||||
*
|
||||
* @param[in] cpu_clk_mhz Desired CPU clock, in MHz
|
||||
*/
|
||||
void a1x_set_cpu_clock(u16 cpu_clk_mhz)
|
||||
{
|
||||
int i = 0;
|
||||
u8 axi, ahb, ahb_exp, apb0, apb0_exp;
|
||||
u32 actual_mhz;
|
||||
|
||||
/*
|
||||
* Rated clock for PLL1 is 2000 MHz, but there is no combination of
|
||||
* parameters that yields that exact frequency. 1944 MHz is the highest.
|
||||
*/
|
||||
if (cpu_clk_mhz > 1944) {
|
||||
printk(BIOS_CRIT, "BUG! maximum PLL1 clock is 1944 MHz,"
|
||||
"but asked to clock CPU at %d MHz\n",
|
||||
cpu_clk_mhz);
|
||||
cpu_clk_mhz = 1944;
|
||||
}
|
||||
/* Find target frequency */
|
||||
while (pll1_table[i].freq_mhz < cpu_clk_mhz)
|
||||
i++;
|
||||
|
||||
actual_mhz = pll1_table[i].freq_mhz;
|
||||
|
||||
if (cpu_clk_mhz != actual_mhz) {
|
||||
printk(BIOS_WARNING, "Parameters for %d MHz not available, "
|
||||
"setting CPU clock at %d MHz\n",
|
||||
cpu_clk_mhz, actual_mhz);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate system clock divisors:
|
||||
* The minimum clock divisor for APB0 is 2, which guarantees that AHB0
|
||||
* will always be in spec, as long as AHB is in spec, although the max
|
||||
* AHB0 clock we can get is 125 MHz
|
||||
*/
|
||||
axi = div_ceil(actual_mhz, 450); /* Max 450 MHz */
|
||||
ahb = div_ceil(actual_mhz/axi, 250); /* Max 250 MHz */
|
||||
apb0 = 2; /* Max 150 MHz */
|
||||
|
||||
ahb_exp = log2_ceil(ahb);
|
||||
ahb = 1 << ahb_exp;
|
||||
apb0_exp = 1;
|
||||
|
||||
printk(BIOS_INFO, "CPU: %d MHz, AXI %d Mhz, AHB: %d MHz APB0: %d MHz\n",
|
||||
actual_mhz,
|
||||
actual_mhz / axi,
|
||||
actual_mhz / (axi * ahb),
|
||||
actual_mhz / (axi * ahb * apb0));
|
||||
|
||||
/* Keep the CPU off PLL1 while we change PLL parameters */
|
||||
cpu_clk_src_switch(CPU_CLK_SRC_OSC24M);
|
||||
/*
|
||||
* We can't use udelay() here. udelay() relies on timer 0, but timers
|
||||
* have the habit of not ticking when the CPU is clocked from the main
|
||||
* oscillator.
|
||||
*/
|
||||
spin_delay(8);
|
||||
|
||||
change_sys_divisors(axi, ahb_exp, apb0_exp);
|
||||
|
||||
/* Configure PLL1 at the desired frequency */
|
||||
write32(pll1_table[i].pll1_cfg, &ccm->pll1_cfg);
|
||||
spin_delay(8);
|
||||
|
||||
cpu_clk_src_switch(CPU_CLK_SRC_PLL1);
|
||||
/* Here, we're running from PLL, so timers will tick */
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
#endif /* __BOOT_BLOCK__ */
|
||||
|
|
|
@ -256,4 +256,7 @@ void a1x_pll5_enable_dram_clock_output(void);
|
|||
void a1x_ungate_dram_clock_output(void);
|
||||
void a1x_gate_dram_clock_output(void);
|
||||
|
||||
/* Not available in bootblock */
|
||||
void a1x_set_cpu_clock(u16 cpu_clk_mhz);
|
||||
|
||||
#endif /* CPU_ALLWINNER_A10_CLOCK_H */
|
||||
|
|
Loading…
Reference in New Issue