diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2024-03-15 13:15:18 +0100 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2024-03-15 13:15:18 +0100 |
commit | e4a37472999e8b8305cdcc77998514fae7c785d1 (patch) | |
tree | cc4ca3a24c07a4e861781cfe54e83dd7b3933ee8 | |
parent | 22de6c70a26c3c62ddb03a89602fb5d720c7c860 (diff) | |
parent | 276048a59a225f543e16563ac35103cfe5f2a6e8 (diff) | |
download | barebox-e4a37472999e8b8305cdcc77998514fae7c785d1.tar.gz barebox-e4a37472999e8b8305cdcc77998514fae7c785d1.tar.xz |
Merge branch 'for-next/hwrng'
-rw-r--r-- | arch/arm/dts/rk356x.dtsi | 8 | ||||
-rw-r--r-- | arch/arm/lib32/io.c | 67 | ||||
-rw-r--r-- | arch/arm/mach-omap/am33xx_clock.c | 5 | ||||
-rw-r--r-- | drivers/base/driver.c | 19 | ||||
-rw-r--r-- | drivers/hw_random/Kconfig | 50 | ||||
-rw-r--r-- | drivers/hw_random/Makefile | 6 | ||||
-rw-r--r-- | drivers/hw_random/atmel-rng.c | 165 | ||||
-rw-r--r-- | drivers/hw_random/bcm2835-rng.c | 199 | ||||
-rw-r--r-- | drivers/hw_random/core.c | 8 | ||||
-rw-r--r-- | drivers/hw_random/iproc-rng200.c | 220 | ||||
-rw-r--r-- | drivers/hw_random/omap-rng.c | 436 | ||||
-rw-r--r-- | drivers/hw_random/rockchip-rng.c | 259 | ||||
-rw-r--r-- | drivers/hw_random/timeriomem-rng.c | 145 | ||||
-rw-r--r-- | include/driver.h | 4 | ||||
-rw-r--r-- | include/linux/clk.h | 33 | ||||
-rw-r--r-- | include/linux/hw_random.h | 6 | ||||
-rw-r--r-- | include/mach/omap/am33xx-clock.h | 1 |
17 files changed, 1610 insertions, 21 deletions
diff --git a/arch/arm/dts/rk356x.dtsi b/arch/arm/dts/rk356x.dtsi index 195b42a8b0..923e18e7cc 100644 --- a/arch/arm/dts/rk356x.dtsi +++ b/arch/arm/dts/rk356x.dtsi @@ -22,4 +22,12 @@ reg = <0x0a 0x10>; }; }; + + rng: rng@fe388000 { + compatible = "rockchip,rk3568-rng", "rockchip,cryptov2-rng"; + reg = <0x0 0xfe388000 0x0 0x2000>; + clocks = <&cru CLK_TRNG_NS>, <&cru HCLK_TRNG_NS>; + clock-names = "trng_clk", "trng_hclk"; + resets = <&cru SRST_TRNG_NS>; + }; }; diff --git a/arch/arm/lib32/io.c b/arch/arm/lib32/io.c index a12da49c0a..780b1083a6 100644 --- a/arch/arm/lib32/io.c +++ b/arch/arm/lib32/io.c @@ -2,48 +2,89 @@ #include <module.h> #include <linux/types.h> +#include <asm/unaligned.h> #include <io.h> /* * Copy data from IO memory space to "real" memory space. - * This needs to be optimized. */ void memcpy_fromio(void *to, const volatile void __iomem *from, size_t count) { - unsigned char *t = to; - while (count) { + while (count && !PTR_IS_ALIGNED(from, 4)) { + *(u8 *)to = __raw_readb(from); + from++; + to++; count--; - *t = readb(from); - t++; + } + + while (count >= 4) { + put_unaligned(__raw_readl(from), (u32 *)to); + from += 4; + to += 4; + count -= 4; + } + + while (count) { + *(u8 *)to = __raw_readb(from); from++; + to++; + count--; } } /* * Copy data from "real" memory space to IO memory space. - * This needs to be optimized. */ void memcpy_toio(volatile void __iomem *to, const void *from, size_t count) { - const unsigned char *f = from; - while (count) { + while (count && !IS_ALIGNED((unsigned long)to, 4)) { + __raw_writeb(*(u8 *)from, to); + from++; + to++; count--; - writeb(*f, to); - f++; + } + + while (count >= 4) { + __raw_writel(get_unaligned((u32 *)from), to); + from += 4; + to += 4; + count -= 4; + } + + while (count) { + __raw_writeb(*(u8 *)from, to); + from++; to++; + count--; } } /* * "memset" on IO memory space. - * This needs to be optimized. */ void memset_io(volatile void __iomem *dst, int c, size_t count) { - while (count) { + u32 qc = (u8)c; + + qc |= qc << 8; + qc |= qc << 16; + + while (count && !PTR_IS_ALIGNED(dst, 4)) { + __raw_writeb(c, dst); + dst++; count--; - writeb(c, dst); + } + + while (count >= 4) { + __raw_writel(qc, dst); + dst += 4; + count -= 4; + } + + while (count) { + __raw_writeb(c, dst); dst++; + count--; } } diff --git a/arch/arm/mach-omap/am33xx_clock.c b/arch/arm/mach-omap/am33xx_clock.c index eeb7e93329..4ba10491be 100644 --- a/arch/arm/mach-omap/am33xx_clock.c +++ b/arch/arm/mach-omap/am33xx_clock.c @@ -169,6 +169,11 @@ void am33xx_enable_per_clocks(void) __raw_writel(PRCM_MOD_EN, CM_WKUP_ADC_TSC_CLKCTRL); while (__raw_readl(CM_WKUP_ADC_TSC_CLKCTRL) != PRCM_MOD_EN); + if (IS_ENABLED(CONFIG_HW_RANDOM_OMAP)) { + __raw_writel(PRCM_MOD_EN, CM_PER_RNG_CLKCTRL); + while ((__raw_readl(CM_PER_RNG_CLKCTRL) & 0x30000) != 0x0); + } + clkdcoldo = __raw_readl(CM_CLKDCOLDO_DPLL_PER); clkdcoldo = clkdcoldo | 0x100; __raw_writel(clkdcoldo, CM_CLKDCOLDO_DPLL_PER); diff --git a/drivers/base/driver.c b/drivers/base/driver.c index b2d428b705..fbc5cbebe0 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -538,6 +538,25 @@ void __iomem *dev_request_mem_region_by_name(struct device *dev, } EXPORT_SYMBOL(dev_request_mem_region_by_name); +void __iomem *dev_platform_get_and_ioremap_resource(struct device *dev, + int num, + struct resource **out_res) +{ + struct resource *res; + + res = dev_request_mem_resource(dev, num); + if (IS_ERR(res)) + return IOMEM_ERR_PTR(PTR_ERR(res)); + else if (WARN_ON(IS_ERR_VALUE(res->start))) + return IOMEM_ERR_PTR(-EINVAL); + + if (out_res) + *out_res = res; + + return IOMEM(res->start); +} +EXPORT_SYMBOL(dev_platform_get_and_ioremap_resource); + struct resource *dev_request_mem_resource(struct device *dev, int num) { struct resource *res; diff --git a/drivers/hw_random/Kconfig b/drivers/hw_random/Kconfig index 6daff64906..763929f7d6 100644 --- a/drivers/hw_random/Kconfig +++ b/drivers/hw_random/Kconfig @@ -8,11 +8,18 @@ menuconfig HWRNG if HWRNG +config HW_RANDOM_TIMERIOMEM + tristate "Timer IOMEM HW Random Number Generator support" + help + This driver provides barebox support for a generic Random + Number Generator used by reading a 'dumb' iomem address that + is to be read no faster than, for example, once a second. + config HWRNG_MXC_RNGC tristate "Freescale i.MX RNGC Random Number Generator" depends on ARCH_IMX25 || ARCH_IMX35 || ARCH_IMX53 || COMPILE_TEST help - This driver provides kernel-side support for the Random Number + This driver provides barebox support for the Random Number Generator hardware found on some Freescale i.MX processors. config HWRNG_STM32 @@ -54,10 +61,45 @@ config HW_RANDOM_EFI config HW_RANDOM_OPTEE tristate "OP-TEE based Random Number Generator support" depends on OPTEE - default HW_RANDOM help - This driver provides support for OP-TEE based Random Number + This driver provides support for OP-TEE based Random Number Generator on ARM SoCs where hardware entropy sources are not - accessible to normal world (Linux). + accessible to normal world (barebox and e.g. Linux after it). + +config HW_RANDOM_ATMEL + tristate "Atmel Random Number Generator support" + depends on ARCH_AT91 || COMPILE_TEST + help + This driver provides barebox support for the Random Number + Generator hardware found on Atmel AT91 devices. + +config HW_RANDOM_BCM2835 + tristate "Broadcom BCM2835/BCM63xx Random Number Generator support" + depends on ARCH_BCM283X || COMPILE_TEST + help + This driver provides barebox support for the Random Number + Generator hardware found on the Broadcom BCM2835 SoCs. + +config HW_RANDOM_IPROC_RNG200 + tristate "Broadcom iProc/STB RNG200 support" + depends on ARCH_BCM283X || COMPILE_TEST + help + This driver provides barebox support for the RNG200 + hardware found on the BCM2711. + +config HW_RANDOM_ROCKCHIP + tristate "Rockchip Random Number Generator support" + depends on ARCH_ROCKCHIP || COMPILE_TEST + help + This driver provides barebox support for the Random Number + Generator hardware found on Rockchip cpus. + +config HW_RANDOM_OMAP + tristate "OMAP Random Number Generator support" + depends on ARCH_OMAP || ARCH_K3 || COMPILE_TEST + help + This driver provides barebox support for the Random Number + Generator hardware found on OMAP2/3/4/5, AM33xx/AM43xx + multimedia processors, and Marvell Armada 7k/8k SoCs. endif diff --git a/drivers/hw_random/Makefile b/drivers/hw_random/Makefile index 2c7d196f2f..7f65a6c41e 100644 --- a/drivers/hw_random/Makefile +++ b/drivers/hw_random/Makefile @@ -7,3 +7,9 @@ obj-$(CONFIG_HW_RANDOM_VIRTIO) += virtio-rng.o obj-$(CONFIG_HW_RANDOM_STARFIVE) += starfive-vic-rng.o obj-$(CONFIG_HW_RANDOM_EFI) += efi-rng.o obj-$(CONFIG_HW_RANDOM_OPTEE) += optee-rng.o +obj-$(CONFIG_HW_RANDOM_ATMEL) += atmel-rng.o +obj-$(CONFIG_HW_RANDOM_BCM2835) += bcm2835-rng.o +obj-$(CONFIG_HW_RANDOM_IPROC_RNG200) += iproc-rng200.o +obj-$(CONFIG_HW_RANDOM_ROCKCHIP) += rockchip-rng.o +obj-$(CONFIG_HW_RANDOM_TIMERIOMEM) += timeriomem-rng.o +obj-$(CONFIG_HW_RANDOM_OMAP) += omap-rng.o diff --git a/drivers/hw_random/atmel-rng.c b/drivers/hw_random/atmel-rng.c new file mode 100644 index 0000000000..bdd2139b08 --- /dev/null +++ b/drivers/hw_random/atmel-rng.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2011 Peter Korsgaard <jacmet@sunsite.dk> + +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/hw_random.h> +#include <of.h> +#include <linux/device.h> + +#define TRNG_CR 0x00 +#define TRNG_MR 0x04 +#define TRNG_ISR 0x1c +#define TRNG_ISR_DATRDY BIT(0) +#define TRNG_ODATA 0x50 + +#define TRNG_KEY 0x524e4700 /* RNG */ + +#define TRNG_HALFR BIT(0) /* generate RN every 168 cycles */ + +struct atmel_trng_data { + bool has_half_rate; +}; + +struct atmel_trng { + struct clk *clk; + void __iomem *base; + struct hwrng rng; + bool has_half_rate; +}; + +static bool atmel_trng_wait_ready(struct atmel_trng *trng, bool wait) +{ + int ready; + + ready = readl(trng->base + TRNG_ISR) & TRNG_ISR_DATRDY; + if (!ready && wait) + readl_poll_timeout(trng->base + TRNG_ISR, ready, + ready & TRNG_ISR_DATRDY, 20000); + + return !!ready; +} + +static int atmel_trng_read(struct hwrng *rng, void *buf, size_t max, + bool wait) +{ + struct atmel_trng *trng = container_of(rng, struct atmel_trng, rng); + u32 *data = buf; + int ret; + + ret = atmel_trng_wait_ready(trng, wait); + if (!ret) + return 0; + + *data = readl(trng->base + TRNG_ODATA); + /* + * ensure data ready is only set again AFTER the next data word is ready + * in case it got set between checking ISR and reading ODATA, so we + * don't risk re-reading the same word + */ + readl(trng->base + TRNG_ISR); + ret = 4; + + return ret; +} + +static int atmel_trng_init(struct hwrng *rng) +{ + struct atmel_trng *trng = container_of(rng, struct atmel_trng, rng); + unsigned long rate; + int ret; + + ret = clk_prepare_enable(trng->clk); + if (ret) + return ret; + + if (trng->has_half_rate) { + rate = clk_get_rate(trng->clk); + + /* if peripheral clk is above 100MHz, set HALFR */ + if (rate > 100000000) + writel(TRNG_HALFR, trng->base + TRNG_MR); + } + + writel(TRNG_KEY | 1, trng->base + TRNG_CR); + + return 0; +} + +static void atmel_trng_cleanup(struct atmel_trng *trng) +{ + writel(TRNG_KEY, trng->base + TRNG_CR); + clk_disable_unprepare(trng->clk); +} + +static int atmel_trng_probe(struct device *dev) +{ + struct atmel_trng *trng; + const struct atmel_trng_data *data; + + trng = devm_kzalloc(dev, sizeof(*trng), GFP_KERNEL); + if (!trng) + return -ENOMEM; + + trng->base = dev_platform_ioremap_resource(dev, 0); + if (IS_ERR(trng->base)) + return PTR_ERR(trng->base); + + trng->clk = clk_get(dev, NULL); + if (IS_ERR(trng->clk)) + return PTR_ERR(trng->clk); + data = device_get_match_data(dev); + if (!data) + return -ENODEV; + + trng->has_half_rate = data->has_half_rate; + trng->rng.name = dev_name(dev); + trng->rng.read = atmel_trng_read; + trng->rng.init = atmel_trng_init; + dev->priv = trng; + + return hwrng_register(dev, &trng->rng); +} + +static void atmel_trng_remove(struct device *dev) +{ + atmel_trng_cleanup(dev->priv); +} + +static const struct atmel_trng_data at91sam9g45_config = { + .has_half_rate = false, +}; + +static const struct atmel_trng_data sam9x60_config = { + .has_half_rate = true, +}; + +static const struct of_device_id atmel_trng_dt_ids[] = { + { + .compatible = "atmel,at91sam9g45-trng", + .data = &at91sam9g45_config, + }, { + .compatible = "microchip,sam9x60-trng", + .data = &sam9x60_config, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, atmel_trng_dt_ids); + +static struct driver atmel_trng_driver = { + .name = "atmel-trng", + .probe = atmel_trng_probe, + .remove = atmel_trng_remove, + .of_match_table = atmel_trng_dt_ids, +}; +device_platform_driver(atmel_trng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>"); +MODULE_DESCRIPTION("Atmel true random number generator driver"); diff --git a/drivers/hw_random/bcm2835-rng.c b/drivers/hw_random/bcm2835-rng.c new file mode 100644 index 0000000000..d82331c950 --- /dev/null +++ b/drivers/hw_random/bcm2835-rng.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2010-2012 Broadcom. All rights reserved. + * Copyright (c) 2013 Lubomir Rintel + */ + +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/reset.h> +#include <of.h> +#include <linux/device.h> +#include <linux/printk.h> +#include <linux/clk.h> +#include <linux/reset.h> + +#define RNG_CTRL 0x0 +#define RNG_STATUS 0x4 +#define RNG_DATA 0x8 +#define RNG_INT_MASK 0x10 + +/* enable rng */ +#define RNG_RBGEN 0x1 + +/* the initial numbers generated are "less random" so will be discarded */ +#define RNG_WARMUP_COUNT 0x40000 + +#define RNG_INT_OFF 0x1 + +struct bcm2835_rng_priv { + struct hwrng rng; + void __iomem *base; + bool mask_interrupts; + struct clk *clk; + struct reset_control *reset; +}; + +static inline struct bcm2835_rng_priv *to_rng_priv(struct hwrng *rng) +{ + return container_of(rng, struct bcm2835_rng_priv, rng); +} + +static inline u32 rng_readl(struct bcm2835_rng_priv *priv, u32 offset) +{ + /* MIPS chips strapped for BE will automagically configure the + * peripheral registers for CPU-native byte order. + */ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + return __raw_readl(priv->base + offset); + else + return readl(priv->base + offset); +} + +static inline void rng_writel(struct bcm2835_rng_priv *priv, u32 val, + u32 offset) +{ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + __raw_writel(val, priv->base + offset); + else + writel(val, priv->base + offset); +} + +static int bcm2835_rng_read(struct hwrng *rng, void *buf, size_t max, + bool wait) +{ + struct bcm2835_rng_priv *priv = to_rng_priv(rng); + u32 max_words = max / sizeof(u32); + u32 num_words, count; + + while ((rng_readl(priv, RNG_STATUS) >> 24) == 0) { + if (!wait) + return 0; + hwrng_yield(rng); + } + + num_words = rng_readl(priv, RNG_STATUS) >> 24; + if (num_words > max_words) + num_words = max_words; + + for (count = 0; count < num_words; count++) + ((u32 *)buf)[count] = rng_readl(priv, RNG_DATA); + + return num_words * sizeof(u32); +} + +static int bcm2835_rng_init(struct hwrng *rng) +{ + struct bcm2835_rng_priv *priv = to_rng_priv(rng); + int ret = 0; + u32 val; + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + ret = reset_control_reset(priv->reset); + if (ret) + return ret; + + if (priv->mask_interrupts) { + /* mask the interrupt */ + val = rng_readl(priv, RNG_INT_MASK); + val |= RNG_INT_OFF; + rng_writel(priv, val, RNG_INT_MASK); + } + + /* set warm-up count & enable */ + rng_writel(priv, RNG_WARMUP_COUNT, RNG_STATUS); + rng_writel(priv, RNG_RBGEN, RNG_CTRL); + + return ret; +} + +static void bcm2835_rng_cleanup(struct bcm2835_rng_priv *priv) +{ + /* disable rng hardware */ + rng_writel(priv, 0, RNG_CTRL); + + clk_disable_unprepare(priv->clk); +} + +struct bcm2835_rng_of_data { + bool mask_interrupts; +}; + +static const struct bcm2835_rng_of_data nsp_rng_of_data = { + .mask_interrupts = true, +}; + +static const struct of_device_id bcm2835_rng_of_match[] = { + { .compatible = "brcm,bcm2835-rng"}, + { .compatible = "brcm,bcm-nsp-rng", .data = &nsp_rng_of_data }, + { .compatible = "brcm,bcm5301x-rng", .data = &nsp_rng_of_data }, + { .compatible = "brcm,bcm6368-rng"}, + {}, +}; + +static int bcm2835_rng_probe(struct device *dev) +{ + const struct bcm2835_rng_of_data *of_data; + struct bcm2835_rng_priv *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* map peripheral */ + priv->base = dev_platform_ioremap_resource(dev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + /* Clock is optional on most platforms */ + priv->clk = clk_get_optional(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->reset = reset_control_get_optional(dev, NULL); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + priv->rng.name = dev_name(dev); + priv->rng.init = bcm2835_rng_init; + priv->rng.read = bcm2835_rng_read; + + of_data = device_get_match_data(dev); + if (of_data) + priv->mask_interrupts = of_data->mask_interrupts; + + /* register driver */ + err = hwrng_register(dev, &priv->rng); + if (err) + dev_err(dev, "hwrng registration failed\n"); + else + dev_info(dev, "hwrng registered\n"); + + dev->priv = priv; + + return err; +} + +static void bcm2835_rng_remove(struct device *dev) +{ + bcm2835_rng_cleanup(dev->priv); +} + +MODULE_DEVICE_TABLE(of, bcm2835_rng_of_match); + +static struct driver bcm2835_rng_driver = { + .name = "bcm2835-rng", + .of_match_table = bcm2835_rng_of_match, + .probe = bcm2835_rng_probe, + .remove = bcm2835_rng_remove, +}; +device_platform_driver(bcm2835_rng_driver); + +MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); +MODULE_DESCRIPTION("BCM2835 Random Number Generator (RNG) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hw_random/core.c b/drivers/hw_random/core.c index 89b979ade8..7bc3c33319 100644 --- a/drivers/hw_random/core.c +++ b/drivers/hw_random/core.c @@ -44,10 +44,10 @@ static ssize_t rng_dev_read(struct cdev *cdev, void *buf, size_t size, while (count) { int max = min(count, (size_t)RNG_BUFFER_SIZE); len = hwrng_get_data(rng, rng->buf, max, true); - if (len < 0) { - cur = len; - break; - } + if (len < 0) + return len; + if (!len && ctrlc()) + return cur; memcpy(buf + cur, rng->buf, len); diff --git a/drivers/hw_random/iproc-rng200.c b/drivers/hw_random/iproc-rng200.c new file mode 100644 index 0000000000..4cb3573a7d --- /dev/null +++ b/drivers/hw_random/iproc-rng200.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2015 Broadcom Corporation +* +*/ +/* + * DESCRIPTION: The Broadcom iProc RNG200 Driver + */ + +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/device.h> +#include <clock.h> + +/* Registers */ +#define RNG_CTRL_OFFSET 0x00 +#define RNG_CTRL_RNG_RBGEN_MASK 0x00001FFF +#define RNG_CTRL_RNG_RBGEN_ENABLE 0x00000001 + +#define RNG_SOFT_RESET_OFFSET 0x04 +#define RNG_SOFT_RESET 0x00000001 + +#define RBG_SOFT_RESET_OFFSET 0x08 +#define RBG_SOFT_RESET 0x00000001 + +#define RNG_INT_STATUS_OFFSET 0x18 +#define RNG_INT_STATUS_MASTER_FAIL_LOCKOUT_IRQ_MASK 0x80000000 +#define RNG_INT_STATUS_STARTUP_TRANSITIONS_MET_IRQ_MASK 0x00020000 +#define RNG_INT_STATUS_NIST_FAIL_IRQ_MASK 0x00000020 +#define RNG_INT_STATUS_TOTAL_BITS_COUNT_IRQ_MASK 0x00000001 + +#define RNG_FIFO_DATA_OFFSET 0x20 + +#define RNG_FIFO_COUNT_OFFSET 0x24 +#define RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK 0x000000FF + +struct iproc_rng200_dev { + struct hwrng rng; + void __iomem *base; +}; + +#define to_rng_priv(rng) container_of(rng, struct iproc_rng200_dev, rng) + +static void iproc_rng200_enable_set(void __iomem *rng_base, bool enable) +{ + u32 val; + + val = ioread32(rng_base + RNG_CTRL_OFFSET); + val &= ~RNG_CTRL_RNG_RBGEN_MASK; + + if (enable) + val |= RNG_CTRL_RNG_RBGEN_ENABLE; + + iowrite32(val, rng_base + RNG_CTRL_OFFSET); +} + +static void iproc_rng200_restart(void __iomem *rng_base) +{ + uint32_t val; + + iproc_rng200_enable_set(rng_base, false); + + /* Clear all interrupt status */ + iowrite32(0xFFFFFFFFUL, rng_base + RNG_INT_STATUS_OFFSET); + + /* Reset RNG and RBG */ + val = ioread32(rng_base + RBG_SOFT_RESET_OFFSET); + val |= RBG_SOFT_RESET; + iowrite32(val, rng_base + RBG_SOFT_RESET_OFFSET); + + val = ioread32(rng_base + RNG_SOFT_RESET_OFFSET); + val |= RNG_SOFT_RESET; + iowrite32(val, rng_base + RNG_SOFT_RESET_OFFSET); + + val = ioread32(rng_base + RNG_SOFT_RESET_OFFSET); + val &= ~RNG_SOFT_RESET; + iowrite32(val, rng_base + RNG_SOFT_RESET_OFFSET); + + val = ioread32(rng_base + RBG_SOFT_RESET_OFFSET); + val &= ~RBG_SOFT_RESET; + iowrite32(val, rng_base + RBG_SOFT_RESET_OFFSET); + + iproc_rng200_enable_set(rng_base, true); +} + +static int iproc_rng200_read(struct hwrng *rng, void *buf, size_t max, + bool wait) +{ + struct iproc_rng200_dev *priv = to_rng_priv(rng); + uint32_t num_remaining = max; + uint32_t status; + u64 start; + + #define MAX_RESETS_PER_READ 1 + uint32_t num_resets = 0; + + #define MAX_IDLE_TIME_NS (NSEC_PER_SEC) + + start = get_time_ns(); + + while ((num_remaining > 0) && !is_timeout(start, MAX_IDLE_TIME_NS)) { + + /* Is RNG sane? If not, reset it. */ + status = ioread32(priv->base + RNG_INT_STATUS_OFFSET); + if ((status & (RNG_INT_STATUS_MASTER_FAIL_LOCKOUT_IRQ_MASK | + RNG_INT_STATUS_NIST_FAIL_IRQ_MASK)) != 0) { + + if (num_resets >= MAX_RESETS_PER_READ) + return max - num_remaining; + + iproc_rng200_restart(priv->base); + num_resets++; + } + + /* Are there any random numbers available? */ + if ((ioread32(priv->base + RNG_FIFO_COUNT_OFFSET) & + RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK) > 0) { + + if (num_remaining >= sizeof(uint32_t)) { + /* Buffer has room to store entire word */ + *(uint32_t *)buf = ioread32(priv->base + + RNG_FIFO_DATA_OFFSET); + buf += sizeof(uint32_t); + num_remaining -= sizeof(uint32_t); + } else { + /* Buffer can only store partial word */ + uint32_t rnd_number = ioread32(priv->base + + RNG_FIFO_DATA_OFFSET); + memcpy(buf, &rnd_number, num_remaining); + buf += num_remaining; + num_remaining = 0; + } + + /* Reset the IDLE timeout */ + start = get_time_ns(); + } else { + if (!wait) + /* Cannot wait, return immediately */ + return max - num_remaining; + } + } + + return max - num_remaining; +} + +static int iproc_rng200_init(struct hwrng *rng) +{ + struct iproc_rng200_dev *priv = to_rng_priv(rng); + + iproc_rng200_enable_set(priv->base, true); + + return 0; +} + +static void iproc_rng200_cleanup(struct iproc_rng200_dev *priv) +{ + iproc_rng200_enable_set(priv->base, false); +} + +static int iproc_rng200_probe(struct device *dev) +{ + struct iproc_rng200_dev *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Map peripheral */ + priv->base = dev_platform_ioremap_resource(dev, 0); + if (IS_ERR(priv->base)) { + dev_err(dev, "failed to remap rng regs\n"); + return PTR_ERR(priv->base); + } + + dev->priv = priv; + + priv->rng.name = "iproc-rng200"; + priv->rng.read = iproc_rng200_read; + priv->rng.init = iproc_rng200_init; + + /* Register driver */ + ret = hwrng_register(dev, &priv->rng); + if (ret) { + dev_err(dev, "hwrng registration failed\n"); + return ret; + } + + dev_info(dev, "hwrng registered\n"); + + return 0; +} + +static void iproc_rng200_remove(struct device *dev) +{ + iproc_rng200_cleanup(dev->priv); +} + +static const struct of_device_id iproc_rng200_of_match[] = { + { .compatible = "brcm,bcm2711-rng200", }, + { .compatible = "brcm,bcm7211-rng200", }, + { .compatible = "brcm,bcm7278-rng200", }, + { .compatible = "brcm,iproc-rng200", }, + {}, +}; +MODULE_DEVICE_TABLE(of, iproc_rng200_of_match); + +static struct driver iproc_rng200_driver = { + .name = "iproc-rng200", + .of_match_table = iproc_rng200_of_match, + .probe = iproc_rng200_probe, + .remove = iproc_rng200_remove, +}; +device_platform_driver(iproc_rng200_driver); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("iProc RNG200 Random Number Generator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hw_random/omap-rng.c b/drivers/hw_random/omap-rng.c new file mode 100644 index 0000000000..9fa50bc8e7 --- /dev/null +++ b/drivers/hw_random/omap-rng.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * omap-rng.c - RNG driver for TI OMAP CPU family + * + * Author: Deepak Saxena <dsaxena@plexity.net> + * + * Copyright 2005 (c) MontaVista Software, Inc. + * + * Mostly based on original driver: + * + * Copyright (C) 2005 Nokia Corporation + * Author: Juha Yrjölä <juha.yrjola@nokia.com> + */ + +#include <init.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/hw_random.h> +#include <clock.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <of.h> +#include <linux/clk.h> +#include <linux/io.h> + +#define RNG_REG_STATUS_RDY (1 << 0) + +#define RNG_REG_INTACK_RDY_MASK (1 << 0) +#define RNG_REG_INTACK_SHUTDOWN_OFLO_MASK (1 << 1) +#define RNG_SHUTDOWN_OFLO_MASK (1 << 1) + +#define RNG_CONTROL_STARTUP_CYCLES_SHIFT 16 +#define RNG_CONTROL_STARTUP_CYCLES_MASK (0xffff << 16) +#define RNG_CONTROL_ENABLE_TRNG_SHIFT 10 +#define RNG_CONTROL_ENABLE_TRNG_MASK (1 << 10) + +#define RNG_CONFIG_MAX_REFIL_CYCLES_SHIFT 16 +#define RNG_CONFIG_MAX_REFIL_CYCLES_MASK (0xffff << 16) +#define RNG_CONFIG_MIN_REFIL_CYCLES_SHIFT 0 +#define RNG_CONFIG_MIN_REFIL_CYCLES_MASK (0xff << 0) + +#define RNG_CONTROL_STARTUP_CYCLES 0xff +#define RNG_CONFIG_MIN_REFIL_CYCLES 0x21 +#define RNG_CONFIG_MAX_REFIL_CYCLES 0x22 + +#define RNG_ALARMCNT_ALARM_TH_SHIFT 0x0 +#define RNG_ALARMCNT_ALARM_TH_MASK (0xff << 0) +#define RNG_ALARMCNT_SHUTDOWN_TH_SHIFT 16 +#define RNG_ALARMCNT_SHUTDOWN_TH_MASK (0x1f << 16) +#define RNG_ALARM_THRESHOLD 0xff +#define RNG_SHUTDOWN_THRESHOLD 0x4 + +#define RNG_REG_FROENABLE_MASK 0xffffff +#define RNG_REG_FRODETUNE_MASK 0xffffff + +#define OMAP2_RNG_OUTPUT_SIZE 0x4 +#define OMAP4_RNG_OUTPUT_SIZE 0x8 +#define EIP76_RNG_OUTPUT_SIZE 0x10 + +/* + * EIP76 RNG takes approx. 700us to produce 16 bytes of output data + * as per testing results. And to account for the lack of udelay()'s + * reliability, we keep the timeout as 1000us. + */ +#define RNG_DATA_FILL_TIMEOUT 100 + +enum { + RNG_OUTPUT_0_REG = 0, + RNG_OUTPUT_1_REG, + RNG_OUTPUT_2_REG, + RNG_OUTPUT_3_REG, + RNG_STATUS_REG, + RNG_INTMASK_REG, + RNG_INTACK_REG, + RNG_CONTROL_REG, + RNG_CONFIG_REG, + RNG_ALARMCNT_REG, + RNG_FROENABLE_REG, + RNG_FRODETUNE_REG, + RNG_ALARMMASK_REG, + RNG_ALARMSTOP_REG, + RNG_REV_REG, + RNG_SYSCONFIG_REG, +}; + +static const u16 reg_map_omap2[] = { + [RNG_OUTPUT_0_REG] = 0x0, + [RNG_STATUS_REG] = 0x4, + [RNG_CONFIG_REG] = 0x28, + [RNG_REV_REG] = 0x3c, + [RNG_SYSCONFIG_REG] = 0x40, +}; + +static const u16 reg_map_omap4[] = { + [RNG_OUTPUT_0_REG] = 0x0, + [RNG_OUTPUT_1_REG] = 0x4, + [RNG_STATUS_REG] = 0x8, + [RNG_INTMASK_REG] = 0xc, + [RNG_INTACK_REG] = 0x10, + [RNG_CONTROL_REG] = 0x14, + [RNG_CONFIG_REG] = 0x18, + [RNG_ALARMCNT_REG] = 0x1c, + [RNG_FROENABLE_REG] = 0x20, + [RNG_FRODETUNE_REG] = 0x24, + [RNG_ALARMMASK_REG] = 0x28, + [RNG_ALARMSTOP_REG] = 0x2c, + [RNG_REV_REG] = 0x1FE0, + [RNG_SYSCONFIG_REG] = 0x1FE4, +}; + +static const u16 reg_map_eip76[] = { + [RNG_OUTPUT_0_REG] = 0x0, + [RNG_OUTPUT_1_REG] = 0x4, + [RNG_OUTPUT_2_REG] = 0x8, + [RNG_OUTPUT_3_REG] = 0xc, + [RNG_STATUS_REG] = 0x10, + [RNG_INTACK_REG] = 0x10, + [RNG_CONTROL_REG] = 0x14, + [RNG_CONFIG_REG] = 0x18, + [RNG_ALARMCNT_REG] = 0x1c, + [RNG_FROENABLE_REG] = 0x20, + [RNG_FRODETUNE_REG] = 0x24, + [RNG_ALARMMASK_REG] = 0x28, + [RNG_ALARMSTOP_REG] = 0x2c, + [RNG_REV_REG] = 0x7c, +}; + +struct omap_rng_dev; +/** + * struct omap_rng_pdata - RNG IP block-specific data + * @regs: Pointer to the register offsets structure. + * @data_size: No. of bytes in RNG output. + * @data_present: Callback to determine if data is available. + * @init: Callback for IP specific initialization sequence. + * @cleanup: Callback for IP specific cleanup sequence. + */ +struct omap_rng_pdata { + u16 *regs; + u32 data_size; + u32 (*data_present)(struct omap_rng_dev *priv); + int (*init)(struct omap_rng_dev *priv); + void (*cleanup)(struct omap_rng_dev *priv); +}; + +struct omap_rng_dev { + void __iomem *base; + struct device *dev; + const struct omap_rng_pdata *pdata; + struct hwrng rng; + struct clk *clk; + struct clk *clk_reg; +}; + +static inline u32 omap_rng_read(struct omap_rng_dev *priv, u16 reg) +{ + return __raw_readl(priv->base + priv->pdata->regs[reg]); +} + +static inline void omap_rng_write(struct omap_rng_dev *priv, u16 reg, + u32 val) +{ + __raw_writel(val, priv->base + priv->pdata->regs[reg]); +} + + +static int omap_rng_do_read(struct hwrng *rng, void *data, size_t max, + bool wait) +{ + struct omap_rng_dev *priv; + int i, present; + + priv = (struct omap_rng_dev *)rng->priv; + + /* In Linux, max is always at least 32 bytes, which is greater than + * the 4 bytes required by the IP not to raise a data abort. + * In barebox, reading 4 bytes from a HWRNG is something we want + * support, so we check against 4 here and restrict memcpy_fromio + * size below. + */ + if (max < sizeof(u32)) + return -EFAULT; + + for (i = 0; i < RNG_DATA_FILL_TIMEOUT; i++) { + present = priv->pdata->data_present(priv); + if (present || !wait) + break; + + udelay(10); + } + if (!present) + return 0; + + max = min(max, priv->pdata->data_size); + + memcpy_fromio(data, priv->base + priv->pdata->regs[RNG_OUTPUT_0_REG], max); + + if (priv->pdata->regs[RNG_INTACK_REG]) + omap_rng_write(priv, RNG_INTACK_REG, RNG_REG_INTACK_RDY_MASK); + + return max; +} + +static int omap_rng_init(struct hwrng *rng) +{ + struct omap_rng_dev *priv; + + priv = (struct omap_rng_dev *)rng->priv; + return priv->pdata->init(priv); +} + +static inline u32 omap2_rng_data_present(struct omap_rng_dev *priv) +{ + return omap_rng_read(priv, RNG_STATUS_REG) ? 0 : 1; +} + +static int omap2_rng_init(struct omap_rng_dev *priv) +{ + omap_rng_write(priv, RNG_SYSCONFIG_REG, 0x1); + return 0; +} + +static void omap2_rng_cleanup(struct omap_rng_dev *priv) +{ + omap_rng_write(priv, RNG_SYSCONFIG_REG, 0x0); +} + +static struct omap_rng_pdata omap2_rng_pdata = { + .regs = (u16 *)reg_map_omap2, + .data_size = OMAP2_RNG_OUTPUT_SIZE, + .data_present = omap2_rng_data_present, + .init = omap2_rng_init, + .cleanup = omap2_rng_cleanup, +}; + +static inline u32 omap4_rng_data_present(struct omap_rng_dev *priv) +{ + return omap_rng_read(priv, RNG_STATUS_REG) & RNG_REG_STATUS_RDY; +} + +static int eip76_rng_init(struct omap_rng_dev *priv) +{ + u32 val; + + /* Return if RNG is already running. */ + if (omap_rng_read(priv, RNG_CONTROL_REG) & RNG_CONTROL_ENABLE_TRNG_MASK) + return 0; + + /* Number of 512 bit blocks of raw Noise Source output data that must + * be processed by either the Conditioning Function or the + * SP 800-90 DRBG ‘BC_DF’ functionality to yield a ‘full entropy’ + * output value. + */ + val = 0x5 << RNG_CONFIG_MIN_REFIL_CYCLES_SHIFT; + + /* Number of FRO samples that are XOR-ed together into one bit to be + * shifted into the main shift register + */ + val |= RNG_CONFIG_MAX_REFIL_CYCLES << RNG_CONFIG_MAX_REFIL_CYCLES_SHIFT; + omap_rng_write(priv, RNG_CONFIG_REG, val); + + /* Enable TRNG */ + val = RNG_CONTROL_ENABLE_TRNG_MASK; + omap_rng_write(priv, RNG_CONTROL_REG, val); + + return 0; +} + +static int omap4_rng_init(struct omap_rng_dev *priv) +{ + u32 val; + + /* Return if RNG is already running. */ + if (omap_rng_read(priv, RNG_CONTROL_REG) & RNG_CONTROL_ENABLE_TRNG_MASK) + return 0; + + val = RNG_CONFIG_MIN_REFIL_CYCLES << RNG_CONFIG_MIN_REFIL_CYCLES_SHIFT; + val |= RNG_CONFIG_MAX_REFIL_CYCLES << RNG_CONFIG_MAX_REFIL_CYCLES_SHIFT; + omap_rng_write(priv, RNG_CONFIG_REG, val); + + val = RNG_CONTROL_STARTUP_CYCLES << RNG_CONTROL_STARTUP_CYCLES_SHIFT; + val |= RNG_CONTROL_ENABLE_TRNG_MASK; + omap_rng_write(priv, RNG_CONTROL_REG, val); + + return 0; +} + +static void omap4_rng_cleanup(struct omap_rng_dev *priv) +{ + int val; + + val = omap_rng_read(priv, RNG_CONTROL_REG); + val &= ~RNG_CONTROL_ENABLE_TRNG_MASK; + omap_rng_write(priv, RNG_CONTROL_REG, val); +} + +static struct omap_rng_pdata omap4_rng_pdata = { + .regs = (u16 *)reg_map_omap4, + .data_size = OMAP4_RNG_OUTPUT_SIZE, + .data_present = omap4_rng_data_present, + .init = omap4_rng_init, + .cleanup = omap4_rng_cleanup, +}; + +static struct omap_rng_pdata eip76_rng_pdata = { + .regs = (u16 *)reg_map_eip76, + .data_size = EIP76_RNG_OUTPUT_SIZE, + .data_present = omap4_rng_data_present, + .init = eip76_rng_init, + .cleanup = omap4_rng_cleanup, +}; + +static const struct of_device_id omap_rng_of_match[] __maybe_unused = { + { + .compatible = "ti,omap2-rng", + .data = &omap2_rng_pdata, + }, + { + .compatible = "ti,omap4-rng", + .data = &omap4_rng_pdata, + }, + { + .compatible = "inside-secure,safexcel-eip76", + .data = &eip76_rng_pdata, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_rng_of_match); + +static int of_get_omap_rng_device_details(struct omap_rng_dev *priv, + struct device *dev) +{ + priv->pdata = device_get_match_data(dev); + if (!priv->pdata) + return -ENODEV; + + return 0; +} + +static struct clk *ti_sysc_clk_get_enabled(struct device *dev, const char *clk_id) +{ + struct clk *clk; + + clk = clk_get_optional_enabled(dev, clk_id); + if (!clk) + clk = clk_get_optional_enabled(dev->parent, clk_id); + + if (IS_ERR(clk)) + dev_errp_probe(dev, clk, "Unable to enable the clk\n"); + return clk; +} + +static int omap_rng_probe(struct device *dev) +{ + struct omap_rng_dev *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(struct omap_rng_dev), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->rng.read = omap_rng_do_read; + priv->rng.init = omap_rng_init; + + priv->rng.priv = (unsigned long)priv; + dev->priv = priv; + priv->dev = dev; + + priv->base = dev_platform_ioremap_resource(dev, 0); + if (IS_ERR(priv->base)) { + ret = PTR_ERR(priv->base); + goto err_ioremap; + } + + priv->rng.name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL); + if (!priv->rng.name) { + ret = -ENOMEM; + goto err_ioremap; + } + + priv->clk = ti_sysc_clk_get_enabled(dev, NULL); + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + goto err_ioremap; + } + + priv->clk_reg = ti_sysc_clk_get_enabled(dev, "reg"); + if (IS_ERR(priv->clk_reg)) { + ret = PTR_ERR(priv->clk_reg); + goto err_ioremap; + } + + ret = of_get_omap_rng_device_details(priv, dev); + if (ret) + goto err_register; + + ret = hwrng_register(dev, &priv->rng); + if (ret) + goto err_register; + + dev_info(dev, "Random Number Generator ver. %02x\n", + omap_rng_read(priv, RNG_REV_REG)); + + return 0; + +err_register: + priv->base = NULL; + + clk_disable_unprepare(priv->clk_reg); + clk_disable_unprepare(priv->clk); +err_ioremap: + dev_err(dev, "initialization failed.\n"); + return ret; +} + +static void omap_rng_remove(struct device *dev) +{ + struct omap_rng_dev *priv = dev->priv; + + + priv->pdata->cleanup(priv); + + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_reg); +} + +static struct driver omap_rng_driver = { + .name = "omap_rng", + .of_match_table = of_match_ptr(omap_rng_of_match), + .probe = omap_rng_probe, + .remove = omap_rng_remove, +}; + +device_platform_driver(omap_rng_driver); +MODULE_ALIAS("platform:omap_rng"); +MODULE_AUTHOR("Deepak Saxena (and others)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hw_random/rockchip-rng.c b/drivers/hw_random/rockchip-rng.c new file mode 100644 index 0000000000..990e5fc111 --- /dev/null +++ b/drivers/hw_random/rockchip-rng.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rockchip-rng.c Random Number Generator driver for the Rockchip + * + * Copyright (c) 2018, Fuzhou Rockchip Electronics Co., Ltd. + * Author: Lin Jinhan <troy.lin@rock-chips.com> + * + */ +#include <linux/clk.h> +#include <linux/hw_random.h> +#include <linux/iopoll.h> +#include <linux/reset.h> +#include <linux/bitfield.h> +#include <linux/mod_devicetable.h> +#include <of.h> +#include <of_address.h> +#include <linux/device.h> + +#define HIWORD_UPDATE(val, mask, shift) \ + ((val) << (shift) | (mask) << ((shift) + 16)) + +#define ROCKCHIP_AUTOSUSPEND_DELAY 100 +#define ROCKCHIP_POLL_PERIOD_US 100 +#define ROCKCHIP_POLL_TIMEOUT_US 10000 +#define RK_MAX_RNG_BYTE (32) + +#define CRYPTO_V1_CTRL 0x0008 +#define CRYPTO_V1_RNG_START BIT(8) +#define CRYPTO_V1_RNG_FLUSH BIT(9) +#define CRYPTO_V1_TRNG_CTRL 0x0200 +#define CRYPTO_V1_OSC_ENABLE BIT(16) +#define CRYPTO_V1_TRNG_SAMPLE_PERIOD(x) (x) +#define CRYPTO_V1_TRNG_DOUT_0 0x0204 + +#define CRYPTO_V2_RNG_CTL 0x0400 +#define CRYPTO_V2_RNG_BIT_LEN GENMASK(5, 4) +#define CRYPTO_V2_RNG_64_BIT_LEN FIELD_PREP(CRYPTO_V2_RNG_BIT_LEN, 0) +#define CRYPTO_V2_RNG_128_BIT_LEN FIELD_PREP(CRYPTO_V2_RNG_BIT_LEN, 1) +#define CRYPTO_V2_RNG_192_BIT_LEN FIELD_PREP(CRYPTO_V2_RNG_BIT_LEN, 2) +#define CRYPTO_V2_RNG_256_BIT_LEN FIELD_PREP(CRYPTO_V2_RNG_BIT_LEN, 3) +#define CRYPTO_V2_RNG_SOC_RING GENMASK(3, 2) +#define CRYPTO_V2_RNG_FASTEST_SOC_RING FIELD_PREP(CRYPTO_V2_RNG_SOC_RING, 0) +#define CRYPTO_V2_RNG_SLOWER_SOC_RING_0 FIELD_PREP(CRYPTO_V2_RNG_SOC_RING, 1) +#define CRYPTO_V2_RNG_SLOWER_SOC_RING_1 FIELD_PREP(CRYPTO_V2_RNG_SOC_RING, 2) +#define CRYPTO_V2_RNG_SLOWEST_SOC_RING FIELD_PREP(CRYPTO_V2_RNG_SOC_RING, 3) +#define CRYPTO_V2_RNG_ENABLE BIT(1) +#define CRYPTO_V2_RNG_START BIT(0) +#define CRYPTO_V2_RNG_SAMPLE_CNT 0x0404 +#define CRYPTO_V2_RNG_DOUT_0 0x0410 + +struct rk_rng_soc_data { + const char * const *clks; + int clks_num; + int (*rk_rng_read)(struct hwrng *rng, void *buf, size_t max, bool wait); +}; + +struct rk_rng { + struct device *dev; + struct hwrng rng; + void __iomem *mem; + struct rk_rng_soc_data *soc_data; + u32 clk_num; + struct clk_bulk_data *clk_bulks; +}; + +static void rk_rng_writel(struct rk_rng *rng, u32 val, u32 offset) +{ + __raw_writel(val, rng->mem + offset); +} + +static u32 rk_rng_readl(struct rk_rng *rng, u32 offset) +{ + return __raw_readl(rng->mem + offset); +} + +static int rk_rng_init(struct hwrng *rng) +{ + int ret; + struct rk_rng *rk_rng = container_of(rng, struct rk_rng, rng); + + ret = clk_bulk_prepare_enable(rk_rng->clk_num, rk_rng->clk_bulks); + if (ret < 0) { + dev_err(rk_rng->dev, "failed to enable clks %d\n", ret); + return ret; + } + + return 0; +} + +static void rk_rng_cleanup(struct rk_rng *rk_rng) +{ + clk_bulk_disable_unprepare(rk_rng->clk_num, rk_rng->clk_bulks); +} + +static void rk_rng_read_regs(struct rk_rng *rng, u32 offset, void *buf, + size_t size) +{ + u32 i, sample; + + for (i = 0; i < size; i += 4) { + sample = rk_rng_readl(rng, offset + i); + memcpy(buf + i, &sample, sizeof(sample)); + } +} + +static int rk_rng_v1_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + int ret = 0; + u32 reg_ctrl = 0; + struct rk_rng *rk_rng = container_of(rng, struct rk_rng, rng); + + /* enable osc_ring to get entropy, sample period is set as 100 */ + reg_ctrl = CRYPTO_V1_OSC_ENABLE | CRYPTO_V1_TRNG_SAMPLE_PERIOD(100); + rk_rng_writel(rk_rng, reg_ctrl, CRYPTO_V1_TRNG_CTRL); + + reg_ctrl = HIWORD_UPDATE(CRYPTO_V1_RNG_START, CRYPTO_V1_RNG_START, 0); + + rk_rng_writel(rk_rng, reg_ctrl, CRYPTO_V1_CTRL); + + ret = readl_poll_timeout(rk_rng->mem + CRYPTO_V1_CTRL, reg_ctrl, + !(reg_ctrl & CRYPTO_V1_RNG_START), + ROCKCHIP_POLL_TIMEOUT_US); + if (ret < 0) + goto out; + + ret = min_t(size_t, max, RK_MAX_RNG_BYTE); + + rk_rng_read_regs(rk_rng, CRYPTO_V1_TRNG_DOUT_0, buf, ret); + +out: + /* close TRNG */ + rk_rng_writel(rk_rng, HIWORD_UPDATE(0, CRYPTO_V1_RNG_START, 0), + CRYPTO_V1_CTRL); + + return ret; +} + +static int rk_rng_v2_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + int ret = 0; + u32 reg_ctrl = 0; + struct rk_rng *rk_rng = container_of(rng, struct rk_rng, rng); + + /* enable osc_ring to get entropy, sample period is set as 100 */ + rk_rng_writel(rk_rng, 100, CRYPTO_V2_RNG_SAMPLE_CNT); + + reg_ctrl |= CRYPTO_V2_RNG_256_BIT_LEN; + reg_ctrl |= CRYPTO_V2_RNG_SLOWER_SOC_RING_0; + reg_ctrl |= CRYPTO_V2_RNG_ENABLE; + reg_ctrl |= CRYPTO_V2_RNG_START; + + rk_rng_writel(rk_rng, HIWORD_UPDATE(reg_ctrl, 0xffff, 0), + CRYPTO_V2_RNG_CTL); + + ret = readl_poll_timeout(rk_rng->mem + CRYPTO_V2_RNG_CTL, reg_ctrl, + !(reg_ctrl & CRYPTO_V2_RNG_START), + ROCKCHIP_POLL_TIMEOUT_US); + if (ret < 0) + goto out; + + ret = min_t(size_t, max, RK_MAX_RNG_BYTE); + + rk_rng_read_regs(rk_rng, CRYPTO_V2_RNG_DOUT_0, buf, ret); + +out: + /* close TRNG */ + rk_rng_writel(rk_rng, HIWORD_UPDATE(0, 0xffff, 0), CRYPTO_V2_RNG_CTL); + + return ret; +} + +static const struct rk_rng_soc_data rk_rng_rk3399_soc_data = { + .clks_num = 3, + .rk_rng_read = rk_rng_v1_read, +}; + +static const struct rk_rng_soc_data rk_rng_v1_soc_data = { + .clks_num = 2, + .rk_rng_read = rk_rng_v1_read, +}; + +static const struct rk_rng_soc_data rk_rng_v2_soc_data = { + .clks_num = 2, + .rk_rng_read = rk_rng_v2_read, +}; + +static const struct of_device_id rk_rng_dt_match[] = { + { + .compatible = "rockchip,rk3399-crypto", + .data = (void *)&rk_rng_rk3399_soc_data, + }, + { + .compatible = "rockchip,cryptov1-rng", + .data = (void *)&rk_rng_v1_soc_data, + }, + { + .compatible = "rockchip,cryptov2-rng", + .data = (void *)&rk_rng_v2_soc_data, + }, + { }, +}; + +MODULE_DEVICE_TABLE(of, rk_rng_dt_match); + +static int rk_rng_probe(struct device *dev) +{ + int ret; + struct rk_rng *rk_rng; + struct device_node *np = dev->of_node; + const struct of_device_id *match; + + rk_rng = devm_kzalloc(dev, sizeof(struct rk_rng), GFP_KERNEL); + if (!rk_rng) + return -ENOMEM; + + match = of_match_node(rk_rng_dt_match, np); + rk_rng->soc_data = (struct rk_rng_soc_data *)match->data; + + rk_rng->dev = dev; + rk_rng->rng.name = "rockchip"; + rk_rng->rng.init = rk_rng_init; + rk_rng->rng.read = rk_rng->soc_data->rk_rng_read; + + rk_rng->clk_num = clk_bulk_get_all(dev, &rk_rng->clk_bulks); + if (rk_rng->clk_num < rk_rng->soc_data->clks_num) + return dev_err_probe(dev, -EINVAL, + "Missing clocks, got %d instead of %d\n", + rk_rng->clk_num, rk_rng->soc_data->clks_num); + + ret = device_reset_us(dev, 2); + if (ret) + return ret; + + rk_rng->mem = of_iomap(dev->device_node, 0); + if (IS_ERR(rk_rng->mem)) + return PTR_ERR(rk_rng->mem); + + dev->priv = rk_rng; + + return hwrng_register(dev, &rk_rng->rng); +} + +static void rk_rng_remove(struct device *dev) +{ + rk_rng_cleanup(dev->priv); +} + +static struct driver rk_rng_driver = { + .name = "rockchip-rng", + .of_match_table = rk_rng_dt_match, + .probe = rk_rng_probe, + .remove = rk_rng_remove, +}; + +device_platform_driver(rk_rng_driver); + +MODULE_DESCRIPTION("ROCKCHIP H/W Random Number Generator driver"); +MODULE_AUTHOR("Lin Jinhan <troy.lin@rock-chips.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hw_random/timeriomem-rng.c b/drivers/hw_random/timeriomem-rng.c new file mode 100644 index 0000000000..8d47058306 --- /dev/null +++ b/drivers/hw_random/timeriomem-rng.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/char/hw_random/timeriomem-rng.c + * + * Copyright (C) 2009 Alexander Clouter <alex@digriz.org.uk> + * + * Derived from drivers/char/hw_random/omap-rng.c + * Copyright 2005 (c) MontaVista Software, Inc. + * Author: Deepak Saxena <dsaxena@plexity.net> + * + * Overview: + * This driver is useful for platforms that have an IO range that provides + * periodic random data from a single IO memory address. All the platform + * has to do is provide the address and 'wait time' that new data becomes + * available. + * + * TODO: add support for reading sizes other than 32bits and masking + */ + +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/ktime.h> +#include <of.h> +#include <linux/device.h> +#include <linux/time.h> + +struct timeriomem_rng_private { + void __iomem *io_base; + + ktime_t period; + ktime_t next_read; + + struct hwrng rng_ops; +}; + +static int timeriomem_rng_read(struct hwrng *hwrng, void *data, + size_t max, bool wait) +{ + struct timeriomem_rng_private *priv = + container_of(hwrng, struct timeriomem_rng_private, rng_ops); + int retval = 0; + int period_us = ktime_to_us(priv->period); + ktime_t now = ktime_get(); + + /* + * There may not have been enough time for new data to be generated + * since the last request. If the caller doesn't want to wait, let them + * bail out. Otherwise, wait for the completion. If the new data has + * already been generated, the completion should already be available. + */ + if (ktime_before(now, priv->next_read)) { + if (!wait) + return 0; + + udelay(ktime_to_us(ktime_sub(priv->next_read, now))); + } + + do { + /* + * After the first read, all additional reads will need to wait + * for the RNG to generate new data. Since the period can have + * a wide range of values (1us to 1s have been observed), allow + * for 1% tolerance in the sleep time rather than a fixed value. + */ + if (retval > 0) + udelay(period_us); + + *(u32 *)data = readl(priv->io_base); + retval += sizeof(u32); + data += sizeof(u32); + max -= sizeof(u32); + } while (wait && max > sizeof(u32)); + + /* + * Block any new callers until the RNG has had time to generate new + * data. + */ + priv->next_read = ktime_add(ktime_get(), priv->period); + + return retval; +} + +static int timeriomem_rng_probe(struct device *dev) +{ + struct timeriomem_rng_private *priv; + struct resource *res; + int err = 0; + int period; + + /* Allocate memory for the device structure (and zero it) */ + priv = devm_kzalloc(dev, + sizeof(struct timeriomem_rng_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->io_base = dev_platform_get_and_ioremap_resource(dev, 0, &res); + if (IS_ERR(priv->io_base)) + return PTR_ERR(priv->io_base); + + if (res->start % 4 != 0 || resource_size(res) < 4) { + dev_err(dev, + "address must be at least four bytes wide and 32-bit aligned\n"); + return -EINVAL; + } + + if (of_property_read_u32(dev->of_node, "period", &period)) + return dev_err_probe(dev, -EINVAL, "missing period\n"); + + priv->period = ns_to_ktime(period * NSEC_PER_USEC); + + priv->rng_ops.name = dev_name(dev); + priv->rng_ops.read = timeriomem_rng_read; + + /* Assume random data is already available. */ + priv->next_read = ktime_get(); + + err = hwrng_register(dev, &priv->rng_ops); + if (err) { + dev_err(dev, "problem registering\n"); + return err; + } + + dev_info(dev, "32bits from 0x%p @ %dus\n", + priv->io_base, period); + + return 0; +} + +static const struct of_device_id timeriomem_rng_match[] = { + { .compatible = "timeriomem_rng" }, + {}, +}; +MODULE_DEVICE_TABLE(of, timeriomem_rng_match); + +static struct driver timeriomem_rng_driver = { + .name = "timeriomem_rng", + .of_match_table = timeriomem_rng_match, + .probe = timeriomem_rng_probe, +}; + +device_platform_driver(timeriomem_rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alexander Clouter <alex@digriz.org.uk>"); +MODULE_DESCRIPTION("Timer IOMEM H/W RNG driver"); diff --git a/include/driver.h b/include/driver.h index 7ff65d6394..a61b9dca22 100644 --- a/include/driver.h +++ b/include/driver.h @@ -289,6 +289,10 @@ struct resource *dev_request_mem_resource(struct device *dev, int num); struct resource *dev_request_mem_resource_by_name(struct device *dev, const char *name); +void __iomem *dev_platform_get_and_ioremap_resource(struct device *dev, + int num, + struct resource **out_res); + /* * exlusively request register base 'num' for a device * will return NULL on error diff --git a/include/linux/clk.h b/include/linux/clk.h index fe0b1ce3e3..7ba0679d03 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -1098,6 +1098,39 @@ static inline struct clk *clk_get_enabled(struct device *dev, const char *id) } /** + * clk_get_optional_enabled - clk_get_optional() + + * clk_prepare_enable() + * @dev: device for clock "consumer" + * @id: clock consumer ID + * + * Return: a struct clk corresponding to the clock producer, or + * valid IS_ERR() condition containing errno. The implementation + * uses @dev and @id to determine the clock consumer, and thereby + * the clock producer. If no such clk is found, it returns NULL + * which serves as a dummy clk. That's the only difference compared + * to clk_get_enabled(). + * + * The returned clk (if valid) is enabled. + */ +static inline struct clk *clk_get_optional_enabled(struct device *dev, const char *id) +{ + struct clk *clk; + int ret; + + clk = clk_get_optional(dev, id); + if (IS_ERR_OR_NULL(clk)) + return clk; + + ret = clk_enable(clk); + if (ret) { + clk_put(clk); + return ERR_PTR(ret); + } + + return clk; +} + +/** * clk_get_if_available - get clock, ignoring known unavailable clock controller * @dev: device for clock "consumer" * @id: clock consumer ID diff --git a/include/linux/hw_random.h b/include/linux/hw_random.h index 9143ba1f8d..ff6d3bb582 100644 --- a/include/linux/hw_random.h +++ b/include/linux/hw_random.h @@ -33,6 +33,7 @@ struct hwrng { struct cdev cdev; struct device *dev; void *buf; + unsigned long priv; }; /* Register a new Hardware Random Number Generator driver. */ @@ -51,4 +52,9 @@ static inline int hwrng_get_data(struct hwrng *rng, void *buffer, size_t size, i void hwrng_unregister(struct hwrng *rng); +static inline long hwrng_yield(struct hwrng *rng) +{ + return 0; +} + #endif /* LINUX_HWRANDOM_H_ */ diff --git a/include/mach/omap/am33xx-clock.h b/include/mach/omap/am33xx-clock.h index b0293db990..af47a0f3e7 100644 --- a/include/mach/omap/am33xx-clock.h +++ b/include/mach/omap/am33xx-clock.h @@ -139,6 +139,7 @@ #define CM_PER_UART4_CLKCTRL (CM_PER + 0x78) /* UART4 */ #define CM_PER_I2C1_CLKCTRL (CM_PER + 0x48) /* I2C1 */ #define CM_PER_I2C2_CLKCTRL (CM_PER + 0x44) /* I2C2 */ +#define CM_PER_RNG_CLKCTRL (CM_PER + 0x90) /* RNG */ #define CM_WKUP_GPIO0_CLKCTRL (CM_WKUP + 0x8) /* GPIO0 */ #define CM_WKUP_ADC_TSC_CLKCTRL (CM_WKUP + 0xbc)/* TSCADC */ |