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 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | // SPDX-License-Identifier: GPL-2.0 /* * RNG driver for Exynos TRNGs * * Author: Łukasz Stelmach <l.stelmach@samsung.com> * * Copyright 2017 (c) Samsung Electronics Software, Inc. * * Based on the Exynos PRNG driver drivers/crypto/exynos-rng by * Krzysztof Kozłowski <krzk@kernel.org> */ #include <linux/clk.h> #include <linux/crypto.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/hw_random.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #define EXYNOS_TRNG_CLKDIV (0x0) #define EXYNOS_TRNG_CTRL (0x20) #define EXYNOS_TRNG_CTRL_RNGEN BIT(31) #define EXYNOS_TRNG_POST_CTRL (0x30) #define EXYNOS_TRNG_ONLINE_CTRL (0x40) #define EXYNOS_TRNG_ONLINE_STAT (0x44) #define EXYNOS_TRNG_ONLINE_MAXCHI2 (0x48) #define EXYNOS_TRNG_FIFO_CTRL (0x50) #define EXYNOS_TRNG_FIFO_0 (0x80) #define EXYNOS_TRNG_FIFO_1 (0x84) #define EXYNOS_TRNG_FIFO_2 (0x88) #define EXYNOS_TRNG_FIFO_3 (0x8c) #define EXYNOS_TRNG_FIFO_4 (0x90) #define EXYNOS_TRNG_FIFO_5 (0x94) #define EXYNOS_TRNG_FIFO_6 (0x98) #define EXYNOS_TRNG_FIFO_7 (0x9c) #define EXYNOS_TRNG_FIFO_LEN (8) #define EXYNOS_TRNG_CLOCK_RATE (500000) struct exynos_trng_dev { struct device *dev; void __iomem *mem; struct clk *clk; struct hwrng rng; }; static int exynos_trng_do_read(struct hwrng *rng, void *data, size_t max, bool wait) { struct exynos_trng_dev *trng; int val; max = min_t(size_t, max, (EXYNOS_TRNG_FIFO_LEN * 4)); trng = (struct exynos_trng_dev *)rng->priv; writel_relaxed(max * 8, trng->mem + EXYNOS_TRNG_FIFO_CTRL); val = readl_poll_timeout(trng->mem + EXYNOS_TRNG_FIFO_CTRL, val, val == 0, 200, 1000000); if (val < 0) return val; memcpy_fromio(data, trng->mem + EXYNOS_TRNG_FIFO_0, max); return max; } static int exynos_trng_init(struct hwrng *rng) { struct exynos_trng_dev *trng = (struct exynos_trng_dev *)rng->priv; unsigned long sss_rate; u32 val; sss_rate = clk_get_rate(trng->clk); /* * For most TRNG circuits the clock frequency of under 500 kHz * is safe. */ val = sss_rate / (EXYNOS_TRNG_CLOCK_RATE * 2); if (val > 0x7fff) { dev_err(trng->dev, "clock divider too large: %d", val); return -ERANGE; } val = val << 1; writel_relaxed(val, trng->mem + EXYNOS_TRNG_CLKDIV); /* Enable the generator. */ val = EXYNOS_TRNG_CTRL_RNGEN; writel_relaxed(val, trng->mem + EXYNOS_TRNG_CTRL); /* * Disable post-processing. /dev/hwrng is supposed to deliver * unprocessed data. */ writel_relaxed(0, trng->mem + EXYNOS_TRNG_POST_CTRL); return 0; } static int exynos_trng_probe(struct platform_device *pdev) { struct exynos_trng_dev *trng; int ret = -ENOMEM; trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL); if (!trng) return ret; trng->rng.name = devm_kstrdup(&pdev->dev, dev_name(&pdev->dev), GFP_KERNEL); if (!trng->rng.name) return ret; trng->rng.init = exynos_trng_init; trng->rng.read = exynos_trng_do_read; trng->rng.priv = (unsigned long) trng; platform_set_drvdata(pdev, trng); trng->dev = &pdev->dev; trng->mem = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(trng->mem)) return PTR_ERR(trng->mem); pm_runtime_enable(&pdev->dev); ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) { dev_err(&pdev->dev, "Could not get runtime PM.\n"); goto err_pm_get; } trng->clk = devm_clk_get(&pdev->dev, "secss"); if (IS_ERR(trng->clk)) { ret = PTR_ERR(trng->clk); dev_err(&pdev->dev, "Could not get clock.\n"); goto err_clock; } ret = clk_prepare_enable(trng->clk); if (ret) { dev_err(&pdev->dev, "Could not enable the clk.\n"); goto err_clock; } ret = devm_hwrng_register(&pdev->dev, &trng->rng); if (ret) { dev_err(&pdev->dev, "Could not register hwrng device.\n"); goto err_register; } dev_info(&pdev->dev, "Exynos True Random Number Generator.\n"); return 0; err_register: clk_disable_unprepare(trng->clk); err_clock: pm_runtime_put_noidle(&pdev->dev); err_pm_get: pm_runtime_disable(&pdev->dev); return ret; } static int exynos_trng_remove(struct platform_device *pdev) { struct exynos_trng_dev *trng = platform_get_drvdata(pdev); clk_disable_unprepare(trng->clk); pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); return 0; } static int __maybe_unused exynos_trng_suspend(struct device *dev) { pm_runtime_put_sync(dev); return 0; } static int __maybe_unused exynos_trng_resume(struct device *dev) { int ret; ret = pm_runtime_resume_and_get(dev); if (ret < 0) { dev_err(dev, "Could not get runtime PM.\n"); return ret; } return 0; } static SIMPLE_DEV_PM_OPS(exynos_trng_pm_ops, exynos_trng_suspend, exynos_trng_resume); static const struct of_device_id exynos_trng_dt_match[] = { { .compatible = "samsung,exynos5250-trng", }, { }, }; MODULE_DEVICE_TABLE(of, exynos_trng_dt_match); static struct platform_driver exynos_trng_driver = { .driver = { .name = "exynos-trng", .pm = &exynos_trng_pm_ops, .of_match_table = exynos_trng_dt_match, }, .probe = exynos_trng_probe, .remove = exynos_trng_remove, }; module_platform_driver(exynos_trng_driver); MODULE_AUTHOR("Łukasz Stelmach"); MODULE_DESCRIPTION("H/W TRNG driver for Exynos chips"); MODULE_LICENSE("GPL v2"); |