diff options
-rw-r--r-- | drivers/regulator/virtual.c | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/drivers/regulator/virtual.c b/drivers/regulator/virtual.c new file mode 100644 index 000000000000..5ddb464b1c3f --- /dev/null +++ b/drivers/regulator/virtual.c | |||
@@ -0,0 +1,345 @@ | |||
1 | /* | ||
2 | * reg-virtual-consumer.c | ||
3 | * | ||
4 | * Copyright 2008 Wolfson Microelectronics PLC. | ||
5 | * | ||
6 | * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License as | ||
10 | * published by the Free Software Foundation; either version 2 of the | ||
11 | * License, or (at your option) any later version. | ||
12 | */ | ||
13 | |||
14 | #include <linux/err.h> | ||
15 | #include <linux/mutex.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | #include <linux/regulator/consumer.h> | ||
18 | |||
19 | struct virtual_consumer_data { | ||
20 | struct mutex lock; | ||
21 | struct regulator *regulator; | ||
22 | int enabled; | ||
23 | int min_uV; | ||
24 | int max_uV; | ||
25 | int min_uA; | ||
26 | int max_uA; | ||
27 | unsigned int mode; | ||
28 | }; | ||
29 | |||
30 | static void update_voltage_constraints(struct virtual_consumer_data *data) | ||
31 | { | ||
32 | int ret; | ||
33 | |||
34 | if (data->min_uV && data->max_uV | ||
35 | && data->min_uV <= data->max_uV) { | ||
36 | ret = regulator_set_voltage(data->regulator, | ||
37 | data->min_uV, data->max_uV); | ||
38 | if (ret != 0) { | ||
39 | printk(KERN_ERR "regulator_set_voltage() failed: %d\n", | ||
40 | ret); | ||
41 | return; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | if (data->min_uV && data->max_uV && !data->enabled) { | ||
46 | ret = regulator_enable(data->regulator); | ||
47 | if (ret == 0) | ||
48 | data->enabled = 1; | ||
49 | else | ||
50 | printk(KERN_ERR "regulator_enable() failed: %d\n", | ||
51 | ret); | ||
52 | } | ||
53 | |||
54 | if (!(data->min_uV && data->max_uV) && data->enabled) { | ||
55 | ret = regulator_disable(data->regulator); | ||
56 | if (ret == 0) | ||
57 | data->enabled = 0; | ||
58 | else | ||
59 | printk(KERN_ERR "regulator_disable() failed: %d\n", | ||
60 | ret); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | static void update_current_limit_constraints(struct virtual_consumer_data | ||
65 | *data) | ||
66 | { | ||
67 | int ret; | ||
68 | |||
69 | if (data->max_uA | ||
70 | && data->min_uA <= data->max_uA) { | ||
71 | ret = regulator_set_current_limit(data->regulator, | ||
72 | data->min_uA, data->max_uA); | ||
73 | if (ret != 0) { | ||
74 | pr_err("regulator_set_current_limit() failed: %d\n", | ||
75 | ret); | ||
76 | return; | ||
77 | } | ||
78 | } | ||
79 | |||
80 | if (data->max_uA && !data->enabled) { | ||
81 | ret = regulator_enable(data->regulator); | ||
82 | if (ret == 0) | ||
83 | data->enabled = 1; | ||
84 | else | ||
85 | printk(KERN_ERR "regulator_enable() failed: %d\n", | ||
86 | ret); | ||
87 | } | ||
88 | |||
89 | if (!(data->min_uA && data->max_uA) && data->enabled) { | ||
90 | ret = regulator_disable(data->regulator); | ||
91 | if (ret == 0) | ||
92 | data->enabled = 0; | ||
93 | else | ||
94 | printk(KERN_ERR "regulator_disable() failed: %d\n", | ||
95 | ret); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | static ssize_t show_min_uV(struct device *dev, | ||
100 | struct device_attribute *attr, char *buf) | ||
101 | { | ||
102 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | ||
103 | return sprintf(buf, "%d\n", data->min_uV); | ||
104 | } | ||
105 | |||
106 | static ssize_t set_min_uV(struct device *dev, struct device_attribute *attr, | ||
107 | const char *buf, size_t count) | ||
108 | { | ||
109 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | ||
110 | long val; | ||
111 | |||
112 | if (strict_strtol(buf, 10, &val) != 0) | ||
113 | return count; | ||
114 | |||
115 | mutex_lock(&data->lock); | ||
116 | |||
117 | data->min_uV = val; | ||
118 | update_voltage_constraints(data); | ||
119 | |||
120 | mutex_unlock(&data->lock); | ||
121 | |||
122 | return count; | ||
123 | } | ||
124 | |||
125 | static ssize_t show_max_uV(struct device *dev, | ||
126 | struct device_attribute *attr, char *buf) | ||
127 | { | ||
128 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | ||
129 | return sprintf(buf, "%d\n", data->max_uV); | ||
130 | } | ||
131 | |||
132 | static ssize_t set_max_uV(struct device *dev, struct device_attribute *attr, | ||
133 | const char *buf, size_t count) | ||
134 | { | ||
135 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | ||
136 | long val; | ||
137 | |||
138 | if (strict_strtol(buf, 10, &val) != 0) | ||
139 | return count; | ||
140 | |||
141 | mutex_lock(&data->lock); | ||
142 | |||
143 | data->max_uV = val; | ||
144 | update_voltage_constraints(data); | ||
145 | |||
146 | mutex_unlock(&data->lock); | ||
147 | |||
148 | return count; | ||
149 | } | ||
150 | |||
151 | static ssize_t show_min_uA(struct device *dev, | ||
152 | struct device_attribute *attr, char *buf) | ||
153 | { | ||
154 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | ||
155 | return sprintf(buf, "%d\n", data->min_uA); | ||
156 | } | ||
157 | |||
158 | static ssize_t set_min_uA(struct device *dev, struct device_attribute *attr, | ||
159 | const char *buf, size_t count) | ||
160 | { | ||
161 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | ||
162 | long val; | ||
163 | |||
164 | if (strict_strtol(buf, 10, &val) != 0) | ||
165 | return count; | ||
166 | |||
167 | mutex_lock(&data->lock); | ||
168 | |||
169 | data->min_uA = val; | ||
170 | update_current_limit_constraints(data); | ||
171 | |||
172 | mutex_unlock(&data->lock); | ||
173 | |||
174 | return count; | ||
175 | } | ||
176 | |||
177 | static ssize_t show_max_uA(struct device *dev, | ||
178 | struct device_attribute *attr, char *buf) | ||
179 | { | ||
180 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | ||
181 | return sprintf(buf, "%d\n", data->max_uA); | ||
182 | } | ||
183 | |||
184 | static ssize_t set_max_uA(struct device *dev, struct device_attribute *attr, | ||
185 | const char *buf, size_t count) | ||
186 | { | ||
187 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | ||
188 | long val; | ||
189 | |||
190 | if (strict_strtol(buf, 10, &val) != 0) | ||
191 | return count; | ||
192 | |||
193 | mutex_lock(&data->lock); | ||
194 | |||
195 | data->max_uA = val; | ||
196 | update_current_limit_constraints(data); | ||
197 | |||
198 | mutex_unlock(&data->lock); | ||
199 | |||
200 | return count; | ||
201 | } | ||
202 | |||
203 | static ssize_t show_mode(struct device *dev, | ||
204 | struct device_attribute *attr, char *buf) | ||
205 | { | ||
206 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | ||
207 | |||
208 | switch (data->mode) { | ||
209 | case REGULATOR_MODE_FAST: | ||
210 | return sprintf(buf, "fast\n"); | ||
211 | case REGULATOR_MODE_NORMAL: | ||
212 | return sprintf(buf, "normal\n"); | ||
213 | case REGULATOR_MODE_IDLE: | ||
214 | return sprintf(buf, "idle\n"); | ||
215 | case REGULATOR_MODE_STANDBY: | ||
216 | return sprintf(buf, "standby\n"); | ||
217 | default: | ||
218 | return sprintf(buf, "unknown\n"); | ||
219 | } | ||
220 | } | ||
221 | |||
222 | static ssize_t set_mode(struct device *dev, struct device_attribute *attr, | ||
223 | const char *buf, size_t count) | ||
224 | { | ||
225 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | ||
226 | unsigned int mode; | ||
227 | int ret; | ||
228 | |||
229 | if (strncmp(buf, "fast", strlen("fast")) == 0) | ||
230 | mode = REGULATOR_MODE_FAST; | ||
231 | else if (strncmp(buf, "normal", strlen("normal")) == 0) | ||
232 | mode = REGULATOR_MODE_NORMAL; | ||
233 | else if (strncmp(buf, "idle", strlen("idle")) == 0) | ||
234 | mode = REGULATOR_MODE_IDLE; | ||
235 | else if (strncmp(buf, "standby", strlen("standby")) == 0) | ||
236 | mode = REGULATOR_MODE_STANDBY; | ||
237 | else { | ||
238 | dev_err(dev, "Configuring invalid mode\n"); | ||
239 | return count; | ||
240 | } | ||
241 | |||
242 | mutex_lock(&data->lock); | ||
243 | ret = regulator_set_mode(data->regulator, mode); | ||
244 | if (ret == 0) | ||
245 | data->mode = mode; | ||
246 | else | ||
247 | dev_err(dev, "Failed to configure mode: %d\n", ret); | ||
248 | mutex_unlock(&data->lock); | ||
249 | |||
250 | return count; | ||
251 | } | ||
252 | |||
253 | static DEVICE_ATTR(min_microvolts, 0666, show_min_uV, set_min_uV); | ||
254 | static DEVICE_ATTR(max_microvolts, 0666, show_max_uV, set_max_uV); | ||
255 | static DEVICE_ATTR(min_microamps, 0666, show_min_uA, set_min_uA); | ||
256 | static DEVICE_ATTR(max_microamps, 0666, show_max_uA, set_max_uA); | ||
257 | static DEVICE_ATTR(mode, 0666, show_mode, set_mode); | ||
258 | |||
259 | struct device_attribute *attributes[] = { | ||
260 | &dev_attr_min_microvolts, | ||
261 | &dev_attr_max_microvolts, | ||
262 | &dev_attr_min_microamps, | ||
263 | &dev_attr_max_microamps, | ||
264 | &dev_attr_mode, | ||
265 | }; | ||
266 | |||
267 | static int regulator_virtual_consumer_probe(struct platform_device *pdev) | ||
268 | { | ||
269 | char *reg_id = pdev->dev.platform_data; | ||
270 | struct virtual_consumer_data *drvdata; | ||
271 | int ret, i; | ||
272 | |||
273 | drvdata = kzalloc(sizeof(struct virtual_consumer_data), GFP_KERNEL); | ||
274 | if (drvdata == NULL) { | ||
275 | ret = -ENOMEM; | ||
276 | goto err; | ||
277 | } | ||
278 | |||
279 | mutex_init(&drvdata->lock); | ||
280 | |||
281 | drvdata->regulator = regulator_get(&pdev->dev, reg_id); | ||
282 | if (IS_ERR(drvdata->regulator)) { | ||
283 | ret = PTR_ERR(drvdata->regulator); | ||
284 | goto err; | ||
285 | } | ||
286 | |||
287 | for (i = 0; i < ARRAY_SIZE(attributes); i++) { | ||
288 | ret = device_create_file(&pdev->dev, attributes[i]); | ||
289 | if (ret != 0) | ||
290 | goto err; | ||
291 | } | ||
292 | |||
293 | drvdata->mode = regulator_get_mode(drvdata->regulator); | ||
294 | |||
295 | platform_set_drvdata(pdev, drvdata); | ||
296 | |||
297 | return 0; | ||
298 | |||
299 | err: | ||
300 | for (i = 0; i < ARRAY_SIZE(attributes); i++) | ||
301 | device_remove_file(&pdev->dev, attributes[i]); | ||
302 | kfree(drvdata); | ||
303 | return ret; | ||
304 | } | ||
305 | |||
306 | static int regulator_virtual_consumer_remove(struct platform_device *pdev) | ||
307 | { | ||
308 | struct virtual_consumer_data *drvdata = platform_get_drvdata(pdev); | ||
309 | int i; | ||
310 | |||
311 | for (i = 0; i < ARRAY_SIZE(attributes); i++) | ||
312 | device_remove_file(&pdev->dev, attributes[i]); | ||
313 | if (drvdata->enabled) | ||
314 | regulator_disable(drvdata->regulator); | ||
315 | regulator_put(drvdata->regulator); | ||
316 | |||
317 | kfree(drvdata); | ||
318 | |||
319 | return 0; | ||
320 | } | ||
321 | |||
322 | static struct platform_driver regulator_virtual_consumer_driver = { | ||
323 | .probe = regulator_virtual_consumer_probe, | ||
324 | .remove = regulator_virtual_consumer_remove, | ||
325 | .driver = { | ||
326 | .name = "reg-virt-consumer", | ||
327 | }, | ||
328 | }; | ||
329 | |||
330 | |||
331 | static int __init regulator_virtual_consumer_init(void) | ||
332 | { | ||
333 | return platform_driver_register(®ulator_virtual_consumer_driver); | ||
334 | } | ||
335 | module_init(regulator_virtual_consumer_init); | ||
336 | |||
337 | static void __exit regulator_virtual_consumer_exit(void) | ||
338 | { | ||
339 | platform_driver_unregister(®ulator_virtual_consumer_driver); | ||
340 | } | ||
341 | module_exit(regulator_virtual_consumer_exit); | ||
342 | |||
343 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | ||
344 | MODULE_DESCRIPTION("Virtual regulator consumer"); | ||
345 | MODULE_LICENSE("GPL"); | ||