| /* 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 */ |
| |
| #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" |
| |
| /* Console output macros */ |
| #define CPUTS(outstr) cputs(CC_CLOCK, outstr) |
| #define CPRINTF(format, args...) cprintf(CC_CLOCK, format, ## args) |
| |
| /* High-speed oscillator default is 64 MHz */ |
| #define STM32_HSI_CLOCK 64000000 |
| /* Low-speed oscillator is 32-Khz */ |
| #define STM32_LSI_CLOCK 32000 |
| |
| /* |
| * 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 |
| #define LPTIM_PRESCALER (1 << 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; |
| static int current_osc = OSC_HSI; |
| |
| int clock_get_freq(void) |
| { |
| return 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 dummy __attribute__((unused)); |
| |
| if (bus == BUS_AHB) { |
| while (cycles--) |
| dummy = STM32_GPIO_IDR(GPIO_A); |
| } else { /* APB */ |
| while (cycles--) |
| dummy = STM32_USART_BRR(STM32_USART1_BASE); |
| } |
| } |
| |
| static void clock_flash_latency(uint32_t target_acr) |
| { |
| STM32_FLASH_ACR(0) = target_acr; |
| while (STM32_FLASH_ACR(0) != target_acr) |
| ; |
| } |
| |
| static void clock_enable_osc(enum clock_osc osc) |
| { |
| 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: |
| return; |
| } |
| |
| if (!(STM32_RCC_CR & ready)) { |
| STM32_RCC_CR |= on; |
| while (!(STM32_RCC_CR & 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 clock_set_osc(enum clock_osc osc) |
| { |
| if (osc == current_osc) |
| return; |
| |
| hook_notify(HOOK_PRE_FREQ_CHANGE); |
| |
| switch (osc) { |
| 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; |
| /* Use more optimized flash latency settings for 64-MHz ACLK */ |
| clock_flash_latency(FLASH_ACLK_64MHZ); |
| /* Turn off the PLL1 to save power */ |
| STM32_RCC_CR &= ~STM32_RCC_CR_PLL1ON; |
| break; |
| |
| case OSC_PLL: |
| /* 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; |
| /* Increase flash latency before transition the clock */ |
| clock_flash_latency(FLASH_ACLK_200MHZ); |
| /* Switch to PLL */ |
| clock_switch_osc(OSC_PLL); |
| break; |
| default: |
| 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 |= 1 << 26; /* [26] wkup26: USART1 wake-up */ |
| STM32_EXTI_CPUIMR2 |= 1 << 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: %.6lds\n", |
| idle_dsleep_time_us); |
| ccprintf("Total time on: %.6lds\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(FLASH_ACLK_64MHZ); |
| |
| /* 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", freq); |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(clock, command_clock, |
| "hsi | pll", "Set clock frequency"); |