/** @defgroup rcc_file RCC peripheral API
*
* @ingroup peripheral_apis
*
* @brief libopencm3 STM32U5xx Reset and Clock Control
*
* @version 1.0.0
*
* @date 09 Oct 2024
*
* This library supports the Reset and Clock Control System in the STM32U5xx
* series of ARM Cortex Microcontrollers by ST Microelectronics.
*
* LGPL License Terms @ref lgpl_license
*/
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2009 Federico Ruiz-Ugalde
* Copyright (C) 2009 Uwe Hermann
* Copyright (C) 2010 Thomas Otto
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*/
/**@{*/
#include
#include
#include
#include
#include
#include
#define HZ_PER_MHZ 1000000UL
#define HZ_PER_KHZ 1000UL
/* Set the default clock frequencies */
#define RCC_DEFAULT_MSIS_FREQUENCY 4000000U
#define RCC_DEFAULT_HSI48_FREQUENCY 48000000U
#define RCC_DEFAULT_SHSI_FREQUENCY 48000000U
#define RCC_DEFAULT_HSI16_FREQUENCY 16000000U
#define RCC_DEFAULT_LSI_FREQUENCY 32000U
#define RCC_DEFAULT_LSE_FREQUENCY 32768U
/* Local private copy of the clock configuration for providing user with clock tree data. */
static struct {
uint32_t sysclk;
uint32_t hclk;
uint32_t msis;
uint32_t msik;
uint32_t pclk1; /* APB1 clock. */
uint32_t pclk2; /* APB2 clock. */
uint32_t pclk3; /* APB3 clock. */
struct pll_clocks { /* Each PLL output set of data. */
uint32_t p;
uint32_t q;
uint32_t r;
} pll1, pll2, pll3;
uint16_t hse_khz; /* This can't exceed 50MHz */
} rcc_clock_tree = {
.sysclk = RCC_HSI_BASE_FREQUENCY,
.hclk = RCC_HSI_BASE_FREQUENCY,
.msis = RCC_MSI_RANGE_4MHZ,
.msik = RCC_MSI_RANGE_4MHZ,
.pclk1 = RCC_HSI_BASE_FREQUENCY,
.pclk2 = RCC_HSI_BASE_FREQUENCY,
.pclk3 = RCC_HSI_BASE_FREQUENCY,
};
typedef struct pll_clocks pll_clocks_s;
const struct rcc_clock_scale rcc_hsi16mhz_configs = {
.hpre = RCC_CFGR2_HPRE_NODIV,
.ppre1 = RCC_PPRE_NODIV,
.ppre2 = RCC_PPRE_NODIV,
.ahb_frequency = RCC_DEFAULT_HSI16_FREQUENCY,
.apb1_frequency = RCC_DEFAULT_HSI16_FREQUENCY,
.apb2_frequency = RCC_DEFAULT_HSI16_FREQUENCY,
};
static volatile uint32_t *rcc_pll_cfg_reg(const pll_number_e pll_num)
{
/* Dispatch, returning the config register for the selected PLL */
switch (pll_num) {
case RCC_PLL_NUM_1:
return &RCC_PLL1CFGR;
case RCC_PLL_NUM_2:
return &RCC_PLL2CFGR;
case RCC_PLL_NUM_3:
return &RCC_PLL3CFGR;
}
/* Turn any other PLL number passed in into UB */
__builtin_unreachable();
}
static volatile uint32_t *rcc_pll_div_reg(const pll_number_e pll_num)
{
/* Dispatch, returning the config register for the selected PLL */
switch (pll_num) {
case RCC_PLL_NUM_1:
return &RCC_PLL1DIVR;
case RCC_PLL_NUM_2:
return &RCC_PLL2DIVR;
case RCC_PLL_NUM_3:
return &RCC_PLL3DIVR;
}
/* Turn any other PLL number passed in into UB */
__builtin_unreachable();
}
static volatile uint32_t *rcc_pll_frac_reg(const pll_number_e pll_num)
{
/* Dispatch, returning the config register for the selected PLL */
switch (pll_num) {
case RCC_PLL_NUM_1:
return &RCC_PLL1FRACR;
case RCC_PLL_NUM_2:
return &RCC_PLL2FRACR;
case RCC_PLL_NUM_3:
return &RCC_PLL3FRACR;
}
/* Turn any other PLL number passed in into UB */
__builtin_unreachable();
}
static pll_clocks_s *rcc_pll_clock_tree(const pll_number_e pll_num)
{
/* Dispatch, returning the clock tree pointer for the selected PLL */
switch (pll_num) {
case RCC_PLL_NUM_1:
return &rcc_clock_tree.pll1;
case RCC_PLL_NUM_2:
return &rcc_clock_tree.pll2;
case RCC_PLL_NUM_3:
return &rcc_clock_tree.pll3;
}
/* Turn any other PLL number passed in into UB */
__builtin_unreachable();
}
static uint32_t rcc_msi_frequency(const uint8_t range)
{
switch (range) {
case RCC_MSI_RANGE_48MHZ:
return 48000000U;
case RCC_MSI_RANGE_24MHZ:
return 24000000U;
case RCC_MSI_RANGE_16MHZ:
return 16000000U;
case RCC_MSI_RANGE_12MHZ:
return 12000000U;
case RCC_MSI_RANGE_4MHZ:
return 4000000U;
case RCC_MSI_RANGE_2MHZ:
return 2000000U;
case RCC_MSI_RANGE_1M33HZ:
return 1330000U;
case RCC_MSI_RANGE_1MHZ:
return 1000000U;
case RCC_MSI_RANGE_3M072HZ:
return 3072000U;
case RCC_MSI_RANGE_1M536HZ:
return 1536000U;
case RCC_MSI_RANGE_1M024HZ:
return 1024000U;
case RCC_MSI_RANGE_768KHZ:
return 768000U;
case RCC_MSI_RANGE_400KHZ:
return 400000U;
case RCC_MSI_RANGE_200KHZ:
return 200000U;
case RCC_MSI_RANGE_133KHZ:
return 133000U;
case RCC_MSI_RANGE_100KHZ:
return 100000U;
default:
/* All possible values are handled, so turn this into UB */
__builtin_unreachable();
}
}
static uint32_t rcc_pll_input_frequency(const uint32_t hse_frequency, const uint8_t pll_source)
{
switch (pll_source) {
case RCC_PLLCFGR_PLLSRC_MSIS:
return rcc_clock_tree.msis;
case RCC_PLLCFGR_PLLSRC_HSI16:
return RCC_HSI_BASE_FREQUENCY;
case RCC_PLLCFGR_PLLSRC_HSE:
return hse_frequency;
}
/* Turn any other PLL input source passed in into UB */
__builtin_unreachable();
}
static void rcc_configure_pll(
const uint32_t hse_frequency, const pll_config_s *const config, const pll_number_e pll_num)
{
/* Extract the registers we need to touch to (re)configure this PLL */
volatile uint32_t *const pll_cfg_reg = rcc_pll_cfg_reg(pll_num);
volatile uint32_t *const pll_div_reg = rcc_pll_div_reg(pll_num);
volatile uint32_t *const pll_frac_reg = rcc_pll_frac_reg(pll_num);
(void)pll_frac_reg; /* For now we don't support the fractional component of this */
/* Set the PLL source and early return if this PLL is not actually used */
if (config->pll_source == RCC_PLLCFGR_PLLSRC_NONE) {
*pll_cfg_reg &= ~(RCC_PLLCFGR_PLLREN | RCC_PLLCFGR_PLLQEN | RCC_PLLCFGR_PLLPEN | RCC_PLLCFGR_PLLSRC);
return;
}
pll_clocks_s *const pll_tree_ptr = rcc_pll_clock_tree(pll_num);
/* Set up the multiplication factor from the config and route the right clock to the PLL */
*pll_div_reg = RCC_PLLDIVR_DIVN(config->divn);
*pll_cfg_reg &= ~(RCC_PLLCFGR_PLLREN | RCC_PLLCFGR_PLLQEN | RCC_PLLCFGR_PLLPEN | RCC_PLLCFGR_PLLM |
RCC_PLLCFGR_PLLRGE | RCC_PLLCFGR_PLLSRC);
*pll_cfg_reg |= RCC_PLLCFGR_DIVM(config->divm) | (config->pll_source << RCC_PLLCFGR_PLLSRC_SHIFT);
/* Set the PLL input frequency range */
const uint32_t input_frequency = rcc_pll_input_frequency(hse_frequency, config->pll_source);
uint32_t pll_clk = input_frequency / config->divm;
if (pll_clk >= (4U * HZ_PER_MHZ) && pll_clk < (8U * HZ_PER_MHZ)) {
*pll_cfg_reg |= RCC_PLLCFGR_PLLRGE_4_8_MHZ << RCC_PLLCFGR_PLLRGE_SHIFT;
} else if (pll_clk >= (8U * HZ_PER_MHZ) && pll_clk < (16U * HZ_PER_MHZ)) {
*pll_cfg_reg |= RCC_PLLCFGR_PLLRGE_8_16_MHZ << RCC_PLLCFGR_PLLRGE_SHIFT;
}
/* Enable each output the config asks for */
uint32_t pll_vco_clk = pll_clk * config->divn;
if (config->divp > 0U) {
*pll_div_reg |= RCC_PLLDIVR_DIVP(config->divp);
*pll_cfg_reg |= RCC_PLLCFGR_PLLPEN;
pll_tree_ptr->p = pll_vco_clk / config->divp;
}
if (config->divq > 0U) {
*pll_div_reg |= RCC_PLLDIVR_DIVQ(config->divq);
*pll_cfg_reg |= RCC_PLLCFGR_PLLQEN;
pll_tree_ptr->q = pll_vco_clk / config->divq;
}
if (config->divr > 0U) {
*pll_div_reg |= RCC_PLLDIVR_DIVR(config->divr);
*pll_cfg_reg |= RCC_PLLCFGR_PLLREN;
pll_tree_ptr->r = pll_vco_clk / config->divr;
}
/* Attempt to enable and lock PLL. */
const size_t cr_addshift = 2U * pll_num;
RCC_CR |= RCC_CR_PLL1ON << cr_addshift;
while (!(RCC_CR & (RCC_CR_PLL1RDY << cr_addshift)))
continue;
}
static void rcc_set_and_enable_plls(const rcc_pll_config_s *const config)
{
rcc_configure_pll(config->hse_frequency, &config->pll1, RCC_PLL_NUM_1);
rcc_configure_pll(config->hse_frequency, &config->pll2, RCC_PLL_NUM_2);
rcc_configure_pll(config->hse_frequency, &config->pll3, RCC_PLL_NUM_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 uint32_t rcc_prediv_log_skip32_div(uint32_t clk, uint32_t div_val)
{
if (div_val < 8U) {
return clk;
}
if (div_val <= 11U) {
return clk >> (div_val - 7U);
}
return clk >> (div_val - 6U);
}
/* This is a helper to help calculate simple 3-bit log dividers with top bit
* used as enable bit. */
static uint32_t rcc_prediv_3bit_log_div(uint32_t clk, uint32_t div_val)
{
if (div_val < 4U) {
return clk;
}
return clk >> (div_val - 3U);
}
static uint16_t rcc_map_pll_to_epod_div(const uint8_t divm)
{
if (divm >= 16U)
return RCC_PLL1CFGR_PLL1MBOOST_DIV16;
if (divm >= 14U)
return RCC_PLL1CFGR_PLL1MBOOST_DIV14;
if (divm >= 12U)
return RCC_PLL1CFGR_PLL1MBOOST_DIV12;
if (divm >= 10U)
return RCC_PLL1CFGR_PLL1MBOOST_DIV10;
if (divm >= 8U)
return RCC_PLL1CFGR_PLL1MBOOST_DIV8;
if (divm >= 6U)
return RCC_PLL1CFGR_PLL1MBOOST_DIV6;
if (divm >= 4U)
return RCC_PLL1CFGR_PLL1MBOOST_DIV4;
if (divm >= 2U)
return RCC_PLL1CFGR_PLL1MBOOST_DIV2;
return RCC_PLL1CFGR_PLL1MBOOST_DIV1;
}
static void rcc_setup_epod_boost(const uint8_t pll_source, const uint8_t divm)
{
/*
* Set up PLL1 to support the Embedded Power Distribution (EPOD) booster.
* We have one of two paths to take here - either we copy the prescaler for the PLL,
* or we have to drive HSI16 to the PLL so it's definitely in the 4-16MHz window required.
* Which we do is based on whether PLL1 is actually configured to be used.
*/
RCC_PLL1CFGR &= ~(RCC_PLL1CFGR_PLL1MBOOST | RCC_PLLCFGR_PLLSRC_SHIFT);
if (pll_source != RCC_PLLCFGR_PLLSRC_NONE) {
/* Copy the source and prescaling settings over for the PLL */
RCC_PLL1CFGR |=
(rcc_map_pll_to_epod_div(divm) << RCC_PLL1CFGR_PLL1MBOOST_SHIFT) | (pll_source << RCC_PLLCFGR_PLLSRC_SHIFT);
} else {
/* Enable HSI16 and wait for it to become ready */
RCC_CR |= RCC_CR_HSION;
while ((RCC_CR & RCC_CR_HSIRDY) == 0U)
continue;
/* Route it through to PLL1 w/ no prescaling for the boost clock */
RCC_PLL1CFGR |= (RCC_PLLCFGR_PLLSRC_HSI16 << RCC_PLLCFGR_PLLSRC_SHIFT) |
(RCC_PLL1CFGR_PLL1MBOOST_DIV1 << RCC_PLL1CFGR_PLL1MBOOST_SHIFT);
}
}
static void rcc_clock_setup_sysclk_source(const rcc_osc_e sysclock_source)
{
/* Figure out what basic source is required for SYSCLK, and bring that up */
switch (sysclock_source) {
case RCC_MSIS:
RCC_CR |= RCC_CR_MSISON;
while ((RCC_CR & RCC_CR_MSISRDY) == 0U)
continue;
break;
case RCC_HSI16:
RCC_CR |= RCC_CR_HSION;
while ((RCC_CR & RCC_CR_HSIRDY) == 0U)
continue;
break;
case RCC_HSE:
RCC_CR |= RCC_CR_HSEON;
while ((RCC_CR & RCC_CR_HSERDY) == 0U)
continue;
break;
default:
break;
}
}
static void rcc_clock_setup_pll_source(const uint8_t pll_source)
{
/* Figure out what source is required for this PLL, then bring it up */
switch (pll_source) {
case RCC_PLLCFGR_PLLSRC_MSIS:
RCC_CR |= RCC_CR_MSISON;
while ((RCC_CR & RCC_CR_MSISRDY) == 0U)
continue;
break;
case RCC_PLLCFGR_PLLSRC_HSI16:
RCC_CR |= RCC_CR_HSION;
while ((RCC_CR & RCC_CR_HSIRDY) == 0U)
continue;
break;
case RCC_PLLCFGR_PLLSRC_HSE:
RCC_CR |= RCC_CR_HSEON;
while ((RCC_CR & RCC_CR_HSERDY) == 0U)
continue;
break;
default:
break;
}
}
static void rcc_switch_sysclk_source(const uint8_t sw_source)
{
/* Set the source we want to switch to */
RCC_CFGR = (RCC_CFGR & ~RCC_CFGR_SW) | (sw_source << RCC_CFGR_SW_SHIFT);
/* Now spin looking for that source to become active */
while (((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SWS_MASK) != sw_source)
continue;
}
static void rcc_msis_change_range(const uint8_t msis_range)
{
/* Bring the LSE up if this config is for the MSIS to be PLL'd */
if (msis_range & RCC_MSIS_RANGE_PLL) {
RCC_BDCR |= RCC_BDCR_LSEON;
while ((RCC_BDCR & RCC_BDCR_LSERDY) == 0)
continue;
}
/* Temporarily bring up the HSI16 and switch to it so we can reconfig the MSIS */
const bool hsi_off = !(RCC_CR & RCC_CR_HSION);
RCC_CR |= RCC_CR_HSION;
while (!(RCC_CR & RCC_CR_HSIRDY))
continue;
rcc_switch_sysclk_source(RCC_CFGR_SW_HSI16);
/* Switch to the requested range */
const uint8_t range = msis_range & RCC_MSIS_RANGE_MASK;
RCC_ICSCR1 = (RCC_ICSCR1 & ~RCC_ICSCR1_MSISRANGE) | (range << RCC_ICSCR1_MSISRANGE_SHIFT) | RCC_ICSCR1_MSIRGSEL;
RCC_CR |= RCC_CR_MSISON;
/* And wait for it to become ready, switching back to MSIS as source */
while (!(RCC_CR & RCC_CR_MSISRDY))
continue;
rcc_switch_sysclk_source(RCC_CFGR_SW_MSIS);
/* Turn the HSI16 back off if it's not going to be used */
if (hsi_off)
RCC_CR &= ~RCC_CR_HSION;
/* And then ask the MSI PLL to come up if this config requires it */
if (msis_range & RCC_MSIS_RANGE_PLL) {
RCC_CR |= RCC_CR_MSIPLLEN | RCC_CR_MSIPLLSEL;
}
}
void rcc_clock_setup_pll(const rcc_pll_config_s *const config)
{
/* First, set system clock to utilize MSIS, then disable all but MSIS. */
RCC_CR |= RCC_CR_MSISON;
while ((RCC_CR & RCC_CR_MSISRDY) == 0U)
continue;
rcc_switch_sysclk_source(RCC_CFGR_SW_MSIS);
RCC_CR = RCC_CR_MSISON;
/* Now we've got known clocking, make sure the PWR block is enabled */
rcc_periph_clock_enable(RCC_PWR);
/* Bring up all the sources the user needs for this config */
rcc_clock_tree.hse_khz = config->hse_frequency / HZ_PER_KHZ;
rcc_clock_setup_sysclk_source(config->sysclock_source);
rcc_clock_setup_pll_source(config->pll1.pll_source);
rcc_clock_setup_pll_source(config->pll2.pll_source);
rcc_clock_setup_pll_source(config->pll3.pll_source);
/* Now that we're safely running on MSIS, if we're targeting a >55MHz config, set up PLL1MBOOST */
if (config->voltage_scale == PWR_VOS_SCALE_1 || config->voltage_scale == PWR_VOS_SCALE_2) {
rcc_setup_epod_boost(config->pll1.pll_source, config->pll1.divm);
}
/* Now let's setup the power system Vcore source and voltage scaling for the target frequency */
pwr_set_mode(config->power_mode);
pwr_set_vos_scale(config->voltage_scale);
/* Set Flash waitstates. U5 doesn't support prefetch, so nothing to do WRT this. */
flash_set_ws(config->flash_waitstates);
/* 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))
continue;
}
/* User has specified a MSIS frequency */
else if (config->msis_range != RCC_MSIS_RANGE_OFF)
rcc_msis_change_range(config->msis_range);
rcc_clock_tree.msis = rcc_msi_frequency((RCC_ICSCR1 >> RCC_ICSCR1_MSISRANGE_SHIFT) & RCC_ICSCR1_MSISRANGE_MASK);
rcc_clock_tree.msik = rcc_msi_frequency((RCC_ICSCR1 >> RCC_ICSCR1_MSIKRANGE_SHIFT) & RCC_ICSCR1_MSIKRANGE_MASK);
/* Set, enable and lock all of the PLLs from the config. */
rcc_set_and_enable_plls(config);
/* Set up the AHB and APB dividers so we don't exceed any f(max)'s */
RCC_CFGR2 = (RCC_CFGR2 & ~(RCC_CFGR2_HPRE | RCC_CFGR2_PPRE1 | RCC_CFGR2_PPRE2)) |
(config->hpre << RCC_CFGR2_HPRE_SHIFT) | (config->ppre1 << RCC_CFGR2_PPRE1_SHIFT) |
(config->ppre2 << RCC_CFGR2_HPRE_SHIFT);
RCC_CFGR3 = (RCC_CFGR3 & ~RCC_CFGR3_PPRE3) | (config->ppre3 << RCC_CFGR3_PPRE3_SHIFT);
/* Populate our base sysclk settings for use and switch the clock to the requested source */
if (config->sysclock_source == RCC_MSIS) {
/* We're already on the MSIS so just use that as the sysclk directly */
rcc_clock_tree.sysclk = rcc_clock_tree.msis;
} else if (config->sysclock_source == RCC_HSI16) {
/* HSI16 was enabled by rcc_clock_setup_sysclk_source(), so switch to it */
rcc_switch_sysclk_source(RCC_CFGR_SW_HSI16);
rcc_clock_tree.sysclk = RCC_HSI_BASE_FREQUENCY;
} else if (config->sysclock_source == RCC_HSE) {
/* HSE was enabled by rcc_clock_setup_sysclk_source(), so switch to it */
rcc_switch_sysclk_source(RCC_CFGR_SW_HSE);
rcc_clock_tree.sysclk = config->hse_frequency;
} else {
/* PLL1R was enabled by rcc_set_and_enable_plls(), so switch to it */
rcc_switch_sysclk_source(RCC_CFGR_SW_PLL);
rcc_clock_tree.sysclk = rcc_clock_tree.pll1.r;
}
/* Store the running speeds of the divided clock domains now we're on the chosen source for SYSCLK */
rcc_clock_tree.hclk = rcc_prediv_log_skip32_div(rcc_clock_tree.sysclk, config->hpre);
rcc_clock_tree.pclk1 = rcc_prediv_3bit_log_div(rcc_clock_tree.hclk, config->ppre1);
rcc_clock_tree.pclk2 = rcc_prediv_3bit_log_div(rcc_clock_tree.hclk, config->ppre2);
rcc_clock_tree.pclk3 = rcc_prediv_3bit_log_div(rcc_clock_tree.hclk, config->ppre3);
}
void rcc_clock_setup_hsi48(void)
{
RCC_CR |= RCC_CR_HSI48ON;
while (!(RCC_CR & RCC_CR_HSI48RDY))
continue;
}
uint32_t rcc_get_bus_clk_freq(const rcc_clock_source_e source)
{
switch (source) {
case RCC_SYSTICKCLK:
/* Check if we're sourcing SysTick from the CPU clock or not */
if ((STK_CSR & STK_CSR_CLKSOURCE) == STK_CSR_CLKSOURCE_AHB) {
return rcc_clock_tree.hclk;
}
/* Externally sourced means using the SysTick mux in the RCC */
switch ((RCC_CCIPR1 >> RCC_CCIPR1_SYSTICKSEL_SHIFT) & RCC_CCIPR1_SYSTICKSEL_MASK) {
case RCC_CCIPR1_SYSTICKSEL_HCLK_DIV8:
return rcc_clock_tree.hclk / 8U;
case RCC_CCIPR1_SYSTICKSEL_LSI:
/* Check if LSI is divided down or not */
if (RCC_BDCR & RCC_BDCR_LSIPREDIV) {
return RCC_LSI_BASE_FREQUENCY / 128U;
} else {
return RCC_LSI_BASE_FREQUENCY;
}
case RCC_CCIPR1_SYSTICKSEL_LSE:
return RCC_LSE_BASE_FREQUENCY;
default:
cm3_assert_not_reached();
}
case RCC_MSISCLK:
return rcc_clock_tree.msis;
case RCC_MSIKCLK:
return rcc_clock_tree.msik;
case RCC_ICLK:
switch ((RCC_CCIPR1 >> RCC_CCIPR1_ICLKSEL_SHIFT) & RCC_CCIPR1_ICLKSEL_MASK) {
case RCC_CCIPR1_ICLKSEL_HSI48:
return RCC_HSI48_BASE_FREQUENCY;
case RCC_CCIPR1_ICLKSEL_PLL2Q:
return rcc_clock_tree.pll2.q;
case RCC_CCIPR1_ICLKSEL_PLL1Q:
return rcc_clock_tree.pll1.q;
case RCC_CCIPR1_ICLKSEL_MSIK:
return rcc_clock_tree.msik;
default:
cm3_assert_not_reached();
}
case RCC_SYSCLK:
return rcc_clock_tree.sysclk;
case RCC_CPUCLK:
case RCC_HCLK:
case RCC_AHBCLK:
return rcc_clock_tree.hclk;
case RCC_APB1CLK:
return rcc_clock_tree.pclk1;
case RCC_APB2CLK:
return rcc_clock_tree.pclk2;
case RCC_APB3CLK:
return rcc_clock_tree.pclk3;
default:
cm3_assert_not_reached();
}
}
/**
* 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_CFGR2 = (RCC_CFGR2 & ~(RCC_CFGR2_HPRE | RCC_CFGR2_PPRE1 | RCC_CFGR2_PPRE2)) |
(clock->hpre << RCC_CFGR2_HPRE_SHIFT) | (clock->ppre1 << RCC_CFGR2_PPRE1_SHIFT) |
(clock->ppre2 << RCC_CFGR2_HPRE_SHIFT);
RCC_CFGR3 = (RCC_CFGR3 & ~RCC_CFGR3_PPRE3) | (clock->ppre3 << RCC_CFGR3_PPRE3_SHIFT);
rcc_wait_for_osc_ready(RCC_HSI);
rcc_switch_sysclk_source(RCC_CFGR_SW_HSI16);
/* Set the peripheral clock frequencies used. */
rcc_clock_tree.hclk = clock->ahb_frequency;
rcc_clock_tree.pclk1 = clock->apb1_frequency;
rcc_clock_tree.pclk2 = clock->apb2_frequency;
}
/*---------------------------------------------------------------------------*/
/** @brief RCC Get the System Clock Source.
* @returns Unsigned int32. System clock source:
* @li 00 indicates MSIS
* @li 01 indicates HSI16
* @li 02 indicates HSE
* @li 03 indicates PLL
*/
uint32_t rcc_system_clock_source(void)
{
return (RCC_CFGR & RCC_CFGR_SWS) >> RCC_CFGR_SWS_SHIFT;
}
void rcc_wait_for_osc_ready(enum rcc_osc osc)
{
while (!rcc_is_osc_ready(osc))
continue;
}
void rcc_css_enable(void)
{
RCC_CR |= RCC_CR_CSSON;
}
void rcc_css_disable(void)
{
RCC_CR &= ~RCC_CR_CSSON;
}
/*---------------------------------------------------------------------------*/
/** @brief RCC Turn off an Oscillator.
Disable an oscillator and power off.
@note An oscillator cannot be turned off if it is selected as the system clock.
@note The LSE clock is in the backup domain and cannot be disabled until the
backup domain write protection has been removed (see
@ref pwr_disable_backup_domain_write_protect) or the backup domain has been
(see reset @ref rcc_backupdomain_reset).
@param[in] osc Oscillator ID
*/
void rcc_osc_off(enum rcc_osc osc)
{
switch (osc) {
case RCC_PLL3:
RCC_CR &= ~RCC_CR_PLL3ON;
break;
case RCC_PLL2:
RCC_CR &= ~RCC_CR_PLL2ON;
break;
case RCC_PLL1:
RCC_CR &= ~RCC_CR_PLL1ON;
break;
case RCC_HSE:
RCC_CR &= ~RCC_CR_HSEON;
break;
case RCC_SHSI:
RCC_CR &= ~RCC_CR_SHSION;
break;
case RCC_HSI48:
RCC_CR &= ~RCC_CR_HSI48ON;
break;
case RCC_HSI:
RCC_CR &= ~RCC_CR_HSION;
break;
case RCC_MSI:
RCC_CR &= ~RCC_CR_MSIKON;
break;
// TODO: Should we add MSIKER?
case RCC_MSIS:
RCC_CR &= ~RCC_CR_MSISON;
break;
default:
cm3_assert_not_reached();
break;
}
}
/*---------------------------------------------------------------------------*/
/** @brief RCC Turn on an Oscillator.
*
* Enable an oscillator and power on. Each oscillator requires an amount of
* time to settle to a usable state. Refer to datasheets for time delay
* information. A status flag is available to indicate when the oscillator
* becomes ready (see @ref rcc_osc_ready_int_flag and @ref
* rcc_wait_for_osc_ready).
*
* @param osc Oscillator ID
*/
void rcc_osc_on(enum rcc_osc osc)
{
switch (osc) {
case RCC_PLL3:
RCC_CR |= RCC_CR_PLL3ON;
break;
case RCC_PLL2:
RCC_CR |= RCC_CR_PLL2ON;
break;
case RCC_PLL1:
RCC_CR |= RCC_CR_PLL1ON;
break;
case RCC_HSE:
RCC_CR |= RCC_CR_HSEON;
break;
case RCC_SHSI:
RCC_CR |= RCC_CR_SHSION;
break;
case RCC_HSI48:
RCC_CR |= RCC_CR_HSI48ON;
break;
case RCC_HSI:
RCC_CR |= RCC_CR_HSION;
break;
case RCC_MSI:
RCC_CR |= RCC_CR_MSIKON;
break;
// TODO: Should we add MSIKER?
case RCC_MSIS:
RCC_CR |= RCC_CR_MSISON;
break;
default:
cm3_assert_not_reached();
break;
}
}
/*---------------------------------------------------------------------------*/
/** @brief Returns if the oscillator is ready.
*
* @param osc Oscillator ID
*/
bool rcc_is_osc_ready(enum rcc_osc osc)
{
switch (osc) {
case RCC_PLL3:
return RCC_CR & RCC_CR_PLL3RDY;
case RCC_PLL2:
return RCC_CR & RCC_CR_PLL2RDY;
case RCC_PLL1:
return RCC_CR & RCC_CR_PLL1RDY;
case RCC_HSE:
return RCC_CR & RCC_CR_HSERDY;
case RCC_SHSI:
return RCC_CR & RCC_CR_SHSIRDY;
case RCC_HSI48:
return RCC_CR & RCC_CR_HSI48RDY;
case RCC_HSI:
return RCC_CR & RCC_CR_HSIRDY;
case RCC_MSI:
return RCC_CR & RCC_CR_MSIKRDY;
case RCC_MSIS:
return RCC_CR & RCC_CR_MSISRDY;
default:
break;
}
return false;
}
/*---------------------------------------------------------------------------*/
/** @brief RCC Set the Source for the System Clock.
*
* @param clk Oscillator ID.
*/
void rcc_set_sysclk_source(enum rcc_osc clk)
{
uint8_t sw = 0U;
switch (clk) {
case RCC_MSIS:
sw = RCC_CFGR_SW_MSIS;
break;
case RCC_HSI16:
sw = RCC_CFGR_SW_HSI16;
break;
case RCC_HSE:
sw = RCC_CFGR_SW_HSE;
break;
case RCC_PLL1:
sw = RCC_CFGR_SW_PLL;
break;
default:
cm3_assert_not_reached();
break;
}
rcc_switch_sysclk_source(sw);
}
/**
* @brief Set the peripheral clock source
* @param periph peripheral of choice, eg XXX_BASE
* @param sel periphral clock source
*/
void rcc_set_peripheral_clk_sel(uintptr_t periph, uint32_t sel)
{
volatile uint32_t *reg32;
uint32_t shift;
uint32_t mask;
switch (periph) {
/* Handle U(S)ARTs */
case USART1_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_USART1SEL_SHIFT;
mask = RCC_CCIPR1_USARTxSEL_MASK;
break;
case USART2_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_USART2SEL_SHIFT;
mask = RCC_CCIPR1_USARTxSEL_MASK;
break;
case USART3_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_USART3SEL_SHIFT;
mask = RCC_CCIPR1_USARTxSEL_MASK;
break;
case USART4_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_UART4SEL_SHIFT;
mask = RCC_CCIPR1_USARTxSEL_MASK;
break;
case USART5_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_UART5SEL_SHIFT;
mask = RCC_CCIPR1_USARTxSEL_MASK;
break;
case USART6_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_USART6SEL_SHIFT;
mask = RCC_CCIPR2_USART6SEL_MASK;
break;
case LPUART1_BASE:
reg32 = &RCC_CCIPR3;
shift = RCC_CCIPR3_LPUART1SEL_SHIFT;
mask = RCC_CCIPR3_LPUART1SEL_MASK;
break;
/* Handle timers */
case LPTIM1_BASE:
reg32 = &RCC_CCIPR3;
shift = RCC_CCIPR3_LPTIM1SEL_SHIFT;
mask = RCC_CCIPR3_LPTIM1SEL_SHIFT;
break;
case LPTIM2_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_LPTIM2SEL_SHIFT;
mask = RCC_CCIPR1_LPTIM2SEL_SHIFT;
break;
case LPTIM3_BASE:
case LPTIM4_BASE:
reg32 = &RCC_CCIPR3;
shift = RCC_CCIPR3_LPTIM34SEL_SHIFT;
mask = RCC_CCIPR3_LPTIM34SEL_SHIFT;
break;
/* Handle I²C interfaces */
case I2C1_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_I2C1SEL_SHIFT;
mask = RCC_CCIPR1_I2CxSEL_MASK;
break;
case I2C2_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_I2C2SEL_SHIFT;
mask = RCC_CCIPR1_I2CxSEL_MASK;
break;
case I2C3_BASE:
reg32 = &RCC_CCIPR3;
shift = RCC_CCIPR3_I2C3SEL_SHIFT;
mask = RCC_CCIPR3_I2C3SEL_MASK;
break;
case I2C4_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_I2C4SEL_SHIFT;
mask = RCC_CCIPR1_I2CxSEL_MASK;
break;
case I2C5_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_I2C5SEL_SHIFT;
mask = RCC_CCIPR2_I2CxSEL_MASK;
break;
case I2C6_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_I2C6SEL_SHIFT;
mask = RCC_CCIPR2_I2CxSEL_MASK;
break;
/* Handle SPI interfaces */
case SPI1_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_SPI1SEL_SHIFT;
mask = RCC_CCIPR1_SPI1SEL_MASK;
break;
case SPI2_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_SPI2SEL_SHIFT;
mask = RCC_CCIPR1_SPI2SEL_MASK;
break;
case SPI3_BASE:
reg32 = &RCC_CCIPR3;
shift = RCC_CCIPR3_SPI3SEL_SHIFT;
mask = RCC_CCIPR3_SPI3SEL_MASK;
break;
case HSPI1_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_HSPI1SEL_SHIFT;
mask = RCC_CCIPR2_HSPI1SEL_MASK;
break;
case OCTOSPI1_BASE:
case OCTOSPI2_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_OCTOSPISEL_SHIFT;
mask = RCC_CCIPR2_OCTOSPISEL_MASK;
break;
/* Handle FDCAN interfaces */
case FDCAN1_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_FDCAN1SEL_SHIFT;
mask = RCC_CCIPR1_FDCAN1SEL_MASK;
break;
/* Handle USB interfaces */
case USB_OTG_HS_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_OTGHSSEL_SHIFT;
mask = RCC_CCIPR2_OTGHSSEL_MASK;
break;
/* Handle LTDC peripheral */
case LTDC_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_LTDCSEL_SHIFT;
mask = RCC_CCIPR2_LTDCSEL_MASK;
break;
/* Handle DSI peripheral */
case DSI_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_DSISEL_SHIFT;
mask = RCC_CCIPR2_DSISEL_MASK;
break;
/* Handle SDMMC peripherals */
case SDMMC1_BASE:
case SDMMC2_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_SDMMCSEL_SHIFT;
mask = RCC_CCIPR2_SDMMCSEL_MASK;
break;
/* Handle audio peripherals */
case SAES_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_SAESSEL_SHIFT;
mask = RCC_CCIPR2_SAESSEL_MASK;
break;
case SAI1_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_SAI1SEL_SHIFT;
mask = RCC_CCIPR2_SAI1SEL_MASK;
break;
case SAI2_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_SAI2SEL_SHIFT;
mask = RCC_CCIPR2_SAI2SEL_MASK;
break;
case MDF1_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_MDF1SEL_SHIFT;
mask = RCC_CCIPR2_MDF1SEL_MASK;
break;
case ADF1_BASE:
reg32 = &RCC_CCIPR3;
shift = RCC_CCIPR3_ADF1SEL_SHIFT;
mask = RCC_CCIPR3_ADF1SEL_MASK;
break;
/* Handle analog <=> digital conversion peripherals */
case ADC1_BASE:
case ADC2_BASE:
case ADC4_BASE:
case DAC1_BASE:
reg32 = &RCC_CCIPR3;
shift = RCC_CCIPR3_ADCDACSEL_SHIFT;
mask = RCC_CCIPR3_ADCDACSEL_MASK;
break;
/* Handle misc other blocks */
case SYS_TICK_BASE:
reg32 = &RCC_CCIPR1;
shift = RCC_CCIPR1_SYSTICKSEL_SHIFT;
mask = RCC_CCIPR1_SYSTICKSEL_MASK;
break;
case RNG_BASE:
reg32 = &RCC_CCIPR2;
shift = RCC_CCIPR2_RNGSEL_SHIFT;
mask = RCC_CCIPR2_RNGSEL_MASK;
break;
default:
cm3_assert_not_reached();
break;
}
(*reg32) = ((*reg32) & ~(mask << shift)) | (sel << shift);
}
void rcc_set_iclk_clksel(uint32_t clksel)
{
RCC_CCIPR1 &= ~(RCC_CCIPR1_ICLKSEL_MASK << RCC_CCIPR1_ICLKSEL_SHIFT);
RCC_CCIPR1 |= (clksel << RCC_CCIPR1_ICLKSEL_SHIFT);
}
void rcc_set_timic_clksel(uint32_t clksel)
{
RCC_CCIPR1 &= ~(RCC_CCIPR1_TIMICSEL_MASK << RCC_CCIPR1_TIMICSEL_SHIFT);
RCC_CCIPR1 |= (clksel << RCC_CCIPR1_TIMICSEL_SHIFT);
}
void rcc_set_dac1_clksel(uint32_t clksel)
{
RCC_CCIPR3 &= ~(RCC_CCIPR3_DAC1SEL_MASK << RCC_CCIPR3_DAC1SEL_SHIFT);
RCC_CCIPR3 |= (clksel << RCC_CCIPR3_DAC1SEL_SHIFT);
}
static uint32_t rcc_get_usart_clksel_freq(const uintptr_t usart, const uint8_t clksel)
{
switch (clksel) {
case RCC_CCIPR_USARTxSEL_PCLKx:
switch (usart) {
case USART1_BASE:
return rcc_clock_tree.pclk2;
default:
return rcc_clock_tree.pclk1;
}
case RCC_CCIPR_USARTxSEL_SYSCLK:
return rcc_clock_tree.hclk;
case RCC_CCIPR_USARTxSEL_HSI16:
return RCC_DEFAULT_HSI16_FREQUENCY;
case RCC_CCIPR_USARTxSEL_LSE:
return RCC_DEFAULT_LSE_FREQUENCY;
default:
cm3_assert_not_reached();
break;
}
}
uint32_t rcc_get_usart_clk_freq(const uintptr_t usart)
{
switch (usart) {
case USART1_BASE:
return rcc_get_usart_clksel_freq(usart, (RCC_CCIPR1 >> RCC_CCIPR1_USART1SEL_SHIFT) & RCC_CCIPR_USARTxSEL_MASK);
case USART2_BASE:
return rcc_get_usart_clksel_freq(usart, (RCC_CCIPR1 >> RCC_CCIPR1_USART2SEL_SHIFT) & RCC_CCIPR_USARTxSEL_MASK);
case USART3_BASE:
return rcc_get_usart_clksel_freq(usart, (RCC_CCIPR1 >> RCC_CCIPR1_USART3SEL_SHIFT) & RCC_CCIPR_USARTxSEL_MASK);
case USART4_BASE:
return rcc_get_usart_clksel_freq(usart, (RCC_CCIPR1 >> RCC_CCIPR1_UART4SEL_SHIFT) & RCC_CCIPR_USARTxSEL_MASK);
case USART5_BASE:
return rcc_get_usart_clksel_freq(usart, (RCC_CCIPR1 >> RCC_CCIPR1_UART5SEL_SHIFT) & RCC_CCIPR_USARTxSEL_MASK);
case USART6_BASE:
return rcc_get_usart_clksel_freq(usart, (RCC_CCIPR2 >> RCC_CCIPR2_USART6SEL_SHIFT) & RCC_CCIPR_USARTxSEL_MASK);
default:
break;
}
cm3_assert_not_reached();
}
static uint32_t rcc_get_timer_clksel_freq(const uintptr_t timer, const uint8_t clksel)
{
switch (clksel) {
case RCC_CCIPR_LPTIMxSEL_PER_TIMER_SRC:
switch (timer) {
case LPTIM1_BASE:
case LPTIM3_BASE:
case LPTIM4_BASE:
return rcc_clock_tree.msik;
case LPTIM2_BASE:
return rcc_clock_tree.pclk1;
default:
cm3_assert_not_reached();
break;
}
case RCC_CCIPR_LPTIMxSEL_LSI:
return RCC_LSI_BASE_FREQUENCY;
case RCC_CCIPR_LPTIMxSEL_HSI16:
return RCC_DEFAULT_HSI16_FREQUENCY;
case RCC_CCIPR_LPTIMxSEL_LSE:
return RCC_DEFAULT_LSE_FREQUENCY;
default:
cm3_assert_not_reached();
break;
}
}
uint32_t rcc_get_timer_clk_freq(const uintptr_t timer)
{
switch (timer) {
case TIM2_BASE:
case TIM3_BASE:
case TIM4_BASE:
case TIM5_BASE:
case TIM6_BASE:
case TIM7_BASE:
return rcc_clock_tree.pclk1;
case TIM1_BASE:
case TIM8_BASE:
case TIM15_BASE:
case TIM16_BASE:
case TIM17_BASE:
return rcc_clock_tree.pclk2;
case LPTIM1_BASE:
return rcc_get_timer_clksel_freq(timer, (RCC_CCIPR3 >> RCC_CCIPR3_LPTIM1SEL_SHIFT) & RCC_CCIPR3_LPTIM1SEL_MASK);
case LPTIM2_BASE:
return rcc_get_timer_clksel_freq(timer, (RCC_CCIPR1 >> RCC_CCIPR1_LPTIM2SEL_SHIFT) & RCC_CCIPR1_LPTIM2SEL_MASK);
case LPTIM3_BASE:
case LPTIM4_BASE:
return rcc_get_timer_clksel_freq(
timer, (RCC_CCIPR3 >> RCC_CCIPR3_LPTIM34SEL_SHIFT) & RCC_CCIPR3_LPTIM34SEL_MASK);
default:
break;
}
cm3_assert_not_reached();
}
static uint32_t rcc_get_i2c_clksel_freq(const uintptr_t i2c, const uint8_t clksel)
{
switch (clksel) {
case RCC_CCIPR_I2CxSEL_PCLKx:
switch (i2c) {
case I2C1_BASE:
case I2C2_BASE:
case I2C4_BASE:
case I2C5_BASE:
case I2C6_BASE:
return rcc_clock_tree.pclk1;
case I2C3_BASE:
return rcc_clock_tree.pclk3;
default:
cm3_assert_not_reached();
break;
}
case RCC_CCIPR_I2CxSEL_SYSCLK:
return rcc_clock_tree.sysclk;
case RCC_CCIPR_I2CxSEL_HSI16:
return RCC_DEFAULT_HSI16_FREQUENCY;
case RCC_CCIPR_I2CxSEL_MSIK:
return rcc_clock_tree.msik;
default:
cm3_assert_not_reached();
break;
}
}
uint32_t rcc_get_i2c_clk_freq(const uintptr_t i2c)
{
switch (i2c) {
case I2C1_BASE:
return rcc_get_i2c_clksel_freq(i2c, (RCC_CCIPR1 >> RCC_CCIPR1_I2C1SEL_SHIFT) & RCC_CCIPR1_I2CxSEL_MASK);
case I2C2_BASE:
return rcc_get_i2c_clksel_freq(i2c, (RCC_CCIPR1 >> RCC_CCIPR1_I2C2SEL_SHIFT) & RCC_CCIPR1_I2CxSEL_MASK);
case I2C3_BASE:
return rcc_get_i2c_clksel_freq(i2c, (RCC_CCIPR3 >> RCC_CCIPR3_I2C3SEL_SHIFT) & RCC_CCIPR3_I2C3SEL_MASK);
case I2C4_BASE:
return rcc_get_i2c_clksel_freq(i2c, (RCC_CCIPR1 >> RCC_CCIPR1_I2C4SEL_SHIFT) & RCC_CCIPR1_I2CxSEL_MASK);
case I2C5_BASE:
return rcc_get_i2c_clksel_freq(i2c, (RCC_CCIPR2 >> RCC_CCIPR2_I2C5SEL_SHIFT) & RCC_CCIPR2_I2CxSEL_MASK);
case I2C6_BASE:
return rcc_get_i2c_clksel_freq(i2c, (RCC_CCIPR2 >> RCC_CCIPR2_I2C6SEL_SHIFT) & RCC_CCIPR2_I2CxSEL_MASK);
default:
break;
}
cm3_assert_not_reached();
}
static uint32_t rcc_get_spi_clksel_freq(const uintptr_t spi, const uint8_t clksel)
{
switch (clksel) {
case RCC_CCIPR_SPIxSEL_PCLKx:
switch (spi) {
case SPI1_BASE:
return rcc_clock_tree.pclk2;
case SPI2_BASE:
return rcc_clock_tree.pclk1;
case SPI3_BASE:
return rcc_clock_tree.pclk3;
default:
cm3_assert_not_reached();
break;
}
case RCC_CCIPR_SPIxSEL_SYSCLK:
return rcc_clock_tree.sysclk;
case RCC_CCIPR_SPIxSEL_HSI16:
return RCC_DEFAULT_HSI16_FREQUENCY;
case RCC_CCIPR_SPIxSEL_MSIK:
return rcc_clock_tree.msik;
default:
cm3_assert_not_reached();
break;
}
}
uint32_t rcc_get_spi_clk_freq(const uintptr_t spi)
{
switch (spi) {
case SPI1_BASE:
return rcc_get_spi_clksel_freq(spi, (RCC_CCIPR1 >> RCC_CCIPR1_SPI1SEL_SHIFT) & RCC_CCIPR1_SPI1SEL_MASK);
case SPI2_BASE:
return rcc_get_spi_clksel_freq(spi, (RCC_CCIPR1 >> RCC_CCIPR1_SPI2SEL_SHIFT) & RCC_CCIPR1_SPI2SEL_MASK);
case SPI3_BASE:
return rcc_get_spi_clksel_freq(spi, (RCC_CCIPR3 >> RCC_CCIPR3_SPI3SEL_SHIFT) & RCC_CCIPR3_SPI3SEL_MASK);
default:
break;
}
cm3_assert_not_reached();
}
/**@}*/