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/slab.h>
12 #include <linux/err.h>
13 #include <linux/clk.h>
14 #include <linux/io.h>
15 #include <linux/hw_random.h>
16 #include <linux/platform_device.h>
17 
18 #define TRNG_CR		0x00
19 #define TRNG_ISR	0x1c
20 #define TRNG_ODATA	0x50
21 
22 #define TRNG_KEY	0x524e4700 /* RNG */
23 
24 struct atmel_trng {
25 	struct clk *clk;
26 	void __iomem *base;
27 	struct hwrng rng;
28 };
29 
30 static int atmel_trng_read(struct hwrng *rng, void *buf, size_t max,
31 			   bool wait)
32 {
33 	struct atmel_trng *trng = container_of(rng, struct atmel_trng, rng);
34 	u32 *data = buf;
35 
36 	/* data ready? */
37 	if (readl(trng->base + TRNG_ODATA) & 1) {
38 		*data = readl(trng->base + TRNG_ODATA);
39 		return 4;
40 	} else
41 		return 0;
42 }
43 
44 static int atmel_trng_probe(struct platform_device *pdev)
45 {
46 	struct atmel_trng *trng;
47 	struct resource *res;
48 	int ret;
49 
50 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
51 	if (!res)
52 		return -EINVAL;
53 
54 	trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL);
55 	if (!trng)
56 		return -ENOMEM;
57 
58 	if (!devm_request_mem_region(&pdev->dev, res->start,
59 				     resource_size(res), pdev->name))
60 		return -EBUSY;
61 
62 	trng->base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
63 	if (!trng->base)
64 		return -EBUSY;
65 
66 	trng->clk = clk_get(&pdev->dev, NULL);
67 	if (IS_ERR(trng->clk))
68 		return PTR_ERR(trng->clk);
69 
70 	ret = clk_enable(trng->clk);
71 	if (ret)
72 		goto err_enable;
73 
74 	writel(TRNG_KEY | 1, trng->base + TRNG_CR);
75 	trng->rng.name = pdev->name;
76 	trng->rng.read = atmel_trng_read;
77 
78 	ret = hwrng_register(&trng->rng);
79 	if (ret)
80 		goto err_register;
81 
82 	platform_set_drvdata(pdev, trng);
83 
84 	return 0;
85 
86 err_register:
87 	clk_disable(trng->clk);
88 err_enable:
89 	clk_put(trng->clk);
90 
91 	return ret;
92 }
93 
94 static int __devexit atmel_trng_remove(struct platform_device *pdev)
95 {
96 	struct atmel_trng *trng = platform_get_drvdata(pdev);
97 
98 	hwrng_unregister(&trng->rng);
99 
100 	writel(TRNG_KEY, trng->base + TRNG_CR);
101 	clk_disable(trng->clk);
102 	clk_put(trng->clk);
103 
104 	platform_set_drvdata(pdev, NULL);
105 
106 	return 0;
107 }
108 
109 #ifdef CONFIG_PM
110 static int atmel_trng_suspend(struct device *dev)
111 {
112 	struct atmel_trng *trng = dev_get_drvdata(dev);
113 
114 	clk_disable(trng->clk);
115 
116 	return 0;
117 }
118 
119 static int atmel_trng_resume(struct device *dev)
120 {
121 	struct atmel_trng *trng = dev_get_drvdata(dev);
122 
123 	return clk_enable(trng->clk);
124 }
125 
126 static const struct dev_pm_ops atmel_trng_pm_ops = {
127 	.suspend	= atmel_trng_suspend,
128 	.resume		= atmel_trng_resume,
129 };
130 #endif /* CONFIG_PM */
131 
132 static struct platform_driver atmel_trng_driver = {
133 	.probe		= atmel_trng_probe,
134 	.remove		= __devexit_p(atmel_trng_remove),
135 	.driver		= {
136 		.name	= "atmel-trng",
137 		.owner	= THIS_MODULE,
138 #ifdef CONFIG_PM
139 		.pm	= &atmel_trng_pm_ops,
140 #endif /* CONFIG_PM */
141 	},
142 };
143 
144 module_platform_driver(atmel_trng_driver);
145 
146 MODULE_LICENSE("GPL");
147 MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>");
148 MODULE_DESCRIPTION("Atmel true random number generator driver");
149