/* * Copyright (c) 2022, STMicroelectronics - All Rights Reserved * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #include #include #if STM32_RNG_VER == 2 #define DT_RNG_COMPAT "st,stm32-rng" #endif #if STM32_RNG_VER == 4 #define DT_RNG_COMPAT "st,stm32mp13-rng" #endif #define RNG_CR 0x00U #define RNG_SR 0x04U #define RNG_DR 0x08U #define RNG_CR_RNGEN BIT(2) #define RNG_CR_IE BIT(3) #define RNG_CR_CED BIT(5) #define RNG_CR_CLKDIV GENMASK(19, 16) #define RNG_CR_CLKDIV_SHIFT 16U #define RNG_CR_CONDRST BIT(30) #define RNG_SR_DRDY BIT(0) #define RNG_SR_CECS BIT(1) #define RNG_SR_SECS BIT(2) #define RNG_SR_CEIS BIT(5) #define RNG_SR_SEIS BIT(6) #define RNG_TIMEOUT_US 100000U #define RNG_TIMEOUT_STEP_US 10U #define TIMEOUT_US_1MS 1000U #define RNG_NIST_CONFIG_A 0x00F40F00U #define RNG_NIST_CONFIG_B 0x01801000U #define RNG_NIST_CONFIG_C 0x00F00D00U #define RNG_NIST_CONFIG_MASK GENMASK(25, 8) #define RNG_MAX_NOISE_CLK_FREQ 48000000U struct stm32_rng_instance { uintptr_t base; unsigned long clock; }; static struct stm32_rng_instance stm32_rng; static void seed_error_recovery(void) { uint8_t i __maybe_unused; /* Recommended by the SoC reference manual */ mmio_clrbits_32(stm32_rng.base + RNG_SR, RNG_SR_SEIS); dmbsy(); #if STM32_RNG_VER == 2 /* No Auto-reset on version 2, need to clean FIFO */ for (i = 12U; i != 0U; i--) { (void)mmio_read_32(stm32_rng.base + RNG_DR); } dmbsy(); #endif if ((mmio_read_32(stm32_rng.base + RNG_SR) & RNG_SR_SEIS) != 0U) { ERROR("RNG noise\n"); panic(); } } static uint32_t stm32_rng_clock_freq_restrain(void) { unsigned long clock_rate; uint32_t clock_div = 0U; clock_rate = clk_get_rate(stm32_rng.clock); /* * Get the exponent to apply on the CLKDIV field in RNG_CR register * No need to handle the case when clock-div > 0xF as it is physically * impossible */ while ((clock_rate >> clock_div) > RNG_MAX_NOISE_CLK_FREQ) { clock_div++; } VERBOSE("RNG clk rate : %lu\n", clk_get_rate(stm32_rng.clock) >> clock_div); return clock_div; } static int stm32_rng_enable(void) { uint32_t sr; uint64_t timeout; uint32_t clock_div __maybe_unused; #if STM32_RNG_VER == 2 mmio_write_32(stm32_rng.base + RNG_CR, RNG_CR_RNGEN | RNG_CR_CED); #endif #if STM32_RNG_VER == 4 /* Reset internal block and disable CED bit */ clock_div = stm32_rng_clock_freq_restrain(); /* Update configuration fields */ mmio_clrsetbits_32(stm32_rng.base + RNG_CR, RNG_NIST_CONFIG_MASK, RNG_NIST_CONFIG_A | RNG_CR_CONDRST | RNG_CR_CED); mmio_clrsetbits_32(stm32_rng.base + RNG_CR, RNG_CR_CLKDIV, (clock_div << RNG_CR_CLKDIV_SHIFT)); mmio_clrsetbits_32(stm32_rng.base + RNG_CR, RNG_CR_CONDRST, RNG_CR_RNGEN); #endif timeout = timeout_init_us(RNG_TIMEOUT_US); sr = mmio_read_32(stm32_rng.base + RNG_SR); while ((sr & RNG_SR_DRDY) == 0U) { if (timeout_elapsed(timeout)) { WARN("Timeout waiting\n"); return -ETIMEDOUT; } if ((sr & (RNG_SR_SECS | RNG_SR_SEIS)) != 0U) { seed_error_recovery(); timeout = timeout_init_us(RNG_TIMEOUT_US); } udelay(RNG_TIMEOUT_STEP_US); sr = mmio_read_32(stm32_rng.base + RNG_SR); } VERBOSE("Init RNG done\n"); return 0; } /* * stm32_rng_read - Read a number of random bytes from RNG * out: pointer to the output buffer * size: number of bytes to be read * Return 0 on success, non-0 on failure */ int stm32_rng_read(uint8_t *out, uint32_t size) { uint8_t *buf = out; size_t len = size; int nb_tries; uint32_t data32; int rc = 0; unsigned int count; if (stm32_rng.base == 0U) { return -EPERM; } while (len != 0U) { nb_tries = RNG_TIMEOUT_US / RNG_TIMEOUT_STEP_US; do { uint32_t status = mmio_read_32(stm32_rng.base + RNG_SR); if ((status & (RNG_SR_SECS | RNG_SR_SEIS)) != 0U) { seed_error_recovery(); } udelay(RNG_TIMEOUT_STEP_US); nb_tries--; if (nb_tries == 0) { rc = -ETIMEDOUT; goto bail; } } while ((mmio_read_32(stm32_rng.base + RNG_SR) & RNG_SR_DRDY) == 0U); count = 4U; while (len != 0U) { data32 = mmio_read_32(stm32_rng.base + RNG_DR); count--; memcpy(buf, &data32, MIN(len, sizeof(uint32_t))); buf += MIN(len, sizeof(uint32_t)); len -= MIN(len, sizeof(uint32_t)); if (count == 0U) { break; } } } bail: if (rc != 0) { memset(out, 0, buf - out); } return rc; } /* * stm32_rng_init: Initialize rng from DT * return 0 on success, negative value on failure */ int stm32_rng_init(void) { void *fdt; struct dt_node_info dt_rng; int node; if (stm32_rng.base != 0U) { /* Driver is already initialized */ return 0; } if (fdt_get_address(&fdt) == 0) { panic(); } node = dt_get_node(&dt_rng, -1, DT_RNG_COMPAT); if (node < 0) { return 0; } if (dt_rng.status == DT_DISABLED) { return 0; } assert(dt_rng.base != 0U); stm32_rng.base = dt_rng.base; if (dt_rng.clock < 0) { panic(); } stm32_rng.clock = (unsigned long)dt_rng.clock; clk_enable(stm32_rng.clock); if (dt_rng.reset >= 0) { int ret; ret = stm32mp_reset_assert((unsigned long)dt_rng.reset, TIMEOUT_US_1MS); if (ret != 0) { panic(); } udelay(20); ret = stm32mp_reset_deassert((unsigned long)dt_rng.reset, TIMEOUT_US_1MS); if (ret != 0) { panic(); } } return stm32_rng_enable(); }