diff options
Diffstat (limited to 'chip/stm32/clock-stm32h7.c')
-rw-r--r-- | chip/stm32/clock-stm32h7.c | 620 |
1 files changed, 0 insertions, 620 deletions
diff --git a/chip/stm32/clock-stm32h7.c b/chip/stm32/clock-stm32h7.c deleted file mode 100644 index ba233dbd76..0000000000 --- a/chip/stm32/clock-stm32h7.c +++ /dev/null @@ -1,620 +0,0 @@ -/* Copyright 2017 The Chromium OS Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -/* - * 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" -#include "common.h" -#include "console.h" -#include "cpu.h" -#include "hooks.h" -#include "hwtimer.h" -#include "registers.h" -#include "system.h" -#include "task.h" -#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 FREQ_64MHZ -/* Low-speed oscillator is 32-Khz */ -#define STM32_LSI_CLOCK FREQ_32KHZ - -/* - * LPTIM is a 16-bit counter clocked by LSI - * with /4 prescaler (2^2): period 125 us, full range ~8s - */ -#define LPTIM_PRESCALER_LOG2 2 -/* - * LPTIM_PRESCALER and LPTIM_PERIOD_US have to be signed, because they - * determine the signedness of the comparison with |next_delay| in - * __idle(), where |next_delay| is negative if no next event. - */ -#define LPTIM_PRESCALER ((int)BIT(LPTIM_PRESCALER_LOG2)) -#define LPTIM_PERIOD_US (SECOND / (STM32_LSI_CLOCK / LPTIM_PRESCALER)) - -/* 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 current_bus_freq; -} - -int clock_get_timer_freq(void) -{ - return clock_get_freq(); -} - -void clock_wait_bus_cycles(enum bus_type bus, uint32_t cycles) -{ - volatile uint32_t unused __attribute__((unused)); - - if (bus == BUS_AHB) { - while (cycles--) - unused = STM32_GPIO_IDR(GPIO_A); - } else { /* APB */ - while (cycles--) - unused = STM32_USART_BRR(STM32_USART1_BASE); - } -} - -/* 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) - ; -} - -/** - * @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; - - switch (osc) { - case OSC_HSI: - ready = STM32_RCC_CR_HSIRDY; - on = STM32_RCC_CR_HSION; - break; - case OSC_PLL: - ready = STM32_RCC_CR_PLL1RDY; - 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 */ - wait_for_ready(&STM32_RCC_CR, on, ready); -} - -static void clock_switch_osc(enum clock_osc osc) -{ - uint32_t sw; - uint32_t sws; - - switch (osc) { - case OSC_HSI: - sw = STM32_RCC_CFGR_SW_HSI; - sws = STM32_RCC_CFGR_SWS_HSI; - break; - case OSC_PLL: - sw = STM32_RCC_CFGR_SW_PLL1; - sws = STM32_RCC_CFGR_SWS_PLL1; - break; - default: - return; - } - - STM32_RCC_CFGR = sw; - while ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) != sws) - ; -} - -static void switch_voltage_scale(enum voltage_scale vos) -{ - 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); - current_bus_freq = clock_peripheral_configure(target_sysclk_freq); - /* Use more optimized flash latency settings for 64-MHz ACLK */ - clock_flash_latency(current_bus_freq, target_voltage_scale); - /* Turn off the PLL1 to save power */ - clock_enable_osc(OSC_PLL, false); - switch_voltage_scale(target_voltage_scale); - break; - - case OSC_PLL: - /* - * 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(current_bus_freq, target_voltage_scale); - - /* Switch to PLL */ - clock_switch_osc(OSC_PLL); - break; - } - - current_osc = osc; - hook_notify(HOOK_FREQ_CHANGE); -} - -void clock_enable_module(enum module_id module, int enable) -{ - /* Assume we have a single task using MODULE_FAST_CPU */ - if (module == MODULE_FAST_CPU) { - /* the PLL would be off in low power mode, disable it */ - if (enable) - disable_sleep(SLEEP_MASK_PLL); - else - enable_sleep(SLEEP_MASK_PLL); - clock_set_osc(enable ? OSC_PLL : OSC_HSI); - } -} - -#ifdef CONFIG_LOW_POWER_IDLE -/* Low power idle statistics */ -static int idle_sleep_cnt; -static int idle_dsleep_cnt; -static uint64_t idle_dsleep_time_us; -static int dsleep_recovery_margin_us = 1000000; - -/* STOP_MODE_LATENCY: delay to wake up from STOP mode with flash off in SVOS5 */ -#define STOP_MODE_LATENCY 50 /* us */ - -static void low_power_init(void) -{ - /* Clock LPTIM1 on the 32-kHz LSI for STOP mode time keeping */ - STM32_RCC_D2CCIP2R = (STM32_RCC_D2CCIP2R & - ~STM32_RCC_D2CCIP2_LPTIM1SEL_MASK) - | STM32_RCC_D2CCIP2_LPTIM1SEL_LSI; - - /* configure LPTIM1 as our 1-Khz low power timer in STOP mode */ - STM32_RCC_APB1LENR |= STM32_RCC_PB1_LPTIM1; - STM32_LPTIM_CR(1) = 0; /* ensure it's disabled before configuring */ - STM32_LPTIM_CFGR(1) = LPTIM_PRESCALER_LOG2 << 9; /* Prescaler /4 */ - STM32_LPTIM_IER(1) = STM32_LPTIM_INT_CMPM; /* Compare int for wake-up */ - /* Start the 16-bit free-running counter */ - STM32_LPTIM_CR(1) = STM32_LPTIM_CR_ENABLE; - STM32_LPTIM_ARR(1) = 0xFFFF; - STM32_LPTIM_CR(1) = STM32_LPTIM_CR_ENABLE | STM32_LPTIM_CR_CNTSTRT; - task_enable_irq(STM32_IRQ_LPTIM1); - - /* Wake-up interrupts from EXTI for USART and LPTIM */ - STM32_EXTI_CPUIMR1 |= BIT(26); /* [26] wkup26: USART1 wake-up */ - STM32_EXTI_CPUIMR2 |= BIT(15); /* [15] wkup47: LPTIM1 wake-up */ - - /* optimize power vs latency in STOP mode */ - STM32_PWR_CR = (STM32_PWR_CR & ~STM32_PWR_CR_SVOS_MASK) - | STM32_PWR_CR_SVOS5 - | STM32_PWR_CR_FLPS; -} - -void clock_refresh_console_in_use(void) -{ -} - -void lptim_interrupt(void) -{ - STM32_LPTIM_ICR(1) = STM32_LPTIM_INT_CMPM; -} -DECLARE_IRQ(STM32_IRQ_LPTIM1, lptim_interrupt, 2); - -static uint16_t lptim_read(void) -{ - uint16_t cnt; - - do { - cnt = STM32_LPTIM_CNT(1); - } while (cnt != STM32_LPTIM_CNT(1)); - - return cnt; -} - -static void set_lptim_event(int delay_us, uint16_t *lptim_cnt) -{ - uint16_t cnt = lptim_read(); - - STM32_LPTIM_CMP(1) = cnt + MIN(delay_us / LPTIM_PERIOD_US - 1, 0xffff); - /* clean-up previous event */ - STM32_LPTIM_ICR(1) = STM32_LPTIM_INT_CMPM; - *lptim_cnt = cnt; -} - -void __idle(void) -{ - timestamp_t t0; - int next_delay; - int margin_us, t_diff; - uint16_t lptim0; - - while (1) { - asm volatile("cpsid i"); - - t0 = get_time(); - next_delay = __hw_clock_event_get() - t0.le.lo; - - if (DEEP_SLEEP_ALLOWED && - next_delay > LPTIM_PERIOD_US + STOP_MODE_LATENCY) { - /* deep-sleep in STOP mode */ - idle_dsleep_cnt++; - - uart_enable_wakeup(1); - - /* set deep sleep bit */ - CPU_SCB_SYSCTRL |= 0x4; - - set_lptim_event(next_delay - STOP_MODE_LATENCY, - &lptim0); - - /* ensure outstanding memory transactions complete */ - asm volatile("dsb"); - - asm("wfi"); - - CPU_SCB_SYSCTRL &= ~0x4; - - /* fast forward timer according to low power counter */ - if (STM32_PWR_CPUCR & STM32_PWR_CPUCR_STOPF) { - uint16_t lptim_dt = lptim_read() - lptim0; - - t_diff = (int)lptim_dt * LPTIM_PERIOD_US; - t0.val = t0.val + t_diff; - force_time(t0); - /* clear STOPF flag */ - STM32_PWR_CPUCR |= STM32_PWR_CPUCR_CSSF; - } else { /* STOP entry was aborted, no fixup */ - t_diff = 0; - } - - uart_enable_wakeup(0); - - /* Record time spent in deep sleep. */ - idle_dsleep_time_us += t_diff; - - /* Calculate how close we were to missing deadline */ - margin_us = next_delay - t_diff; - if (margin_us < 0) - /* Use CPUTS to save stack space */ - CPUTS("Overslept!\n"); - - /* Record the closest to missing a deadline. */ - if (margin_us < dsleep_recovery_margin_us) - dsleep_recovery_margin_us = margin_us; - } else { - idle_sleep_cnt++; - - /* normal idle : only CPU clock stopped */ - asm("wfi"); - } - asm volatile("cpsie i"); - } -} - -#ifdef CONFIG_CMD_IDLE_STATS -/** - * Print low power idle statistics - */ -static int command_idle_stats(int argc, char **argv) -{ - timestamp_t ts = get_time(); - - ccprintf("Num idle calls that sleep: %d\n", idle_sleep_cnt); - ccprintf("Num idle calls that deep-sleep: %d\n", idle_dsleep_cnt); - ccprintf("Time spent in deep-sleep: %.6llds\n", - idle_dsleep_time_us); - ccprintf("Total time on: %.6llds\n", ts.val); - ccprintf("Deep-sleep closest to wake deadline: %dus\n", - dsleep_recovery_margin_us); - - return EC_SUCCESS; -} -DECLARE_CONSOLE_COMMAND(idlestats, command_idle_stats, - "", - "Print last idle stats"); -#endif /* CONFIG_CMD_IDLE_STATS */ -#endif /* CONFIG_LOW_POWER_IDLE */ - -void clock_init(void) -{ - /* - * STM32H743 Errata 2.2.15: - * 'Reading from AXI SRAM might lead to data read corruption' - * - * limit concurrent read access on AXI master to 1. - */ - STM32_AXI_TARG_FN_MOD(7) |= READ_ISS_OVERRIDE; - - /* - * Lock (SCUEN=0) power configuration with the LDO enabled. - * - * The STM32H7 Reference Manual says: - * The lower byte of this register is written once after POR and shall - * be written before changing VOS level or ck_sys clock frequency. - * - * The interesting side-effect of this that while the LDO is enabled by - * default at startup, if we enter STOP mode without locking it the MCU - * seems to freeze forever. - */ - STM32_PWR_CR3 = STM32_PWR_CR3_LDOEN; - /* - * Ensure the SPI is always clocked at the same frequency - * by putting it on the fixed 64-Mhz HSI clock. - * per_ck is clocked directly by the HSI (as per the default settings). - */ - STM32_RCC_D2CCIP1R = (STM32_RCC_D2CCIP1R & - ~(STM32_RCC_D2CCIP1R_SPI123SEL_MASK | - STM32_RCC_D2CCIP1R_SPI45SEL_MASK)) - | STM32_RCC_D2CCIP1R_SPI123SEL_PERCK - | STM32_RCC_D2CCIP1R_SPI45SEL_HSI; - - /* Use more optimized flash latency settings for ACLK = HSI = 64 Mhz */ - clock_flash_latency(FREQ_64MHZ, VOLTAGE_SCALE3); - - /* Ensure that LSI is ON to clock LPTIM1 and IWDG */ - STM32_RCC_CSR |= STM32_RCC_CSR_LSION; - while (!(STM32_RCC_CSR & STM32_RCC_CSR_LSIRDY)) - ; - -#ifdef CONFIG_LOW_POWER_IDLE - low_power_init(); -#endif -} - -static int command_clock(int argc, char **argv) -{ - if (argc >= 2) { - if (!strcasecmp(argv[1], "hsi")) - clock_set_osc(OSC_HSI); - else if (!strcasecmp(argv[1], "pll")) - clock_set_osc(OSC_PLL); - else - return EC_ERROR_PARAM1; - } - ccprintf("Clock frequency is now %d Hz\n", clock_get_freq()); - return EC_SUCCESS; -} -DECLARE_CONSOLE_COMMAND(clock, command_clock, - "hsi | pll", "Set clock frequency"); |