summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Palatin <vpalatin@chromium.org>2020-11-06 13:46:16 +0100
committerCommit Bot <commit-bot@chromium.org>2020-12-02 09:38:58 +0000
commit876bd4056b54df64ff834f1e5a17754fe65ee77d (patch)
tree96fc9146b3351c065d9c4999040eb9202cc75c23
parent696492499f433806a26b4cb0733026444e698c35 (diff)
downloadchrome-ec-876bd4056b54df64ff834f1e5a17754fe65ee77d.tar.gz
stm32: add dynamic clocking for STM32F4
Allow to transition between the PLL to run at full speed and the bare HSI (internal RC oscillator at 16Mhz) to save power. On HSI, as our sysclk frequency is low, we run all peripheral clocks undivided at the same frequency. Keep the configuration for other platforms running from the HSE (external crystal) Signed-off-by: Vincent Palatin <vpalatin@chromium.org> BUG=b:130561737 TEST=manual, verify the timers are working by checking the output of the 'gettime' console command against the wall clock. TEST=manual, verify the UART console works as expected. TEST=measure MCU power on bloonchipper in various modes through the INA: PLL 19.20 mW HSI 4.73 mW STOP 2.10 mW BRANCH=fpmcu-bloonchipper Change-Id: I1185e04c9a7819fec05dd643b7026116b146f3b8 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2527049 Tested-by: Vincent Palatin <vpalatin@chromium.org> Reviewed-by: Nicolas Boichat <drinkcat@chromium.org> Commit-Queue: Vincent Palatin <vpalatin@chromium.org>
-rw-r--r--chip/stm32/clock-stm32f4.c323
-rw-r--r--chip/stm32/registers-stm32f4.h8
2 files changed, 239 insertions, 92 deletions
diff --git a/chip/stm32/clock-stm32f4.c b/chip/stm32/clock-stm32f4.c
index 7b83b8507e..51d6027ef6 100644
--- a/chip/stm32/clock-stm32f4.c
+++ b/chip/stm32/clock-stm32f4.c
@@ -23,37 +23,204 @@
#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
-#define RTC_PREDIV_A 39
-#define RTC_FREQ ((STM32F4_RTC_REQ) / (RTC_PREDIV_A + 1)) /* Hz */
-#else
-/* LSI clock is 40kHz-ish */
-#define RTC_PREDIV_A 1
-#define RTC_FREQ (40000 / (RTC_PREDIV_A + 1)) /* Hz */
-#endif
-#define RTC_PREDIV_S (RTC_FREQ - 1)
-#define US_PER_RTC_TICK (1000000 / RTC_FREQ)
+/* 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))
-int32_t rtcss_to_us(uint32_t rtcss)
+/* 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 ((RTC_PREDIV_S - rtcss) * US_PER_RTC_TICK);
+ return current_io_freq;
}
-uint32_t us_to_rtcss(int32_t us)
+int clock_get_timer_freq(void)
{
- return (RTC_PREDIV_S - (us / US_PER_RTC_TICK));
+ return current_timer_freq;
}
-void config_hispeed_clock(void)
+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;
- int clk_check_mask = STM32_RCC_CR_HSERDY;
- int clk_enable_mask = STM32_RCC_CR_HSEON;
#else
int srcclock = STM32F4_HSI_CLOCK;
- int clk_check_mask = STM32_RCC_CR_HSIRDY;
- int clk_enable_mask = STM32_RCC_CR_HSION;
#endif
int plldiv, pllinputclock;
int pllmult, vcoclock;
@@ -61,17 +228,6 @@ void config_hispeed_clock(void)
int usbdiv;
int i2sdiv;
- int ahbpre, apb1pre, apb2pre;
- int rtcdiv = 0;
-
- /* If PLL is the clock source, PLL has already been set up. */
- if ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) ==
- STM32_RCC_CFGR_SWS_PLL)
- return;
-
- /* Ensure that HSE/HSI is ON */
- wait_for_ready(&(STM32_RCC_CR), clk_enable_mask, clk_check_mask);
-
/* PLL input must be between 1-2MHz, near 2 */
/* Valid values 2-63 */
plldiv = (srcclock + STM32F4_PLL_REQ - 1) / STM32F4_PLL_REQ;
@@ -90,45 +246,6 @@ void config_hispeed_clock(void)
/* SYSTEM/I2S: same system clock */
i2sdiv = (vcoclock + (systemclock / 2)) / systemclock;
- /* All IO clocks at STM32F4_IO_CLOCK
- * For STM32F446: max 45 MHz
- * For STM32F412: max 50 MHz
- */
- /* AHB Prescalar */
- ahbpre = STM32F4_AHB_PRE;
- /* NOTE: If apbXpre is not 0, timers are x2 clocked. RM0390 Fig. 13
- * One should define STM32F4_TIMER_CLOCK when apbXpre is not 0.
- * STM32F4_TIMER_CLOCK is used for hwtimer in EC. */
- apb1pre = STM32F4_APB1_PRE;
- apb2pre = STM32F4_APB2_PRE;
-
-#ifdef CONFIG_STM32_CLOCK_HSE_HZ
- /* RTC clock = 1MHz */
- rtcdiv = (CONFIG_STM32_CLOCK_HSE_HZ + (STM32F4_RTC_REQ / 2))
- / STM32F4_RTC_REQ;
-#endif
- /* Switch SYSCLK to PLL, setup prescalars.
- * EC codebase doesn't understand multiple clock domains
- * so we enforce a clock config that keeps AHB = APB1 = APB2
- * allowing ec codebase assumptions about consistent clock
- * rates to remain true.
- *
- * 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.
- */
- STM32_RCC_CFGR =
- (2 << 30) | /* MCO2 <- HSE */
- (0 << 27) | /* MCO2 div / 4 */
- (6 << 24) | /* MCO1 div / 4 */
- (3 << 21) | /* MCO1 <- PLL */
- CFGR_RTCPRE(rtcdiv) |
- CFGR_PPRE2(apb2pre) |
- CFGR_PPRE1(apb1pre) |
- CFGR_HPRE(ahbpre) |
- STM32_RCC_CFGR_SW_PLL;
-
/* Set up PLL */
STM32_RCC_PLLCFGR =
PLLCFGR_PLLM(plldiv) |
@@ -141,35 +258,23 @@ void config_hispeed_clock(void)
#endif
PLLCFGR_PLLQ(usbdiv) |
PLLCFGR_PLLR(i2sdiv);
+}
- wait_for_ready(&(STM32_RCC_CR),
- STM32_RCC_CR_PLLON, STM32_RCC_CR_PLLRDY);
-
- /* Wait until the PLL is the clock source */
- if ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) ==
- STM32_RCC_CFGR_SWS_PLL)
- ;
-
- /* Setup RTC Clock input */
+void config_hispeed_clock(void)
+{
#ifdef CONFIG_STM32_CLOCK_HSE_HZ
- STM32_RCC_BDCR = STM32_RCC_BDCR_RTCEN | BDCR_RTCSEL(BDCR_SRC_HSE);
-#else
- /* Ensure that LSI 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);
+ /* Ensure that HSE is ON */
+ clock_enable_osc(OSC_HSE, true);
#endif
-}
-int clock_get_timer_freq(void)
-{
- return STM32F4_TIMER_CLOCK;
-}
+ /* Put the PLL settings, they are never changing */
+ clock_pll_configure();
+ clock_enable_osc(OSC_PLL, true);
-int clock_get_freq(void)
-{
- return STM32F4_IO_CLOCK;
+ /* Switch SYSCLK to PLL, setup bus prescalers. */
+ clock_switch_osc(OSC_PLL);
+ /* we cannot go to low power mode as we are running on the PLL */
+ disable_sleep(SLEEP_MASK_PLL);
}
void clock_wait_bus_cycles(enum bus_type bus, uint32_t cycles)
@@ -222,8 +327,42 @@ void clock_enable_module(enum module_id module, int enable)
}
}
+/* 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)
+#define US_PER_RTC_TICK (1000000 / RTC_FREQ)
+
+int32_t rtcss_to_us(uint32_t rtcss)
+{
+ return ((RTC_PREDIV_S - rtcss) * US_PER_RTC_TICK);
+}
+
+uint32_t us_to_rtcss(int32_t us)
+{
+ return (RTC_PREDIV_S - (us / US_PER_RTC_TICK));
+}
+
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 */
diff --git a/chip/stm32/registers-stm32f4.h b/chip/stm32/registers-stm32f4.h
index 4e81c11226..065b8bb035 100644
--- a/chip/stm32/registers-stm32f4.h
+++ b/chip/stm32/registers-stm32f4.h
@@ -417,6 +417,8 @@
#define STM32F4_APB1_PRE 0x0
#define STM32F4_APB2_PRE 0x0
#define STM32_FLASH_ACR_LATENCY BIT(0)
+/* optimized flash latency for < 30Mhz clock (0-WS) e.g. HSI/HSE */
+#define STM32_FLASH_ACR_LATENCY_SLOW 0
#elif defined(CHIP_VARIANT_STM32F412)
/* Required or recommended clocks for stm32f412 */
@@ -433,6 +435,8 @@
#define STM32F4_APB1_PRE 0x4
#define STM32F4_APB2_PRE 0x4
#define STM32_FLASH_ACR_LATENCY (3 << 0)
+/* optimized flash latency for < 30Mhz clock (0-WS) e.g. HSI/HSE */
+#define STM32_FLASH_ACR_LATENCY_SLOW 0
#elif defined(CHIP_VARIANT_STM32F411)
/* Required or recommended clocks for stm32f411 */
@@ -449,6 +453,8 @@
#define STM32F4_APB1_PRE 0x0
#define STM32F4_APB2_PRE 0x0
#define STM32_FLASH_ACR_LATENCY BIT(0)
+/* optimized flash latency for < 30Mhz clock (0-WS) e.g. HSI/HSE */
+#define STM32_FLASH_ACR_LATENCY_SLOW 0
#elif defined(CHIP_VARIANT_STM32F76X)
/* Required or recommended clocks for stm32f767/769 */
@@ -465,6 +471,8 @@
#define STM32F4_APB1_PRE 0x5 /* APB1 = AHB /4 = 45 Mhz */
#define STM32F4_APB2_PRE 0x5 /* APB2 = AHB /4 = 45 Mhz */
#define STM32_FLASH_ACR_LATENCY (5 << 0)
+/* optimized flash latency for < 30Mhz clock (0-WS) e.g. HSI/HSE */
+#define STM32_FLASH_ACR_LATENCY_SLOW 0
#else
#error "No valid clocks defined"