diff options
author | Scott Collyer <scollyer@google.com> | 2020-04-14 15:07:06 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-07-29 03:49:12 +0000 |
commit | 4213a3086f09ee74b1fc1306ef679ca8676ffaba (patch) | |
tree | 62a17fb2d94e703579fcaa20b774cae93f69595a /chip/stm32/clock-stm32g4.c | |
parent | cfc1ae3de25687f6c4350dc99878c2a980ff0a90 (diff) | |
download | chrome-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.c | 277 |
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); + } +} |