From 095ed8511a82e7f7da5c59c42ca116bb228a20d6 Mon Sep 17 00:00:00 2001 From: Karl Palsson Date: Fri, 19 Aug 2016 11:26:18 +0000 Subject: [PATCH] stm32l1: rcc: reliably clock up/down When changing the system clock, you must take care to not exceed the legal ranges based on voltage and flash wait states. Existing code made it possible to provide a valid clock structure, that would run out of bounds temporarily. Some boards would crash with various Usage faults / Bus errors due to this. --- lib/stm32/l1/rcc.c | 47 ++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/stm32/l1/rcc.c b/lib/stm32/l1/rcc.c index deb54908..a67c2ae4 100644 --- a/lib/stm32/l1/rcc.c +++ b/lib/stm32/l1/rcc.c @@ -497,31 +497,46 @@ void rcc_clock_setup_msi(const struct rcc_clock_scale *clock) rcc_apb2_frequency = clock->apb2_frequency; } + +/** + * Switch sysclock to HSI with the given parameters. + * This should be usable from any point in time, but only if you have used + * library functions to manage clocks. It relies on the global + * @ref rcc_ahb_frequency to ensure that it reliably scales voltage up or down + * as appropriate. + * @param clock full struct with desired parameters + */ void rcc_clock_setup_hsi(const struct rcc_clock_scale *clock) { /* Enable internal high-speed oscillator. */ rcc_osc_on(RCC_HSI); - rcc_wait_for_osc_ready(RCC_HSI); - - /* Select HSI as SYSCLK source. */ - rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_HSICLK); - - /* - * Set prescalers for AHB, ADC, ABP1, ABP2. - * Do this before touching the PLL (TODO: why?). - */ - rcc_set_hpre(clock->hpre); - rcc_set_ppre1(clock->ppre1); - rcc_set_ppre2(clock->ppre2); - rcc_periph_clock_enable(RCC_PWR); - pwr_set_vos_scale(clock->voltage_scale); /* I guess this should be in the settings? */ flash_64bit_enable(); flash_prefetch_enable(); - /* Configure flash settings. */ - flash_set_ws(clock->flash_config); + + /* Don't try and go to fast for a voltage range! */ + if (clock->ahb_frequency > rcc_ahb_frequency) { + /* Going up, power up first */ + pwr_set_vos_scale(clock->voltage_scale); + rcc_set_hpre(clock->hpre); + rcc_set_ppre1(clock->ppre1); + rcc_set_ppre2(clock->ppre2); + flash_set_ws(clock->flash_config); + } else { + /* going down, slow down before cutting power */ + rcc_set_hpre(clock->hpre); + rcc_set_ppre1(clock->ppre1); + rcc_set_ppre2(clock->ppre2); + flash_set_ws(clock->flash_config); + pwr_set_vos_scale(clock->voltage_scale); + } + + rcc_wait_for_osc_ready(RCC_HSI); + while (PWR_CSR & PWR_CSR_VOSF) + ; + rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_HSICLK); /* Set the peripheral clock frequencies used. */ rcc_ahb_frequency = clock->ahb_frequency;