diff options
-rw-r--r-- | drivers/char/hw_random/Kconfig | 12 | ||||
-rw-r--r-- | drivers/char/hw_random/Makefile | 1 | ||||
-rw-r--r-- | drivers/char/hw_random/exynos-rng.c | 182 |
3 files changed, 195 insertions, 0 deletions
diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index f45dad39a18b..b01d67328243 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig | |||
@@ -263,3 +263,15 @@ config HW_RANDOM_PSERIES | |||
263 | module will be called pseries-rng. | 263 | module will be called pseries-rng. |
264 | 264 | ||
265 | If unsure, say Y. | 265 | If unsure, say Y. |
266 | |||
267 | config HW_RANDOM_EXYNOS | ||
268 | tristate "EXYNOS HW random number generator support" | ||
269 | depends on HW_RANDOM && HAS_IOMEM && HAVE_CLK | ||
270 | ---help--- | ||
271 | This driver provides kernel-side support for the Random Number | ||
272 | Generator hardware found on EXYNOS SOCs. | ||
273 | |||
274 | To compile this driver as a module, choose M here: the | ||
275 | module will be called exynos-rng. | ||
276 | |||
277 | If unsure, say Y. | ||
diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index d901dfa30321..8d6d173b65e6 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile | |||
@@ -23,3 +23,4 @@ obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o | |||
23 | obj-$(CONFIG_HW_RANDOM_PICOXCELL) += picoxcell-rng.o | 23 | obj-$(CONFIG_HW_RANDOM_PICOXCELL) += picoxcell-rng.o |
24 | obj-$(CONFIG_HW_RANDOM_PPC4XX) += ppc4xx-rng.o | 24 | obj-$(CONFIG_HW_RANDOM_PPC4XX) += ppc4xx-rng.o |
25 | obj-$(CONFIG_HW_RANDOM_PSERIES) += pseries-rng.o | 25 | obj-$(CONFIG_HW_RANDOM_PSERIES) += pseries-rng.o |
26 | obj-$(CONFIG_HW_RANDOM_EXYNOS) += exynos-rng.o | ||
diff --git a/drivers/char/hw_random/exynos-rng.c b/drivers/char/hw_random/exynos-rng.c new file mode 100644 index 000000000000..232ba9ce579c --- /dev/null +++ b/drivers/char/hw_random/exynos-rng.c | |||
@@ -0,0 +1,182 @@ | |||
1 | /* | ||
2 | * exynos-rng.c - Random Number Generator driver for the exynos | ||
3 | * | ||
4 | * Copyright (C) 2012 Samsung Electronics | ||
5 | * Jonghwa Lee <jonghwa3.lee@smasung.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #include <linux/hw_random.h> | ||
23 | #include <linux/kernel.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <linux/io.h> | ||
27 | #include <linux/platform_device.h> | ||
28 | #include <linux/clk.h> | ||
29 | #include <linux/pm_runtime.h> | ||
30 | #include <linux/err.h> | ||
31 | |||
32 | #define EXYNOS_PRNG_STATUS_OFFSET 0x10 | ||
33 | #define EXYNOS_PRNG_SEED_OFFSET 0x140 | ||
34 | #define EXYNOS_PRNG_OUT1_OFFSET 0x160 | ||
35 | #define SEED_SETTING_DONE BIT(1) | ||
36 | #define PRNG_START 0x18 | ||
37 | #define PRNG_DONE BIT(5) | ||
38 | #define EXYNOS_AUTOSUSPEND_DELAY 100 | ||
39 | |||
40 | struct exynos_rng { | ||
41 | struct device *dev; | ||
42 | struct hwrng rng; | ||
43 | void __iomem *mem; | ||
44 | struct clk *clk; | ||
45 | }; | ||
46 | |||
47 | static u32 exynos_rng_readl(struct exynos_rng *rng, u32 offset) | ||
48 | { | ||
49 | return __raw_readl(rng->mem + offset); | ||
50 | } | ||
51 | |||
52 | static void exynos_rng_writel(struct exynos_rng *rng, u32 val, u32 offset) | ||
53 | { | ||
54 | __raw_writel(val, rng->mem + offset); | ||
55 | } | ||
56 | |||
57 | static int exynos_init(struct hwrng *rng) | ||
58 | { | ||
59 | struct exynos_rng *exynos_rng = container_of(rng, | ||
60 | struct exynos_rng, rng); | ||
61 | int i; | ||
62 | int ret = 0; | ||
63 | |||
64 | pm_runtime_get_sync(exynos_rng->dev); | ||
65 | |||
66 | for (i = 0 ; i < 5 ; i++) | ||
67 | exynos_rng_writel(exynos_rng, jiffies, | ||
68 | EXYNOS_PRNG_SEED_OFFSET + 4*i); | ||
69 | |||
70 | if (!(exynos_rng_readl(exynos_rng, EXYNOS_PRNG_STATUS_OFFSET) | ||
71 | & SEED_SETTING_DONE)) | ||
72 | ret = -EIO; | ||
73 | |||
74 | pm_runtime_put_noidle(exynos_rng->dev); | ||
75 | |||
76 | return ret; | ||
77 | } | ||
78 | |||
79 | static int exynos_read(struct hwrng *rng, void *buf, | ||
80 | size_t max, bool wait) | ||
81 | { | ||
82 | struct exynos_rng *exynos_rng = container_of(rng, | ||
83 | struct exynos_rng, rng); | ||
84 | u32 *data = buf; | ||
85 | |||
86 | pm_runtime_get_sync(exynos_rng->dev); | ||
87 | |||
88 | exynos_rng_writel(exynos_rng, PRNG_START, 0); | ||
89 | |||
90 | while (!(exynos_rng_readl(exynos_rng, | ||
91 | EXYNOS_PRNG_STATUS_OFFSET) & PRNG_DONE)) | ||
92 | cpu_relax(); | ||
93 | |||
94 | exynos_rng_writel(exynos_rng, PRNG_DONE, EXYNOS_PRNG_STATUS_OFFSET); | ||
95 | |||
96 | *data = exynos_rng_readl(exynos_rng, EXYNOS_PRNG_OUT1_OFFSET); | ||
97 | |||
98 | pm_runtime_mark_last_busy(exynos_rng->dev); | ||
99 | pm_runtime_autosuspend(exynos_rng->dev); | ||
100 | |||
101 | return 4; | ||
102 | } | ||
103 | |||
104 | static int __devinit exynos_rng_probe(struct platform_device *pdev) | ||
105 | { | ||
106 | struct exynos_rng *exynos_rng; | ||
107 | |||
108 | exynos_rng = devm_kzalloc(&pdev->dev, sizeof(struct exynos_rng), | ||
109 | GFP_KERNEL); | ||
110 | if (!exynos_rng) | ||
111 | return -ENOMEM; | ||
112 | |||
113 | exynos_rng->dev = &pdev->dev; | ||
114 | exynos_rng->rng.name = "exynos"; | ||
115 | exynos_rng->rng.init = exynos_init; | ||
116 | exynos_rng->rng.read = exynos_read; | ||
117 | exynos_rng->clk = devm_clk_get(&pdev->dev, "secss"); | ||
118 | if (IS_ERR(exynos_rng->clk)) { | ||
119 | dev_err(&pdev->dev, "Couldn't get clock.\n"); | ||
120 | return -ENOENT; | ||
121 | } | ||
122 | |||
123 | exynos_rng->mem = devm_request_and_ioremap(&pdev->dev, | ||
124 | platform_get_resource(pdev, IORESOURCE_MEM, 0)); | ||
125 | if (!exynos_rng->mem) | ||
126 | return -EBUSY; | ||
127 | |||
128 | platform_set_drvdata(pdev, exynos_rng); | ||
129 | |||
130 | pm_runtime_set_autosuspend_delay(&pdev->dev, EXYNOS_AUTOSUSPEND_DELAY); | ||
131 | pm_runtime_use_autosuspend(&pdev->dev); | ||
132 | pm_runtime_enable(&pdev->dev); | ||
133 | |||
134 | return hwrng_register(&exynos_rng->rng); | ||
135 | } | ||
136 | |||
137 | static int __devexit exynos_rng_remove(struct platform_device *pdev) | ||
138 | { | ||
139 | struct exynos_rng *exynos_rng = platform_get_drvdata(pdev); | ||
140 | |||
141 | hwrng_unregister(&exynos_rng->rng); | ||
142 | |||
143 | return 0; | ||
144 | } | ||
145 | |||
146 | static int exynos_rng_runtime_suspend(struct device *dev) | ||
147 | { | ||
148 | struct platform_device *pdev = to_platform_device(dev); | ||
149 | struct exynos_rng *exynos_rng = platform_get_drvdata(pdev); | ||
150 | |||
151 | clk_disable_unprepare(exynos_rng->clk); | ||
152 | |||
153 | return 0; | ||
154 | } | ||
155 | |||
156 | static int exynos_rng_runtime_resume(struct device *dev) | ||
157 | { | ||
158 | struct platform_device *pdev = to_platform_device(dev); | ||
159 | struct exynos_rng *exynos_rng = platform_get_drvdata(pdev); | ||
160 | |||
161 | return clk_prepare_enable(exynos_rng->clk); | ||
162 | } | ||
163 | |||
164 | |||
165 | UNIVERSAL_DEV_PM_OPS(exynos_rng_pm_ops, exynos_rng_runtime_suspend, | ||
166 | exynos_rng_runtime_resume, NULL); | ||
167 | |||
168 | static struct platform_driver exynos_rng_driver = { | ||
169 | .driver = { | ||
170 | .name = "exynos-rng", | ||
171 | .owner = THIS_MODULE, | ||
172 | .pm = &exynos_rng_pm_ops, | ||
173 | }, | ||
174 | .probe = exynos_rng_probe, | ||
175 | .remove = __devexit_p(exynos_rng_remove), | ||
176 | }; | ||
177 | |||
178 | module_platform_driver(exynos_rng_driver); | ||
179 | |||
180 | MODULE_DESCRIPTION("EXYNOS 4 H/W Random Number Generator driver"); | ||
181 | MODULE_AUTHOR("Jonghwa Lee <jonghwa3.lee@samsung.com>"); | ||
182 | MODULE_LICENSE("GPL"); | ||