Commit | Line | Data |
---|---|---|
677d3e2f PK |
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> | |
ac316725 | 11 | #include <linux/mod_devicetable.h> |
677d3e2f PK |
12 | #include <linux/slab.h> |
13 | #include <linux/err.h> | |
14 | #include <linux/clk.h> | |
15 | #include <linux/io.h> | |
16 | #include <linux/hw_random.h> | |
5c49645c | 17 | #include <linux/of_device.h> |
677d3e2f PK |
18 | #include <linux/platform_device.h> |
19 | ||
20 | #define TRNG_CR 0x00 | |
5c49645c | 21 | #define TRNG_MR 0x04 |
677d3e2f PK |
22 | #define TRNG_ISR 0x1c |
23 | #define TRNG_ODATA 0x50 | |
24 | ||
25 | #define TRNG_KEY 0x524e4700 /* RNG */ | |
26 | ||
5c49645c CC |
27 | #define TRNG_HALFR BIT(0) /* generate RN every 168 cycles */ |
28 | ||
29 | struct atmel_trng_data { | |
30 | bool has_half_rate; | |
31 | }; | |
32 | ||
677d3e2f PK |
33 | struct atmel_trng { |
34 | struct clk *clk; | |
35 | void __iomem *base; | |
36 | struct hwrng rng; | |
37 | }; | |
38 | ||
39 | static int atmel_trng_read(struct hwrng *rng, void *buf, size_t max, | |
40 | bool wait) | |
41 | { | |
42 | struct atmel_trng *trng = container_of(rng, struct atmel_trng, rng); | |
43 | u32 *data = buf; | |
44 | ||
45 | /* data ready? */ | |
c475c06f | 46 | if (readl(trng->base + TRNG_ISR) & 1) { |
677d3e2f | 47 | *data = readl(trng->base + TRNG_ODATA); |
121daad8 PK |
48 | /* |
49 | ensure data ready is only set again AFTER the next data | |
50 | word is ready in case it got set between checking ISR | |
51 | and reading ODATA, so we don't risk re-reading the | |
52 | same word | |
53 | */ | |
54 | readl(trng->base + TRNG_ISR); | |
677d3e2f PK |
55 | return 4; |
56 | } else | |
57 | return 0; | |
58 | } | |
59 | ||
a1fa98d8 WY |
60 | static void atmel_trng_enable(struct atmel_trng *trng) |
61 | { | |
62 | writel(TRNG_KEY | 1, trng->base + TRNG_CR); | |
63 | } | |
64 | ||
65 | static void atmel_trng_disable(struct atmel_trng *trng) | |
66 | { | |
67 | writel(TRNG_KEY, trng->base + TRNG_CR); | |
68 | } | |
69 | ||
677d3e2f PK |
70 | static int atmel_trng_probe(struct platform_device *pdev) |
71 | { | |
72 | struct atmel_trng *trng; | |
5c49645c | 73 | const struct atmel_trng_data *data; |
677d3e2f PK |
74 | int ret; |
75 | ||
677d3e2f PK |
76 | trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL); |
77 | if (!trng) | |
78 | return -ENOMEM; | |
79 | ||
bc49534d | 80 | trng->base = devm_platform_ioremap_resource(pdev, 0); |
bfaff75b JH |
81 | if (IS_ERR(trng->base)) |
82 | return PTR_ERR(trng->base); | |
677d3e2f | 83 | |
0c0becd0 | 84 | trng->clk = devm_clk_get(&pdev->dev, NULL); |
677d3e2f PK |
85 | if (IS_ERR(trng->clk)) |
86 | return PTR_ERR(trng->clk); | |
5c49645c CC |
87 | data = of_device_get_match_data(&pdev->dev); |
88 | if (!data) | |
89 | return -ENODEV; | |
90 | ||
91 | if (data->has_half_rate) { | |
92 | unsigned long rate = clk_get_rate(trng->clk); | |
93 | ||
94 | /* if peripheral clk is above 100MHz, set HALFR */ | |
95 | if (rate > 100000000) | |
96 | writel(TRNG_HALFR, trng->base + TRNG_MR); | |
97 | } | |
677d3e2f | 98 | |
21961efa | 99 | ret = clk_prepare_enable(trng->clk); |
677d3e2f | 100 | if (ret) |
0c0becd0 | 101 | return ret; |
677d3e2f | 102 | |
a1fa98d8 | 103 | atmel_trng_enable(trng); |
677d3e2f PK |
104 | trng->rng.name = pdev->name; |
105 | trng->rng.read = atmel_trng_read; | |
106 | ||
3e75241b | 107 | ret = devm_hwrng_register(&pdev->dev, &trng->rng); |
677d3e2f PK |
108 | if (ret) |
109 | goto err_register; | |
110 | ||
111 | platform_set_drvdata(pdev, trng); | |
112 | ||
113 | return 0; | |
114 | ||
115 | err_register: | |
b516b88a | 116 | clk_disable_unprepare(trng->clk); |
677d3e2f PK |
117 | return ret; |
118 | } | |
119 | ||
39af33fc | 120 | static int atmel_trng_remove(struct platform_device *pdev) |
677d3e2f PK |
121 | { |
122 | struct atmel_trng *trng = platform_get_drvdata(pdev); | |
123 | ||
677d3e2f | 124 | |
a1fa98d8 | 125 | atmel_trng_disable(trng); |
21961efa | 126 | clk_disable_unprepare(trng->clk); |
677d3e2f | 127 | |
677d3e2f PK |
128 | return 0; |
129 | } | |
130 | ||
131 | #ifdef CONFIG_PM | |
132 | static int atmel_trng_suspend(struct device *dev) | |
133 | { | |
134 | struct atmel_trng *trng = dev_get_drvdata(dev); | |
135 | ||
a1fa98d8 | 136 | atmel_trng_disable(trng); |
21961efa | 137 | clk_disable_unprepare(trng->clk); |
677d3e2f PK |
138 | |
139 | return 0; | |
140 | } | |
141 | ||
142 | static int atmel_trng_resume(struct device *dev) | |
143 | { | |
144 | struct atmel_trng *trng = dev_get_drvdata(dev); | |
a1fa98d8 WY |
145 | int ret; |
146 | ||
147 | ret = clk_prepare_enable(trng->clk); | |
148 | if (ret) | |
149 | return ret; | |
677d3e2f | 150 | |
a1fa98d8 WY |
151 | atmel_trng_enable(trng); |
152 | ||
153 | return 0; | |
677d3e2f PK |
154 | } |
155 | ||
156 | static const struct dev_pm_ops atmel_trng_pm_ops = { | |
157 | .suspend = atmel_trng_suspend, | |
158 | .resume = atmel_trng_resume, | |
159 | }; | |
160 | #endif /* CONFIG_PM */ | |
161 | ||
5c49645c CC |
162 | static const struct atmel_trng_data at91sam9g45_config = { |
163 | .has_half_rate = false, | |
164 | }; | |
165 | ||
166 | static const struct atmel_trng_data sam9x60_config = { | |
167 | .has_half_rate = true, | |
168 | }; | |
169 | ||
4951db7e | 170 | static const struct of_device_id atmel_trng_dt_ids[] = { |
5c49645c CC |
171 | { |
172 | .compatible = "atmel,at91sam9g45-trng", | |
173 | .data = &at91sam9g45_config, | |
174 | }, { | |
175 | .compatible = "microchip,sam9x60-trng", | |
176 | .data = &sam9x60_config, | |
177 | }, { | |
178 | /* sentinel */ | |
179 | } | |
4951db7e BB |
180 | }; |
181 | MODULE_DEVICE_TABLE(of, atmel_trng_dt_ids); | |
182 | ||
677d3e2f PK |
183 | static struct platform_driver atmel_trng_driver = { |
184 | .probe = atmel_trng_probe, | |
bcd2982a | 185 | .remove = atmel_trng_remove, |
677d3e2f PK |
186 | .driver = { |
187 | .name = "atmel-trng", | |
677d3e2f PK |
188 | #ifdef CONFIG_PM |
189 | .pm = &atmel_trng_pm_ops, | |
190 | #endif /* CONFIG_PM */ | |
4951db7e | 191 | .of_match_table = atmel_trng_dt_ids, |
677d3e2f PK |
192 | }, |
193 | }; | |
194 | ||
b21cb324 | 195 | module_platform_driver(atmel_trng_driver); |
677d3e2f PK |
196 | |
197 | MODULE_LICENSE("GPL"); | |
198 | MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>"); | |
199 | MODULE_DESCRIPTION("Atmel true random number generator driver"); |