// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2019, Linaro Limited * Copyright (c) 2020 Ahmad Fatoum, Pengutronix */ #define pr_fmt(fmt) "stm32-rng: " fmt #include #include #include #include #include #include #include #include #include #define RNG_CR 0x00 #define RNG_CR_RNGEN BIT(2) #define RNG_CR_CED BIT(5) #define RNG_SR 0x04 #define RNG_SR_SEIS BIT(6) #define RNG_SR_CEIS BIT(5) #define RNG_SR_SECS BIT(2) #define RNG_SR_DRDY BIT(0) #define RNG_DR 0x08 struct stm32_rng { struct clk *clk; void __iomem *base; struct hwrng hwrng; }; static inline struct stm32_rng *to_stm32_rng(struct hwrng *hwrng) { return container_of(hwrng, struct stm32_rng, hwrng); } static int stm32_rng_read(struct hwrng *hwrng, void *data, size_t len, bool wait) { int ret = 0; u32 sr, count, reg; size_t increment; struct stm32_rng *rng = to_stm32_rng(hwrng); size_t remaining = len; while (remaining) { ret = readl_poll_timeout(rng->base + RNG_SR, sr, sr & RNG_SR_DRDY, 10 * USEC_PER_MSEC); if (ret) goto out; if (sr & (RNG_SR_SEIS | RNG_SR_SECS)) { int i; /* As per SoC TRM */ clrbits_le32(rng->base + RNG_SR, RNG_SR_SEIS); for (i = 0; i < 12; i++) readl(rng->base + RNG_DR); if (readl(rng->base + RNG_SR) & RNG_SR_SEIS) { pr_warn("RNG Noise"); ret = -EIO; goto out; } /* start again */ continue; } /* * Once the DRDY bit is set, the RNG_DR register can * be read four consecutive times. */ count = 4; while (remaining && count) { reg = readl(rng->base + RNG_DR); memcpy(data, ®, min(remaining, sizeof(u32))); increment = min(remaining, sizeof(u32)); data += increment; remaining -= increment; count--; } } out: return len ?: ret; } static int stm32_rng_init(struct hwrng *hwrng) { int err; struct stm32_rng *rng = to_stm32_rng(hwrng); err = clk_enable(rng->clk); if (err) return err; /* Disable CED */ writel(RNG_CR_RNGEN | RNG_CR_CED, rng->base + RNG_CR); /* clear error indicators */ writel(0, rng->base + RNG_SR); return 0; } static void stm32_rng_remove(struct device_d *dev) { struct stm32_rng *rng = dev->priv; writel(0, rng->base + RNG_CR); clk_disable(rng->clk); } static int stm32_rng_probe(struct device_d *dev) { struct stm32_rng *rng; struct resource *res; int ret; rng = xzalloc(sizeof(*rng)); dev->priv = rng; res = dev_request_mem_resource(dev, 0); if (IS_ERR(res)) return PTR_ERR(res); rng->base = IOMEM(res->start); rng->clk = clk_get(dev, NULL); if (IS_ERR(rng->clk)) { dev_err(dev, "Can not get clock\n"); return PTR_ERR(rng->clk); } ret = device_reset_us(dev, 20); if (ret) return ret; rng->hwrng.name = dev->name; rng->hwrng.read = stm32_rng_read; rng->hwrng.init = stm32_rng_init; ret = hwrng_register(dev, &rng->hwrng); if (ret) stm32_rng_remove(dev); return ret; } static const struct of_device_id stm32_rng_dt_ids[] = { { .compatible = "st,stm32-rng" }, { /* sentinel */}, }; static struct driver_d stm32_rng_driver = { .name = "stm32-rng", .probe = stm32_rng_probe, .remove = stm32_rng_remove, .of_compatible = stm32_rng_dt_ids, }; device_platform_driver(stm32_rng_driver);