summaryrefslogtreecommitdiff
path: root/chip/stm32/clock-stm32g4.c
diff options
context:
space:
mode:
authorScott Collyer <scollyer@google.com>2020-04-14 15:07:06 -0700
committerCommit Bot <commit-bot@chromium.org>2020-07-29 03:49:12 +0000
commit4213a3086f09ee74b1fc1306ef679ca8676ffaba (patch)
tree62a17fb2d94e703579fcaa20b774cae93f69595a /chip/stm32/clock-stm32g4.c
parentcfc1ae3de25687f6c4350dc99878c2a980ff0a90 (diff)
downloadchrome-ec-4213a3086f09ee74b1fc1306ef679ca8676ffaba.tar.gz
stm32g4: Clock module driver
This CL adds support to configure rcc module for stm32g4. The driver from the F4 family was used as a reference. Support for RTC was not ported as it's not being used for honeybuns. The function wait_for_ready() was moved to the common clock file for the F-family so it would not need to be replicated for G4 as well. BUG=148493929 BRANCH=None TEST=verfied that the GPIO, clocks, and EC console over LPUART Signed-off-by: Scott Collyer <scollyer@google.com> Change-Id: I980c8889965a2e5da401ccd6291079a0bdfa8e4f Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2195545 Tested-by: Scott Collyer <scollyer@chromium.org> Reviewed-by: Daisuke Nojiri <dnojiri@chromium.org> Commit-Queue: Scott Collyer <scollyer@chromium.org>
Diffstat (limited to 'chip/stm32/clock-stm32g4.c')
-rw-r--r--chip/stm32/clock-stm32g4.c277
1 files changed, 277 insertions, 0 deletions
diff --git a/chip/stm32/clock-stm32g4.c b/chip/stm32/clock-stm32g4.c
new file mode 100644
index 0000000000..488adacf59
--- /dev/null
+++ b/chip/stm32/clock-stm32g4.c
@@ -0,0 +1,277 @@
+/* Copyright 2020 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 configuration routines */
+
+#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)
+
+#define MHZ(x) ((x) * 1000000)
+#define WAIT_STATE_FREQ_STEP_HZ MHZ(2)
+/* PLL configuration constants */
+#define STM32G4_SYSCLK_MAX_HZ MHZ(17)
+#define STM32G4_HSI_CLK_HZ MHZ(16)
+#define STM32G4_PLL_IN_FREQ_HZ MHZ(4)
+#define STM32G4_PLL_R 2
+#define STM32G4_AHB_PRE 1
+#define STM32G4_APB1_PRE 1
+#define STM32G4_APB2_PRE 1
+
+enum rcc_clksrc {
+ sysclk_rsvd,
+ sysclk_hsi,
+ sysclk_hse,
+ sysclk_pll,
+};
+
+static void stm32g4_config_pll(uint32_t hclk_hz, uint32_t pll_src,
+ uint32_t pll_clk_in_hz)
+{
+ /*
+ * The pll output frequency (Fhclkc) is determined by:
+ * Fvco = Fosc_in * (PLL_N / PLL_M)
+ * Fsysclk = Fvco / PLL_R
+ * Fhclk = Fsysclk / AHBpre = (Fosc * N) /(M * R * AHBpre)
+ *
+ * PLL_N: 8 <= N <= 127
+ * PLL_M: 1 <= M <= 16
+ * PLL_R: 2, 4, 6, or 8
+ *
+ * PLL_input freq (4 - 16 MHz)
+ * Fvco: 2.66 MHz <= Fvco_in <= 8 MHz
+ * 64 MHz <= Fvco_out <= 344 MHz
+ * Fhclk <= 170 MHz
+ *
+ * PLL config parameters are selected given the following assumptions:
+ * - PLL input freq = 4 MHz
+ * - PLL_R divider = 2
+ * With these assumptions the value N can be calculated by:
+ * N = (Fhclk * M * R * AHBpre) / Fosc
+ * where M = Fosc / F_pllin
+ * Replacing M gives:
+ * N = (Fhclk * R * AHBpre) / Fpll_in
+ */
+ uint32_t pll_n;
+ uint32_t pll_m;
+ uint32_t hclk_freq;
+
+ /* Pll input divider = input freq / desired_input_freq */
+ pll_m = pll_clk_in_hz / STM32G4_PLL_IN_FREQ_HZ;
+ pll_n = (hclk_hz * STM32G4_PLL_R * STM32G4_AHB_PRE) /
+ STM32G4_PLL_IN_FREQ_HZ;
+
+ /* Sanity checks */
+ ASSERT(pll_m && (pll_m <= 16));
+ ASSERT((pll_n >= 8) && (pll_n <= 127));
+
+ hclk_freq = pll_clk_in_hz * pll_n / (pll_m *
+ STM32G4_PLL_R * STM32G4_AHB_PRE);
+ /* Ensure that there aren't any integer rounding errors */
+ ASSERT(hclk_freq == hclk_hz);
+
+ /* Program PLL config register */
+ STM32_RCC_PLLCFGR = PLLCFGR_PLLP(0) |
+ PLLCFGR_PLLR(STM32G4_PLL_R / 2 - 1) |
+ PLLCFGR_PLLR_EN |
+ PLLCFGR_PLLQ(0) |
+ PLLCFGR_PLLQ_EN |
+ PLLCFGR_PLLN(pll_n) |
+ PLLCFGR_PLLM(pll_m - 1) |
+ pll_src;
+
+ /* Wait until PLL is locked */
+ wait_for_ready(&(STM32_RCC_CR), STM32_RCC_CR_PLLON,
+ STM32_RCC_CR_PLLRDY);
+
+ /*
+ * Program prescalers and set system clock source as PLL
+ * Assuming AHB, APB1, and APB2 prescalers are 1, and no clock output
+ * desired so MCO fields are left at reset value.
+ */
+ STM32_RCC_CFGR = STM32_RCC_CFGR_SW_PLL;
+
+ /* Wait until the PLL is the system clock source */
+ while ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) !=
+ STM32_RCC_CFGR_SWS_PLL)
+ ;
+}
+
+static void stm32g4_config_low_speed_clock(void)
+{
+ /* 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);
+}
+
+static void stm32g4_config_high_speed_clock(uint32_t hclk_hz,
+ enum rcc_clksrc sysclk_src,
+ uint32_t pll_clksrc)
+{
+ /* TODO(b/161502871): PLL is currently only supported clock source */
+ ASSERT(sysclk_src == sysclk_pll);
+
+ /* Ensure that HSI is ON */
+ wait_for_ready(&(STM32_RCC_CR), STM32_RCC_CR_HSION,
+ STM32_RCC_CR_HSIRDY);
+
+ if (sysclk_src == sysclk_pll) {
+ /*
+ * If PLL_R is the desired clock source, then need to calculate
+ * PLL multilier/diviber parameters. Once the PLL output is
+ * stable, then the PLL must be selected as the clock
+ * source. Note, that if the current clock source selection is
+ * the PLL and sysclk frequency == hclk_hz, there is nothing
+ * that needs to be done here.
+ */
+ /* 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;
+ stm32g4_config_pll(hclk_hz, pll_clksrc, STM32G4_HSI_CLK_HZ);
+ }
+}
+
+void stm32g4_set_flash_ws(uint32_t freq_hz)
+{
+
+ ASSERT(freq_hz <= STM32G4_SYSCLK_MAX_HZ);
+ /*
+ * Need to calculate and then set number of wait states (in CPU cycles)
+ * required for access to internal flash. The required values can be
+ * found in Table 9 of RM0440 - STM32G4 technical reference manual. A
+ * table lookup is not required though as WS = HCLK (MHz) / 20
+ */
+ STM32_FLASH_ACR = (freq_hz / WAIT_STATE_FREQ_STEP_HZ) |
+ STM32_FLASH_ACR_PRFTEN;
+ /* Enable data and instruction cache */
+ STM32_FLASH_ACR |= STM32_FLASH_ACR_DCEN | STM32_FLASH_ACR_ICEN |
+ STM32_FLASH_ACR_PRFTEN;
+}
+
+void clock_init(void)
+{
+ /*
+ * The STM32G4 has 3 potential sysclk sources:
+ * 1. HSE -> external cyrstal oscillator circuit
+ * 2. HSI -> Internal RC oscillator (16 MHz output)
+ * 3. PLL -> input from either HSI or HSI
+ *
+ * SYSCLK is routed to AHB via the AHB prescaler. The AHB clock is fed
+ * directly to AHB bus, core, memory, DMA, and cortex FCLK. The AHB bus
+ * clock is then fed to both APB1 and APB2 buses via the APB1 and APB2
+ * prescalers.
+ *
+ * CrosEC doesn't support having multiple clocks of different
+ * frequencies and therefore f(AHB) = f(APB1) = f(APB2) must be
+ * enforced. The max frequency of all these clocks is 170 MHz. Max input
+ * frequency to the PLL is 48 MHz. The M divider can be used to lower
+ * the PLL input frequency if necessary. The PLL has 3 different output
+ * clocks, PLL_P, PLL_Q, and PLL_R. PLL_R is the clock which can be used
+ * as SYSCLK.
+ *
+ * The STM32G4 has an additional 48 MHz internal oscillator that is fed
+ * directly to the USB and RNG blocks.
+ *
+ * The STM32G4 also has a low speed clock which feeds the RTC and IWDG
+ * blocks and as a low power clock source that can be kept running
+ * during stop and standby modes. The low speed clock is generated from:
+ * 1. LSE -> external crystal oscillator (max = 1 MHz)
+ * 2. LSI -> internal fixed 32 kHz
+ *
+ * The initial state following system reset:
+ * SYSCLK from HSI, AHB, APB1, and APB2 presecaler = 1
+ * PLL unlocked, RTC enabled on LSE
+ */
+
+ /* Configure flash wait state and enable I/D cache */
+ stm32g4_set_flash_ws(CPU_CLOCK);
+ /* Set up high speed clock and enable PLL */
+ stm32g4_config_high_speed_clock(CPU_CLOCK, sysclk_pll,
+ PLLCFGR_PLLSRC_HSI);
+ /* Set up low speed clock */
+ stm32g4_config_low_speed_clock();
+}
+
+int clock_get_timer_freq(void)
+{
+ /*
+ * STM32G4 timer clocks (TCLK) are either at the same frequency as
+ * PCLK_N when the APB prescaler is 1, and TLCK = 2 * PCLK if
+ * APBn_pre > 1. It's expected that PCLK1 == PCLK2, so only have to
+ * check either of the apb prescalar settings.
+ */
+ return (STM32G4_APB1_PRE > 1 ? CPU_CLOCK * 2 : CPU_CLOCK);
+}
+
+int clock_get_freq(void)
+{
+ return CPU_CLOCK;
+}
+
+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_DMA1_REGS->isr;
+ } else { /* APB */
+ while (cycles--)
+ dummy = STM32_USART_BRR(STM32_USART1_BASE);
+ }
+}
+
+void clock_enable_module(enum module_id module, int enable)
+{
+ if (module == MODULE_USB) {
+ if (enable)
+ STM32_RCC_CRRCR |= RCC_CRRCR_HSI48O;
+ else
+ STM32_RCC_CRRCR &= ~RCC_CRRCR_HSI48O;
+ } else if (module == MODULE_I2C) {
+ if (enable) {
+ /* Enable clocks to I2C modules if necessary */
+ STM32_RCC_APB1ENR1 |=
+ STM32_RCC_APB1ENR1_I2C1EN |
+ STM32_RCC_APB1ENR1_I2C2EN |
+ STM32_RCC_APB1ENR1_I2C3EN;
+ STM32_RCC_APB1ENR2 |= STM32_RCC_APB1ENR2_I2C4EN;
+ } else {
+ STM32_RCC_APB1ENR1 &=
+ ~(STM32_RCC_APB1ENR1_I2C1EN |
+ STM32_RCC_APB1ENR1_I2C2EN |
+ STM32_RCC_APB1ENR1_I2C3EN);
+ STM32_RCC_APB1ENR2 &= ~STM32_RCC_APB1ENR2_I2C4EN;
+ }
+ } else if (module == MODULE_ADC) {
+ /* TODO does clock select need to be set here too? */
+ if (enable)
+ STM32_RCC_AHB2ENR |= (STM32_RCC_AHB2ENR_ADC12EN |
+ STM32_RCC_APB2ENR_ADC345EN);
+ else
+ STM32_RCC_AHB2ENR &= ~(STM32_RCC_AHB2ENR_ADC12EN |
+ STM32_RCC_APB2ENR_ADC345EN);
+ } else {
+ CPRINTS("stm32g4: enable clock module %d not supported",
+ module);
+ }
+}