summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Hesling <hesling@chromium.org>2020-03-09 16:39:38 -0700
committerCommit Bot <commit-bot@chromium.org>2020-03-20 02:25:15 +0000
commit1e540b547fd213a0995d3dce5db10090e0df6fe2 (patch)
treebb0b566a3b1f704e17a65d0dc41e8bb468185d6d
parente8900daaaf5d9082f5dfc18178ae113dbbd4c2cc (diff)
downloadchrome-ec-1e540b547fd213a0995d3dce5db10090e0df6fe2.tar.gz
stm32: Refactor clock-stm32h7.c
This cleans up and modularizes the clock configuration code for the STM32H743. This makes it easier and cleaner to add the STM32H7A3 variant. This brings no functional change, as coarsely verified with crrev.com/c/2096017 . BRANCH=icetower BUG=b:130296790 TEST=Verified all impacted registers values with and without this change using crrev.com/c/2096017 on the Nucleo-H743ZI. The sequence of commands used to test was the following: # After fresh startup (from reset) * > clock * > clock hsi * > clock pll * > clock hsi * > clock hsi * > clock * > waitms 5000 * # Run timer to check accuracy of 5 seconds * > clock pll * > waitms 5000 * # Still broken -> Watchdog should still kick-in early and render MCU unusable. Signed-off-by: Craig Hesling <hesling@chromium.org> Change-Id: I4fbf6982190c0d660e31c2027b5ad07cae48755e Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2095853 Reviewed-by: Tom Hughes <tomhughes@chromium.org>
-rw-r--r--chip/stm32/clock-stm32h7.c314
1 files changed, 244 insertions, 70 deletions
diff --git a/chip/stm32/clock-stm32h7.c b/chip/stm32/clock-stm32h7.c
index bd7cf65d3f..4bf9909553 100644
--- a/chip/stm32/clock-stm32h7.c
+++ b/chip/stm32/clock-stm32h7.c
@@ -3,7 +3,18 @@
* found in the LICENSE file.
*/
-/* Clocks and power management settings */
+/*
+ * 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"
@@ -18,14 +29,51 @@
#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 64000000
+#define STM32_HSI_CLOCK FREQ_64MHZ
/* Low-speed oscillator is 32-Khz */
-#define STM32_LSI_CLOCK 32000
+#define STM32_LSI_CLOCK FREQ_32KHZ
/*
* LPTIM is a 16-bit counter clocked by LSI
@@ -40,41 +88,13 @@
#define LPTIM_PRESCALER ((int)BIT(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;
+/* 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 freq;
+ return current_bus_freq;
}
int clock_get_timer_freq(void)
@@ -95,14 +115,131 @@ void clock_wait_bus_cycles(enum bus_type bus, uint32_t cycles)
}
}
-static void clock_flash_latency(uint32_t target_acr)
+/* 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)
;
}
-static void clock_enable_osc(enum clock_osc osc)
+/**
+ * @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;
@@ -117,11 +254,20 @@ static void clock_enable_osc(enum clock_osc osc)
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 */
if (!(STM32_RCC_CR & ready)) {
STM32_RCC_CR |= on;
+ /* Wait until ready */
while (!(STM32_RCC_CR & ready))
;
}
@@ -150,62 +296,90 @@ static void clock_switch_osc(enum clock_osc osc)
;
}
-static void switch_voltage_scale(uint32_t vos)
+static void switch_voltage_scale(enum voltage_scale vos)
{
- STM32_PWR_D3CR &= ~STM32_PWR_D3CR_VOSMASK;
- STM32_PWR_D3CR |= vos;
- while (!(STM32_PWR_D3CR & STM32_PWR_D3CR_VOSRDY))
+ 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);
- 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;
+ current_bus_freq = clock_peripheral_configure(target_sysclk_freq);
/* Use more optimized flash latency settings for 64-MHz ACLK */
- clock_flash_latency(FLASH_ACLK_64MHZ);
+ clock_flash_latency(current_bus_freq, target_voltage_scale);
/* Turn off the PLL1 to save power */
- STM32_RCC_CR &= ~STM32_RCC_CR_PLL1ON;
- switch_voltage_scale(STM32_PWR_D3CR_VOS3);
+ clock_enable_osc(OSC_PLL, false);
+ switch_voltage_scale(target_voltage_scale);
break;
case OSC_PLL:
- switch_voltage_scale(STM32_PWR_D3CR_VOS1);
- /* 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;
+ /*
+ * 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(FLASH_ACLK_200MHZ);
+ clock_flash_latency(current_bus_freq, target_voltage_scale);
+
/* Switch to PLL */
clock_switch_osc(OSC_PLL);
break;
- default:
- break;
}
current_osc = osc;
@@ -422,7 +596,7 @@ void clock_init(void)
| STM32_RCC_D2CCIP1R_SPI45SEL_HSI;
/* Use more optimized flash latency settings for ACLK = HSI = 64 Mhz */
- clock_flash_latency(FLASH_ACLK_64MHZ);
+ clock_flash_latency(FREQ_64MHZ, VOLTAGE_SCALE3);
/* Ensure that LSI is ON to clock LPTIM1 and IWDG */
STM32_RCC_CSR |= STM32_RCC_CSR_LSION;
@@ -444,7 +618,7 @@ static int command_clock(int argc, char **argv)
else
return EC_ERROR_PARAM1;
}
- ccprintf("Clock frequency is now %d Hz\n", freq);
+ ccprintf("Clock frequency is now %d Hz\n", clock_get_freq());
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(clock, command_clock,