blob: 369477f2c016a1f00a15554cb72e3de455cd54ad [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/*
Duy Truong790f06d2013-02-13 16:38:12 -08002 * Copyright (c) 2011, The Linux Foundation. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07003 *
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>
Ramesh Masavarapu2f963db2011-10-20 15:33:50 -070025#include <mach/msm_iomap.h>
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -070026#include <mach/socinfo.h>
Hariprasad Dhalinarasimha1685be42013-06-03 16:29:56 -070027#include <mach/msm_bus.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070028
29#define DRIVER_NAME "msm_rng"
30
31/* Device specific register offsets */
32#define PRNG_DATA_OUT_OFFSET 0x0000
33#define PRNG_STATUS_OFFSET 0x0004
34#define PRNG_LFSR_CFG_OFFSET 0x0100
35#define PRNG_CONFIG_OFFSET 0x0104
36
37/* Device specific register masks and config values */
38#define PRNG_LFSR_CFG_MASK 0xFFFF0000
39#define PRNG_LFSR_CFG_CLOCKS 0x0000DDDD
40#define PRNG_CONFIG_MASK 0xFFFFFFFD
Ramesh Masavarapu2f963db2011-10-20 15:33:50 -070041#define PRNG_HW_ENABLE 0x00000002
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070042
43#define MAX_HW_FIFO_DEPTH 16 /* FIFO is 16 words deep */
44#define MAX_HW_FIFO_SIZE (MAX_HW_FIFO_DEPTH * 4) /* FIFO is 32 bits wide */
45
Ramesh Masavarapu2f963db2011-10-20 15:33:50 -070046
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070047struct msm_rng_device {
48 struct platform_device *pdev;
49 void __iomem *base;
50 struct clk *prng_clk;
Hariprasad Dhalinarasimha1685be42013-06-03 16:29:56 -070051 uint32_t qrng_perf_client;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070052};
53
54static int msm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
55{
56 struct msm_rng_device *msm_rng_dev;
57 struct platform_device *pdev;
58 void __iomem *base;
59 size_t maxsize;
60 size_t currsize = 0;
61 unsigned long val;
62 unsigned long *retdata = data;
63 int ret;
64
65 msm_rng_dev = (struct msm_rng_device *)rng->priv;
66 pdev = msm_rng_dev->pdev;
67 base = msm_rng_dev->base;
68
69 /* calculate max size bytes to transfer back to caller */
70 maxsize = min_t(size_t, MAX_HW_FIFO_SIZE, max);
71
72 /* no room for word data */
73 if (maxsize < 4)
74 return 0;
75
76 /* enable PRNG clock */
Ramesh Masavarapu801c3922012-04-24 16:28:00 -070077 ret = clk_prepare_enable(msm_rng_dev->prng_clk);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070078 if (ret) {
79 dev_err(&pdev->dev, "failed to enable clock in callback\n");
80 return 0;
81 }
Hariprasad Dhalinarasimha1685be42013-06-03 16:29:56 -070082 if (msm_rng_dev->qrng_perf_client) {
83 ret = msm_bus_scale_client_update_request(
84 msm_rng_dev->qrng_perf_client, 1);
85 if (ret)
86 pr_err("bus_scale_client_update_req failed!\n");
87 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070088 /* read random data from h/w */
89 do {
90 /* check status bit if data is available */
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -070091 if (!(readl_relaxed(base + PRNG_STATUS_OFFSET) & 0x00000001))
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070092 break; /* no data to read so just bail */
93
94 /* read FIFO */
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -070095 val = readl_relaxed(base + PRNG_DATA_OUT_OFFSET);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070096 if (!val)
97 break; /* no data to read so just bail */
98
99 /* write data back to callers pointer */
100 *(retdata++) = val;
101 currsize += 4;
102
103 /* make sure we stay on 32bit boundary */
104 if ((maxsize - currsize) < 4)
105 break;
106 } while (currsize < maxsize);
Hariprasad Dhalinarasimha1685be42013-06-03 16:29:56 -0700107 if (msm_rng_dev->qrng_perf_client)
108 ret = msm_bus_scale_client_update_request(
109 msm_rng_dev->qrng_perf_client, 0);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700110 /* vote to turn off clock */
Ramesh Masavarapu801c3922012-04-24 16:28:00 -0700111 clk_disable_unprepare(msm_rng_dev->prng_clk);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700112
113 return currsize;
114}
115
116static struct hwrng msm_rng = {
117 .name = DRIVER_NAME,
118 .read = msm_rng_read,
119};
120
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700121static int __devinit msm_rng_enable_hw(struct msm_rng_device *msm_rng_dev)
122{
123 unsigned long val = 0;
Ramesh Masavarapu2f963db2011-10-20 15:33:50 -0700124 unsigned long reg_val = 0;
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700125 int ret = 0;
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700126
Hariprasad Dhalinarasimha1685be42013-06-03 16:29:56 -0700127 if (msm_rng_dev->qrng_perf_client) {
128 ret = msm_bus_scale_client_update_request(
129 msm_rng_dev->qrng_perf_client, 1);
130 if (ret)
131 pr_err("bus_scale_client_update_req failed!\n");
132 }
Ramesh Masavarapu2f963db2011-10-20 15:33:50 -0700133 /* Enable the PRNG CLK */
Ramesh Masavarapu801c3922012-04-24 16:28:00 -0700134 ret = clk_prepare_enable(msm_rng_dev->prng_clk);
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700135 if (ret) {
136 dev_err(&(msm_rng_dev->pdev)->dev,
137 "failed to enable clock in probe\n");
Ramesh Masavarapu2f963db2011-10-20 15:33:50 -0700138 return -EPERM;
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700139 }
Ramesh Masavarapu2f963db2011-10-20 15:33:50 -0700140 /* Enable PRNG h/w only if it is NOT ON */
141 val = readl_relaxed(msm_rng_dev->base + PRNG_CONFIG_OFFSET) &
142 PRNG_HW_ENABLE;
143 /* PRNG H/W is not ON */
144 if (val != PRNG_HW_ENABLE) {
Stephen Boyda4100a42012-06-25 15:48:37 -0700145 val = readl_relaxed(msm_rng_dev->base + PRNG_LFSR_CFG_OFFSET);
146 val &= PRNG_LFSR_CFG_MASK;
147 val |= PRNG_LFSR_CFG_CLOCKS;
Ramesh Masavarapua54263b2012-02-01 22:49:01 -0800148 writel_relaxed(val, msm_rng_dev->base + PRNG_LFSR_CFG_OFFSET);
149
150 /* The PRNG CONFIG register should be first written */
151 mb();
152
Ramesh Masavarapu2f963db2011-10-20 15:33:50 -0700153 reg_val = readl_relaxed(msm_rng_dev->base + PRNG_CONFIG_OFFSET)
154 & PRNG_CONFIG_MASK;
155 reg_val |= PRNG_HW_ENABLE;
156 writel_relaxed(reg_val, msm_rng_dev->base + PRNG_CONFIG_OFFSET);
157
158 /* The PRNG clk should be disabled only after we enable the
159 * PRNG h/w by writing to the PRNG CONFIG register.
160 */
161 mb();
162 }
Ramesh Masavarapu801c3922012-04-24 16:28:00 -0700163 clk_disable_unprepare(msm_rng_dev->prng_clk);
Hariprasad Dhalinarasimha1685be42013-06-03 16:29:56 -0700164 if (msm_rng_dev->qrng_perf_client)
165 ret = msm_bus_scale_client_update_request(
166 msm_rng_dev->qrng_perf_client, 0);
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700167
Ramesh Masavarapu2f963db2011-10-20 15:33:50 -0700168 return 0;
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700169}
170
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700171static int __devinit msm_rng_probe(struct platform_device *pdev)
172{
173 struct resource *res;
174 struct msm_rng_device *msm_rng_dev = NULL;
175 void __iomem *base = NULL;
176 int error = 0;
Hariprasad Dhalinarasimha1685be42013-06-03 16:29:56 -0700177 struct msm_bus_scale_pdata *qrng_platform_support = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700178
179 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
180 if (res == NULL) {
181 dev_err(&pdev->dev, "invalid address\n");
182 error = -EFAULT;
183 goto err_exit;
184 }
185
186 msm_rng_dev = kzalloc(sizeof(msm_rng_dev), GFP_KERNEL);
187 if (!msm_rng_dev) {
188 dev_err(&pdev->dev, "cannot allocate memory\n");
189 error = -ENOMEM;
190 goto err_exit;
191 }
192
193 base = ioremap(res->start, resource_size(res));
194 if (!base) {
195 dev_err(&pdev->dev, "ioremap failed\n");
196 error = -ENOMEM;
197 goto err_iomap;
198 }
199 msm_rng_dev->base = base;
200
201 /* create a handle for clock control */
Hariprasad Dhalinarasimha0bef6ed2012-11-07 19:47:21 -0800202 if ((pdev->dev.of_node) && (of_property_read_bool(pdev->dev.of_node,
203 "qcom,msm-rng-iface-clk")))
204 msm_rng_dev->prng_clk = clk_get(&pdev->dev,
205 "iface_clk");
206 else
207 msm_rng_dev->prng_clk = clk_get(&pdev->dev, "core_clk");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700208 if (IS_ERR(msm_rng_dev->prng_clk)) {
209 dev_err(&pdev->dev, "failed to register clock source\n");
210 error = -EPERM;
211 goto err_clk_get;
212 }
213
214 /* save away pdev and register driver data */
215 msm_rng_dev->pdev = pdev;
216 platform_set_drvdata(pdev, msm_rng_dev);
217
Hariprasad Dhalinarasimha1685be42013-06-03 16:29:56 -0700218 if (pdev->dev.of_node) {
219 /* Register bus client */
220 qrng_platform_support = msm_bus_cl_get_pdata(pdev);
221 msm_rng_dev->qrng_perf_client = msm_bus_scale_register_client(
222 qrng_platform_support);
223 if (!msm_rng_dev->qrng_perf_client)
224 pr_err("Unable to register bus client\n");
225 }
226
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700227 /* Enable rng h/w */
Ramesh Masavarapu2f963db2011-10-20 15:33:50 -0700228 error = msm_rng_enable_hw(msm_rng_dev);
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700229
230 if (error)
231 goto rollback_clk;
232
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700233 /* register with hwrng framework */
234 msm_rng.priv = (unsigned long) msm_rng_dev;
235 error = hwrng_register(&msm_rng);
236 if (error) {
237 dev_err(&pdev->dev, "failed to register hwrng\n");
238 error = -EPERM;
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700239 goto rollback_clk;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700240 }
241
242 return 0;
243
Ramesh Masavarapu5ad37392011-10-10 10:44:10 -0700244rollback_clk:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700245 clk_put(msm_rng_dev->prng_clk);
246err_clk_get:
247 iounmap(msm_rng_dev->base);
248err_iomap:
249 kfree(msm_rng_dev);
250err_exit:
251 return error;
252}
253
254static int __devexit msm_rng_remove(struct platform_device *pdev)
255{
256 struct msm_rng_device *msm_rng_dev = platform_get_drvdata(pdev);
257
258 hwrng_unregister(&msm_rng);
259 clk_put(msm_rng_dev->prng_clk);
260 iounmap(msm_rng_dev->base);
261 platform_set_drvdata(pdev, NULL);
Hariprasad Dhalinarasimha1685be42013-06-03 16:29:56 -0700262 if (msm_rng_dev->qrng_perf_client)
263 msm_bus_scale_unregister_client(msm_rng_dev->qrng_perf_client);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700264 kfree(msm_rng_dev);
265 return 0;
266}
267
Hariprasad Dhalinarasimhade991f02012-05-31 13:15:51 -0700268static struct of_device_id qrng_match[] = {
269 { .compatible = "qcom,msm-rng",
270 },
271 {}
272};
273
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700274static struct platform_driver rng_driver = {
275 .probe = msm_rng_probe,
276 .remove = __devexit_p(msm_rng_remove),
277 .driver = {
278 .name = DRIVER_NAME,
279 .owner = THIS_MODULE,
Hariprasad Dhalinarasimhade991f02012-05-31 13:15:51 -0700280 .of_match_table = qrng_match,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700281 }
282};
283
284static int __init msm_rng_init(void)
285{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700286 return platform_driver_register(&rng_driver);
287}
288
289module_init(msm_rng_init);
290
291static void __exit msm_rng_exit(void)
292{
293 platform_driver_unregister(&rng_driver);
294}
295
296module_exit(msm_rng_exit);
297
Duy Truong790f06d2013-02-13 16:38:12 -0800298MODULE_AUTHOR("The Linux Foundation");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700299MODULE_DESCRIPTION("Qualcomm MSM Random Number Driver");
300MODULE_LICENSE("GPL v2");