blob: 7051bf96147bc245db598faeecba835d90b8a92e [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/*
2 * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
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 and
6 * only version 2 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 */
14#include <linux/kernel.h>
15#include <linux/module.h>
16#include <linux/init.h>
17#include <linux/device.h>
18#include <linux/platform_device.h>
19#include <linux/hw_random.h>
20#include <linux/clk.h>
21#include <linux/slab.h>
22#include <linux/io.h>
23#include <linux/err.h>
24#include <linux/types.h>
25
26#define DRIVER_NAME "msm_rng"
27
28/* Device specific register offsets */
29#define PRNG_DATA_OUT_OFFSET 0x0000
30#define PRNG_STATUS_OFFSET 0x0004
31#define PRNG_LFSR_CFG_OFFSET 0x0100
32#define PRNG_CONFIG_OFFSET 0x0104
33
34/* Device specific register masks and config values */
35#define PRNG_LFSR_CFG_MASK 0xFFFF0000
36#define PRNG_LFSR_CFG_CLOCKS 0x0000DDDD
37#define PRNG_CONFIG_MASK 0xFFFFFFFD
38#define PRNG_CONFIG_ENABLE 0x00000002
39
40#define MAX_HW_FIFO_DEPTH 16 /* FIFO is 16 words deep */
41#define MAX_HW_FIFO_SIZE (MAX_HW_FIFO_DEPTH * 4) /* FIFO is 32 bits wide */
42
43struct msm_rng_device {
44 struct platform_device *pdev;
45 void __iomem *base;
46 struct clk *prng_clk;
47};
48
49static int msm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
50{
51 struct msm_rng_device *msm_rng_dev;
52 struct platform_device *pdev;
53 void __iomem *base;
54 size_t maxsize;
55 size_t currsize = 0;
56 unsigned long val;
57 unsigned long *retdata = data;
58 int ret;
59
60 msm_rng_dev = (struct msm_rng_device *)rng->priv;
61 pdev = msm_rng_dev->pdev;
62 base = msm_rng_dev->base;
63
64 /* calculate max size bytes to transfer back to caller */
65 maxsize = min_t(size_t, MAX_HW_FIFO_SIZE, max);
66
67 /* no room for word data */
68 if (maxsize < 4)
69 return 0;
70
71 /* enable PRNG clock */
72 ret = clk_enable(msm_rng_dev->prng_clk);
73 if (ret) {
74 dev_err(&pdev->dev, "failed to enable clock in callback\n");
75 return 0;
76 }
77
78 /* read random data from h/w */
79 do {
80 /* check status bit if data is available */
81 if (!(readl(base + PRNG_STATUS_OFFSET) & 0x00000001))
82 break; /* no data to read so just bail */
83
84 /* read FIFO */
85 val = readl(base + PRNG_DATA_OUT_OFFSET);
86 if (!val)
87 break; /* no data to read so just bail */
88
89 /* write data back to callers pointer */
90 *(retdata++) = val;
91 currsize += 4;
92
93 /* make sure we stay on 32bit boundary */
94 if ((maxsize - currsize) < 4)
95 break;
96 } while (currsize < maxsize);
97
98 /* vote to turn off clock */
99 clk_disable(msm_rng_dev->prng_clk);
100
101 return currsize;
102}
103
104static struct hwrng msm_rng = {
105 .name = DRIVER_NAME,
106 .read = msm_rng_read,
107};
108
109static int __devinit msm_rng_probe(struct platform_device *pdev)
110{
111 struct resource *res;
112 struct msm_rng_device *msm_rng_dev = NULL;
113 void __iomem *base = NULL;
114 int error = 0;
115 unsigned long val;
116 int ret;
117
118 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
119 if (res == NULL) {
120 dev_err(&pdev->dev, "invalid address\n");
121 error = -EFAULT;
122 goto err_exit;
123 }
124
125 msm_rng_dev = kzalloc(sizeof(msm_rng_dev), GFP_KERNEL);
126 if (!msm_rng_dev) {
127 dev_err(&pdev->dev, "cannot allocate memory\n");
128 error = -ENOMEM;
129 goto err_exit;
130 }
131
132 base = ioremap(res->start, resource_size(res));
133 if (!base) {
134 dev_err(&pdev->dev, "ioremap failed\n");
135 error = -ENOMEM;
136 goto err_iomap;
137 }
138 msm_rng_dev->base = base;
139
140 /* create a handle for clock control */
141 msm_rng_dev->prng_clk = clk_get(NULL, "prng_clk");
142 if (IS_ERR(msm_rng_dev->prng_clk)) {
143 dev_err(&pdev->dev, "failed to register clock source\n");
144 error = -EPERM;
145 goto err_clk_get;
146 }
147
148 /* save away pdev and register driver data */
149 msm_rng_dev->pdev = pdev;
150 platform_set_drvdata(pdev, msm_rng_dev);
151
152 ret = clk_enable(msm_rng_dev->prng_clk);
153 if (ret) {
154 dev_err(&pdev->dev, "failed to enable clock in probe\n");
155 error = -EPERM;
156 goto err_clk_enable;
157 }
158
159 /* enable PRNG h/w (this may not work since XPU protect may be enabled
160 * elsewhere which case then the hardware should have already been set
161 * up)
162 */
163 val = readl(base + PRNG_LFSR_CFG_OFFSET) & PRNG_LFSR_CFG_MASK;
164 val |= PRNG_LFSR_CFG_CLOCKS;
165 writel(val, base + PRNG_LFSR_CFG_OFFSET);
166
167 val = readl(base + PRNG_CONFIG_OFFSET) & PRNG_CONFIG_MASK;
168 val |= PRNG_CONFIG_ENABLE;
169 writel(val, base + PRNG_CONFIG_OFFSET);
170
171 clk_disable(msm_rng_dev->prng_clk);
172
173 /* register with hwrng framework */
174 msm_rng.priv = (unsigned long) msm_rng_dev;
175 error = hwrng_register(&msm_rng);
176 if (error) {
177 dev_err(&pdev->dev, "failed to register hwrng\n");
178 error = -EPERM;
179 goto err_hw_register;
180 }
181
182 return 0;
183
184err_hw_register:
185err_clk_enable:
186 clk_put(msm_rng_dev->prng_clk);
187err_clk_get:
188 iounmap(msm_rng_dev->base);
189err_iomap:
190 kfree(msm_rng_dev);
191err_exit:
192 return error;
193}
194
195static int __devexit msm_rng_remove(struct platform_device *pdev)
196{
197 struct msm_rng_device *msm_rng_dev = platform_get_drvdata(pdev);
198
199 hwrng_unregister(&msm_rng);
200 clk_put(msm_rng_dev->prng_clk);
201 iounmap(msm_rng_dev->base);
202 platform_set_drvdata(pdev, NULL);
203 kfree(msm_rng_dev);
204 return 0;
205}
206
207static struct platform_driver rng_driver = {
208 .probe = msm_rng_probe,
209 .remove = __devexit_p(msm_rng_remove),
210 .driver = {
211 .name = DRIVER_NAME,
212 .owner = THIS_MODULE,
213 }
214};
215
216static int __init msm_rng_init(void)
217{
218 return platform_driver_register(&rng_driver);
219}
220
221module_init(msm_rng_init);
222
223static void __exit msm_rng_exit(void)
224{
225 platform_driver_unregister(&rng_driver);
226}
227
228module_exit(msm_rng_exit);
229
230MODULE_AUTHOR("Code Aurora Forum");
231MODULE_DESCRIPTION("Qualcomm MSM Random Number Driver");
232MODULE_LICENSE("GPL v2");