1 | /* |
2 | * Copyright (c) 2011 Peter Korsgaard <jacmet@sunsite.dk> |
3 | * |
4 | * This file is licensed under the terms of the GNU General Public |
5 | * License version 2. This program is licensed "as is" without any |
6 | * warranty of any kind, whether express or implied. |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/module.h> |
11 | #include <linux/mod_devicetable.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/err.h> |
14 | #include <linux/clk.h> |
15 | #include <linux/io.h> |
16 | #include <linux/iopoll.h> |
17 | #include <linux/hw_random.h> |
18 | #include <linux/of.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/pm_runtime.h> |
21 | |
22 | #define TRNG_CR 0x00 |
23 | #define TRNG_MR 0x04 |
24 | #define TRNG_ISR 0x1c |
25 | #define TRNG_ISR_DATRDY BIT(0) |
26 | #define TRNG_ODATA 0x50 |
27 | |
28 | #define TRNG_KEY 0x524e4700 /* RNG */ |
29 | |
30 | #define TRNG_HALFR BIT(0) /* generate RN every 168 cycles */ |
31 | |
32 | struct atmel_trng_data { |
33 | bool has_half_rate; |
34 | }; |
35 | |
36 | struct atmel_trng { |
37 | struct clk *clk; |
38 | void __iomem *base; |
39 | struct hwrng rng; |
40 | bool has_half_rate; |
41 | }; |
42 | |
43 | static bool atmel_trng_wait_ready(struct atmel_trng *trng, bool wait) |
44 | { |
45 | int ready; |
46 | |
47 | ready = readl(addr: trng->base + TRNG_ISR) & TRNG_ISR_DATRDY; |
48 | if (!ready && wait) |
49 | readl_poll_timeout(trng->base + TRNG_ISR, ready, |
50 | ready & TRNG_ISR_DATRDY, 1000, 20000); |
51 | |
52 | return !!ready; |
53 | } |
54 | |
55 | static int atmel_trng_read(struct hwrng *rng, void *buf, size_t max, |
56 | bool wait) |
57 | { |
58 | struct atmel_trng *trng = container_of(rng, struct atmel_trng, rng); |
59 | u32 *data = buf; |
60 | int ret; |
61 | |
62 | ret = pm_runtime_get_sync(dev: (struct device *)trng->rng.priv); |
63 | if (ret < 0) { |
64 | pm_runtime_put_sync(dev: (struct device *)trng->rng.priv); |
65 | return ret; |
66 | } |
67 | |
68 | ret = atmel_trng_wait_ready(trng, wait); |
69 | if (!ret) |
70 | goto out; |
71 | |
72 | *data = readl(addr: trng->base + TRNG_ODATA); |
73 | /* |
74 | * ensure data ready is only set again AFTER the next data word is ready |
75 | * in case it got set between checking ISR and reading ODATA, so we |
76 | * don't risk re-reading the same word |
77 | */ |
78 | readl(addr: trng->base + TRNG_ISR); |
79 | ret = 4; |
80 | |
81 | out: |
82 | pm_runtime_mark_last_busy(dev: (struct device *)trng->rng.priv); |
83 | pm_runtime_put_sync_autosuspend(dev: (struct device *)trng->rng.priv); |
84 | return ret; |
85 | } |
86 | |
87 | static int atmel_trng_init(struct atmel_trng *trng) |
88 | { |
89 | unsigned long rate; |
90 | int ret; |
91 | |
92 | ret = clk_prepare_enable(clk: trng->clk); |
93 | if (ret) |
94 | return ret; |
95 | |
96 | if (trng->has_half_rate) { |
97 | rate = clk_get_rate(clk: trng->clk); |
98 | |
99 | /* if peripheral clk is above 100MHz, set HALFR */ |
100 | if (rate > 100000000) |
101 | writel(TRNG_HALFR, addr: trng->base + TRNG_MR); |
102 | } |
103 | |
104 | writel(TRNG_KEY | 1, addr: trng->base + TRNG_CR); |
105 | |
106 | return 0; |
107 | } |
108 | |
109 | static void atmel_trng_cleanup(struct atmel_trng *trng) |
110 | { |
111 | writel(TRNG_KEY, addr: trng->base + TRNG_CR); |
112 | clk_disable_unprepare(clk: trng->clk); |
113 | } |
114 | |
115 | static int atmel_trng_probe(struct platform_device *pdev) |
116 | { |
117 | struct atmel_trng *trng; |
118 | const struct atmel_trng_data *data; |
119 | int ret; |
120 | |
121 | trng = devm_kzalloc(dev: &pdev->dev, size: sizeof(*trng), GFP_KERNEL); |
122 | if (!trng) |
123 | return -ENOMEM; |
124 | |
125 | trng->base = devm_platform_ioremap_resource(pdev, index: 0); |
126 | if (IS_ERR(ptr: trng->base)) |
127 | return PTR_ERR(ptr: trng->base); |
128 | |
129 | trng->clk = devm_clk_get(dev: &pdev->dev, NULL); |
130 | if (IS_ERR(ptr: trng->clk)) |
131 | return PTR_ERR(ptr: trng->clk); |
132 | data = of_device_get_match_data(dev: &pdev->dev); |
133 | if (!data) |
134 | return -ENODEV; |
135 | |
136 | trng->has_half_rate = data->has_half_rate; |
137 | trng->rng.name = pdev->name; |
138 | trng->rng.read = atmel_trng_read; |
139 | trng->rng.priv = (unsigned long)&pdev->dev; |
140 | platform_set_drvdata(pdev, data: trng); |
141 | |
142 | #ifndef CONFIG_PM |
143 | ret = atmel_trng_init(trng); |
144 | if (ret) |
145 | return ret; |
146 | #endif |
147 | |
148 | pm_runtime_set_autosuspend_delay(dev: &pdev->dev, delay: 100); |
149 | pm_runtime_use_autosuspend(dev: &pdev->dev); |
150 | pm_runtime_enable(dev: &pdev->dev); |
151 | |
152 | ret = devm_hwrng_register(dev: &pdev->dev, rng: &trng->rng); |
153 | if (ret) { |
154 | pm_runtime_disable(dev: &pdev->dev); |
155 | pm_runtime_set_suspended(dev: &pdev->dev); |
156 | #ifndef CONFIG_PM |
157 | atmel_trng_cleanup(trng); |
158 | #endif |
159 | } |
160 | |
161 | return ret; |
162 | } |
163 | |
164 | static int atmel_trng_remove(struct platform_device *pdev) |
165 | { |
166 | struct atmel_trng *trng = platform_get_drvdata(pdev); |
167 | |
168 | atmel_trng_cleanup(trng); |
169 | pm_runtime_disable(dev: &pdev->dev); |
170 | pm_runtime_set_suspended(dev: &pdev->dev); |
171 | |
172 | return 0; |
173 | } |
174 | |
175 | static int __maybe_unused atmel_trng_runtime_suspend(struct device *dev) |
176 | { |
177 | struct atmel_trng *trng = dev_get_drvdata(dev); |
178 | |
179 | atmel_trng_cleanup(trng); |
180 | |
181 | return 0; |
182 | } |
183 | |
184 | static int __maybe_unused atmel_trng_runtime_resume(struct device *dev) |
185 | { |
186 | struct atmel_trng *trng = dev_get_drvdata(dev); |
187 | |
188 | return atmel_trng_init(trng); |
189 | } |
190 | |
191 | static const struct dev_pm_ops __maybe_unused atmel_trng_pm_ops = { |
192 | SET_RUNTIME_PM_OPS(atmel_trng_runtime_suspend, |
193 | atmel_trng_runtime_resume, NULL) |
194 | SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
195 | pm_runtime_force_resume) |
196 | }; |
197 | |
198 | static const struct atmel_trng_data at91sam9g45_config = { |
199 | .has_half_rate = false, |
200 | }; |
201 | |
202 | static const struct atmel_trng_data sam9x60_config = { |
203 | .has_half_rate = true, |
204 | }; |
205 | |
206 | static const struct of_device_id atmel_trng_dt_ids[] = { |
207 | { |
208 | .compatible = "atmel,at91sam9g45-trng" , |
209 | .data = &at91sam9g45_config, |
210 | }, { |
211 | .compatible = "microchip,sam9x60-trng" , |
212 | .data = &sam9x60_config, |
213 | }, { |
214 | /* sentinel */ |
215 | } |
216 | }; |
217 | MODULE_DEVICE_TABLE(of, atmel_trng_dt_ids); |
218 | |
219 | static struct platform_driver atmel_trng_driver = { |
220 | .probe = atmel_trng_probe, |
221 | .remove = atmel_trng_remove, |
222 | .driver = { |
223 | .name = "atmel-trng" , |
224 | .pm = pm_ptr(&atmel_trng_pm_ops), |
225 | .of_match_table = atmel_trng_dt_ids, |
226 | }, |
227 | }; |
228 | |
229 | module_platform_driver(atmel_trng_driver); |
230 | |
231 | MODULE_LICENSE("GPL" ); |
232 | MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>" ); |
233 | MODULE_DESCRIPTION("Atmel true random number generator driver" ); |
234 | |