diff --git a/src/cpu/allwinner/a10/clock.c b/src/cpu/allwinner/a10/clock.c index 0401a72be4..945dfd767b 100644 --- a/src/cpu/allwinner/a10/clock.c +++ b/src/cpu/allwinner/a10/clock.c @@ -8,6 +8,10 @@ #include "clock.h" #include +#include +#include +#include +#include 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__ */ diff --git a/src/cpu/allwinner/a10/clock.h b/src/cpu/allwinner/a10/clock.h index 41400abb7e..0be736cf34 100644 --- a/src/cpu/allwinner/a10/clock.h +++ b/src/cpu/allwinner/a10/clock.h @@ -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 */