Commit | Line | Data |
---|---|---|
ebc915ad | 1 | /* |
c49a7f18 | 2 | * omap-rng.c - RNG driver for TI OMAP CPU family |
ebc915ad MB |
3 | * |
4 | * Author: Deepak Saxena <dsaxena@plexity.net> | |
5 | * | |
6 | * Copyright 2005 (c) MontaVista Software, Inc. | |
7 | * | |
8 | * Mostly based on original driver: | |
9 | * | |
10 | * Copyright (C) 2005 Nokia Corporation | |
96de0e25 | 11 | * Author: Juha Yrjölä <juha.yrjola@nokia.com> |
ebc915ad MB |
12 | * |
13 | * This file is licensed under the terms of the GNU General Public | |
14 | * License version 2. This program is licensed "as is" without any | |
15 | * warranty of any kind, whether express or implied. | |
ebc915ad MB |
16 | */ |
17 | ||
18 | #include <linux/module.h> | |
19 | #include <linux/init.h> | |
20 | #include <linux/random.h> | |
21 | #include <linux/err.h> | |
af2bc7d2 | 22 | #include <linux/platform_device.h> |
ebc915ad | 23 | #include <linux/hw_random.h> |
984e976f | 24 | #include <linux/delay.h> |
02666360 | 25 | #include <linux/slab.h> |
665d92fa | 26 | #include <linux/pm_runtime.h> |
c903970c LV |
27 | #include <linux/of.h> |
28 | #include <linux/of_device.h> | |
29 | #include <linux/of_address.h> | |
e83872c9 | 30 | #include <linux/interrupt.h> |
ebc915ad MB |
31 | |
32 | #include <asm/io.h> | |
ebc915ad | 33 | |
e83872c9 LV |
34 | #define RNG_REG_STATUS_RDY (1 << 0) |
35 | ||
36 | #define RNG_REG_INTACK_RDY_MASK (1 << 0) | |
37 | #define RNG_REG_INTACK_SHUTDOWN_OFLO_MASK (1 << 1) | |
38 | #define RNG_SHUTDOWN_OFLO_MASK (1 << 1) | |
39 | ||
40 | #define RNG_CONTROL_STARTUP_CYCLES_SHIFT 16 | |
41 | #define RNG_CONTROL_STARTUP_CYCLES_MASK (0xffff << 16) | |
42 | #define RNG_CONTROL_ENABLE_TRNG_SHIFT 10 | |
43 | #define RNG_CONTROL_ENABLE_TRNG_MASK (1 << 10) | |
44 | ||
45 | #define RNG_CONFIG_MAX_REFIL_CYCLES_SHIFT 16 | |
46 | #define RNG_CONFIG_MAX_REFIL_CYCLES_MASK (0xffff << 16) | |
47 | #define RNG_CONFIG_MIN_REFIL_CYCLES_SHIFT 0 | |
48 | #define RNG_CONFIG_MIN_REFIL_CYCLES_MASK (0xff << 0) | |
49 | ||
50 | #define RNG_CONTROL_STARTUP_CYCLES 0xff | |
51 | #define RNG_CONFIG_MIN_REFIL_CYCLES 0x21 | |
52 | #define RNG_CONFIG_MAX_REFIL_CYCLES 0x22 | |
53 | ||
54 | #define RNG_ALARMCNT_ALARM_TH_SHIFT 0x0 | |
55 | #define RNG_ALARMCNT_ALARM_TH_MASK (0xff << 0) | |
56 | #define RNG_ALARMCNT_SHUTDOWN_TH_SHIFT 16 | |
57 | #define RNG_ALARMCNT_SHUTDOWN_TH_MASK (0x1f << 16) | |
58 | #define RNG_ALARM_THRESHOLD 0xff | |
59 | #define RNG_SHUTDOWN_THRESHOLD 0x4 | |
60 | ||
61 | #define RNG_REG_FROENABLE_MASK 0xffffff | |
62 | #define RNG_REG_FRODETUNE_MASK 0xffffff | |
63 | ||
64 | #define OMAP2_RNG_OUTPUT_SIZE 0x4 | |
65 | #define OMAP4_RNG_OUTPUT_SIZE 0x8 | |
66 | ||
67 | enum { | |
68 | RNG_OUTPUT_L_REG = 0, | |
69 | RNG_OUTPUT_H_REG, | |
70 | RNG_STATUS_REG, | |
71 | RNG_INTMASK_REG, | |
72 | RNG_INTACK_REG, | |
73 | RNG_CONTROL_REG, | |
74 | RNG_CONFIG_REG, | |
75 | RNG_ALARMCNT_REG, | |
76 | RNG_FROENABLE_REG, | |
77 | RNG_FRODETUNE_REG, | |
78 | RNG_ALARMMASK_REG, | |
79 | RNG_ALARMSTOP_REG, | |
80 | RNG_REV_REG, | |
81 | RNG_SYSCONFIG_REG, | |
82 | }; | |
83 | ||
84 | static const u16 reg_map_omap2[] = { | |
85 | [RNG_OUTPUT_L_REG] = 0x0, | |
86 | [RNG_STATUS_REG] = 0x4, | |
87 | [RNG_CONFIG_REG] = 0x28, | |
88 | [RNG_REV_REG] = 0x3c, | |
89 | [RNG_SYSCONFIG_REG] = 0x40, | |
90 | }; | |
91 | ||
92 | static const u16 reg_map_omap4[] = { | |
93 | [RNG_OUTPUT_L_REG] = 0x0, | |
94 | [RNG_OUTPUT_H_REG] = 0x4, | |
95 | [RNG_STATUS_REG] = 0x8, | |
96 | [RNG_INTMASK_REG] = 0xc, | |
97 | [RNG_INTACK_REG] = 0x10, | |
98 | [RNG_CONTROL_REG] = 0x14, | |
99 | [RNG_CONFIG_REG] = 0x18, | |
100 | [RNG_ALARMCNT_REG] = 0x1c, | |
101 | [RNG_FROENABLE_REG] = 0x20, | |
102 | [RNG_FRODETUNE_REG] = 0x24, | |
103 | [RNG_ALARMMASK_REG] = 0x28, | |
104 | [RNG_ALARMSTOP_REG] = 0x2c, | |
105 | [RNG_REV_REG] = 0x1FE0, | |
106 | [RNG_SYSCONFIG_REG] = 0x1FE4, | |
107 | }; | |
ebc915ad | 108 | |
e83872c9 | 109 | struct omap_rng_dev; |
02666360 | 110 | /** |
e83872c9 LV |
111 | * struct omap_rng_pdata - RNG IP block-specific data |
112 | * @regs: Pointer to the register offsets structure. | |
113 | * @data_size: No. of bytes in RNG output. | |
114 | * @data_present: Callback to determine if data is available. | |
115 | * @init: Callback for IP specific initialization sequence. | |
116 | * @cleanup: Callback for IP specific cleanup sequence. | |
02666360 | 117 | */ |
e83872c9 LV |
118 | struct omap_rng_pdata { |
119 | u16 *regs; | |
120 | u32 data_size; | |
121 | u32 (*data_present)(struct omap_rng_dev *priv); | |
122 | int (*init)(struct omap_rng_dev *priv); | |
123 | void (*cleanup)(struct omap_rng_dev *priv); | |
02666360 | 124 | }; |
ebc915ad | 125 | |
e83872c9 LV |
126 | struct omap_rng_dev { |
127 | void __iomem *base; | |
128 | struct device *dev; | |
129 | const struct omap_rng_pdata *pdata; | |
130 | }; | |
131 | ||
132 | static inline u32 omap_rng_read(struct omap_rng_dev *priv, u16 reg) | |
133 | { | |
134 | return __raw_readl(priv->base + priv->pdata->regs[reg]); | |
135 | } | |
136 | ||
137 | static inline void omap_rng_write(struct omap_rng_dev *priv, u16 reg, | |
138 | u32 val) | |
ebc915ad | 139 | { |
e83872c9 | 140 | __raw_writel(val, priv->base + priv->pdata->regs[reg]); |
ebc915ad MB |
141 | } |
142 | ||
e83872c9 | 143 | static inline u32 omap2_rng_data_present(struct omap_rng_dev *priv) |
ebc915ad | 144 | { |
e83872c9 LV |
145 | return omap_rng_read(priv, RNG_STATUS_REG) ? 0 : 1; |
146 | } | |
147 | ||
148 | static inline u32 omap4_rng_data_present(struct omap_rng_dev *priv) | |
149 | { | |
150 | return omap_rng_read(priv, RNG_STATUS_REG) & RNG_REG_STATUS_RDY; | |
ebc915ad MB |
151 | } |
152 | ||
984e976f | 153 | static int omap_rng_data_present(struct hwrng *rng, int wait) |
ebc915ad | 154 | { |
e83872c9 | 155 | struct omap_rng_dev *priv; |
984e976f PM |
156 | int data, i; |
157 | ||
e83872c9 | 158 | priv = (struct omap_rng_dev *)rng->priv; |
02666360 | 159 | |
984e976f | 160 | for (i = 0; i < 20; i++) { |
e83872c9 | 161 | data = priv->pdata->data_present(priv); |
984e976f PM |
162 | if (data || !wait) |
163 | break; | |
c49a7f18 DB |
164 | /* RNG produces data fast enough (2+ MBit/sec, even |
165 | * during "rngtest" loads, that these delays don't | |
166 | * seem to trigger. We *could* use the RNG IRQ, but | |
167 | * that'd be higher overhead ... so why bother? | |
168 | */ | |
984e976f PM |
169 | udelay(10); |
170 | } | |
171 | return data; | |
ebc915ad MB |
172 | } |
173 | ||
174 | static int omap_rng_data_read(struct hwrng *rng, u32 *data) | |
175 | { | |
e83872c9 LV |
176 | struct omap_rng_dev *priv; |
177 | u32 data_size, i; | |
178 | ||
179 | priv = (struct omap_rng_dev *)rng->priv; | |
180 | data_size = priv->pdata->data_size; | |
181 | ||
182 | for (i = 0; i < data_size / sizeof(u32); i++) | |
183 | data[i] = omap_rng_read(priv, RNG_OUTPUT_L_REG + i); | |
184 | ||
185 | if (priv->pdata->regs[RNG_INTACK_REG]) | |
186 | omap_rng_write(priv, RNG_INTACK_REG, RNG_REG_INTACK_RDY_MASK); | |
187 | return data_size; | |
188 | } | |
189 | ||
190 | static int omap4_rng_init(struct omap_rng_dev *priv) | |
191 | { | |
192 | u32 val; | |
193 | ||
194 | /* Return if RNG is already running. */ | |
195 | if (omap_rng_read(priv, RNG_CONFIG_REG) & RNG_CONTROL_ENABLE_TRNG_MASK) | |
196 | return 0; | |
197 | ||
198 | val = RNG_CONFIG_MIN_REFIL_CYCLES << RNG_CONFIG_MIN_REFIL_CYCLES_SHIFT; | |
199 | val |= RNG_CONFIG_MAX_REFIL_CYCLES << RNG_CONFIG_MAX_REFIL_CYCLES_SHIFT; | |
200 | omap_rng_write(priv, RNG_CONFIG_REG, val); | |
201 | ||
202 | omap_rng_write(priv, RNG_FRODETUNE_REG, 0x0); | |
203 | omap_rng_write(priv, RNG_FROENABLE_REG, RNG_REG_FROENABLE_MASK); | |
204 | val = RNG_ALARM_THRESHOLD << RNG_ALARMCNT_ALARM_TH_SHIFT; | |
205 | val |= RNG_SHUTDOWN_THRESHOLD << RNG_ALARMCNT_SHUTDOWN_TH_SHIFT; | |
206 | omap_rng_write(priv, RNG_ALARMCNT_REG, val); | |
207 | ||
208 | val = RNG_CONTROL_STARTUP_CYCLES << RNG_CONTROL_STARTUP_CYCLES_SHIFT; | |
209 | val |= RNG_CONTROL_ENABLE_TRNG_MASK; | |
210 | omap_rng_write(priv, RNG_CONTROL_REG, val); | |
211 | ||
212 | return 0; | |
213 | } | |
214 | ||
215 | static void omap4_rng_cleanup(struct omap_rng_dev *priv) | |
216 | { | |
217 | int val; | |
218 | ||
219 | val = omap_rng_read(priv, RNG_CONTROL_REG); | |
220 | val &= ~RNG_CONTROL_ENABLE_TRNG_MASK; | |
221 | omap_rng_write(priv, RNG_CONFIG_REG, val); | |
222 | } | |
223 | ||
224 | static int omap2_rng_init(struct omap_rng_dev *priv) | |
225 | { | |
226 | omap_rng_write(priv, RNG_SYSCONFIG_REG, 0x1); | |
227 | return 0; | |
228 | } | |
229 | ||
230 | static void omap2_rng_cleanup(struct omap_rng_dev *priv) | |
231 | { | |
232 | omap_rng_write(priv, RNG_SYSCONFIG_REG, 0x0); | |
233 | } | |
234 | ||
235 | static int omap_rng_init(struct hwrng *rng) | |
236 | { | |
237 | struct omap_rng_dev *priv; | |
238 | ||
239 | priv = (struct omap_rng_dev *)rng->priv; | |
240 | return priv->pdata->init(priv); | |
241 | } | |
242 | ||
243 | static void omap_rng_cleanup(struct hwrng *rng) | |
244 | { | |
245 | struct omap_rng_dev *priv; | |
246 | ||
247 | priv = (struct omap_rng_dev *)rng->priv; | |
248 | priv->pdata->cleanup(priv); | |
249 | } | |
250 | ||
251 | static irqreturn_t omap4_rng_irq(int irq, void *dev_id) | |
252 | { | |
253 | struct omap_rng_dev *priv = dev_id; | |
254 | u32 fro_detune, fro_enable; | |
255 | ||
256 | /* | |
257 | * Interrupt raised by a fro shutdown threshold, do the following: | |
258 | * 1. Clear the alarm events. | |
259 | * 2. De tune the FROs which are shutdown. | |
260 | * 3. Re enable the shutdown FROs. | |
261 | */ | |
262 | omap_rng_write(priv, RNG_ALARMMASK_REG, 0x0); | |
263 | omap_rng_write(priv, RNG_ALARMSTOP_REG, 0x0); | |
264 | ||
265 | fro_enable = omap_rng_read(priv, RNG_FROENABLE_REG); | |
266 | fro_detune = ~fro_enable & RNG_REG_FRODETUNE_MASK; | |
267 | fro_detune = fro_detune | omap_rng_read(priv, RNG_FRODETUNE_REG); | |
268 | fro_enable = RNG_REG_FROENABLE_MASK; | |
02666360 | 269 | |
e83872c9 LV |
270 | omap_rng_write(priv, RNG_FRODETUNE_REG, fro_detune); |
271 | omap_rng_write(priv, RNG_FROENABLE_REG, fro_enable); | |
02666360 | 272 | |
e83872c9 | 273 | omap_rng_write(priv, RNG_INTACK_REG, RNG_REG_INTACK_SHUTDOWN_OFLO_MASK); |
ebc915ad | 274 | |
e83872c9 | 275 | return IRQ_HANDLED; |
ebc915ad MB |
276 | } |
277 | ||
278 | static struct hwrng omap_rng_ops = { | |
279 | .name = "omap", | |
280 | .data_present = omap_rng_data_present, | |
281 | .data_read = omap_rng_data_read, | |
e83872c9 LV |
282 | .init = omap_rng_init, |
283 | .cleanup = omap_rng_cleanup, | |
284 | }; | |
285 | ||
286 | static struct omap_rng_pdata omap2_rng_pdata = { | |
287 | .regs = (u16 *)reg_map_omap2, | |
288 | .data_size = OMAP2_RNG_OUTPUT_SIZE, | |
289 | .data_present = omap2_rng_data_present, | |
290 | .init = omap2_rng_init, | |
291 | .cleanup = omap2_rng_cleanup, | |
ebc915ad MB |
292 | }; |
293 | ||
c903970c | 294 | #if defined(CONFIG_OF) |
e83872c9 LV |
295 | static struct omap_rng_pdata omap4_rng_pdata = { |
296 | .regs = (u16 *)reg_map_omap4, | |
297 | .data_size = OMAP4_RNG_OUTPUT_SIZE, | |
298 | .data_present = omap4_rng_data_present, | |
299 | .init = omap4_rng_init, | |
300 | .cleanup = omap4_rng_cleanup, | |
301 | }; | |
302 | ||
c903970c | 303 | static const struct of_device_id omap_rng_of_match[] = { |
e83872c9 LV |
304 | { |
305 | .compatible = "ti,omap2-rng", | |
306 | .data = &omap2_rng_pdata, | |
307 | }, | |
308 | { | |
309 | .compatible = "ti,omap4-rng", | |
310 | .data = &omap4_rng_pdata, | |
311 | }, | |
c903970c LV |
312 | {}, |
313 | }; | |
314 | MODULE_DEVICE_TABLE(of, omap_rng_of_match); | |
e83872c9 LV |
315 | |
316 | static int of_get_omap_rng_device_details(struct omap_rng_dev *priv, | |
317 | struct platform_device *pdev) | |
318 | { | |
319 | const struct of_device_id *match; | |
320 | struct device *dev = &pdev->dev; | |
321 | int irq, err; | |
322 | ||
323 | match = of_match_device(of_match_ptr(omap_rng_of_match), dev); | |
324 | if (!match) { | |
325 | dev_err(dev, "no compatible OF match\n"); | |
326 | return -EINVAL; | |
327 | } | |
328 | priv->pdata = match->data; | |
329 | ||
330 | if (of_device_is_compatible(dev->of_node, "ti,omap4-rng")) { | |
331 | irq = platform_get_irq(pdev, 0); | |
332 | if (irq < 0) { | |
333 | dev_err(dev, "%s: error getting IRQ resource - %d\n", | |
334 | __func__, irq); | |
335 | return irq; | |
336 | } | |
337 | ||
338 | err = devm_request_irq(dev, irq, omap4_rng_irq, | |
339 | IRQF_TRIGGER_NONE, dev_name(dev), priv); | |
340 | if (err) { | |
341 | dev_err(dev, "unable to request irq %d, err = %d\n", | |
342 | irq, err); | |
343 | return err; | |
344 | } | |
345 | omap_rng_write(priv, RNG_INTMASK_REG, RNG_SHUTDOWN_OFLO_MASK); | |
346 | } | |
347 | return 0; | |
348 | } | |
349 | #else | |
350 | static int of_get_omap_rng_device_details(struct omap_rng_dev *omap_rng, | |
351 | struct platform_device *pdev) | |
352 | { | |
353 | return -EINVAL; | |
354 | } | |
c903970c LV |
355 | #endif |
356 | ||
e83872c9 LV |
357 | static int get_omap_rng_device_details(struct omap_rng_dev *omap_rng) |
358 | { | |
359 | /* Only OMAP2/3 can be non-DT */ | |
360 | omap_rng->pdata = &omap2_rng_pdata; | |
361 | return 0; | |
362 | } | |
363 | ||
bcd2982a | 364 | static int omap_rng_probe(struct platform_device *pdev) |
ebc915ad | 365 | { |
e83872c9 LV |
366 | struct omap_rng_dev *priv; |
367 | struct resource *res; | |
368 | struct device *dev = &pdev->dev; | |
ebc915ad MB |
369 | int ret; |
370 | ||
e83872c9 | 371 | priv = devm_kzalloc(dev, sizeof(struct omap_rng_dev), GFP_KERNEL); |
02666360 PW |
372 | if (!priv) { |
373 | dev_err(&pdev->dev, "could not allocate memory\n"); | |
374 | return -ENOMEM; | |
375 | }; | |
376 | ||
377 | omap_rng_ops.priv = (unsigned long)priv; | |
1f539bcb | 378 | platform_set_drvdata(pdev, priv); |
e83872c9 | 379 | priv->dev = dev; |
ebc915ad | 380 | |
e83872c9 LV |
381 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
382 | priv->base = devm_ioremap_resource(dev, res); | |
c7c9e1c3 TR |
383 | if (IS_ERR(priv->base)) { |
384 | ret = PTR_ERR(priv->base); | |
55c381e4 RK |
385 | goto err_ioremap; |
386 | } | |
ebc915ad | 387 | |
665d92fa PW |
388 | pm_runtime_enable(&pdev->dev); |
389 | pm_runtime_get_sync(&pdev->dev); | |
390 | ||
e83872c9 LV |
391 | ret = (dev->of_node) ? of_get_omap_rng_device_details(priv, pdev) : |
392 | get_omap_rng_device_details(priv); | |
393 | if (ret) | |
394 | goto err_ioremap; | |
395 | ||
ebc915ad | 396 | ret = hwrng_register(&omap_rng_ops); |
55c381e4 RK |
397 | if (ret) |
398 | goto err_register; | |
ebc915ad | 399 | |
af2bc7d2 | 400 | dev_info(&pdev->dev, "OMAP Random Number Generator ver. %02x\n", |
e83872c9 | 401 | omap_rng_read(priv, RNG_REV_REG)); |
ebc915ad MB |
402 | |
403 | return 0; | |
55c381e4 RK |
404 | |
405 | err_register: | |
02666360 | 406 | priv->base = NULL; |
665d92fa | 407 | pm_runtime_disable(&pdev->dev); |
55c381e4 | 408 | err_ioremap: |
e83872c9 | 409 | dev_err(dev, "initialization failed.\n"); |
55c381e4 | 410 | return ret; |
ebc915ad MB |
411 | } |
412 | ||
af2bc7d2 | 413 | static int __exit omap_rng_remove(struct platform_device *pdev) |
ebc915ad | 414 | { |
e83872c9 | 415 | struct omap_rng_dev *priv = platform_get_drvdata(pdev); |
02666360 | 416 | |
ebc915ad MB |
417 | hwrng_unregister(&omap_rng_ops); |
418 | ||
e83872c9 | 419 | priv->pdata->cleanup(priv); |
02666360 | 420 | |
665d92fa PW |
421 | pm_runtime_put_sync(&pdev->dev); |
422 | pm_runtime_disable(&pdev->dev); | |
ebc915ad | 423 | |
ebc915ad MB |
424 | return 0; |
425 | } | |
426 | ||
59596df6 | 427 | #ifdef CONFIG_PM_SLEEP |
ebc915ad | 428 | |
7650572a | 429 | static int omap_rng_suspend(struct device *dev) |
ebc915ad | 430 | { |
e83872c9 | 431 | struct omap_rng_dev *priv = dev_get_drvdata(dev); |
02666360 | 432 | |
e83872c9 | 433 | priv->pdata->cleanup(priv); |
665d92fa | 434 | pm_runtime_put_sync(dev); |
02666360 | 435 | |
ebc915ad MB |
436 | return 0; |
437 | } | |
438 | ||
7650572a | 439 | static int omap_rng_resume(struct device *dev) |
ebc915ad | 440 | { |
e83872c9 | 441 | struct omap_rng_dev *priv = dev_get_drvdata(dev); |
02666360 | 442 | |
665d92fa | 443 | pm_runtime_get_sync(dev); |
e83872c9 | 444 | priv->pdata->init(priv); |
02666360 | 445 | |
af2bc7d2 | 446 | return 0; |
ebc915ad MB |
447 | } |
448 | ||
7650572a RW |
449 | static SIMPLE_DEV_PM_OPS(omap_rng_pm, omap_rng_suspend, omap_rng_resume); |
450 | #define OMAP_RNG_PM (&omap_rng_pm) | |
451 | ||
ebc915ad MB |
452 | #else |
453 | ||
7650572a | 454 | #define OMAP_RNG_PM NULL |
ebc915ad MB |
455 | |
456 | #endif | |
457 | ||
af2bc7d2 DB |
458 | static struct platform_driver omap_rng_driver = { |
459 | .driver = { | |
460 | .name = "omap_rng", | |
461 | .owner = THIS_MODULE, | |
7650572a | 462 | .pm = OMAP_RNG_PM, |
c903970c | 463 | .of_match_table = of_match_ptr(omap_rng_of_match), |
af2bc7d2 | 464 | }, |
ebc915ad MB |
465 | .probe = omap_rng_probe, |
466 | .remove = __exit_p(omap_rng_remove), | |
ebc915ad MB |
467 | }; |
468 | ||
4390f77b LV |
469 | module_platform_driver(omap_rng_driver); |
470 | MODULE_ALIAS("platform:omap_rng"); | |
ebc915ad MB |
471 | MODULE_AUTHOR("Deepak Saxena (and others)"); |
472 | MODULE_LICENSE("GPL"); |