diff options
Diffstat (limited to 'chip/stm32')
-rw-r--r-- | chip/stm32/clock-stm32h7.c | 314 |
1 files changed, 244 insertions, 70 deletions
diff --git a/chip/stm32/clock-stm32h7.c b/chip/stm32/clock-stm32h7.c index bd7cf65d3f..4bf9909553 100644 --- a/chip/stm32/clock-stm32h7.c +++ b/chip/stm32/clock-stm32h7.c @@ -3,7 +3,18 @@ * found in the LICENSE file. */ -/* Clocks and power management settings */ +/* + * Clocks and power management settings + * + * Error Handling and Unimplemented Features: + * Since we are dealing with code critical to the runtime of the CPU, + * our strategy for unimplemented functionality is to ASSERT, but fallback + * to doing nothing if ASSERT is not enabled. This is not a perfect solution, + * but at least yields predictable behavior. + */ + + +#include <stdbool.h> #include "chipset.h" #include "clock.h" @@ -18,14 +29,51 @@ #include "uart.h" #include "util.h" +/* Check chip family and variant for compatibility */ +#ifndef CHIP_FAMILY_STM32H7 +#error Source clock-stm32h7.c does not support this chip family. +#endif +#ifndef CHIP_VARIANT_STM32H7X3 +#error Unsupported chip variant. +#endif + /* Console output macros */ #define CPUTS(outstr) cputs(CC_CLOCK, outstr) #define CPRINTF(format, args...) cprintf(CC_CLOCK, format, ## args) +enum clock_osc { + OSC_HSI = 0, /* High-speed internal oscillator */ + OSC_CSI, /* Multi-speed internal oscillator: NOT IMPLEMENTED */ + OSC_HSE, /* High-speed external oscillator: NOT IMPLEMENTED */ + OSC_PLL, /* PLL */ +}; + +enum voltage_scale { + VOLTAGE_SCALE0 = 0, + VOLTAGE_SCALE1, + VOLTAGE_SCALE2, + VOLTAGE_SCALE3, + VOLTAGE_SCALE_COUNT, +}; + +enum freq { + FREQ_1KHZ = 1000, + FREQ_32KHZ = 32 * FREQ_1KHZ, + FREQ_1MHZ = 1000000, + FREQ_2MHZ = 2 * FREQ_1MHZ, + FREQ_16MHZ = 16 * FREQ_1MHZ, + FREQ_64MHZ = 64 * FREQ_1MHZ, + FREQ_140MHZ = 140 * FREQ_1MHZ, + FREQ_200MHZ = 200 * FREQ_1MHZ, + FREQ_280MHZ = 280 * FREQ_1MHZ, + FREQ_400MHZ = 400 * FREQ_1MHZ, + FREQ_480MHZ = 480 * FREQ_1MHZ, +}; + /* High-speed oscillator default is 64 MHz */ -#define STM32_HSI_CLOCK 64000000 +#define STM32_HSI_CLOCK FREQ_64MHZ /* Low-speed oscillator is 32-Khz */ -#define STM32_LSI_CLOCK 32000 +#define STM32_LSI_CLOCK FREQ_32KHZ /* * LPTIM is a 16-bit counter clocked by LSI @@ -40,41 +88,13 @@ #define LPTIM_PRESCALER ((int)BIT(LPTIM_PRESCALER_LOG2)) #define LPTIM_PERIOD_US (SECOND / (STM32_LSI_CLOCK / LPTIM_PRESCALER)) -/* - * PLL1 configuration: - * CPU freq = VCO / DIVP = HSI / DIVM * DIVN / DIVP - * = 64 / 4 * 50 / 2 - * = 400 Mhz - * System clock = 400 Mhz - * HPRE = /2 => AHB/Timer clock = 200 Mhz - */ -#if !defined(PLL1_DIVM) && !defined(PLL1_DIVN) && !defined(PLL1_DIVP) -#define PLL1_DIVM 4 -#define PLL1_DIVN 50 -#define PLL1_DIVP 2 -#endif -#define PLL1_FREQ (STM32_HSI_CLOCK / PLL1_DIVM * PLL1_DIVN / PLL1_DIVP) - -/* Flash latency settings for AHB/ACLK at 64 Mhz and Vcore in VOS1 range */ -#define FLASH_ACLK_64MHZ (STM32_FLASH_ACR_WRHIGHFREQ_85MHZ | \ - (0 << STM32_FLASH_ACR_LATENCY_SHIFT)) -/* Flash latency settings for AHB/ACLK at 200 Mhz and Vcore in VOS1 range */ -#define FLASH_ACLK_200MHZ (STM32_FLASH_ACR_WRHIGHFREQ_285MHZ | \ - (2 << STM32_FLASH_ACR_LATENCY_SHIFT)) - -enum clock_osc { - OSC_HSI = 0, /* High-speed internal oscillator */ - OSC_CSI, /* Multi-speed internal oscillator: NOT IMPLEMENTED */ - OSC_HSE, /* High-speed external oscillator: NOT IMPLEMENTED */ - OSC_PLL, /* PLL */ -}; - -static int freq = STM32_HSI_CLOCK; +/* This is not the core frequency */ +static enum freq current_bus_freq = STM32_HSI_CLOCK; static int current_osc = OSC_HSI; int clock_get_freq(void) { - return freq; + return current_bus_freq; } int clock_get_timer_freq(void) @@ -95,14 +115,131 @@ void clock_wait_bus_cycles(enum bus_type bus, uint32_t cycles) } } -static void clock_flash_latency(uint32_t target_acr) +/* Flash latency values are dependent on peripheral speed and voltage scale */ +static void clock_flash_latency(enum freq axi_freq, enum voltage_scale vos) { + uint32_t target_acr; + + if (axi_freq == FREQ_64MHZ && vos == VOLTAGE_SCALE3) { + target_acr = STM32_FLASH_ACR_WRHIGHFREQ_85MHZ | + (0 << STM32_FLASH_ACR_LATENCY_SHIFT); + } else if (axi_freq == FREQ_200MHZ && vos == VOLTAGE_SCALE1) { + target_acr = STM32_FLASH_ACR_WRHIGHFREQ_285MHZ | + (2 << STM32_FLASH_ACR_LATENCY_SHIFT); + } else { + ASSERT(0); + return; + } + STM32_FLASH_ACR(0) = target_acr; while (STM32_FLASH_ACR(0) != target_acr) ; } -static void clock_enable_osc(enum clock_osc osc) +/** + * @brief Configure PLL1 to output the specified frequency. + * + * The input frequency to PLL1 is assumed to be the HSI, which + * is 64MHz. + * + * @param output_freq The target output frequency. + */ +static void clock_pll1_configure(enum freq output_freq) { + uint32_t divm = 4; // Input prescaler (16MHz max for PLL -- 64/4 ==> 16) + uint32_t divn; // Pll multiplier + uint32_t divp; // Output 1 prescaler + + switch (output_freq) + { + case FREQ_400MHZ: + /* + * PLL1 configuration: + * CPU freq = VCO / DIVP = HSI / DIVM * DIVN / DIVP + * = 64MHz/4 * 50 / 2 + * = 16MHz * 50 / 2 + * = 400 Mhz + */ + divn = 50; + divp = 2; + break; + case FREQ_200MHZ: + /* + * PLL1 configuration: + * CPU freq = VCO / DIVP = HSI / DIVM * DIVN / DIVP + * = 64 / 4 * 25 / 2 + * = 16MHz * 25 / 2 + * = 200 Mhz + */ + divn = 25; + divp = 2; + break; + case FREQ_280MHZ: + divn = 35; + divp = 2; + break; + case FREQ_480MHZ: + divn = 60; + divp = 2; + break; + default: + ASSERT(0); + return; + } + + /* + * Using VCO wide-range setting, STM32_RCC_PLLCFG_PLL1VCOSEL_WIDE, + * requires input frequency to be between 2MHz and 16MHz. + */ + ASSERT(FREQ_2MHZ <= (STM32_HSI_CLOCK/divm)); + ASSERT((STM32_HSI_CLOCK/divm) <= FREQ_16MHZ); + + /* + * Ensure that we actually reach the target frequency. + */ + ASSERT((STM32_HSI_CLOCK / divm * divn / divp) == output_freq); + + /* Configure PLL1 using 64 Mhz HSI as input */ + STM32_RCC_PLLCKSELR = STM32_RCC_PLLCKSEL_PLLSRC_HSI + | STM32_RCC_PLLCKSEL_DIVM1(divm); + /* in integer mode, wide range VCO with 16Mhz input, use divP */ + STM32_RCC_PLLCFGR = STM32_RCC_PLLCFG_PLL1VCOSEL_WIDE + | STM32_RCC_PLLCFG_PLL1RGE_8M_16M + | STM32_RCC_PLLCFG_DIVP1EN; + STM32_RCC_PLL1DIVR = STM32_RCC_PLLDIV_DIVP(divp) + | STM32_RCC_PLLDIV_DIVN(divn); +} + +/** + * Configure peripheral domain prescalers to allow a given sysclk frequency. + * + * @param sysclk The input system clock, after the system clock prescaler. + * @return The bus clock speed selected and configured + */ +static enum freq clock_peripheral_configure(enum freq sysclk) { + switch (sysclk) + { + case FREQ_64MHZ: + /* Restore /1 HPRE (AHB prescaler) */ + /* Disable downstream prescalers */ + STM32_RCC_D1CFGR = STM32_RCC_D1CFGR_HPRE_DIV1 + | STM32_RCC_D1CFGR_D1PPRE_DIV1 + | STM32_RCC_D1CFGR_D1CPRE_DIV1; + /* TODO(b/149512910): Adjust more peripheral prescalers */ + return FREQ_64MHZ; + case FREQ_400MHZ: + /* Put /2 on HPRE (AHB prescaler) to keep at the 200MHz max */ + STM32_RCC_D1CFGR = STM32_RCC_D1CFGR_HPRE_DIV2 + | STM32_RCC_D1CFGR_D1PPRE_DIV1 + | STM32_RCC_D1CFGR_D1CPRE_DIV1; + /* TODO(b/149512910): Adjust more peripheral prescalers */ + return FREQ_200MHZ; + default: + ASSERT(0); + return 0; + } +} + +static void clock_enable_osc(enum clock_osc osc, bool enabled) { uint32_t ready; uint32_t on; @@ -117,11 +254,20 @@ static void clock_enable_osc(enum clock_osc osc) on = STM32_RCC_CR_PLL1ON; break; default: + ASSERT(0); return; } + /* Turn off the oscillator, but don't wait for shutdown */ + if (!enabled) { + STM32_RCC_CR &= ~on; + return; + } + + /* Turn on the oscillator if not already on */ if (!(STM32_RCC_CR & ready)) { STM32_RCC_CR |= on; + /* Wait until ready */ while (!(STM32_RCC_CR & ready)) ; } @@ -150,62 +296,90 @@ static void clock_switch_osc(enum clock_osc osc) ; } -static void switch_voltage_scale(uint32_t vos) +static void switch_voltage_scale(enum voltage_scale vos) { - STM32_PWR_D3CR &= ~STM32_PWR_D3CR_VOSMASK; - STM32_PWR_D3CR |= vos; - while (!(STM32_PWR_D3CR & STM32_PWR_D3CR_VOSRDY)) + volatile uint32_t *const vos_reg = &STM32_PWR_D3CR; + const uint32_t vos_ready = STM32_PWR_D3CR_VOSRDY; + const uint32_t vos_mask = STM32_PWR_D3CR_VOSMASK; + const uint32_t vos_values[] = { + /* See note below about VOS0. */ + STM32_PWR_D3CR_VOS1, + STM32_PWR_D3CR_VOS1, + STM32_PWR_D3CR_VOS2, + STM32_PWR_D3CR_VOS3, + }; + BUILD_ASSERT(ARRAY_SIZE(vos_values) == VOLTAGE_SCALE_COUNT); + + /* + * Real VOS0 on the H743 requires entering VOS1 and setting an extra + * SYS boost register. We currently do not implement this functionality. + */ + if (vos == VOLTAGE_SCALE0) { + ASSERT(0); + return; + } + + *vos_reg &= ~vos_mask; + *vos_reg |= vos_values[vos]; + while (!(*vos_reg & vos_ready)) ; } static void clock_set_osc(enum clock_osc osc) { + enum freq target_sysclk_freq = FREQ_64MHZ; + enum voltage_scale target_voltage_scale = VOLTAGE_SCALE3; + if (osc == current_osc) return; + switch (osc) { + case OSC_HSI: + case OSC_PLL: + break; + default: + ASSERT(0); + return; + } + hook_notify(HOOK_PRE_FREQ_CHANGE); switch (osc) { + default: case OSC_HSI: /* Switch to HSI */ clock_switch_osc(osc); - freq = STM32_HSI_CLOCK; - /* Restore /1 HPRE (AHB prescaler) */ - STM32_RCC_D1CFGR = STM32_RCC_D1CFGR_HPRE_DIV1 - | STM32_RCC_D1CFGR_D1PPRE_DIV1 - | STM32_RCC_D1CFGR_D1CPRE_DIV1; + current_bus_freq = clock_peripheral_configure(target_sysclk_freq); /* Use more optimized flash latency settings for 64-MHz ACLK */ - clock_flash_latency(FLASH_ACLK_64MHZ); + clock_flash_latency(current_bus_freq, target_voltage_scale); /* Turn off the PLL1 to save power */ - STM32_RCC_CR &= ~STM32_RCC_CR_PLL1ON; - switch_voltage_scale(STM32_PWR_D3CR_VOS3); + clock_enable_osc(OSC_PLL, false); + switch_voltage_scale(target_voltage_scale); break; case OSC_PLL: - switch_voltage_scale(STM32_PWR_D3CR_VOS1); - /* Configure PLL1 using 64 Mhz HSI as input */ - STM32_RCC_PLLCKSELR = STM32_RCC_PLLCKSEL_PLLSRC_HSI | - STM32_RCC_PLLCKSEL_DIVM1(PLL1_DIVM); - /* in integer mode, wide range VCO with 16Mhz input, use divP */ - STM32_RCC_PLLCFGR = STM32_RCC_PLLCFG_PLL1VCOSEL_WIDE - | STM32_RCC_PLLCFG_PLL1RGE_8M_16M - | STM32_RCC_PLLCFG_DIVP1EN; - STM32_RCC_PLL1DIVR = STM32_RCC_PLLDIV_DIVP(PLL1_DIVP) - | STM32_RCC_PLLDIV_DIVN(PLL1_DIVN); - /* turn on PLL1 and wait that it's ready */ - clock_enable_osc(OSC_PLL); - /* Put /2 on HPRE (AHB prescaler) to keep at the 200Mhz max */ - STM32_RCC_D1CFGR = STM32_RCC_D1CFGR_HPRE_DIV2 - | STM32_RCC_D1CFGR_D1PPRE_DIV1 - | STM32_RCC_D1CFGR_D1CPRE_DIV1; - freq = PLL1_FREQ / 2; + /* + * PLL1 configuration: + * CPU freq = VCO / DIVP = HSI / DIVM * DIVN / DIVP + * = 64 / 4 * 50 / 2 + * = 400 Mhz + * System clock = 400 Mhz + * HPRE = /2 => AHB/Timer clock = 200 Mhz + */ + target_sysclk_freq = FREQ_400MHZ; + target_voltage_scale = VOLTAGE_SCALE1; + + switch_voltage_scale(target_voltage_scale); + clock_pll1_configure(target_sysclk_freq); + /* turn on PLL1 and wait until it's ready */ + clock_enable_osc(OSC_PLL, true); + current_bus_freq = clock_peripheral_configure(target_sysclk_freq); /* Increase flash latency before transition the clock */ - clock_flash_latency(FLASH_ACLK_200MHZ); + clock_flash_latency(current_bus_freq, target_voltage_scale); + /* Switch to PLL */ clock_switch_osc(OSC_PLL); break; - default: - break; } current_osc = osc; @@ -422,7 +596,7 @@ void clock_init(void) | STM32_RCC_D2CCIP1R_SPI45SEL_HSI; /* Use more optimized flash latency settings for ACLK = HSI = 64 Mhz */ - clock_flash_latency(FLASH_ACLK_64MHZ); + clock_flash_latency(FREQ_64MHZ, VOLTAGE_SCALE3); /* Ensure that LSI is ON to clock LPTIM1 and IWDG */ STM32_RCC_CSR |= STM32_RCC_CSR_LSION; @@ -444,7 +618,7 @@ static int command_clock(int argc, char **argv) else return EC_ERROR_PARAM1; } - ccprintf("Clock frequency is now %d Hz\n", freq); + ccprintf("Clock frequency is now %d Hz\n", clock_get_freq()); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(clock, command_clock, |