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/hw_random.h>
17#include <linux/of_device.h>
18#include <linux/platform_device.h>
19
20#define TRNG_CR		0x00
21#define TRNG_MR		0x04
22#define TRNG_ISR	0x1c
23#define TRNG_ODATA	0x50
24
25#define TRNG_KEY	0x524e4700 /* RNG */
26
27#define TRNG_HALFR	BIT(0) /* generate RN every 168 cycles */
28
29struct atmel_trng_data {
30	bool has_half_rate;
31};
32
33struct atmel_trng {
34	struct clk *clk;
35	void __iomem *base;
36	struct hwrng rng;
37};
38
39static 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? */
46	if (readl(trng->base + TRNG_ISR) & 1) {
47		*data = readl(trng->base + TRNG_ODATA);
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);
55		return 4;
56	} else
57		return 0;
58}
59
60static void atmel_trng_enable(struct atmel_trng *trng)
61{
62	writel(TRNG_KEY | 1, trng->base + TRNG_CR);
63}
64
65static void atmel_trng_disable(struct atmel_trng *trng)
66{
67	writel(TRNG_KEY, trng->base + TRNG_CR);
68}
69
70static int atmel_trng_probe(struct platform_device *pdev)
71{
72	struct atmel_trng *trng;
73	const struct atmel_trng_data *data;
74	int ret;
75
76	trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL);
77	if (!trng)
78		return -ENOMEM;
79
80	trng->base = devm_platform_ioremap_resource(pdev, 0);
81	if (IS_ERR(trng->base))
82		return PTR_ERR(trng->base);
83
84	trng->clk = devm_clk_get(&pdev->dev, NULL);
85	if (IS_ERR(trng->clk))
86		return PTR_ERR(trng->clk);
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	}
98
99	ret = clk_prepare_enable(trng->clk);
100	if (ret)
101		return ret;
102
103	atmel_trng_enable(trng);
104	trng->rng.name = pdev->name;
105	trng->rng.read = atmel_trng_read;
106
107	ret = devm_hwrng_register(&pdev->dev, &trng->rng);
108	if (ret)
109		goto err_register;
110
111	platform_set_drvdata(pdev, trng);
112
113	return 0;
114
115err_register:
116	clk_disable_unprepare(trng->clk);
117	atmel_trng_disable(trng);
118	return ret;
119}
120
121static int atmel_trng_remove(struct platform_device *pdev)
122{
123	struct atmel_trng *trng = platform_get_drvdata(pdev);
124
125
126	atmel_trng_disable(trng);
127	clk_disable_unprepare(trng->clk);
128
129	return 0;
130}
131
132#ifdef CONFIG_PM
133static int atmel_trng_suspend(struct device *dev)
134{
135	struct atmel_trng *trng = dev_get_drvdata(dev);
136
137	atmel_trng_disable(trng);
138	clk_disable_unprepare(trng->clk);
139
140	return 0;
141}
142
143static int atmel_trng_resume(struct device *dev)
144{
145	struct atmel_trng *trng = dev_get_drvdata(dev);
146	int ret;
147
148	ret = clk_prepare_enable(trng->clk);
149	if (ret)
150		return ret;
151
152	atmel_trng_enable(trng);
153
154	return 0;
155}
156
157static const struct dev_pm_ops atmel_trng_pm_ops = {
158	.suspend	= atmel_trng_suspend,
159	.resume		= atmel_trng_resume,
160};
161#endif /* CONFIG_PM */
162
163static const struct atmel_trng_data at91sam9g45_config = {
164	.has_half_rate = false,
165};
166
167static const struct atmel_trng_data sam9x60_config = {
168	.has_half_rate = true,
169};
170
171static const struct of_device_id atmel_trng_dt_ids[] = {
172	{
173		.compatible = "atmel,at91sam9g45-trng",
174		.data = &at91sam9g45_config,
175	}, {
176		.compatible = "microchip,sam9x60-trng",
177		.data = &sam9x60_config,
178	}, {
179		/* sentinel */
180	}
181};
182MODULE_DEVICE_TABLE(of, atmel_trng_dt_ids);
183
184static struct platform_driver atmel_trng_driver = {
185	.probe		= atmel_trng_probe,
186	.remove		= atmel_trng_remove,
187	.driver		= {
188		.name	= "atmel-trng",
189#ifdef CONFIG_PM
190		.pm	= &atmel_trng_pm_ops,
191#endif /* CONFIG_PM */
192		.of_match_table = atmel_trng_dt_ids,
193	},
194};
195
196module_platform_driver(atmel_trng_driver);
197
198MODULE_LICENSE("GPL");
199MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>");
200MODULE_DESCRIPTION("Atmel true random number generator driver");
201