Commit | Line | Data |
---|---|---|
5efb94ee JI |
1 | /* |
2 | * Copyright (c) 2010-2011 Picochip Ltd., Jamie Iles | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License version 2 as | |
6 | * published by the Free Software Foundation. | |
7 | * | |
8 | * All enquiries to support@picochip.com | |
9 | */ | |
10 | #include <linux/clk.h> | |
11 | #include <linux/delay.h> | |
12 | #include <linux/err.h> | |
13 | #include <linux/hw_random.h> | |
14 | #include <linux/io.h> | |
15 | #include <linux/kernel.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/platform_device.h> | |
18 | ||
19 | #define DATA_REG_OFFSET 0x0200 | |
20 | #define CSR_REG_OFFSET 0x0278 | |
21 | #define CSR_OUT_EMPTY_MASK (1 << 24) | |
22 | #define CSR_FAULT_MASK (1 << 1) | |
23 | #define TRNG_BLOCK_RESET_MASK (1 << 0) | |
24 | #define TAI_REG_OFFSET 0x0380 | |
25 | ||
26 | /* | |
27 | * The maximum amount of time in microseconds to spend waiting for data if the | |
28 | * core wants us to wait. The TRNG should generate 32 bits every 320ns so a | |
29 | * timeout of 20us seems reasonable. The TRNG does builtin tests of the data | |
30 | * for randomness so we can't always assume there is data present. | |
31 | */ | |
32 | #define PICO_TRNG_TIMEOUT 20 | |
33 | ||
34 | static void __iomem *rng_base; | |
35 | static struct clk *rng_clk; | |
36 | struct device *rng_dev; | |
37 | ||
38 | static inline u32 picoxcell_trng_read_csr(void) | |
39 | { | |
40 | return __raw_readl(rng_base + CSR_REG_OFFSET); | |
41 | } | |
42 | ||
43 | static inline bool picoxcell_trng_is_empty(void) | |
44 | { | |
45 | return picoxcell_trng_read_csr() & CSR_OUT_EMPTY_MASK; | |
46 | } | |
47 | ||
48 | /* | |
49 | * Take the random number generator out of reset and make sure the interrupts | |
50 | * are masked. We shouldn't need to get large amounts of random bytes so just | |
51 | * poll the status register. The hardware generates 32 bits every 320ns so we | |
52 | * shouldn't have to wait long enough to warrant waiting for an IRQ. | |
53 | */ | |
54 | static void picoxcell_trng_start(void) | |
55 | { | |
56 | __raw_writel(0, rng_base + TAI_REG_OFFSET); | |
57 | __raw_writel(0, rng_base + CSR_REG_OFFSET); | |
58 | } | |
59 | ||
60 | static void picoxcell_trng_reset(void) | |
61 | { | |
62 | __raw_writel(TRNG_BLOCK_RESET_MASK, rng_base + CSR_REG_OFFSET); | |
63 | __raw_writel(TRNG_BLOCK_RESET_MASK, rng_base + TAI_REG_OFFSET); | |
64 | picoxcell_trng_start(); | |
65 | } | |
66 | ||
67 | /* | |
68 | * Get some random data from the random number generator. The hw_random core | |
69 | * layer provides us with locking. | |
70 | */ | |
71 | static int picoxcell_trng_read(struct hwrng *rng, void *buf, size_t max, | |
72 | bool wait) | |
73 | { | |
74 | int i; | |
75 | ||
76 | /* Wait for some data to become available. */ | |
77 | for (i = 0; i < PICO_TRNG_TIMEOUT && picoxcell_trng_is_empty(); ++i) { | |
78 | if (!wait) | |
79 | return 0; | |
80 | ||
81 | udelay(1); | |
82 | } | |
83 | ||
84 | if (picoxcell_trng_read_csr() & CSR_FAULT_MASK) { | |
85 | dev_err(rng_dev, "fault detected, resetting TRNG\n"); | |
86 | picoxcell_trng_reset(); | |
87 | return -EIO; | |
88 | } | |
89 | ||
90 | if (i == PICO_TRNG_TIMEOUT) | |
91 | return 0; | |
92 | ||
93 | *(u32 *)buf = __raw_readl(rng_base + DATA_REG_OFFSET); | |
94 | return sizeof(u32); | |
95 | } | |
96 | ||
97 | static struct hwrng picoxcell_trng = { | |
98 | .name = "picoxcell", | |
99 | .read = picoxcell_trng_read, | |
100 | }; | |
101 | ||
102 | static int picoxcell_trng_probe(struct platform_device *pdev) | |
103 | { | |
104 | int ret; | |
105 | struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
106 | ||
107 | if (!mem) { | |
108 | dev_warn(&pdev->dev, "no memory resource\n"); | |
109 | return -ENOMEM; | |
110 | } | |
111 | ||
112 | if (!devm_request_mem_region(&pdev->dev, mem->start, resource_size(mem), | |
113 | "picoxcell_trng")) { | |
114 | dev_warn(&pdev->dev, "unable to request io mem\n"); | |
115 | return -EBUSY; | |
116 | } | |
117 | ||
118 | rng_base = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); | |
119 | if (!rng_base) { | |
120 | dev_warn(&pdev->dev, "unable to remap io mem\n"); | |
121 | return -ENOMEM; | |
122 | } | |
123 | ||
124 | rng_clk = clk_get(&pdev->dev, NULL); | |
125 | if (IS_ERR(rng_clk)) { | |
126 | dev_warn(&pdev->dev, "no clk\n"); | |
127 | return PTR_ERR(rng_clk); | |
128 | } | |
129 | ||
130 | ret = clk_enable(rng_clk); | |
131 | if (ret) { | |
132 | dev_warn(&pdev->dev, "unable to enable clk\n"); | |
133 | goto err_enable; | |
134 | } | |
135 | ||
136 | picoxcell_trng_start(); | |
137 | ret = hwrng_register(&picoxcell_trng); | |
138 | if (ret) | |
139 | goto err_register; | |
140 | ||
141 | rng_dev = &pdev->dev; | |
142 | dev_info(&pdev->dev, "pixoxcell random number generator active\n"); | |
143 | ||
144 | return 0; | |
145 | ||
146 | err_register: | |
147 | clk_disable(rng_clk); | |
148 | err_enable: | |
149 | clk_put(rng_clk); | |
150 | ||
151 | return ret; | |
152 | } | |
153 | ||
39af33fc | 154 | static int picoxcell_trng_remove(struct platform_device *pdev) |
5efb94ee JI |
155 | { |
156 | hwrng_unregister(&picoxcell_trng); | |
157 | clk_disable(rng_clk); | |
158 | clk_put(rng_clk); | |
159 | ||
160 | return 0; | |
161 | } | |
162 | ||
163 | #ifdef CONFIG_PM | |
164 | static int picoxcell_trng_suspend(struct device *dev) | |
165 | { | |
166 | clk_disable(rng_clk); | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
171 | static int picoxcell_trng_resume(struct device *dev) | |
172 | { | |
173 | return clk_enable(rng_clk); | |
174 | } | |
175 | ||
176 | static const struct dev_pm_ops picoxcell_trng_pm_ops = { | |
177 | .suspend = picoxcell_trng_suspend, | |
178 | .resume = picoxcell_trng_resume, | |
179 | }; | |
180 | #endif /* CONFIG_PM */ | |
181 | ||
182 | static struct platform_driver picoxcell_trng_driver = { | |
183 | .probe = picoxcell_trng_probe, | |
bcd2982a | 184 | .remove = picoxcell_trng_remove, |
5efb94ee JI |
185 | .driver = { |
186 | .name = "picoxcell-trng", | |
187 | .owner = THIS_MODULE, | |
188 | #ifdef CONFIG_PM | |
189 | .pm = &picoxcell_trng_pm_ops, | |
190 | #endif /* CONFIG_PM */ | |
191 | }, | |
192 | }; | |
193 | ||
b21cb324 | 194 | module_platform_driver(picoxcell_trng_driver); |
5efb94ee JI |
195 | |
196 | MODULE_LICENSE("GPL"); | |
197 | MODULE_AUTHOR("Jamie Iles"); | |
198 | MODULE_DESCRIPTION("Picochip picoXcell TRNG driver"); |