Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | // SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright 2020, Matthias Brugger <mbrugger@suse.com> * * Driver for Raspberry Pi hardware random number generator */ #include <dm.h> #include <rng.h> #include <asm/io.h> #include <linux/delay.h> #define usleep_range(a, b) udelay((b)) #define RNG_CTRL_OFFSET 0x00 #define RNG_CTRL_RNG_RBGEN_MASK 0x00001FFF #define RNG_CTRL_RNG_RBGEN_ENABLE 0x00000001 #define RNG_CTRL_RNG_RBGEN_DISABLE 0x00000000 #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_NIST_FAIL_IRQ_MASK 0x00000020 #define RNG_FIFO_DATA_OFFSET 0x20 #define RNG_FIFO_COUNT_OFFSET 0x24 #define RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK 0x000000FF struct iproc_rng200_plat { void __iomem *base; }; static void iproc_rng200_enable(struct iproc_rng200_plat *pdata, bool enable) { void __iomem *rng_base = pdata->base; u32 val; val = readl(rng_base + RNG_CTRL_OFFSET); val &= ~RNG_CTRL_RNG_RBGEN_MASK; if (enable) val |= RNG_CTRL_RNG_RBGEN_ENABLE; else val &= ~RNG_CTRL_RNG_RBGEN_ENABLE; writel(val, rng_base + RNG_CTRL_OFFSET); } static void iproc_rng200_restart(struct iproc_rng200_plat *pdata) { void __iomem *rng_base = pdata->base; u32 val; iproc_rng200_enable(pdata, false); /* Clear all interrupt status */ writel(0xFFFFFFFFUL, rng_base + RNG_INT_STATUS_OFFSET); /* Reset RNG and RBG */ val = readl(rng_base + RBG_SOFT_RESET_OFFSET); val |= RBG_SOFT_RESET; writel(val, rng_base + RBG_SOFT_RESET_OFFSET); val = readl(rng_base + RNG_SOFT_RESET_OFFSET); val |= RNG_SOFT_RESET; writel(val, rng_base + RNG_SOFT_RESET_OFFSET); val = readl(rng_base + RNG_SOFT_RESET_OFFSET); val &= ~RNG_SOFT_RESET; writel(val, rng_base + RNG_SOFT_RESET_OFFSET); val = readl(rng_base + RBG_SOFT_RESET_OFFSET); val &= ~RBG_SOFT_RESET; writel(val, rng_base + RBG_SOFT_RESET_OFFSET); iproc_rng200_enable(pdata, true); } static int iproc_rng200_read(struct udevice *dev, void *data, size_t len) { struct iproc_rng200_plat *priv = dev_get_plat(dev); char *buf = (char *)data; u32 num_remaining = len; u32 status; #define MAX_RESETS_PER_READ 1 u32 num_resets = 0; while (num_remaining > 0) { /* Is RNG sane? If not, reset it. */ status = readl(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 len - num_remaining; iproc_rng200_restart(priv); num_resets++; } /* Are there any random numbers available? */ if ((readl(priv->base + RNG_FIFO_COUNT_OFFSET) & RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK) > 0) { if (num_remaining >= sizeof(u32)) { /* Buffer has room to store entire word */ *(u32 *)buf = readl(priv->base + RNG_FIFO_DATA_OFFSET); buf += sizeof(u32); num_remaining -= sizeof(u32); } else { /* Buffer can only store partial word */ u32 rnd_number = readl(priv->base + RNG_FIFO_DATA_OFFSET); memcpy(buf, &rnd_number, num_remaining); buf += num_remaining; num_remaining = 0; } } else { /* Can wait, give others chance to run */ usleep_range(min(num_remaining * 10, 500U), 500); } } return 0; } static int iproc_rng200_probe(struct udevice *dev) { struct iproc_rng200_plat *priv = dev_get_plat(dev); iproc_rng200_enable(priv, true); return 0; } static int iproc_rng200_remove(struct udevice *dev) { struct iproc_rng200_plat *priv = dev_get_plat(dev); iproc_rng200_enable(priv, false); return 0; } static int iproc_rng200_of_to_plat(struct udevice *dev) { struct iproc_rng200_plat *pdata = dev_get_plat(dev); pdata->base = devfdt_map_physmem(dev, sizeof(void *)); if (!pdata->base) return -ENODEV; return 0; } static const struct dm_rng_ops iproc_rng200_ops = { .read = iproc_rng200_read, }; static const struct udevice_id iproc_rng200_rng_match[] = { { .compatible = "brcm,bcm2711-rng200", }, { .compatible = "brcm,iproc-rng200", }, {}, }; U_BOOT_DRIVER(iproc_rng200_rng) = { .name = "iproc_rng200-rng", .id = UCLASS_RNG, .of_match = iproc_rng200_rng_match, .ops = &iproc_rng200_ops, .probe = iproc_rng200_probe, .remove = iproc_rng200_remove, .priv_auto = sizeof(struct iproc_rng200_plat), .of_to_plat = iproc_rng200_of_to_plat, }; |