1 | /* |
2 | * omap3-rom-rng.c - RNG driver for TI OMAP3 CPU family |
3 | * |
4 | * Copyright (C) 2009 Nokia Corporation |
5 | * Author: Juha Yrjola <juha.yrjola@solidboot.com> |
6 | * |
7 | * Copyright (C) 2013 Pali Rohár <pali@kernel.org> |
8 | * |
9 | * This file is licensed under the terms of the GNU General Public |
10 | * License version 2. This program is licensed "as is" without any |
11 | * warranty of any kind, whether express or implied. |
12 | */ |
13 | |
14 | #include <linux/module.h> |
15 | #include <linux/init.h> |
16 | #include <linux/random.h> |
17 | #include <linux/hw_random.h> |
18 | #include <linux/workqueue.h> |
19 | #include <linux/clk.h> |
20 | #include <linux/err.h> |
21 | #include <linux/io.h> |
22 | #include <linux/of.h> |
23 | #include <linux/platform_device.h> |
24 | #include <linux/pm_runtime.h> |
25 | |
26 | #define RNG_RESET 0x01 |
27 | #define RNG_GEN_PRNG_HW_INIT 0x02 |
28 | #define RNG_GEN_HW 0x08 |
29 | |
30 | struct omap_rom_rng { |
31 | struct clk *clk; |
32 | struct device *dev; |
33 | struct hwrng ops; |
34 | u32 (*rom_rng_call)(u32 ptr, u32 count, u32 flag); |
35 | }; |
36 | |
37 | static int omap3_rom_rng_read(struct hwrng *rng, void *data, size_t max, bool w) |
38 | { |
39 | struct omap_rom_rng *ddata; |
40 | u32 ptr; |
41 | int r; |
42 | |
43 | ddata = (struct omap_rom_rng *)rng->priv; |
44 | |
45 | r = pm_runtime_get_sync(dev: ddata->dev); |
46 | if (r < 0) { |
47 | pm_runtime_put_noidle(dev: ddata->dev); |
48 | |
49 | return r; |
50 | } |
51 | |
52 | ptr = virt_to_phys(address: data); |
53 | r = ddata->rom_rng_call(ptr, 4, RNG_GEN_HW); |
54 | if (r != 0) |
55 | r = -EINVAL; |
56 | else |
57 | r = 4; |
58 | |
59 | pm_runtime_mark_last_busy(dev: ddata->dev); |
60 | pm_runtime_put_autosuspend(dev: ddata->dev); |
61 | |
62 | return r; |
63 | } |
64 | |
65 | static int __maybe_unused omap_rom_rng_runtime_suspend(struct device *dev) |
66 | { |
67 | struct omap_rom_rng *ddata; |
68 | int r; |
69 | |
70 | ddata = dev_get_drvdata(dev); |
71 | |
72 | r = ddata->rom_rng_call(0, 0, RNG_RESET); |
73 | if (r != 0) |
74 | dev_err(dev, "reset failed: %d\n" , r); |
75 | |
76 | clk_disable_unprepare(clk: ddata->clk); |
77 | |
78 | return 0; |
79 | } |
80 | |
81 | static int __maybe_unused omap_rom_rng_runtime_resume(struct device *dev) |
82 | { |
83 | struct omap_rom_rng *ddata; |
84 | int r; |
85 | |
86 | ddata = dev_get_drvdata(dev); |
87 | |
88 | r = clk_prepare_enable(clk: ddata->clk); |
89 | if (r < 0) |
90 | return r; |
91 | |
92 | r = ddata->rom_rng_call(0, 0, RNG_GEN_PRNG_HW_INIT); |
93 | if (r != 0) { |
94 | clk_disable_unprepare(clk: ddata->clk); |
95 | dev_err(dev, "HW init failed: %d\n" , r); |
96 | |
97 | return -EIO; |
98 | } |
99 | |
100 | return 0; |
101 | } |
102 | |
103 | static void omap_rom_rng_finish(void *data) |
104 | { |
105 | struct omap_rom_rng *ddata = data; |
106 | |
107 | pm_runtime_dont_use_autosuspend(dev: ddata->dev); |
108 | pm_runtime_disable(dev: ddata->dev); |
109 | } |
110 | |
111 | static int omap3_rom_rng_probe(struct platform_device *pdev) |
112 | { |
113 | struct omap_rom_rng *ddata; |
114 | int ret = 0; |
115 | |
116 | ddata = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ddata), GFP_KERNEL); |
117 | if (!ddata) |
118 | return -ENOMEM; |
119 | |
120 | ddata->dev = &pdev->dev; |
121 | ddata->ops.priv = (unsigned long)ddata; |
122 | ddata->ops.name = "omap3-rom" ; |
123 | ddata->ops.read = of_device_get_match_data(dev: &pdev->dev); |
124 | ddata->ops.quality = 900; |
125 | if (!ddata->ops.read) { |
126 | dev_err(&pdev->dev, "missing rom code handler\n" ); |
127 | |
128 | return -ENODEV; |
129 | } |
130 | dev_set_drvdata(dev: ddata->dev, data: ddata); |
131 | |
132 | ddata->rom_rng_call = pdev->dev.platform_data; |
133 | if (!ddata->rom_rng_call) { |
134 | dev_err(ddata->dev, "rom_rng_call is NULL\n" ); |
135 | return -EINVAL; |
136 | } |
137 | |
138 | ddata->clk = devm_clk_get(dev: ddata->dev, id: "ick" ); |
139 | if (IS_ERR(ptr: ddata->clk)) { |
140 | dev_err(ddata->dev, "unable to get RNG clock\n" ); |
141 | return PTR_ERR(ptr: ddata->clk); |
142 | } |
143 | |
144 | pm_runtime_enable(dev: &pdev->dev); |
145 | pm_runtime_set_autosuspend_delay(dev: &pdev->dev, delay: 500); |
146 | pm_runtime_use_autosuspend(dev: &pdev->dev); |
147 | |
148 | ret = devm_add_action_or_reset(ddata->dev, omap_rom_rng_finish, |
149 | ddata); |
150 | if (ret) |
151 | return ret; |
152 | |
153 | return devm_hwrng_register(dev: ddata->dev, rng: &ddata->ops); |
154 | } |
155 | |
156 | static const struct of_device_id omap_rom_rng_match[] = { |
157 | { .compatible = "nokia,n900-rom-rng" , .data = omap3_rom_rng_read, }, |
158 | { /* sentinel */ }, |
159 | }; |
160 | MODULE_DEVICE_TABLE(of, omap_rom_rng_match); |
161 | |
162 | static const struct dev_pm_ops omap_rom_rng_pm_ops = { |
163 | SET_SYSTEM_SLEEP_PM_OPS(omap_rom_rng_runtime_suspend, |
164 | omap_rom_rng_runtime_resume) |
165 | }; |
166 | |
167 | static struct platform_driver omap3_rom_rng_driver = { |
168 | .driver = { |
169 | .name = "omap3-rom-rng" , |
170 | .of_match_table = omap_rom_rng_match, |
171 | .pm = &omap_rom_rng_pm_ops, |
172 | }, |
173 | .probe = omap3_rom_rng_probe, |
174 | }; |
175 | |
176 | module_platform_driver(omap3_rom_rng_driver); |
177 | |
178 | MODULE_ALIAS("platform:omap3-rom-rng" ); |
179 | MODULE_AUTHOR("Juha Yrjola" ); |
180 | MODULE_AUTHOR("Pali Rohár <pali@kernel.org>" ); |
181 | MODULE_LICENSE("GPL" ); |
182 | |