]>
Commit | Line | Data |
---|---|---|
9c3c133b AC |
1 | /* |
2 | * drivers/char/hw_random/timeriomem-rng.c | |
3 | * | |
4 | * Copyright (C) 2009 Alexander Clouter <alex@digriz.org.uk> | |
5 | * | |
6 | * Derived from drivers/char/hw_random/omap-rng.c | |
7 | * Copyright 2005 (c) MontaVista Software, Inc. | |
8 | * Author: Deepak Saxena <dsaxena@plexity.net> | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License version 2 as | |
12 | * published by the Free Software Foundation. | |
13 | * | |
14 | * Overview: | |
15 | * This driver is useful for platforms that have an IO range that provides | |
16 | * periodic random data from a single IO memory address. All the platform | |
17 | * has to do is provide the address and 'wait time' that new data becomes | |
18 | * available. | |
19 | * | |
20 | * TODO: add support for reading sizes other than 32bits and masking | |
21 | */ | |
22 | ||
23 | #include <linux/module.h> | |
24 | #include <linux/kernel.h> | |
25 | #include <linux/platform_device.h> | |
26 | #include <linux/hw_random.h> | |
27 | #include <linux/io.h> | |
1907da78 | 28 | #include <linux/slab.h> |
9c3c133b AC |
29 | #include <linux/timeriomem-rng.h> |
30 | #include <linux/jiffies.h> | |
31 | #include <linux/sched.h> | |
32 | #include <linux/timer.h> | |
33 | #include <linux/completion.h> | |
34 | ||
1907da78 AC |
35 | struct timeriomem_rng_private_data { |
36 | void __iomem *io_base; | |
37 | unsigned int expires; | |
38 | unsigned int period; | |
39 | unsigned int present:1; | |
9c3c133b | 40 | |
1907da78 AC |
41 | struct timer_list timer; |
42 | struct completion completion; | |
43 | ||
44 | struct hwrng timeriomem_rng_ops; | |
45 | }; | |
46 | ||
47 | #define to_rng_priv(rng) \ | |
48 | ((struct timeriomem_rng_private_data *)rng->priv) | |
9c3c133b AC |
49 | |
50 | /* | |
51 | * have data return 1, however return 0 if we have nothing | |
52 | */ | |
53 | static int timeriomem_rng_data_present(struct hwrng *rng, int wait) | |
54 | { | |
1907da78 | 55 | struct timeriomem_rng_private_data *priv = to_rng_priv(rng); |
9c3c133b | 56 | |
1907da78 AC |
57 | if (!wait || priv->present) |
58 | return priv->present; | |
9c3c133b | 59 | |
1907da78 | 60 | wait_for_completion(&priv->completion); |
9c3c133b AC |
61 | |
62 | return 1; | |
63 | } | |
64 | ||
65 | static int timeriomem_rng_data_read(struct hwrng *rng, u32 *data) | |
66 | { | |
1907da78 | 67 | struct timeriomem_rng_private_data *priv = to_rng_priv(rng); |
9c3c133b AC |
68 | unsigned long cur; |
69 | s32 delay; | |
70 | ||
1907da78 | 71 | *data = readl(priv->io_base); |
9c3c133b | 72 | |
1907da78 | 73 | cur = jiffies; |
9c3c133b | 74 | |
1907da78 AC |
75 | delay = cur - priv->expires; |
76 | delay = priv->period - (delay % priv->period); | |
9c3c133b | 77 | |
1907da78 AC |
78 | priv->expires = cur + delay; |
79 | priv->present = 0; | |
9c3c133b | 80 | |
1907da78 AC |
81 | INIT_COMPLETION(priv->completion); |
82 | mod_timer(&priv->timer, priv->expires); | |
9c3c133b AC |
83 | |
84 | return 4; | |
85 | } | |
86 | ||
1907da78 | 87 | static void timeriomem_rng_trigger(unsigned long data) |
9c3c133b | 88 | { |
1907da78 AC |
89 | struct timeriomem_rng_private_data *priv |
90 | = (struct timeriomem_rng_private_data *)data; | |
9c3c133b | 91 | |
1907da78 AC |
92 | priv->present = 1; |
93 | complete(&priv->completion); | |
94 | } | |
9c3c133b | 95 | |
bcd2982a | 96 | static int timeriomem_rng_probe(struct platform_device *pdev) |
9c3c133b | 97 | { |
1907da78 AC |
98 | struct timeriomem_rng_data *pdata = pdev->dev.platform_data; |
99 | struct timeriomem_rng_private_data *priv; | |
08ced854 | 100 | struct resource *res; |
1907da78 AC |
101 | int err = 0; |
102 | int period; | |
9c3c133b | 103 | |
1907da78 AC |
104 | if (!pdata) { |
105 | dev_err(&pdev->dev, "timeriomem_rng_data is missing\n"); | |
106 | return -EINVAL; | |
107 | } | |
3341323b | 108 | |
1907da78 | 109 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
3341323b | 110 | if (!res) |
1907da78 | 111 | return -ENXIO; |
3341323b | 112 | |
1907da78 AC |
113 | if (res->start % 4 != 0 || resource_size(res) != 4) { |
114 | dev_err(&pdev->dev, | |
115 | "address must be four bytes wide and aligned\n"); | |
116 | return -EINVAL; | |
117 | } | |
9c3c133b | 118 | |
1907da78 AC |
119 | /* Allocate memory for the device structure (and zero it) */ |
120 | priv = kzalloc(sizeof(struct timeriomem_rng_private_data), GFP_KERNEL); | |
121 | if (!priv) { | |
122 | dev_err(&pdev->dev, "failed to allocate device structure.\n"); | |
123 | return -ENOMEM; | |
124 | } | |
125 | ||
126 | platform_set_drvdata(pdev, priv); | |
3341323b | 127 | |
1907da78 | 128 | period = pdata->period; |
9c3c133b | 129 | |
1907da78 AC |
130 | priv->period = usecs_to_jiffies(period); |
131 | if (priv->period < 1) { | |
132 | dev_err(&pdev->dev, "period is less than one jiffy\n"); | |
133 | err = -EINVAL; | |
134 | goto out_free; | |
9c3c133b | 135 | } |
9c3c133b | 136 | |
1907da78 AC |
137 | priv->expires = jiffies; |
138 | priv->present = 1; | |
139 | ||
140 | init_completion(&priv->completion); | |
141 | complete(&priv->completion); | |
142 | ||
143 | setup_timer(&priv->timer, timeriomem_rng_trigger, (unsigned long)priv); | |
144 | ||
145 | priv->timeriomem_rng_ops.name = dev_name(&pdev->dev); | |
146 | priv->timeriomem_rng_ops.data_present = timeriomem_rng_data_present; | |
147 | priv->timeriomem_rng_ops.data_read = timeriomem_rng_data_read; | |
148 | priv->timeriomem_rng_ops.priv = (unsigned long)priv; | |
149 | ||
150 | if (!request_mem_region(res->start, resource_size(res), | |
151 | dev_name(&pdev->dev))) { | |
152 | dev_err(&pdev->dev, "request_mem_region failed\n"); | |
153 | err = -EBUSY; | |
154 | goto out_timer; | |
155 | } | |
156 | ||
157 | priv->io_base = ioremap(res->start, resource_size(res)); | |
158 | if (priv->io_base == NULL) { | |
159 | dev_err(&pdev->dev, "ioremap failed\n"); | |
160 | err = -EIO; | |
161 | goto out_release_io; | |
162 | } | |
163 | ||
164 | err = hwrng_register(&priv->timeriomem_rng_ops); | |
165 | if (err) { | |
166 | dev_err(&pdev->dev, "problem registering\n"); | |
167 | goto out; | |
168 | } | |
9c3c133b AC |
169 | |
170 | dev_info(&pdev->dev, "32bits from 0x%p @ %dus\n", | |
1907da78 | 171 | priv->io_base, period); |
9c3c133b AC |
172 | |
173 | return 0; | |
3341323b | 174 | |
1907da78 AC |
175 | out: |
176 | iounmap(priv->io_base); | |
177 | out_release_io: | |
178 | release_mem_region(res->start, resource_size(res)); | |
179 | out_timer: | |
180 | del_timer_sync(&priv->timer); | |
181 | out_free: | |
182 | platform_set_drvdata(pdev, NULL); | |
183 | kfree(priv); | |
184 | return err; | |
9c3c133b AC |
185 | } |
186 | ||
39af33fc | 187 | static int timeriomem_rng_remove(struct platform_device *pdev) |
9c3c133b | 188 | { |
1907da78 AC |
189 | struct timeriomem_rng_private_data *priv = platform_get_drvdata(pdev); |
190 | struct resource *res; | |
191 | ||
192 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
193 | ||
194 | hwrng_unregister(&priv->timeriomem_rng_ops); | |
9c3c133b | 195 | |
1907da78 AC |
196 | del_timer_sync(&priv->timer); |
197 | iounmap(priv->io_base); | |
198 | release_mem_region(res->start, resource_size(res)); | |
199 | platform_set_drvdata(pdev, NULL); | |
200 | kfree(priv); | |
3341323b | 201 | |
9c3c133b AC |
202 | return 0; |
203 | } | |
204 | ||
205 | static struct platform_driver timeriomem_rng_driver = { | |
206 | .driver = { | |
207 | .name = "timeriomem_rng", | |
208 | .owner = THIS_MODULE, | |
209 | }, | |
210 | .probe = timeriomem_rng_probe, | |
bcd2982a | 211 | .remove = timeriomem_rng_remove, |
9c3c133b AC |
212 | }; |
213 | ||
b21cb324 | 214 | module_platform_driver(timeriomem_rng_driver); |
9c3c133b AC |
215 | |
216 | MODULE_LICENSE("GPL"); | |
217 | MODULE_AUTHOR("Alexander Clouter <alex@digriz.org.uk>"); | |
218 | MODULE_DESCRIPTION("Timer IOMEM H/W RNG driver"); |