zephyr/drivers/entropy/entropy_stm32.c
Andreas Sandberg 19c165a119 drivers: entropy: stm32: Add support for STM32L0 SoCs
Add the necessary clock configuration to support STM32L-based
SoCs. This change likely adds support for other STM32 SoCs as well
since the HSI48 clock is configured for all SoCs that support it
(except the STM32L4x) instead of just the STM32G4X.

Signed-off-by: Andreas Sandberg <andreas@sandberg.pp.se>
2020-06-09 14:44:25 +02:00

229 lines
5.3 KiB
C

/*
* Copyright (c) 2017 Erwin Rol <erwin@erwinrol.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT st_stm32_rng
#include <kernel.h>
#include <device.h>
#include <drivers/entropy.h>
#include <random/rand32.h>
#include <init.h>
#include <sys/__assert.h>
#include <sys/util.h>
#include <errno.h>
#include <soc.h>
#include <sys/printk.h>
#include <drivers/clock_control.h>
#include <drivers/clock_control/stm32_clock_control.h>
struct entropy_stm32_rng_dev_cfg {
struct stm32_pclken pclken;
};
struct entropy_stm32_rng_dev_data {
RNG_TypeDef *rng;
struct device *clock;
};
#define DEV_DATA(dev) \
((struct entropy_stm32_rng_dev_data *)(dev)->driver_data)
#define DEV_CFG(dev) \
((const struct entropy_stm32_rng_dev_cfg *)(dev)->config_info)
static void entropy_stm32_rng_reset(RNG_TypeDef *rng)
{
__ASSERT_NO_MSG(rng != NULL);
/* Reset RNG as described in RM0090 Reference manual
* section 24.3.2 Error management.
*/
LL_RNG_ClearFlag_CEIS(rng);
LL_RNG_ClearFlag_SEIS(rng);
LL_RNG_Disable(rng);
LL_RNG_Enable(rng);
}
static int entropy_stm32_got_error(RNG_TypeDef *rng)
{
__ASSERT_NO_MSG(rng != NULL);
if (LL_RNG_IsActiveFlag_CECS(rng)) {
return 1;
}
if (LL_RNG_IsActiveFlag_SECS(rng)) {
return 1;
}
return 0;
}
static int entropy_stm32_wait_ready(RNG_TypeDef *rng)
{
/* Agording to the reference manual it takes 40 periods
* of the RNG_CLK clock signal between two consecutive
* random numbers. Also RNG_CLK may not be smaller than
* HCLK/16. So it should not take more than 640 HCLK
* ticks. Assuming the CPU can do 1 instruction per HCLK
* the number of times to loop before the RNG is ready
* is less than 1000. And that is when assumming the loop
* only takes 1 instruction. So looping a million times
* should be more than enough.
*/
int timeout = 1000000;
__ASSERT_NO_MSG(rng != NULL);
while (!LL_RNG_IsActiveFlag_DRDY(rng)) {
if (entropy_stm32_got_error(rng)) {
return -EIO;
}
if (timeout-- == 0) {
return -ETIMEDOUT;
}
k_yield();
}
if (entropy_stm32_got_error(rng)) {
return -EIO;
} else {
return 0;
}
}
static int entropy_stm32_rng_get_entropy(struct device *dev, uint8_t *buffer,
uint16_t length)
{
struct entropy_stm32_rng_dev_data *dev_data;
int n = sizeof(uint32_t);
int res;
__ASSERT_NO_MSG(dev != NULL);
__ASSERT_NO_MSG(buffer != NULL);
dev_data = DEV_DATA(dev);
__ASSERT_NO_MSG(dev_data != NULL);
/* if the RNG has errors reset it before use */
if (entropy_stm32_got_error(dev_data->rng)) {
entropy_stm32_rng_reset(dev_data->rng);
}
while (length > 0) {
uint32_t rndbits;
uint8_t *p_rndbits = (uint8_t *)&rndbits;
res = entropy_stm32_wait_ready(dev_data->rng);
if (res < 0)
return res;
rndbits = LL_RNG_ReadRandData32(dev_data->rng);
if (length < sizeof(uint32_t))
n = length;
for (int i = 0; i < n; i++) {
*buffer++ = *p_rndbits++;
}
length -= n;
}
return 0;
}
static int entropy_stm32_rng_init(struct device *dev)
{
struct entropy_stm32_rng_dev_data *dev_data;
const struct entropy_stm32_rng_dev_cfg *dev_cfg;
int res;
__ASSERT_NO_MSG(dev != NULL);
dev_data = DEV_DATA(dev);
dev_cfg = DEV_CFG(dev);
__ASSERT_NO_MSG(dev_data != NULL);
__ASSERT_NO_MSG(dev_cfg != NULL);
#if CONFIG_SOC_SERIES_STM32L4X
/* Configure PLLSA11 to enable 48M domain */
LL_RCC_PLLSAI1_ConfigDomain_48M(LL_RCC_PLLSOURCE_MSI,
LL_RCC_PLLM_DIV_1,
24, LL_RCC_PLLSAI1Q_DIV_2);
/* Enable PLLSA1 */
LL_RCC_PLLSAI1_Enable();
/* Enable PLLSAI1 output mapped on 48MHz domain clock */
LL_RCC_PLLSAI1_EnableDomain_48M();
/* Wait for PLLSA1 ready flag */
while (LL_RCC_PLLSAI1_IsReady() != 1) {
}
/* Write the peripherals independent clock configuration register :
* choose PLLSAI1 source as the 48 MHz clock is needed for the RNG
* Linear Feedback Shift Register
*/
LL_RCC_SetRNGClockSource(LL_RCC_RNG_CLKSOURCE_PLLSAI1);
#elif defined(RCC_HSI48_SUPPORT)
#if CONFIG_SOC_SERIES_STM32L0X
/* We need SYSCFG to control VREFINT, so make sure it is clocked */
if (!LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_SYSCFG)) {
return -EINVAL;
}
/* HSI48 requires VREFINT (see RM0376 section 7.2.4). */
LL_SYSCFG_VREFINT_EnableHSI48();
#endif /* CONFIG_SOC_SERIES_STM32L0X */
/* Use the HSI48 for the RNG */
LL_RCC_HSI48_Enable();
while (!LL_RCC_HSI48_IsReady()) {
/* Wait for HSI48 to become ready */
}
LL_RCC_SetRNGClockSource(LL_RCC_RNG_CLKSOURCE_HSI48);
#endif /* CONFIG_SOC_SERIES_STM32L4X */
dev_data->clock = device_get_binding(STM32_CLOCK_CONTROL_NAME);
__ASSERT_NO_MSG(dev_data->clock != NULL);
res = clock_control_on(dev_data->clock,
(clock_control_subsys_t *)&dev_cfg->pclken);
__ASSERT_NO_MSG(res == 0);
LL_RNG_Enable(dev_data->rng);
return 0;
}
static const struct entropy_driver_api entropy_stm32_rng_api = {
.get_entropy = entropy_stm32_rng_get_entropy
};
static const struct entropy_stm32_rng_dev_cfg entropy_stm32_rng_config = {
.pclken = { .bus = DT_INST_CLOCKS_CELL(0, bus),
.enr = DT_INST_CLOCKS_CELL(0, bits) },
};
static struct entropy_stm32_rng_dev_data entropy_stm32_rng_data = {
.rng = (RNG_TypeDef *)DT_INST_REG_ADDR(0),
};
DEVICE_AND_API_INIT(entropy_stm32_rng, DT_INST_LABEL(0),
entropy_stm32_rng_init,
&entropy_stm32_rng_data, &entropy_stm32_rng_config,
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&entropy_stm32_rng_api);