Brian Viele 4953d67aaa stm32h7: per comments, improved consistency with other rcc impls.
Reduced the sea of enums, and renamed config parameters to match other
implementations, cribbing off of the G0 config, as it is closer to the level
of complexity. Updated initialization code to utilize the new values.

Added flash and LDO configuration from RCC init to be more consistent with
STM32 platform initialization.
2020-03-05 22:07:10 +00:00

418 lines
14 KiB
C

/** @defgroup rcc_file RCC peripheral API
*
* @ingroup peripheral_apis
* This library supports the Reset and Clock Control System in the STM32 series
* of ARM Cortex Microcontrollers by ST Microelectronics.
*
* LGPL License Terms @ref lgpl_license
*/
#include <stddef.h>
#include <libopencm3/cm3/assert.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/pwr.h>
#include <libopencm3/stm32/flash.h>
#define HZ_PER_MHZ 1000000UL
#define HZ_PER_KHZ 1000UL
/* Local private copy of the clock configuration for providing user with clock tree data. */
static struct {
uint16_t sysclk_mhz;
uint16_t cpu_mhz;
uint16_t hclk_mhz;
struct {
uint16_t pclk1_mhz; /* APB1 clock. */
uint16_t pclk2_mhz; /* APB2 clock. */
uint16_t pclk3_mhz; /* APB3 clock. */
uint16_t pclk4_mhz; /* APB4 clock. */
} per;
struct pll_clocks { /* Each PLL output set of data. */
uint16_t p_mhz;
uint16_t q_mhz;
uint16_t r_mhz;
} pll1, pll2, pll3;
uint16_t hse_khz; /* This can't exceed 50MHz */
} rcc_clock_tree = {
.sysclk_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
.cpu_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
.hclk_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
.per.pclk1_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
.per.pclk2_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
.per.pclk3_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
.per.pclk4_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ
};
static void rcc_configure_pll(uint32_t clkin, const struct pll_config *config, int pll_num) {
/* Only concern ourselves with the PLL if the input clock is enabled. */
if (config->divm == 0 || pll_num < 1 || pll_num > 3) {
return;
}
struct pll_clocks *pll_tree_ptr;
if (pll_num == 1) {
pll_tree_ptr = &rcc_clock_tree.pll1;
} else if (pll_num == 2) {
pll_tree_ptr = &rcc_clock_tree.pll2;
} else {
pll_tree_ptr = &rcc_clock_tree.pll3;
}
/* Let's write all of the dividers as specified. */
RCC_PLLDIVR(pll_num) = 0;
RCC_PLLDIVR(pll_num) |= RCC_PLLNDIVR_DIVN(config->divn);
/* Setup the PLL config values for this PLL. */
uint8_t vco_addshift = 4 * (pll_num - 1); /* Values spaced by 4 for PLL 1/2/3 */
/* Set the PLL input frequency range. */
uint32_t pll_clk_mhz = (clkin / config->divm) / HZ_PER_MHZ;
if (pll_clk_mhz > 2 && pll_clk_mhz <= 4) {
RCC_PLLCFGR |= (RCC_PLLCFGR_PLLRGE_2_4MHZ << RCC_PLLCFGR_PLL1RGE_SHIFT) << vco_addshift;
} else if (pll_clk_mhz > 4 && pll_clk_mhz <= 8) {
RCC_PLLCFGR |= (RCC_PLLCFGR_PLLRGE_4_8MHZ << RCC_PLLCFGR_PLL1RGE_SHIFT) << vco_addshift;
} else if (pll_clk_mhz > 8) {
RCC_PLLCFGR |= (RCC_PLLCFGR_PLLRGE_8_16MHZ << RCC_PLLCFGR_PLL1RGE_SHIFT) << vco_addshift;
}
/* Set the VCO output frequency range. */
uint32_t pll_vco_clk_mhz = (pll_clk_mhz * config->divn);
if (pll_vco_clk_mhz <= 420) {
RCC_PLLCFGR |= (RCC_PLLCFGR_PLL1VCO_MED << vco_addshift);
}
/* Setup the enable bits for the PLL outputs. */
uint8_t diven_addshift = 3 * (pll_num - 1); /* Values spaced by 3 for PLL1/2/3 */
if (config->divp > 0) {
RCC_PLLDIVR(pll_num) |= RCC_PLLNDIVR_DIVP(config->divp);
RCC_PLLCFGR |= (RCC_PLLCFGR_DIVP1EN << diven_addshift);
pll_tree_ptr->p_mhz = pll_vco_clk_mhz / config->divp;
}
if (config->divq > 0) {
RCC_PLLDIVR(pll_num) |= RCC_PLLNDIVR_DIVQ(config->divq);
RCC_PLLCFGR |= (RCC_PLLCFGR_DIVQ1EN << diven_addshift);
pll_tree_ptr->q_mhz = pll_vco_clk_mhz / config->divq;
}
if (config->divr > 0) {
RCC_PLLDIVR(pll_num) |= RCC_PLLNDIVR_DIVR(config->divr);
RCC_PLLCFGR |= (RCC_PLLCFGR_DIVR1EN << diven_addshift);
pll_tree_ptr->r_mhz = pll_vco_clk_mhz / config->divr;
}
/* Attempt to enable and lock PLL. */
uint8_t cr_addshift = 2 * (pll_num - 1);
RCC_CR |= RCC_CR_PLL1ON << cr_addshift;
while (!(RCC_CR & (RCC_CR_PLL1RDY << cr_addshift)));
}
static void rcc_set_and_enable_plls(const struct rcc_pll_config *config) {
/* It is assumed that this function is entered with PLLs disabled and not
* running. Setup PLL1/2/3 with configurations specified in the config. */
RCC_PLLCKSELR = RCC_PLLCKSELR_DIVM1(config->pll1.divm) |
RCC_PLLCKSELR_DIVM2(config->pll2.divm) |
RCC_PLLCKSELR_DIVM3(config->pll3.divm) |
config->pll_source;
uint32_t clkin = (config->pll_source == RCC_PLLCKSELR_PLLSRC_HSI)
? RCC_HSI_BASE_FREQUENCY : config->hse_frequency;
RCC_PLLCFGR = 0;
rcc_configure_pll(clkin, &config->pll1, 1);
rcc_configure_pll(clkin, &config->pll2, 2);
rcc_configure_pll(clkin, &config->pll3, 3);
}
/* This is a helper to calculate dividers that go 2/4/8/16/64/128/256/512.
* These dividers also use the top bit as an "enable". */
static uint16_t rcc_prediv_log_skip32_div(uint16_t clk_mhz, uint32_t div_val) {
if (div_val < 0x8) {
return clk_mhz;
} else if (div_val <= RCC_D1CFGR_D1CPRE_DIV16) {
return clk_mhz >> (div_val - 7);
} else {
return clk_mhz >> (div_val - 6);
}
}
/* This is a helper to help calculate simple 3-bit log dividers with top bit
* used as enable bit. */
static uint16_t rcc_prediv_3bit_log_div(uint16_t clk_mhz, uint32_t div_val) {
if (div_val < 0x4) {
return clk_mhz;
} else {
return clk_mhz >> (div_val - 3);
}
}
static void rcc_clock_setup_domain1(const struct rcc_pll_config *config) {
RCC_D1CFGR = 0;
RCC_D1CFGR |= RCC_D1CFGR_D1CPRE(config->core_pre) |
RCC_D1CFGR_D1HPRE(config->hpre) | RCC_D1CFGR_D1PPRE(config->ppre3);
/* Update our clock values in our tree based on the config values. */
rcc_clock_tree.cpu_mhz =
rcc_prediv_log_skip32_div(rcc_clock_tree.sysclk_mhz, config->core_pre);
rcc_clock_tree.hclk_mhz =
rcc_prediv_log_skip32_div(rcc_clock_tree.cpu_mhz, config->hpre);
rcc_clock_tree.per.pclk3_mhz =
rcc_prediv_3bit_log_div(rcc_clock_tree.hclk_mhz, config->ppre3);
}
static void rcc_clock_setup_domain2(const struct rcc_pll_config *config) {
RCC_D2CFGR = 0;
RCC_D2CFGR |= RCC_D2CFGR_D2PPRE1(config->ppre1) |
RCC_D2CFGR_D2PPRE2(config->ppre2);
/* Update our clock values in our tree based on the config values. */
rcc_clock_tree.per.pclk2_mhz =
rcc_prediv_3bit_log_div(rcc_clock_tree.hclk_mhz, config->ppre2);
rcc_clock_tree.per.pclk1_mhz =
rcc_prediv_3bit_log_div(rcc_clock_tree.hclk_mhz, config->ppre1);
}
static void rcc_clock_setup_domain3(const struct rcc_pll_config *config) {
RCC_D3CFGR &= 0;
RCC_D3CFGR |= RCC_D3CFGR_D3PPRE(config->ppre4);
/* Update our clock values in our tree based on the config values. */
rcc_clock_tree.per.pclk4_mhz =
rcc_prediv_3bit_log_div(rcc_clock_tree.hclk_mhz, config->ppre4);
}
void rcc_clock_setup_pll(const struct rcc_pll_config *config) {
/* First, set system clock to utilize HSI, then disable all but HSI. */
RCC_CR |= RCC_CR_HSION;
RCC_CFGR &= ~(RCC_CFGR_SW_MASK << RCC_CFGR_SW_SHIFT);
while (((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SWS_MASK) != RCC_CFGR_SWS_HSI);
RCC_CR = RCC_CR_HSION;
/* Now that we're safely running on HSI, let's setup the LDO. */
pwr_set_mode_ldo();
pwr_set_vos_scale(config->voltage_scale);
/* Set flash waitstates. Enable flash prefetch if we have at least 1WS */
flash_set_ws(config->flash_waitstates);
if (config->flash_waitstates > FLASH_ACR_LATENCY_0WS) {
flash_prefetch_enable();
} else {
flash_prefetch_disable();
}
/* User has specified an external oscillator, make sure we turn it on. */
if (config->hse_frequency > 0) {
RCC_CR |= RCC_CR_HSEON;
while (!(RCC_CR & RCC_CR_HSERDY));
rcc_clock_tree.hse_khz = config->hse_frequency / HZ_PER_KHZ;
}
/* Set, enable and lock all of the pll from the config. */
rcc_set_and_enable_plls(config);
/* Populate our base sysclk settings for use with domain clocks. */
if (config->sysclock_source == RCC_PLL) {
rcc_clock_tree.sysclk_mhz = rcc_clock_tree.pll1.p_mhz;
} else if (config->sysclock_source == RCC_HSE) {
rcc_clock_tree.sysclk_mhz = config->hse_frequency / HZ_PER_MHZ;
} else {
rcc_clock_tree.sysclk_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ;
}
/* PLL's are set, now we need to get everything switched over the correct domains. */
rcc_clock_setup_domain1(config);
rcc_clock_setup_domain2(config);
rcc_clock_setup_domain3(config);
/* TODO: Configure custom kernel mappings. */
/* Domains dividers are all configured, now we can switchover to PLL. */
RCC_CFGR |= RCC_CFGR_SW_PLL1;
uint32_t cfgr_sws = ((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SWS_MASK);
while(cfgr_sws != RCC_CFGR_SWS_PLL1) {
cfgr_sws = ((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SWS_MASK);
}
}
uint32_t rcc_get_bus_clk_freq(enum rcc_clock_source source) {
uint32_t clksel;
switch (source) {
case RCC_SYSCLK:
return rcc_clock_tree.sysclk_mhz * HZ_PER_MHZ;
case RCC_CPUCLK:
case RCC_SYSTICKCLK:
return rcc_clock_tree.cpu_mhz * HZ_PER_MHZ;
case RCC_AHBCLK:
case RCC_HCLK3:
return rcc_clock_tree.hclk_mhz * HZ_PER_MHZ;
case RCC_APB1CLK:
return rcc_clock_tree.per.pclk1_mhz * HZ_PER_MHZ;
case RCC_APB2CLK:
return rcc_clock_tree.per.pclk2_mhz * HZ_PER_MHZ;
case RCC_APB3CLK:
return rcc_clock_tree.per.pclk3_mhz * HZ_PER_MHZ;
case RCC_APB4CLK:
return rcc_clock_tree.per.pclk4_mhz * HZ_PER_MHZ;
case RCC_PERCLK:
clksel = (RCC_D1CCIPR >> RCC_D1CCIPR_CKPERSEL_SHIFT) & RCC_D1CCIPR_CKPERSEL_MASK;
if (clksel == RCC_D1CCIPR_CKPERSEL_HSI) {
return RCC_HSI_BASE_FREQUENCY;
} else if (clksel == RCC_D1CCIPR_CKPERSEL_HSE) {
return rcc_clock_tree.hse_khz * HZ_PER_KHZ;
} else {
return 0U;
}
default:
cm3_assert_not_reached();
return 0U;
}
}
uint32_t rcc_get_peripheral_clk_freq(uint32_t periph) {
uint32_t clksel;
switch (periph) {
case FDCAN1_BASE:
case FDCAN2_BASE:
clksel = (RCC_D2CCIP1R >> RCC_D2CCIP1R_FDCANSEL_SHIFT) & RCC_D2CCIP1R_FDCANSEL_MASK;
if (clksel == RCC_D2CCIP1R_FDCANSEL_HSE) {
return rcc_clock_tree.hse_khz * HZ_PER_KHZ;
} else if (clksel == RCC_D2CCIP1R_FDCANSEL_PLL1Q) {
return rcc_clock_tree.pll1.q_mhz * HZ_PER_MHZ;
} else if (clksel == RCC_D2CCIP1R_FDCANSEL_PLL2Q) {
return rcc_clock_tree.pll2.q_mhz * HZ_PER_MHZ;
} else {
return 0U;
}
case SPI1_BASE:
case SPI2_BASE:
case SPI3_BASE:
clksel = (RCC_D2CCIP1R >> RCC_D2CCIP1R_SPI123SEL_SHIFT) & RCC_D2CCIP1R_SPI123SEL_MASK;
if (clksel == RCC_D2CCIP1R_SPI123SEL_PLL1Q) {
return rcc_clock_tree.pll1.q_mhz * HZ_PER_MHZ;
} else if (clksel == RCC_D2CCIP1R_SPI123SEL_PLL2P) {
return rcc_clock_tree.pll2.p_mhz * HZ_PER_MHZ;
} else if (clksel == RCC_D2CCIP1R_SPI123SEL_PLL3P) {
return rcc_clock_tree.pll3.p_mhz * HZ_PER_MHZ;
} else if (clksel == RCC_D2CCIP1R_SPI123SEL_PERCK) {
return rcc_get_bus_clk_freq(RCC_PERCLK);
} else {
return 0U;
}
case SPI4_BASE:
case SPI5_BASE:
clksel = (RCC_D2CCIP1R >> RCC_D2CCIP1R_SPI45SEL_SHIFT) & RCC_D2CCIP1R_SPI45SEL_MASK;
if (clksel == RCC_D2CCIP1R_SPI45SEL_APB4){
return rcc_get_bus_clk_freq(RCC_APB1CLK);
} else if (clksel == RCC_D2CCIP1R_SPI45SEL_PLL2Q){
return rcc_clock_tree.pll2.q_mhz * HZ_PER_MHZ;
} else if (clksel == RCC_D2CCIP1R_SPI45SEL_PLL3Q){
return rcc_clock_tree.pll3.q_mhz * HZ_PER_MHZ;
} else if (clksel == RCC_D2CCIP1R_SPI45SEL_HSI){
return RCC_HSI_BASE_FREQUENCY;
} else if (clksel == RCC_D2CCIP1R_SPI45SEL_HSE) {
return rcc_clock_tree.hse_khz * HZ_PER_KHZ;
} else {
return 0U;
}
case USART1_BASE:
case USART6_BASE:
clksel = (RCC_D2CCIP2R >> RCC_D2CCIP2R_USART16SEL_SHIFT) & RCC_D2CCIP2R_USARTSEL_MASK;
if (clksel == RCC_D2CCIP2R_USART16SEL_PCLK2) {
return rcc_get_bus_clk_freq(RCC_APB2CLK);
} else if (clksel == RCC_D2CCIP2R_USARTSEL_PLL2Q) {
return rcc_clock_tree.pll2.q_mhz * HZ_PER_MHZ;
} else if (clksel == RCC_D2CCIP2R_USARTSEL_PLL3Q) {
return rcc_clock_tree.pll3.q_mhz * HZ_PER_MHZ;
} else if (clksel == RCC_D2CCIP2R_USARTSEL_HSI) {
return RCC_HSI_BASE_FREQUENCY;
} else {
return 0U;
}
case USART2_BASE:
case USART3_BASE:
case UART4_BASE:
case UART5_BASE:
case UART7_BASE:
case UART8_BASE:
clksel = (RCC_D2CCIP2R >> RCC_D2CCIP2R_USART234578SEL_SHIFT) & RCC_D2CCIP2R_USARTSEL_MASK;
if (clksel == RCC_D2CCIP2R_USART234578SEL_PCLK1) {
return rcc_get_bus_clk_freq(RCC_APB1CLK);
} else if (clksel == RCC_D2CCIP2R_USARTSEL_PLL2Q) {
return rcc_clock_tree.pll2.q_mhz * HZ_PER_MHZ;
} else if (clksel == RCC_D2CCIP2R_USARTSEL_PLL3Q) {
return rcc_clock_tree.pll3.q_mhz * HZ_PER_MHZ;
} else if (clksel == RCC_D2CCIP2R_USARTSEL_HSI) {
return RCC_HSI_BASE_FREQUENCY;
} else {
return 0U;
}
default:
cm3_assert_not_reached();
return 0;
}
}
void rcc_set_peripheral_clk_sel(uint32_t periph, uint32_t sel) {
volatile uint32_t *reg;
uint32_t mask;
uint32_t val;
switch (periph) {
case FDCAN1_BASE:
case FDCAN2_BASE:
reg = &RCC_D2CCIP1R;
mask = RCC_D2CCIP1R_FDCANSEL_MASK << RCC_D2CCIP1R_FDCANSEL_SHIFT;
val = sel << RCC_D2CCIP1R_FDCANSEL_SHIFT;
break;
case SPI1_BASE:
case SPI2_BASE:
case SPI3_BASE:
reg = &RCC_D2CCIP2R;
mask = RCC_D2CCIP1R_SPI123SEL_MASK << RCC_D2CCIP1R_SPI123SEL_SHIFT;
val = sel << RCC_D2CCIP1R_SPI123SEL_SHIFT;
break;
case SPI4_BASE:
case SPI5_BASE:
reg = &RCC_D2CCIP1R;
mask = RCC_D2CCIP1R_SPI45SEL_MASK << RCC_D2CCIP1R_SPI45SEL_SHIFT;
val = sel << RCC_D2CCIP1R_SPI45SEL_SHIFT;
break;
case USART1_BASE:
case USART6_BASE:
reg = &RCC_D2CCIP2R;
mask = RCC_D2CCIP2R_USARTSEL_MASK << RCC_D2CCIP2R_USART16SEL_SHIFT;
val = sel << RCC_D2CCIP2R_USART16SEL_SHIFT;
break;
case USART2_BASE:
case USART3_BASE:
case UART4_BASE:
case UART5_BASE:
case UART7_BASE:
case UART8_BASE:
reg = &RCC_D2CCIP2R;
mask = RCC_D2CCIP2R_USARTSEL_MASK << RCC_D2CCIP2R_USART234578SEL_SHIFT;
val = sel << RCC_D2CCIP2R_USART234578SEL_SHIFT;
break;
default:
cm3_assert_not_reached();
return;
}
// Update the register value by masking and oring in new values.
uint32_t regval = (*reg & mask) | val;
*reg = regval;
}
void rcc_set_fdcan_clksel(uint8_t clksel) {
RCC_D2CCIP1R &= ~(RCC_D2CCIP1R_FDCANSEL_MASK << RCC_D2CCIP1R_FDCANSEL_SHIFT);
RCC_D2CCIP1R |= clksel << RCC_D2CCIP1R_FDCANSEL_SHIFT;
}
void rcc_set_spi123_clksel(uint8_t clksel) {
RCC_D2CCIP1R &= ~(RCC_D2CCIP1R_SPI123SEL_MASK << RCC_D2CCIP1R_SPI123SEL_SHIFT);
RCC_D2CCIP1R |= clksel << RCC_D2CCIP1R_SPI123SEL_SHIFT;
}
void rcc_set_spi45_clksel(uint8_t clksel) {
RCC_D2CCIP1R &= ~(RCC_D2CCIP1R_SPI45SEL_MASK << RCC_D2CCIP1R_SPI45SEL_SHIFT);
RCC_D2CCIP1R |= clksel << RCC_D2CCIP1R_SPI45SEL_SHIFT;
}