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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
|
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2024 Linaro Ltd.
* Author: Sam Protsenko <semen.protsenko@linaro.org>
*
* Samsung Exynos TRNG driver (True Random Number Generator).
*/
#include <clk.h>
#include <dm.h>
#include <rng.h>
#include <dm/device.h>
#include <dm/device_compat.h>
#include <asm/io.h>
#include <linux/arm-smccc.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/time.h>
#define EXYNOS_TRNG_CLKDIV 0x0
#define EXYNOS_TRNG_CLKDIV_MASK GENMASK(15, 0)
#define EXYNOS_TRNG_CLOCK_RATE 500000
#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_FIFO_TIMEOUT (1 * USEC_PER_SEC)
#define EXYNOS_SMC_CALL_VAL(func_num) \
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
ARM_SMCCC_SMC_32, \
ARM_SMCCC_OWNER_SIP, \
func_num)
/* SMC command for DTRNG access */
#define SMC_CMD_RANDOM EXYNOS_SMC_CALL_VAL(0x1012)
/* SMC_CMD_RANDOM: arguments */
#define HWRNG_INIT 0x0
#define HWRNG_EXIT 0x1
#define HWRNG_GET_DATA 0x2
/* SMC_CMD_RANDOM: return values */
#define HWRNG_RET_OK 0x0
#define HWRNG_RET_RETRY_ERROR 0x2
#define HWRNG_MAX_TRIES 100
/**
* struct exynos_trng_variant - Chip specific data
*
* @smc: Set "true" if TRNG block has to be accessed via SMC calls
* @init: (Optional) TRNG initialization function to call on probe
* @exit: (Optional) TRNG deinitialization function to call on remove
* @read: Function to read the random data from TRNG block
*/
struct exynos_trng_variant {
bool smc;
int (*init)(struct udevice *dev);
void (*exit)(struct udevice *dev);
int (*read)(struct udevice *dev, void *data, size_t len);
};
/**
* struct exynos_trng_priv - Driver's private data
*
* @base: Base address of MMIO registers of TRNG block
* @clk: Operating clock (needed for TRNG block functioning)
* @pclk: Bus clock (needed for interfacing the TRNG block registers)
* @data: Chip specific data
*/
struct exynos_trng_priv {
void __iomem *base;
struct clk *clk;
struct clk *pclk;
const struct exynos_trng_variant *data;
};
static int exynos_trng_read_reg(struct udevice *dev, void *data, size_t len)
{
struct exynos_trng_priv *trng = dev_get_priv(dev);
int val;
len = min_t(size_t, len, EXYNOS_TRNG_FIFO_LEN * 4);
writel_relaxed(len * 8, trng->base + EXYNOS_TRNG_FIFO_CTRL);
val = readl_poll_timeout(trng->base + EXYNOS_TRNG_FIFO_CTRL, val,
val == 0, EXYNOS_TRNG_FIFO_TIMEOUT);
if (val < 0)
return val;
memcpy_fromio(data, trng->base + EXYNOS_TRNG_FIFO_0, len);
return 0;
}
static int exynos_trng_read_smc(struct udevice *dev, void *data, size_t len)
{
struct arm_smccc_res res;
unsigned int copied = 0;
u32 *buf = data;
int tries = 0;
while (copied < len) {
arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_GET_DATA, 0, 0, 0, 0, 0, 0,
&res);
switch (res.a0) {
case HWRNG_RET_OK:
*buf++ = res.a2;
*buf++ = res.a3;
copied += 8;
tries = 0;
break;
case HWRNG_RET_RETRY_ERROR:
if (++tries >= HWRNG_MAX_TRIES)
return -EIO;
udelay(10);
break;
default:
return -EIO;
}
}
return 0;
}
static int exynos_trng_init_reg(struct udevice *dev)
{
const u32 max_div = EXYNOS_TRNG_CLKDIV_MASK;
struct exynos_trng_priv *trng = dev_get_priv(dev);
unsigned long sss_rate;
u32 div;
sss_rate = clk_get_rate(trng->clk);
/*
* For most TRNG circuits the clock frequency of under 500 kHz is safe.
* The clock divider should be an even number.
*/
div = sss_rate / EXYNOS_TRNG_CLOCK_RATE;
div -= div % 2; /* make sure it's even */
if (div > max_div) {
dev_err(dev, "Clock divider too large: %u", div);
return -ERANGE;
}
writel_relaxed(div, trng->base + EXYNOS_TRNG_CLKDIV);
/* Enable the generator */
writel_relaxed(EXYNOS_TRNG_CTRL_RNGEN, trng->base + EXYNOS_TRNG_CTRL);
/* Disable post-processing */
writel_relaxed(0, trng->base + EXYNOS_TRNG_POST_CTRL);
return 0;
}
static int exynos_trng_init_smc(struct udevice *dev)
{
struct arm_smccc_res res;
int ret = 0;
arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_INIT, 0, 0, 0, 0, 0, 0, &res);
if (res.a0 != HWRNG_RET_OK) {
dev_err(dev, "SMC command for TRNG init failed (%d)\n",
(int)res.a0);
ret = -EIO;
}
if ((int)res.a0 == -1)
dev_info(dev, "Make sure LDFW is loaded\n");
return ret;
}
static void exynos_trng_exit_smc(struct udevice *dev)
{
struct arm_smccc_res res;
arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_EXIT, 0, 0, 0, 0, 0, 0, &res);
}
static int exynos_trng_read(struct udevice *dev, void *data, size_t len)
{
struct exynos_trng_priv *trng = dev_get_priv(dev);
return trng->data->read(dev, data, len);
}
static int exynos_trng_of_to_plat(struct udevice *dev)
{
struct exynos_trng_priv *trng = dev_get_priv(dev);
trng->data = (struct exynos_trng_variant *)dev_get_driver_data(dev);
if (!trng->data->smc) {
trng->base = dev_read_addr_ptr(dev);
if (!trng->base)
return -EINVAL;
}
trng->clk = devm_clk_get(dev, "secss");
if (IS_ERR(trng->clk))
return PTR_ERR(trng->clk);
trng->pclk = devm_clk_get_optional(dev, "pclk");
if (IS_ERR(trng->pclk))
return PTR_ERR(trng->pclk);
return 0;
}
static int exynos_trng_probe(struct udevice *dev)
{
struct exynos_trng_priv *trng = dev_get_priv(dev);
int ret;
ret = clk_enable(trng->pclk);
if (ret)
return ret;
ret = clk_enable(trng->clk);
if (ret)
return ret;
if (trng->data->init)
ret = trng->data->init(dev);
return ret;
}
static int exynos_trng_remove(struct udevice *dev)
{
struct exynos_trng_priv *trng = dev_get_priv(dev);
if (trng->data->exit)
trng->data->exit(dev);
/* Keep SSS clocks enabled, they are needed for EL3_MON and kernel */
return 0;
}
static const struct dm_rng_ops exynos_trng_ops = {
.read = exynos_trng_read,
};
static const struct exynos_trng_variant exynos5250_trng_data = {
.init = exynos_trng_init_reg,
.read = exynos_trng_read_reg,
};
static const struct exynos_trng_variant exynos850_trng_data = {
.smc = true,
.init = exynos_trng_init_smc,
.exit = exynos_trng_exit_smc,
.read = exynos_trng_read_smc,
};
static const struct udevice_id exynos_trng_match[] = {
{
.compatible = "samsung,exynos5250-trng",
.data = (ulong)&exynos5250_trng_data,
}, {
.compatible = "samsung,exynos850-trng",
.data = (ulong)&exynos850_trng_data,
},
{ },
};
U_BOOT_DRIVER(exynos_trng) = {
.name = "exynos-trng",
.id = UCLASS_RNG,
.of_match = exynos_trng_match,
.of_to_plat = exynos_trng_of_to_plat,
.probe = exynos_trng_probe,
.remove = exynos_trng_remove,
.ops = &exynos_trng_ops,
.priv_auto = sizeof(struct exynos_trng_priv),
};
|