| /* Copyright 2016 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 "clock-f.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 "timer.h" |
| #include "util.h" |
| |
| /* Console output macros */ |
| #define CPUTS(outstr) cputs(CC_CLOCK, outstr) |
| #define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args) |
| |
| enum clock_osc { |
| OSC_HSI = 0, /* High-speed internal oscillator */ |
| OSC_HSE, /* High-speed external oscillator */ |
| OSC_PLL, /* PLL */ |
| }; |
| |
| /* |
| * NOTE: Sweetberry requires MCO2 <- HSE @ 24MHz |
| * MCO outputs are selected here but are not changeable later. |
| * A CONFIG may be needed if other boards have different MCO |
| * requirements. |
| */ |
| #define RCC_CFGR_MCO_CONFIG ((2 << 30) | /* MCO2 <- HSE */ \ |
| (0 << 27) | /* MCO2 div / 4 */ \ |
| (6 << 24) | /* MCO1 div / 4 */ \ |
| (3 << 21)) /* MCO1 <- PLL */ |
| |
| #ifdef CONFIG_STM32_CLOCK_HSE_HZ |
| /* RTC clock must 1 Mhz when derived from HSE */ |
| #define RTC_DIV DIV_ROUND_NEAREST(CONFIG_STM32_CLOCK_HSE_HZ, STM32F4_RTC_REQ) |
| #else /* !CONFIG_STM32_CLOCK_HSE_HZ */ |
| /* RTC clock not derived from HSE, turn it off */ |
| #define RTC_DIV 0 |
| #endif /* CONFIG_STM32_CLOCK_HSE_HZ */ |
| |
| |
| /* Bus clocks dividers depending on the configuration */ |
| /* |
| * max speed configuration with the PLL ON |
| * as defined in the registers file. |
| * For STM32F446: max 45 MHz |
| * For STM32F412: max AHB 100 MHz / APB2 100 Mhz / APB1 50 Mhz |
| */ |
| #define RCC_CFGR_DIVIDERS_WITH_PLL (RCC_CFGR_MCO_CONFIG | \ |
| CFGR_RTCPRE(RTC_DIV) | \ |
| CFGR_PPRE2(STM32F4_APB2_PRE) | \ |
| CFGR_PPRE1(STM32F4_APB1_PRE) | \ |
| CFGR_HPRE(STM32F4_AHB_PRE)) |
| /* |
| * lower power configuration without the PLL |
| * the frequency will be low (8-24Mhz), we don't want dividers to the |
| * peripheral clocks, put /1 everywhere. |
| */ |
| #define RCC_CFGR_DIVIDERS_NO_PLL (RCC_CFGR_MCO_CONFIG | CFGR_RTCPRE(0) | \ |
| CFGR_PPRE2(0) | CFGR_PPRE1(0) | CFGR_HPRE(0)) |
| |
| /* PLL output frequency */ |
| #define STM32F4_PLL_CLOCK (STM32F4_VCO_CLOCK / STM32F4_PLLP_DIV) |
| |
| /* current clock settings (PLL is initialized at startup) */ |
| static int current_osc = OSC_PLL; |
| static int current_io_freq = STM32F4_IO_CLOCK; |
| static int current_timer_freq = STM32F4_TIMER_CLOCK; |
| |
| /* the EC code expects to get the USART/I2C clock frequency here (APB clock) */ |
| int clock_get_freq(void) |
| { |
| return current_io_freq; |
| } |
| |
| int clock_get_timer_freq(void) |
| { |
| return current_timer_freq; |
| } |
| |
| 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_HSE: |
| ready = STM32_RCC_CR_HSERDY; |
| on = STM32_RCC_CR_HSEON; |
| break; |
| case OSC_PLL: |
| ready = STM32_RCC_CR_PLLRDY; |
| on = STM32_RCC_CR_PLLON; |
| 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 | RCC_CFGR_DIVIDERS_NO_PLL; |
| sws = STM32_RCC_CFGR_SWS_HSI; |
| break; |
| case OSC_HSE: |
| sw = STM32_RCC_CFGR_SW_HSE | RCC_CFGR_DIVIDERS_NO_PLL; |
| sws = STM32_RCC_CFGR_SWS_HSE; |
| break; |
| case OSC_PLL: |
| sw = STM32_RCC_CFGR_SW_PLL | RCC_CFGR_DIVIDERS_WITH_PLL; |
| sws = STM32_RCC_CFGR_SWS_PLL; |
| break; |
| default: |
| return; |
| } |
| |
| STM32_RCC_CFGR = sw; |
| while ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) != sws) |
| ; |
| } |
| |
| void clock_set_osc(enum clock_osc osc) |
| { |
| volatile uint32_t unused __attribute__((unused)); |
| |
| if (osc == current_osc) |
| return; |
| |
| hook_notify(HOOK_PRE_FREQ_CHANGE); |
| |
| switch (osc) { |
| default: |
| case OSC_HSI: |
| /* new clock settings: no dividers */ |
| current_io_freq = STM32F4_HSI_CLOCK; |
| current_timer_freq = STM32F4_HSI_CLOCK; |
| /* Switch to HSI */ |
| clock_switch_osc(OSC_HSI); |
| /* optimized flash latency settings for <30Mhz clock (0-WS) */ |
| STM32_FLASH_ACR = (STM32_FLASH_ACR & ~STM32_FLASH_ACR_LAT_MASK) |
| | STM32_FLASH_ACR_LATENCY_SLOW; |
| /* read-back the latency as advised by the Reference Manual */ |
| unused = STM32_FLASH_ACR; |
| /* Turn off the PLL1 to save power */ |
| clock_enable_osc(OSC_PLL, false); |
| break; |
| |
| #ifdef CONFIG_STM32_CLOCK_HSE_HZ |
| case OSC_HSE: |
| /* new clock settings: no dividers */ |
| current_io_freq = CONFIG_STM32_CLOCK_HSE_HZ; |
| current_timer_freq = CONFIG_STM32_CLOCK_HSE_HZ; |
| /* Switch to HSE */ |
| clock_switch_osc(OSC_HSE); |
| /* optimized flash latency settings for <30Mhz clock (0-WS) */ |
| STM32_FLASH_ACR = (STM32_FLASH_ACR & ~STM32_FLASH_ACR_LAT_MASK) |
| | STM32_FLASH_ACR_LATENCY_SLOW; |
| /* read-back the latency as advised by the Reference Manual */ |
| unused = STM32_FLASH_ACR; |
| /* Turn off the PLL1 to save power */ |
| clock_enable_osc(OSC_PLL, false); |
| break; |
| #endif /* CONFIG_STM32_CLOCK_HSE_HZ */ |
| |
| case OSC_PLL: |
| /* new clock settings */ |
| current_io_freq = STM32F4_IO_CLOCK; |
| current_timer_freq = STM32F4_TIMER_CLOCK; |
| /* turn on PLL and wait until it's ready */ |
| clock_enable_osc(OSC_PLL, true); |
| /* |
| * Increase flash latency before transition the clock |
| * Use the minimum Wait States value optimized for the platform. |
| */ |
| STM32_FLASH_ACR = (STM32_FLASH_ACR & ~STM32_FLASH_ACR_LAT_MASK) |
| | STM32_FLASH_ACR_LATENCY; |
| /* read-back the latency as advised by the Reference Manual */ |
| unused = STM32_FLASH_ACR; |
| /* Switch to PLL */ |
| clock_switch_osc(OSC_PLL); |
| |
| break; |
| } |
| |
| current_osc = osc; |
| hook_notify(HOOK_FREQ_CHANGE); |
| } |
| |
| static void clock_pll_configure(void) |
| { |
| #ifdef CONFIG_STM32_CLOCK_HSE_HZ |
| int srcclock = CONFIG_STM32_CLOCK_HSE_HZ; |
| #else |
| int srcclock = STM32F4_HSI_CLOCK; |
| #endif |
| int plldiv, pllinputclock; |
| int pllmult, vcoclock; |
| int systemclock; |
| int usbdiv; |
| int i2sdiv; |
| |
| /* PLL input must be between 1-2MHz, near 2 */ |
| /* Valid values 2-63 */ |
| plldiv = (srcclock + STM32F4_PLL_REQ - 1) / STM32F4_PLL_REQ; |
| pllinputclock = srcclock / plldiv; |
| |
| /* PLL output clock: Must be 100-432MHz */ |
| pllmult = (STM32F4_VCO_CLOCK + (pllinputclock / 2)) / pllinputclock; |
| vcoclock = pllinputclock * pllmult; |
| |
| /* CPU/System clock */ |
| systemclock = vcoclock / STM32F4_PLLP_DIV; |
| /* USB clock = 48MHz exactly */ |
| usbdiv = (vcoclock + (STM32F4_USB_REQ / 2)) / STM32F4_USB_REQ; |
| assert(vcoclock / usbdiv == STM32F4_USB_REQ); |
| |
| /* SYSTEM/I2S: same system clock */ |
| i2sdiv = (vcoclock + (systemclock / 2)) / systemclock; |
| |
| /* Set up PLL */ |
| STM32_RCC_PLLCFGR = |
| PLLCFGR_PLLM(plldiv) | |
| PLLCFGR_PLLN(pllmult) | |
| PLLCFGR_PLLP(STM32F4_PLLP_DIV / 2 - 1) | |
| #if defined(CONFIG_STM32_CLOCK_HSE_HZ) |
| PLLCFGR_PLLSRC_HSE | |
| #else |
| PLLCFGR_PLLSRC_HSI | |
| #endif |
| PLLCFGR_PLLQ(usbdiv) | |
| PLLCFGR_PLLR(i2sdiv); |
| } |
| |
| void low_power_init(void); |
| |
| void config_hispeed_clock(void) |
| { |
| #ifdef CONFIG_STM32_CLOCK_HSE_HZ |
| /* Ensure that HSE is ON */ |
| clock_enable_osc(OSC_HSE, true); |
| #endif |
| |
| /* Put the PLL settings, they are never changing */ |
| clock_pll_configure(); |
| clock_enable_osc(OSC_PLL, true); |
| |
| /* Switch SYSCLK to PLL, setup bus prescalers. */ |
| clock_switch_osc(OSC_PLL); |
| |
| #ifdef CONFIG_LOW_POWER_IDLE |
| low_power_init(); |
| #endif |
| } |
| |
| 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_DMA_GET_ISR(0); |
| } else { /* APB */ |
| while (cycles--) |
| unused = STM32_USART_BRR(STM32_USART1_BASE); |
| } |
| } |
| |
| void clock_enable_module(enum module_id module, int enable) |
| { |
| if (module == MODULE_USB) { |
| if (enable) { |
| STM32_RCC_AHB2ENR |= STM32_RCC_AHB2ENR_OTGFSEN; |
| STM32_RCC_AHB1ENR |= STM32_RCC_AHB1ENR_OTGHSEN | |
| STM32_RCC_AHB1ENR_OTGHSULPIEN; |
| } else { |
| STM32_RCC_AHB2ENR &= ~STM32_RCC_AHB2ENR_OTGFSEN; |
| STM32_RCC_AHB1ENR &= ~STM32_RCC_AHB1ENR_OTGHSEN & |
| ~STM32_RCC_AHB1ENR_OTGHSULPIEN; |
| } |
| return; |
| } else if (module == MODULE_I2C) { |
| if (enable) { |
| /* Enable clocks to I2C modules if necessary */ |
| STM32_RCC_APB1ENR |= |
| STM32_RCC_I2C1EN | STM32_RCC_I2C2EN |
| | STM32_RCC_I2C3EN | STM32_RCC_FMPI2C4EN; |
| STM32_RCC_DCKCFGR2 = |
| (STM32_RCC_DCKCFGR2 & ~DCKCFGR2_FMPI2C1SEL_MASK) |
| | DCKCFGR2_FMPI2C1SEL(FMPI2C1SEL_APB); |
| } else { |
| STM32_RCC_APB1ENR &= |
| ~(STM32_RCC_I2C1EN | STM32_RCC_I2C2EN | |
| STM32_RCC_I2C3EN | STM32_RCC_FMPI2C4EN); |
| } |
| return; |
| } else if (module == MODULE_ADC) { |
| if (enable) |
| STM32_RCC_APB2ENR |= STM32_RCC_APB2ENR_ADC1EN; |
| else |
| STM32_RCC_APB2ENR &= ~STM32_RCC_APB2ENR_ADC1EN; |
| return; |
| } |
| } |
| |
| /* Real Time Clock (RTC) */ |
| |
| #ifdef CONFIG_STM32_CLOCK_HSE_HZ |
| #define RTC_PREDIV_A 39 |
| #define RTC_FREQ ((STM32F4_RTC_REQ) / (RTC_PREDIV_A + 1)) /* Hz */ |
| #else /* from LSI clock */ |
| #define RTC_PREDIV_A 1 |
| #define RTC_FREQ (STM32F4_LSI_CLOCK / (RTC_PREDIV_A + 1)) /* Hz */ |
| #endif |
| #define RTC_PREDIV_S (RTC_FREQ - 1) |
| /* |
| * Scaling factor to ensure that the intermediate values computed from/to the |
| * RTC frequency are fitting in a 32-bit integer. |
| */ |
| #define SCALING 1000 |
| |
| int32_t rtcss_to_us(uint32_t rtcss) |
| { |
| return ((RTC_PREDIV_S - rtcss) * (SECOND/SCALING) / (RTC_FREQ/SCALING)); |
| } |
| |
| uint32_t us_to_rtcss(int32_t us) |
| { |
| return (RTC_PREDIV_S - (us * (RTC_FREQ/SCALING) / (SECOND/SCALING))); |
| } |
| |
| void rtc_init(void) |
| { |
| /* Setup RTC Clock input */ |
| #ifdef CONFIG_STM32_CLOCK_HSE_HZ |
| /* RTC clocked from the HSE */ |
| STM32_RCC_BDCR = STM32_RCC_BDCR_RTCEN | BDCR_RTCSEL(BDCR_SRC_HSE); |
| #else |
| /* RTC clocked from the LSI, ensure first it is ON */ |
| wait_for_ready(&(STM32_RCC_CSR), |
| STM32_RCC_CSR_LSION, STM32_RCC_CSR_LSIRDY); |
| |
| STM32_RCC_BDCR = STM32_RCC_BDCR_RTCEN | BDCR_RTCSEL(BDCR_SRC_LSI); |
| #endif |
| |
| rtc_unlock_regs(); |
| |
| /* Enter RTC initialize mode */ |
| STM32_RTC_ISR |= STM32_RTC_ISR_INIT; |
| while (!(STM32_RTC_ISR & STM32_RTC_ISR_INITF)) |
| ; |
| |
| /* Set clock prescalars: Needs two separate writes. */ |
| STM32_RTC_PRER = |
| (STM32_RTC_PRER & ~STM32_RTC_PRER_S_MASK) | RTC_PREDIV_S; |
| STM32_RTC_PRER = |
| (STM32_RTC_PRER & ~STM32_RTC_PRER_A_MASK) |
| | (RTC_PREDIV_A << 16); |
| |
| /* Start RTC timer */ |
| STM32_RTC_ISR &= ~STM32_RTC_ISR_INIT; |
| while (STM32_RTC_ISR & STM32_RTC_ISR_INITF) |
| ; |
| |
| /* Enable RTC alarm interrupt */ |
| STM32_RTC_CR |= STM32_RTC_CR_ALRAIE | STM32_RTC_CR_BYPSHAD; |
| STM32_EXTI_RTSR |= EXTI_RTC_ALR_EVENT; |
| task_enable_irq(STM32_IRQ_RTC_ALARM); |
| |
| rtc_lock_regs(); |
| } |
| |
| #if defined(CONFIG_CMD_RTC) || defined(CONFIG_HOSTCMD_RTC) |
| void rtc_set(uint32_t sec) |
| { |
| struct rtc_time_reg rtc; |
| |
| sec_to_rtc(sec, &rtc); |
| rtc_unlock_regs(); |
| |
| /* Disable alarm */ |
| STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE; |
| |
| /* Enter RTC initialize mode */ |
| STM32_RTC_ISR |= STM32_RTC_ISR_INIT; |
| while (!(STM32_RTC_ISR & STM32_RTC_ISR_INITF)) |
| ; |
| |
| /* Set clock prescalars */ |
| STM32_RTC_PRER = (RTC_PREDIV_A << 16) | RTC_PREDIV_S; |
| |
| STM32_RTC_TR = rtc.rtc_tr; |
| STM32_RTC_DR = rtc.rtc_dr; |
| /* Start RTC timer */ |
| STM32_RTC_ISR &= ~STM32_RTC_ISR_INIT; |
| |
| rtc_lock_regs(); |
| } |
| #endif |
| |
| #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 main regulator off */ |
| #define STOP_MODE_LATENCY 50 /* us */ |
| /* PLL_LOCK_LATENCY: delay to switch from HSI to PLL */ |
| #define PLL_LOCK_LATENCY 150 /* us */ |
| /* |
| * SET_RTC_MATCH_DELAY: max time to set RTC match alarm. If we set the alarm |
| * in the past, it will never wake up and cause a watchdog. |
| */ |
| #define SET_RTC_MATCH_DELAY 120 /* us */ |
| |
| |
| void low_power_init(void) |
| { |
| /* Turn off the main regulator during stop mode */ |
| STM32_PWR_CR |= STM32_PWR_CR_LPSDSR /* aka LPDS */; |
| } |
| |
| void clock_refresh_console_in_use(void) |
| { |
| } |
| |
| void __idle(void) |
| { |
| timestamp_t t0; |
| uint32_t rtc_diff; |
| int next_delay, margin_us; |
| struct rtc_time_reg rtc0, rtc1; |
| |
| while (1) { |
| asm volatile("cpsid i"); |
| |
| t0 = get_time(); |
| next_delay = __hw_clock_event_get() - t0.le.lo; |
| |
| if (DEEP_SLEEP_ALLOWED && |
| (next_delay > (STOP_MODE_LATENCY + PLL_LOCK_LATENCY + |
| SET_RTC_MATCH_DELAY))) { |
| /* Deep-sleep in STOP mode */ |
| idle_dsleep_cnt++; |
| |
| /* |
| * TODO(b/174337385) no support for wake-up on USART |
| * uart_enable_wakeup(1); |
| */ |
| |
| /* Set deep sleep bit */ |
| CPU_SCB_SYSCTRL |= 0x4; |
| |
| set_rtc_alarm(0, next_delay - STOP_MODE_LATENCY |
| - PLL_LOCK_LATENCY, |
| &rtc0, 0); |
| |
| /* Switch to HSI */ |
| clock_switch_osc(OSC_HSI); |
| /* Turn off the PLL1 to save power */ |
| clock_enable_osc(OSC_PLL, false); |
| |
| /* ensure outstanding memory transactions complete */ |
| asm volatile("dsb"); |
| |
| asm("wfi"); |
| |
| CPU_SCB_SYSCTRL &= ~0x4; |
| |
| /* turn on PLL and wait until it's ready */ |
| clock_enable_osc(OSC_PLL, true); |
| /* Switch to PLL */ |
| clock_switch_osc(OSC_PLL); |
| |
| /*uart_enable_wakeup(0);*/ |
| |
| /* Fast forward timer according to RTC counter */ |
| reset_rtc_alarm(&rtc1); |
| rtc_diff = get_rtc_diff(&rtc0, &rtc1); |
| t0.val = t0.val + rtc_diff; |
| force_time(t0); |
| |
| /* Record time spent in deep sleep. */ |
| idle_dsleep_time_us += rtc_diff; |
| |
| /* Calculate how close we were to missing deadline */ |
| margin_us = next_delay - rtc_diff; |
| if (margin_us < 0) |
| /* Use CPUTS to save stack space */ |
| CPUTS("Idle 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"); |
| } |
| } |
| |
| /* 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_LOW_POWER_IDLE */ |