Browse Source
Add control driver for STM32WB0 series, with support for all clock sources. Signed-off-by: Mathieu Choplain <mathieu.choplain@st.com>pull/77806/head
5 changed files with 932 additions and 0 deletions
@ -0,0 +1,792 @@
@@ -0,0 +1,792 @@
|
||||
/*
|
||||
* Copyright (c) 2024 STMicroelectronics |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
#include <soc.h> |
||||
#include <stm32_ll_bus.h> |
||||
#include <stm32_ll_pwr.h> |
||||
#include <stm32_ll_rcc.h> |
||||
#include <stm32_ll_radio.h> |
||||
#include <stm32_ll_system.h> |
||||
#include <stm32_ll_utils.h> |
||||
|
||||
#include <zephyr/irq.h> |
||||
#include <zephyr/kernel.h> |
||||
#include <zephyr/sys/util.h> |
||||
#include <zephyr/toolchain.h> |
||||
#include <zephyr/sys/__assert.h> |
||||
#include <zephyr/arch/common/sys_io.h> |
||||
#include <zephyr/arch/common/sys_bitops.h> |
||||
#include <zephyr/drivers/clock_control/stm32_clock_control.h> |
||||
|
||||
/* Driver definitions */ |
||||
#define RCC_REG(_reg_offset) (DT_REG_ADDR(STM32_CLOCK_CONTROL_NODE) + (_reg_offset)) |
||||
#define RADIO_CTRL_IRQn 21 /* Not provided by CMSIS; must be declared manually */ |
||||
|
||||
#define CLOCK_FREQ_64MHZ (64000000U) |
||||
#define CLOCK_FREQ_32MHZ (32000000U) |
||||
#define CLOCK_FREQ_16MHZ (16000000U) |
||||
|
||||
/* Device tree node definitions */ |
||||
#define DT_RCC_SLOWCLK_NODE DT_PHANDLE(STM32_CLOCK_CONTROL_NODE, slow_clock) |
||||
#define DT_LSI_NODE DT_NODELABEL(clk_lsi) |
||||
|
||||
/* Device tree properties definitions */ |
||||
#define STM32_WB0_CLKSYS_PRESCALER \ |
||||
DT_PROP(STM32_CLOCK_CONTROL_NODE, clksys_prescaler) |
||||
|
||||
#if DT_NODE_HAS_PROP(STM32_CLOCK_CONTROL_NODE, slow_clock) |
||||
|
||||
# if !DT_NODE_HAS_STATUS(DT_RCC_SLOWCLK_NODE, okay) |
||||
# error slow-clock source is not enabled |
||||
# endif |
||||
|
||||
# if DT_SAME_NODE(DT_RCC_SLOWCLK_NODE, DT_LSI_NODE) |
||||
# define STM32_WB0_SLOWCLK_SRC LL_RCC_LSCO_CLKSOURCE_LSI |
||||
# elif DT_SAME_NODE(DT_RCC_SLOWCLK_NODE, DT_NODELABEL(clk_lse)) |
||||
# define STM32_WB0_SLOWCLK_SRC LL_RCC_LSCO_CLKSOURCE_LSE |
||||
# elif DT_SAME_NODE(DT_RCC_SLOWCLK_NODE, DT_NODELABEL(clk_16mhz_div512)) |
||||
# define STM32_WB0_SLOWCLK_SRC LL_RCC_LSCO_CLKSOURCE_HSI64M_DIV2048 |
||||
# else |
||||
# error Invalid device selected as slow-clock |
||||
# endif |
||||
|
||||
#endif /* DT_NODE_HAS_PROP(STM32_CLOCK_CONTROL_NODE, slow_clock) */ |
||||
|
||||
#if DT_NODE_HAS_PROP(DT_LSI_NODE, runtime_measurement_interval) |
||||
#define STM32_WB0_RUNTIME_LSI_CALIBRATION 1 |
||||
#define STM32_WB0_LSI_RUNTIME_CALIB_INTERVAL \ |
||||
DT_PROP(DT_LSI_NODE, runtime_measurement_interval) |
||||
#endif /* DT_NODE_HAS_PROP(clk_lsi, runtime_calibration_settings) */ |
||||
|
||||
/* Verify device tree properties are correct */ |
||||
BUILD_ASSERT(!IS_ENABLED(STM32_SYSCLK_SRC_HSE) || STM32_WB0_CLKSYS_PRESCALER != 64, |
||||
"clksys-prescaler cannot be 64 when SYSCLK source is Direct HSE"); |
||||
|
||||
#if defined(STM32_LSI_ENABLED) |
||||
/* Check clock configuration allows MR_BLE IP to work.
|
||||
* This IP is required to perform LSI measurements. |
||||
*/ |
||||
# if defined(STM32_SYSCLK_SRC_HSI) |
||||
/* When using HSI without PLL, the "16MHz" output is not actually 16MHz, since
|
||||
* the RC64M generator is imprecise. In this configuration, MR_BLE is broken. |
||||
* The CPU and MR_BLE must be running at 32MHz for MR_BLE to work with HSI. |
||||
*/ |
||||
BUILD_ASSERT(CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC >= CLOCK_FREQ_32MHZ, |
||||
"System clock frequency must be at least 32MHz to use LSI"); |
||||
# else |
||||
/* In PLL or Direct HSE mode, the clock is stable, so 16MHz can be used. */ |
||||
BUILD_ASSERT(CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC >= CLOCK_FREQ_16MHZ, |
||||
"System clock frequency must be at least 16MHz to use LSI"); |
||||
# endif /* STM32_SYSCLK_SRC_HSI */ |
||||
|
||||
/**
|
||||
* @brief Variable holding the "current frequency of LSI", according |
||||
* to the measurement process. This variable is updated each time |
||||
* a new measurement of the LSI frequency is performed. |
||||
*/ |
||||
static volatile uint32_t stm32wb0_lsi_frequency = STM32_LSI_FREQ; |
||||
|
||||
/**
|
||||
* @brief Perform a measurement of the LSI frequency and updates |
||||
* the @p stm32wb0_lsi_frequency global variable based on the results. |
||||
* |
||||
* @param wait_event Semaphore to wait for completion of the measurement |
||||
* If NULL, RADIO_CONTROL registers are polled instead. |
||||
*/ |
||||
static void measure_lsi_frequency(struct k_sem *wait_event) |
||||
{ |
||||
uint32_t fast_clock_cycles_elapsed; |
||||
|
||||
/* Ensure calibration flag is clear */ |
||||
LL_RADIO_TIMER_ClearFlag_LSICalibrationEnded(RADIO_CTRL); |
||||
|
||||
/* Setup the calibration parameters
|
||||
* |
||||
* NOTE: (size - 1) is required to get the correct count, |
||||
* because the value in the register is one less than the |
||||
* actual number of periods requested for calibration. |
||||
*/ |
||||
LL_RADIO_TIMER_SetLSIWindowCalibrationLength(RADIO_CTRL, |
||||
(CONFIG_STM32WB0_LSI_MEASUREMENT_WINDOW - 1)); |
||||
|
||||
/* Start LSI calibration */ |
||||
LL_RADIO_TIMER_StartLSICalibration(RADIO_CTRL); |
||||
|
||||
if (wait_event) { |
||||
/* Wait for semaphore to be signaled */ |
||||
k_sem_take(wait_event, K_FOREVER); |
||||
} else { |
||||
while (!LL_RADIO_TIMER_IsActiveFlag_LSICalibrationEnded(RADIO_CTRL)) { |
||||
/* Wait for calibration to finish (polling) */ |
||||
} |
||||
|
||||
/* Clear calibration complete flag / interrupt */ |
||||
LL_RADIO_TIMER_ClearFlag_LSICalibrationEnded(RADIO_CTRL); |
||||
} |
||||
|
||||
/* Read calibration results */ |
||||
fast_clock_cycles_elapsed = LL_RADIO_TIMER_GetLSIPeriod(RADIO_CTRL); |
||||
|
||||
/**
|
||||
* Calculate LSI frequency from calibration results and update |
||||
* the corresponding global variable |
||||
* |
||||
* LSI calibration counts the amount of 16MHz clock half-periods that |
||||
* occur until a certain amount of slow clock cycles have been observed. |
||||
* |
||||
* @p fast_clock_cycles_elapsed is the number of 16MHz clock half-periods |
||||
* elapsed while waiting for @p STM32_WB0_LSI_MEASURE_WINDOW_SIZE LSI periods |
||||
* to occur. The LSI frequency can be calculated the following way: |
||||
* |
||||
* t = <number of periods counted> / <clock frequency> |
||||
* |
||||
* ==> Time taken for calibration: |
||||
* |
||||
* tCALIB = @p fast_clock_cycles_elapsed / (2 * 16MHz) |
||||
* |
||||
* ==> LSI period: |
||||
* |
||||
* tLSI = tCALIB / @p STM32_WB0_LSI_MEASURE_WINDOW_SIZE |
||||
* |
||||
* ( @p fast_clock_cycles_elapsed / (2 * 16MHz) ) |
||||
* = ------------------------------------------------ |
||||
* @p STM32_WB0_LSI_MEASURE_WINDOW_SIZE |
||||
* |
||||
* ==> LSI frequency: |
||||
* |
||||
* fLSI = (1 / tLSI) |
||||
* |
||||
* @p STM32_WB0_LSI_MEASURE_WINDOW_SIZE |
||||
* = ------------------------------------------------ |
||||
* ( @p fast_clock_cycles_elapsed / (2 * 16MHz) ) |
||||
* |
||||
* (2 * 16MHz) * @p STM32_WB0_LSI_MEASURE_WINDOW_SIZE |
||||
* = ----------------------------------------------------- |
||||
* @p fast_clock_cycles_elapsed |
||||
* |
||||
* NOTE: The division must be performed first to avoid 32-bit overflow. |
||||
*/ |
||||
stm32wb0_lsi_frequency = |
||||
(CLOCK_FREQ_32MHZ / fast_clock_cycles_elapsed) |
||||
* CONFIG_STM32WB0_LSI_MEASUREMENT_WINDOW; |
||||
} |
||||
#endif /* STM32_LSI_ENABLED */ |
||||
|
||||
/** @brief Verifies if provided domain / bus clock is currently active */ |
||||
int enabled_clock(uint32_t src_clk) |
||||
{ |
||||
int r = 0; |
||||
|
||||
switch (src_clk) { |
||||
case STM32_SRC_SYSCLK: |
||||
break; |
||||
case STM32_SRC_LSE: |
||||
if (!IS_ENABLED(STM32_LSE_ENABLED)) { |
||||
r = -ENOTSUP; |
||||
} |
||||
break; |
||||
case STM32_SRC_LSI: |
||||
if (!IS_ENABLED(STM32_LSI_ENABLED)) { |
||||
r = -ENOTSUP; |
||||
} |
||||
break; |
||||
case STM32_SRC_CLKSLOWMUX: |
||||
break; |
||||
case STM32_SRC_CLK16MHZ: |
||||
break; |
||||
case STM32_SRC_CLK32MHZ: |
||||
break; |
||||
default: |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
return r; |
||||
} |
||||
|
||||
static inline int stm32_clock_control_on(const struct device *dev, |
||||
clock_control_subsys_t sub_system) |
||||
{ |
||||
struct stm32_pclken *pclken = (struct stm32_pclken *)(sub_system); |
||||
const mem_addr_t reg = RCC_REG(pclken->bus); |
||||
volatile uint32_t temp; |
||||
|
||||
ARG_UNUSED(dev); |
||||
if (!IN_RANGE(pclken->bus, STM32_PERIPH_BUS_MIN, STM32_PERIPH_BUS_MAX)) { |
||||
/* Attempting to change domain clock */ |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
sys_set_bits(reg, pclken->enr); |
||||
|
||||
/* Read back register to be blocked by RCC
|
||||
* until peripheral clock enabling is complete |
||||
*/ |
||||
temp = sys_read32(reg); |
||||
UNUSED(temp); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static inline int stm32_clock_control_off(const struct device *dev, |
||||
clock_control_subsys_t sub_system) |
||||
{ |
||||
struct stm32_pclken *pclken = (struct stm32_pclken *)(sub_system); |
||||
const mem_addr_t reg = RCC_REG(pclken->bus); |
||||
|
||||
ARG_UNUSED(dev); |
||||
if (!IN_RANGE(pclken->bus, STM32_PERIPH_BUS_MIN, STM32_PERIPH_BUS_MAX)) { |
||||
/* Attempting to change domain clock */ |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
sys_clear_bits(reg, pclken->enr); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static inline int stm32_clock_control_configure(const struct device *dev, |
||||
clock_control_subsys_t sub_system, |
||||
void *data) |
||||
{ |
||||
struct stm32_pclken *pclken = (struct stm32_pclken *)sub_system; |
||||
const uint32_t shift = STM32_CLOCK_SHIFT_GET(pclken->enr); |
||||
mem_addr_t reg = RCC_REG(STM32_CLOCK_REG_GET(pclken->enr)); |
||||
int err; |
||||
|
||||
ARG_UNUSED(dev); |
||||
ARG_UNUSED(data); |
||||
|
||||
err = enabled_clock(pclken->bus); |
||||
if (err < 0) { |
||||
/* Attempting to configure an unavailable or invalid clock */ |
||||
return err; |
||||
} |
||||
|
||||
sys_clear_bits(reg, STM32_CLOCK_MASK_GET(pclken->enr) << shift); |
||||
sys_set_bits(reg, STM32_CLOCK_VAL_GET(pclken->enr) << shift); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static inline int get_apb0_periph_clkrate(uint32_t enr, uint32_t *rate, |
||||
uint32_t slow_clock, uint32_t sysclk, uint32_t clk_sys) |
||||
{ |
||||
switch (enr) { |
||||
/* Slow clock peripherals: RTC & IWDG */ |
||||
case LL_APB0_GRP1_PERIPH_RTC: |
||||
case LL_APB0_GRP1_PERIPH_WDG: |
||||
*rate = slow_clock; |
||||
break; |
||||
|
||||
/* SYSCLK peripherals: all timers */ |
||||
#if defined(TIM1) |
||||
case LL_APB0_GRP1_PERIPH_TIM1: |
||||
#endif |
||||
#if defined(TIM2) |
||||
case LL_APB0_GRP1_PERIPH_TIM2: |
||||
#endif |
||||
#if defined(TIM16) |
||||
case LL_APB0_GRP1_PERIPH_TIM16: |
||||
#endif |
||||
#if defined(TIM17) |
||||
case LL_APB0_GRP1_PERIPH_TIM17: |
||||
#endif |
||||
*rate = sysclk; |
||||
break; |
||||
|
||||
/* CLK_SYS peripherals: SYSCFG */ |
||||
case LL_APB0_GRP1_PERIPH_SYSCFG: |
||||
*rate = clk_sys; |
||||
break; |
||||
default: |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static inline int get_apb1_periph_clkrate(uint32_t enr, uint32_t *rate, |
||||
uint32_t clk_sys) |
||||
{ |
||||
switch (enr) { |
||||
#if defined(SPI1) |
||||
case LL_APB1_GRP1_PERIPH_SPI1: |
||||
*rate = clk_sys; |
||||
break; |
||||
#endif |
||||
#if defined(SPI2) |
||||
case LL_APB1_GRP1_PERIPH_SPI2: |
||||
switch (LL_RCC_GetSPI2I2SClockSource()) { |
||||
case LL_RCC_SPI2_I2S_CLK16M: |
||||
*rate = CLOCK_FREQ_16MHZ; |
||||
break; |
||||
case LL_RCC_SPI2_I2S_CLK32M: |
||||
*rate = CLOCK_FREQ_32MHZ; |
||||
break; |
||||
default: |
||||
CODE_UNREACHABLE; |
||||
} |
||||
break; |
||||
#endif |
||||
case LL_APB1_GRP1_PERIPH_SPI3: |
||||
switch (LL_RCC_GetSPI3I2SClockSource()) { |
||||
case LL_RCC_SPI3_I2S_CLK16M: |
||||
*rate = CLOCK_FREQ_16MHZ; |
||||
break; |
||||
case LL_RCC_SPI3_I2S_CLK32M: |
||||
*rate = CLOCK_FREQ_32MHZ; |
||||
break; |
||||
#if defined(LL_RCC_SPI3_I2S_CLK64M) |
||||
case LL_RCC_SPI3_I2S_CLK64M: |
||||
*rate = CLOCK_FREQ_64MHZ; |
||||
break; |
||||
#endif |
||||
default: |
||||
CODE_UNREACHABLE; |
||||
} |
||||
break; |
||||
|
||||
case LL_APB1_GRP1_PERIPH_I2C1: |
||||
#if defined(I2C2) |
||||
case LL_APB1_GRP1_PERIPH_I2C2: |
||||
#endif |
||||
*rate = CLOCK_FREQ_16MHZ; |
||||
break; |
||||
|
||||
case LL_APB1_GRP1_PERIPH_ADCDIG: |
||||
case LL_APB1_GRP1_PERIPH_ADCANA: |
||||
case (LL_APB1_GRP1_PERIPH_ADCDIG | LL_APB1_GRP1_PERIPH_ADCANA): |
||||
/* ADC has two enable bits - accept all combinations. */ |
||||
*rate = CLOCK_FREQ_16MHZ; |
||||
break; |
||||
|
||||
case LL_APB1_GRP1_PERIPH_USART1: |
||||
*rate = CLOCK_FREQ_16MHZ; |
||||
break; |
||||
|
||||
case LL_APB1_GRP1_PERIPH_LPUART1: |
||||
#if !defined(RCC_CFGR_LPUCLKSEL) |
||||
*rate = CLOCK_FREQ_16MHZ; |
||||
#else |
||||
switch (LL_RCC_GetLPUARTClockSource()) { |
||||
case LL_RCC_LPUCLKSEL_CLK16M: |
||||
*rate = CLOCK_FREQ_16MHZ; |
||||
break; |
||||
case LL_RCC_LPUCLKSEL_CLKLSE: |
||||
*rate = STM32_LSE_FREQ; |
||||
break; |
||||
default: |
||||
CODE_UNREACHABLE; |
||||
} |
||||
#endif |
||||
break; |
||||
default: |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int stm32_clock_control_get_subsys_rate(const struct device *dev, |
||||
clock_control_subsys_t sub_system, |
||||
uint32_t *rate) |
||||
{ |
||||
|
||||
struct stm32_pclken *pclken = (struct stm32_pclken *)(sub_system); |
||||
uint32_t sysclk, slow_clock, clk_sys; |
||||
#if defined(STM32_LSI_ENABLED) |
||||
const uint32_t clk_lsi = stm32wb0_lsi_frequency; |
||||
#else |
||||
const uint32_t clk_lsi = 0; |
||||
#endif |
||||
|
||||
|
||||
ARG_UNUSED(dev); |
||||
|
||||
/* Obtain SYSCLK frequency by checking which source drives high-speed clock tree.
|
||||
* If Direct HSE is enabled, the high-speed tree is clocked by HSE @ 32MHz. |
||||
* Otherwise, the high-speed tree is clocked by the RC64MPLL clock @ 64MHz. |
||||
* |
||||
* NOTE: it is NOT possible to use the usual 'SystemCoreClock * Prescaler' approach on |
||||
* STM32WB0 because the prescaler configuration is not affected by input clock variation: |
||||
* setting CLKSYSDIV = 1 results in 32MHz CLK_SYS, regardless of SYSCLK being 32 or 64MHZ. |
||||
*/ |
||||
if (LL_RCC_DIRECT_HSE_IsEnabled()) { |
||||
sysclk = STM32_HSE_FREQ; |
||||
} else { |
||||
sysclk = STM32_HSI_FREQ; |
||||
} |
||||
|
||||
/* Obtain CLK_SYS (AHB0) frequency by using the CLKSYSDIV prescaler value.
|
||||
* |
||||
* NOTE: LL_RCC_GetRC64MPLLPrescaler is strictly identical to LL_RCC_GetDirectHSEPrescaler |
||||
* and can be used regardless of which source is driving the high-speed clock tree. |
||||
* |
||||
* NOTE: the prescaler value must be interpreted as if source clock is 64MHz, regardless |
||||
* of which source is actually driving the high-speed clock tree. This allows using the |
||||
* following formula for calculations. |
||||
* |
||||
* NOTE: (x >> y) is equivalent to (x / 2^y) or (x / (1 << y)). |
||||
*/ |
||||
clk_sys = CLOCK_FREQ_64MHZ >> LL_RCC_GetRC64MPLLPrescaler(); |
||||
|
||||
/* Obtain slow clock tree source by reading RCC_CFGR->LCOSEL.
|
||||
* From this, we can deduce at which frequency the slow clock tree is running. |
||||
*/ |
||||
switch (LL_RCC_LSCO_GetSource()) { |
||||
case LL_RCC_LSCO_CLKSOURCE_LSE: |
||||
slow_clock = STM32_LSE_FREQ; |
||||
break; |
||||
case LL_RCC_LSCO_CLKSOURCE_LSI: |
||||
slow_clock = clk_lsi; |
||||
break; |
||||
case LL_RCC_LSCO_CLKSOURCE_HSI64M_DIV2048: |
||||
slow_clock = CLOCK_FREQ_64MHZ / 2048; |
||||
break; |
||||
default: |
||||
__ASSERT(0, "Illegal slow clock source!"); |
||||
CODE_UNREACHABLE; |
||||
} |
||||
|
||||
switch (pclken->bus) { |
||||
case STM32_CLOCK_BUS_AHB0: |
||||
/* All peripherals on AHB0 are clocked by CLK_SYS. */ |
||||
*rate = clk_sys; |
||||
break; |
||||
case STM32_CLOCK_BUS_APB0: |
||||
return get_apb0_periph_clkrate(pclken->enr, rate, |
||||
slow_clock, sysclk, clk_sys); |
||||
case STM32_CLOCK_BUS_APB1: |
||||
return get_apb1_periph_clkrate(pclken->enr, rate, |
||||
clk_sys); |
||||
case STM32_SRC_SYSCLK: |
||||
*rate = sysclk; |
||||
break; |
||||
case STM32_SRC_LSE: |
||||
*rate = STM32_LSE_FREQ; |
||||
break; |
||||
case STM32_SRC_LSI: |
||||
*rate = clk_lsi; |
||||
break; |
||||
case STM32_SRC_CLKSLOWMUX: |
||||
*rate = slow_clock; |
||||
break; |
||||
case STM32_SRC_CLK16MHZ: |
||||
*rate = CLOCK_FREQ_16MHZ; |
||||
break; |
||||
case STM32_SRC_CLK32MHZ: |
||||
*rate = CLOCK_FREQ_32MHZ; |
||||
break; |
||||
default: |
||||
case STM32_CLOCK_BUS_APB2: |
||||
/* The only periperhal on APB2 is the MR_BLE radio. However,
|
||||
* it is clocked by two sources that run at different frequencies, |
||||
* and we are unable to determine which one this API's caller cares |
||||
* about. For this reason, return ENOTSUP anyways. |
||||
* |
||||
* Note that since the only driver that might call this API is the |
||||
* Bluetooth driver, and since it can already determine both frequencies |
||||
* very easily, this should not pose any problem. |
||||
*/ |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static enum clock_control_status stm32_clock_control_get_status(const struct device *dev, |
||||
clock_control_subsys_t sub_system) |
||||
{ |
||||
struct stm32_pclken *pclken = (struct stm32_pclken *)sub_system; |
||||
|
||||
ARG_UNUSED(dev); |
||||
|
||||
if (IN_RANGE(pclken->bus, STM32_PERIPH_BUS_MIN, STM32_PERIPH_BUS_MAX)) { |
||||
/* Bus / gated clock */ |
||||
if ((sys_read32(RCC_REG(pclken->bus)) & pclken->enr) == pclken->enr) { |
||||
return CLOCK_CONTROL_STATUS_ON; |
||||
} else { |
||||
return CLOCK_CONTROL_STATUS_OFF; |
||||
} |
||||
} else { |
||||
/* Domain clock */ |
||||
if (enabled_clock(pclken->bus) == 0) { |
||||
return CLOCK_CONTROL_STATUS_ON; |
||||
} else { |
||||
return CLOCK_CONTROL_STATUS_OFF; |
||||
} |
||||
} |
||||
} |
||||
|
||||
static struct clock_control_driver_api stm32_clock_control_api = { |
||||
.on = stm32_clock_control_on, |
||||
.off = stm32_clock_control_off, |
||||
.get_rate = stm32_clock_control_get_subsys_rate, |
||||
.get_status = stm32_clock_control_get_status, |
||||
.configure = stm32_clock_control_configure, |
||||
}; |
||||
|
||||
static void set_up_fixed_clock_sources(void) |
||||
{ |
||||
if (IS_ENABLED(STM32_HSE_ENABLED)) { |
||||
/* Enable HSE */ |
||||
LL_RCC_HSE_Enable(); |
||||
while (LL_RCC_HSE_IsReady() != 1) { |
||||
/* Wait for HSE ready */ |
||||
} |
||||
} |
||||
|
||||
if (IS_ENABLED(STM32_HSI_ENABLED)) { |
||||
/* Enable HSI if not enabled */ |
||||
if (LL_RCC_HSI_IsReady() != 1) { |
||||
/* Enable HSI */ |
||||
LL_RCC_HSI_Enable(); |
||||
while (LL_RCC_HSI_IsReady() != 1) { |
||||
/* Wait for HSI ready */ |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (IS_ENABLED(STM32_LSI_ENABLED)) { |
||||
LL_RCC_LSI_Enable(); |
||||
while (LL_RCC_LSI_IsReady() != 1) { |
||||
/* Wait for LSI ready */ |
||||
} |
||||
} |
||||
|
||||
if (IS_ENABLED(STM32_LSE_ENABLED)) { |
||||
#if STM32_LSE_DRIVING |
||||
/* Configure driving capability */ |
||||
LL_RCC_LSE_SetDriveCapability(STM32_LSE_DRIVING << RCC_CSSWCR_LSEDRV_Pos); |
||||
#endif |
||||
/* Unconditionally disable pull-up & pull-down on LSE pins */ |
||||
LL_PWR_SetNoPullB(LL_PWR_GPIO_BIT_12 | LL_PWR_GPIO_BIT_13); |
||||
|
||||
if (IS_ENABLED(STM32_LSE_BYPASS)) { |
||||
/* Configure LSE bypass */ |
||||
LL_RCC_LSE_EnableBypass(); |
||||
} |
||||
|
||||
/* Enable LSE Oscillator (32.768 kHz) */ |
||||
LL_RCC_LSE_Enable(); |
||||
while (!LL_RCC_LSE_IsReady()) { |
||||
/* Wait for LSE ready */ |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* The STM32WB0 prescaler division factor defines vary depending on
|
||||
* whether SYSCLK runs at 32MHz (Direct HSE) or 64MHz (RC64MPLL). |
||||
* The following helper macro wraps this difference. |
||||
*/ |
||||
#if defined(STM32_SYSCLK_SRC_HSE) |
||||
#define LL_PRESCALER(x) LL_RCC_DIRECT_HSE_DIV_ ##x |
||||
#else |
||||
#define LL_PRESCALER(x) LL_RCC_RC64MPLL_DIV_ ##x |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief Converts the Kconfig STM32_WB0_CLKSYS_PRESCALER option |
||||
* to a LL_RCC_RC64MPLL_DIV_x value understandable by the LL. |
||||
*/ |
||||
static inline uint32_t kconfig_to_ll_prescaler(uint32_t kcfg_pre) |
||||
{ |
||||
switch (kcfg_pre) { |
||||
case 1: |
||||
return LL_PRESCALER(1); |
||||
case 2: |
||||
return LL_PRESCALER(2); |
||||
case 4: |
||||
return LL_PRESCALER(4); |
||||
case 8: |
||||
return LL_PRESCALER(8); |
||||
case 16: |
||||
return LL_PRESCALER(16); |
||||
case 32: |
||||
return LL_PRESCALER(32); |
||||
#if !defined(STM32_SYSCLK_SRC_HSE) |
||||
/* A prescaler value of 64 is only valid when running
|
||||
* off RC64MPLL because CLK_SYS must be at least 1MHz |
||||
*/ |
||||
case 64: |
||||
return LL_RCC_RC64MPLL_DIV_64; |
||||
#endif |
||||
} |
||||
CODE_UNREACHABLE; |
||||
} |
||||
#undef LL_PRESCALER /* Undefine helper macro */ |
||||
|
||||
#if CONFIG_STM32WB0_LSI_RUNTIME_MEASUREMENT_INTERVAL != 0 |
||||
K_SEM_DEFINE(lsi_measurement_sema, 0, 1); |
||||
|
||||
#define NUM_SLOW_CLOCK_PERIPHERALS 3 |
||||
|
||||
/**
|
||||
* Reserve one slot for each slow clock peripheral to ensure each |
||||
* peripheral's driver can register a callback to cope with clock drift. |
||||
*/ |
||||
static lsi_update_cb_t lsi_update_callbacks[NUM_SLOW_CLOCK_PERIPHERALS]; |
||||
|
||||
int stm32wb0_register_lsi_update_callback(lsi_update_cb_t cb) |
||||
{ |
||||
for (size_t i = 0; i < ARRAY_SIZE(lsi_update_callbacks); i++) { |
||||
if (lsi_update_callbacks[i] == NULL) { |
||||
lsi_update_callbacks[i] = cb; |
||||
return 0; |
||||
} |
||||
} |
||||
return -ENOMEM; |
||||
} |
||||
|
||||
static void radio_ctrl_isr(void) |
||||
{ |
||||
/* Clear calibration complete flag / interrupt */ |
||||
LL_RADIO_TIMER_ClearFlag_LSICalibrationEnded(RADIO_CTRL); |
||||
|
||||
/* Release the measurement thread */ |
||||
k_sem_give(&lsi_measurement_sema); |
||||
} |
||||
|
||||
static void lsi_rt_measure_loop(void) |
||||
{ |
||||
uint32_t old, new; |
||||
|
||||
while (1) { |
||||
/* Sleep until calibration interval elapses */ |
||||
k_sleep(K_MSEC(CONFIG_STM32WB0_LSI_RUNTIME_MEASUREMENT_INTERVAL)); |
||||
|
||||
old = stm32wb0_lsi_frequency; |
||||
|
||||
/* Ensure the MR_BLE IP clock is enabled. */ |
||||
if (!LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_MRBLE)) { |
||||
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_MRBLE); |
||||
} |
||||
|
||||
/* Perform measurement, making sure we sleep on the semaphore
|
||||
* signaled by the "measurement complete" interrupt handler |
||||
*/ |
||||
measure_lsi_frequency(&lsi_measurement_sema); |
||||
|
||||
new = stm32wb0_lsi_frequency; |
||||
|
||||
/* If LSI frequency changed, invoke all registered callbacks */ |
||||
if (new != old) { |
||||
for (size_t i = 0; i < ARRAY_SIZE(lsi_update_callbacks); i++) { |
||||
if (lsi_update_callbacks[i]) { |
||||
lsi_update_callbacks[i](stm32wb0_lsi_frequency); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#define LSI_RTM_THREAD_STACK_SIZE 512 |
||||
#define LSI_RTM_THREAD_PRIORITY K_LOWEST_APPLICATION_THREAD_PRIO |
||||
|
||||
K_KERNEL_THREAD_DEFINE(lsi_rt_measurement_thread, |
||||
LSI_RTM_THREAD_STACK_SIZE, |
||||
lsi_rt_measure_loop, |
||||
NULL, NULL, NULL, |
||||
LSI_RTM_THREAD_PRIORITY, /* No options */ 0, |
||||
/* No delay (automatic start by kernel) */ 0); |
||||
#endif |
||||
|
||||
int stm32_clock_control_init(const struct device *dev) |
||||
{ |
||||
ARG_UNUSED(dev); |
||||
|
||||
/* Set flash latency according to target CLK_SYS frequency:
|
||||
* - 1 wait state when CLK_SYS > 32MHz (i.e., 64MHz configuration) |
||||
* - 0 wait states otherwise (CLK_SYS <= 32MHz) |
||||
*/ |
||||
LL_FLASH_SetLatency( |
||||
(CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC >= CLOCK_FREQ_32MHZ) |
||||
? LL_FLASH_LATENCY_1 |
||||
: LL_FLASH_LATENCY_0 |
||||
); |
||||
|
||||
/* Unconditionally enable SYSCFG clock for other drivers */ |
||||
LL_APB0_GRP1_EnableClock(LL_APB0_GRP1_PERIPH_SYSCFG); |
||||
|
||||
/* Set up indiviual enabled clocks */ |
||||
set_up_fixed_clock_sources(); |
||||
|
||||
/* Set up the slow clock mux */ |
||||
#if defined(STM32_WB0_SLOWCLK_SRC) |
||||
LL_RCC_LSCO_SetSource(STM32_WB0_SLOWCLK_SRC); |
||||
#endif |
||||
|
||||
#if defined(STM32_SYSCLK_SRC_HSE) |
||||
/* Select Direct HSE as SYSCLK source */ |
||||
LL_RCC_DIRECT_HSE_Enable(); |
||||
|
||||
while (LL_RCC_DIRECT_HSE_IsEnabled() == 0) { |
||||
/* Wait until Direct HSE is ready */ |
||||
} |
||||
#elif defined(STM32_SYSCLK_SRC_HSI) || defined(STM32_SYSCLK_SRC_PLL) |
||||
/* Select RC64MPLL (HSI/PLL) block as SYSCLK source. */ |
||||
LL_RCC_DIRECT_HSE_Disable(); |
||||
|
||||
# if defined(STM32_SYSCLK_SRC_PLL) |
||||
BUILD_ASSERT(IS_ENABLED(STM32_HSE_ENABLED), |
||||
"STM32WB0 PLL requires HSE to be enabled!"); |
||||
|
||||
/* Turn on the PLL part of RC64MPLL block */ |
||||
LL_RCC_RC64MPLL_Enable(); |
||||
while (LL_RCC_RC64MPLL_IsReady() == 0) { |
||||
/* Wait until PLL is ready */ |
||||
} |
||||
|
||||
# endif /* STM32_SYSCLK_SRC_PLL */ |
||||
#endif /* STM32_SYSCLK_SRC_* */ |
||||
|
||||
/* Set CLK_SYS prescaler */ |
||||
LL_RCC_SetRC64MPLLPrescaler( |
||||
kconfig_to_ll_prescaler(STM32_WB0_CLKSYS_PRESCALER)); |
||||
|
||||
SystemCoreClock = CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC; |
||||
|
||||
#if defined(STM32_LSI_ENABLED) |
||||
/* Enable MR_BLE clock for LSI measurement.
|
||||
* This is needed because we use part of the MR_BLE hardware |
||||
* to perform this measurement. |
||||
*/ |
||||
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_MRBLE); |
||||
|
||||
/* Perform a measure of the LSI frequency */ |
||||
measure_lsi_frequency(NULL); |
||||
|
||||
#if CONFIG_STM32WB0_LSI_RUNTIME_MEASUREMENT_INTERVAL == 0 |
||||
/* Disable the MR_BLE clock after the measurement */ |
||||
LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_MRBLE); |
||||
#else |
||||
/* MR_BLE clock must not be disabled, as we're
|
||||
* about to access registers of the IP again. |
||||
*/ |
||||
|
||||
/* Enable LSI measurement complete IRQ at NVIC level */ |
||||
IRQ_CONNECT(RADIO_CTRL_IRQn, IRQ_PRIO_LOWEST, |
||||
radio_ctrl_isr, NULL, 0); |
||||
irq_enable(RADIO_CTRL_IRQn); |
||||
|
||||
/* Unmask IRQ at peripheral level */ |
||||
LL_RADIO_TIMER_EnableLSICalibrationIT(RADIO_CTRL); |
||||
#endif /* CONFIG_STM32WB0_LSI_RUNTIME_MEASUREMENT_INTERVAL == 0 */ |
||||
#endif /* STM32_LSI_ENABLED*/ |
||||
return 0; |
||||
} |
||||
|
||||
/**
|
||||
* @brief Reset & Clock Controller device |
||||
* Note that priority is intentionally set to 1, |
||||
* so that RCC init runs just after SoC init. |
||||
*/ |
||||
DEVICE_DT_DEFINE(STM32_CLOCK_CONTROL_NODE, |
||||
&stm32_clock_control_init, |
||||
NULL, NULL, NULL, |
||||
PRE_KERNEL_1, |
||||
CONFIG_CLOCK_CONTROL_INIT_PRIORITY, |
||||
&stm32_clock_control_api); |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2024 STMicroelectronics |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_STM32WB0_CLOCK_H_ |
||||
#define ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_STM32WB0_CLOCK_H_ |
||||
|
||||
/** Define system & low-speed clocks */ |
||||
#include "stm32_common_clocks.h" |
||||
|
||||
/** Other fixed clocks.
|
||||
* - CLKSLOWMUX: used to query slow clock tree frequency |
||||
* - CLK16MHZ: secondary clock for LPUART, SPI3/I2S and BLE |
||||
* - CLK32MHZ: secondary clock for SPI3/I2S and BLE |
||||
*/ |
||||
#define STM32_SRC_CLKSLOWMUX (STM32_SRC_LSI + 1) |
||||
#define STM32_SRC_CLK16MHZ (STM32_SRC_CLKSLOWMUX + 1) |
||||
#define STM32_SRC_CLK32MHZ (STM32_SRC_CLK16MHZ + 1) |
||||
|
||||
/** Bus clocks */ |
||||
#define STM32_CLOCK_BUS_AHB0 0x50 |
||||
#define STM32_CLOCK_BUS_APB0 0x54 |
||||
#define STM32_CLOCK_BUS_APB1 0x58 |
||||
#define STM32_CLOCK_BUS_APB2 0x60 |
||||
|
||||
#define STM32_PERIPH_BUS_MIN STM32_CLOCK_BUS_AHB0 |
||||
#define STM32_PERIPH_BUS_MAX STM32_CLOCK_BUS_APB2 |
||||
|
||||
#define STM32_CLOCK_REG_MASK (0xFFFFU) |
||||
#define STM32_CLOCK_REG_SHIFT (0U) |
||||
#define STM32_CLOCK_SHIFT_MASK (0x3FU) |
||||
#define STM32_CLOCK_SHIFT_SHIFT (16U) |
||||
#define STM32_CLOCK_MASK_MASK (0x1FU) |
||||
#define STM32_CLOCK_MASK_SHIFT (22U) |
||||
#define STM32_CLOCK_VAL_MASK STM32_CLOCK_MASK_MASK |
||||
#define STM32_CLOCK_VAL_SHIFT (27U) |
||||
|
||||
/**
|
||||
* @brief STM32 clock configuration bit field |
||||
* |
||||
* @param reg Offset to target configuration register in RCC |
||||
* @param shift Position of field within RCC register (= field LSB's index) |
||||
* @param mask Mask of field in RCC register |
||||
* @param val Field value |
||||
* |
||||
* @note 'reg' range: 0x0~0xFFFF [ 00 : 15 ] |
||||
* @note 'shift' range: 0~63 [ 16 : 21 ] |
||||
* @note 'mask' range: 0x00~0x1F [ 22 : 26 ] |
||||
* @note 'val' range: 0x00~0x1F [ 27 : 31 ] |
||||
*/ |
||||
#define STM32_CLOCK(val, mask, shift, reg) \ |
||||
((((reg) & STM32_CLOCK_REG_MASK) << STM32_CLOCK_REG_SHIFT) | \ |
||||
(((shift) & STM32_CLOCK_SHIFT_MASK) << STM32_CLOCK_SHIFT_SHIFT) | \ |
||||
(((mask) & STM32_CLOCK_MASK_MASK) << STM32_CLOCK_MASK_SHIFT) | \ |
||||
(((val) & STM32_CLOCK_VAL_MASK) << STM32_CLOCK_VAL_SHIFT)) |
||||
|
||||
/** @brief RCC_CFGR register offset */ |
||||
#define CFGR_REG 0x08 |
||||
|
||||
/** @brief RCC_APB2ENR register offset */ |
||||
#define APB2ENR_REG 0x60 |
||||
|
||||
/** @brief Device clk sources selection helpers */ |
||||
#define LPUART1_SEL(val) STM32_CLOCK(val, 1, 13, CFGR_REG) /* WB05/WB09 only */ |
||||
#define SPI2_I2S2_SEL(val) STM32_CLOCK(val, 1, 22, CFGR_REG) /* WB06/WB07 only */ |
||||
/* `mask` is only 0x1 for WB06/WB07, but a single definition with mask=0x3 is acceptable */ |
||||
#define SPI3_I2S3_SEL(val) STM32_CLOCK(val, 3, 22, CFGR_REG) |
||||
|
||||
#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_STM32WB0_CLOCK_H_ */ |
Loading…
Reference in new issue