]>
Commit | Line | Data |
---|---|---|
c080909e MB |
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> | |
5a0e3ad6 | 18 | #include <linux/slab.h> |
c080909e MB |
19 | |
20 | struct virtual_consumer_data { | |
21 | struct mutex lock; | |
22 | struct regulator *regulator; | |
4cf95663 | 23 | bool enabled; |
c080909e MB |
24 | int min_uV; |
25 | int max_uV; | |
26 | int min_uA; | |
27 | int max_uA; | |
28 | unsigned int mode; | |
29 | }; | |
30 | ||
a07ac217 MB |
31 | static void update_voltage_constraints(struct device *dev, |
32 | struct virtual_consumer_data *data) | |
c080909e MB |
33 | { |
34 | int ret; | |
35 | ||
36 | if (data->min_uV && data->max_uV | |
37 | && data->min_uV <= data->max_uV) { | |
a5d2abce MB |
38 | dev_dbg(dev, "Requesting %d-%duV\n", |
39 | data->min_uV, data->max_uV); | |
c080909e | 40 | ret = regulator_set_voltage(data->regulator, |
a07ac217 | 41 | data->min_uV, data->max_uV); |
c080909e | 42 | if (ret != 0) { |
a07ac217 MB |
43 | dev_err(dev, |
44 | "regulator_set_voltage() failed: %d\n", ret); | |
c080909e MB |
45 | return; |
46 | } | |
47 | } | |
48 | ||
49 | if (data->min_uV && data->max_uV && !data->enabled) { | |
a5d2abce | 50 | dev_dbg(dev, "Enabling regulator\n"); |
c080909e MB |
51 | ret = regulator_enable(data->regulator); |
52 | if (ret == 0) | |
4cf95663 | 53 | data->enabled = true; |
c080909e | 54 | else |
a07ac217 | 55 | dev_err(dev, "regulator_enable() failed: %d\n", |
c080909e MB |
56 | ret); |
57 | } | |
58 | ||
59 | if (!(data->min_uV && data->max_uV) && data->enabled) { | |
a5d2abce | 60 | dev_dbg(dev, "Disabling regulator\n"); |
c080909e MB |
61 | ret = regulator_disable(data->regulator); |
62 | if (ret == 0) | |
4cf95663 | 63 | data->enabled = false; |
c080909e | 64 | else |
a07ac217 | 65 | dev_err(dev, "regulator_disable() failed: %d\n", |
c080909e MB |
66 | ret); |
67 | } | |
68 | } | |
69 | ||
a07ac217 MB |
70 | static void update_current_limit_constraints(struct device *dev, |
71 | struct virtual_consumer_data *data) | |
c080909e MB |
72 | { |
73 | int ret; | |
74 | ||
75 | if (data->max_uA | |
76 | && data->min_uA <= data->max_uA) { | |
a5d2abce MB |
77 | dev_dbg(dev, "Requesting %d-%duA\n", |
78 | data->min_uA, data->max_uA); | |
c080909e MB |
79 | ret = regulator_set_current_limit(data->regulator, |
80 | data->min_uA, data->max_uA); | |
81 | if (ret != 0) { | |
a07ac217 MB |
82 | dev_err(dev, |
83 | "regulator_set_current_limit() failed: %d\n", | |
84 | ret); | |
c080909e MB |
85 | return; |
86 | } | |
87 | } | |
88 | ||
89 | if (data->max_uA && !data->enabled) { | |
a5d2abce | 90 | dev_dbg(dev, "Enabling regulator\n"); |
c080909e MB |
91 | ret = regulator_enable(data->regulator); |
92 | if (ret == 0) | |
4cf95663 | 93 | data->enabled = true; |
c080909e | 94 | else |
a07ac217 | 95 | dev_err(dev, "regulator_enable() failed: %d\n", |
c080909e MB |
96 | ret); |
97 | } | |
98 | ||
99 | if (!(data->min_uA && data->max_uA) && data->enabled) { | |
a5d2abce | 100 | dev_dbg(dev, "Disabling regulator\n"); |
c080909e MB |
101 | ret = regulator_disable(data->regulator); |
102 | if (ret == 0) | |
4cf95663 | 103 | data->enabled = false; |
c080909e | 104 | else |
a07ac217 | 105 | dev_err(dev, "regulator_disable() failed: %d\n", |
c080909e MB |
106 | ret); |
107 | } | |
108 | } | |
109 | ||
110 | static ssize_t show_min_uV(struct device *dev, | |
111 | struct device_attribute *attr, char *buf) | |
112 | { | |
113 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | |
114 | return sprintf(buf, "%d\n", data->min_uV); | |
115 | } | |
116 | ||
117 | static ssize_t set_min_uV(struct device *dev, struct device_attribute *attr, | |
118 | const char *buf, size_t count) | |
119 | { | |
120 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | |
121 | long val; | |
122 | ||
123 | if (strict_strtol(buf, 10, &val) != 0) | |
124 | return count; | |
125 | ||
126 | mutex_lock(&data->lock); | |
127 | ||
128 | data->min_uV = val; | |
a07ac217 | 129 | update_voltage_constraints(dev, data); |
c080909e MB |
130 | |
131 | mutex_unlock(&data->lock); | |
132 | ||
133 | return count; | |
134 | } | |
135 | ||
136 | static ssize_t show_max_uV(struct device *dev, | |
137 | struct device_attribute *attr, char *buf) | |
138 | { | |
139 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | |
140 | return sprintf(buf, "%d\n", data->max_uV); | |
141 | } | |
142 | ||
143 | static ssize_t set_max_uV(struct device *dev, struct device_attribute *attr, | |
144 | const char *buf, size_t count) | |
145 | { | |
146 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | |
147 | long val; | |
148 | ||
149 | if (strict_strtol(buf, 10, &val) != 0) | |
150 | return count; | |
151 | ||
152 | mutex_lock(&data->lock); | |
153 | ||
154 | data->max_uV = val; | |
a07ac217 | 155 | update_voltage_constraints(dev, data); |
c080909e MB |
156 | |
157 | mutex_unlock(&data->lock); | |
158 | ||
159 | return count; | |
160 | } | |
161 | ||
162 | static ssize_t show_min_uA(struct device *dev, | |
163 | struct device_attribute *attr, char *buf) | |
164 | { | |
165 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | |
166 | return sprintf(buf, "%d\n", data->min_uA); | |
167 | } | |
168 | ||
169 | static ssize_t set_min_uA(struct device *dev, struct device_attribute *attr, | |
170 | const char *buf, size_t count) | |
171 | { | |
172 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | |
173 | long val; | |
174 | ||
175 | if (strict_strtol(buf, 10, &val) != 0) | |
176 | return count; | |
177 | ||
178 | mutex_lock(&data->lock); | |
179 | ||
180 | data->min_uA = val; | |
a07ac217 | 181 | update_current_limit_constraints(dev, data); |
c080909e MB |
182 | |
183 | mutex_unlock(&data->lock); | |
184 | ||
185 | return count; | |
186 | } | |
187 | ||
188 | static ssize_t show_max_uA(struct device *dev, | |
189 | struct device_attribute *attr, char *buf) | |
190 | { | |
191 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | |
192 | return sprintf(buf, "%d\n", data->max_uA); | |
193 | } | |
194 | ||
195 | static ssize_t set_max_uA(struct device *dev, struct device_attribute *attr, | |
196 | const char *buf, size_t count) | |
197 | { | |
198 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | |
199 | long val; | |
200 | ||
201 | if (strict_strtol(buf, 10, &val) != 0) | |
202 | return count; | |
203 | ||
204 | mutex_lock(&data->lock); | |
205 | ||
206 | data->max_uA = val; | |
a07ac217 | 207 | update_current_limit_constraints(dev, data); |
c080909e MB |
208 | |
209 | mutex_unlock(&data->lock); | |
210 | ||
211 | return count; | |
212 | } | |
213 | ||
214 | static ssize_t show_mode(struct device *dev, | |
215 | struct device_attribute *attr, char *buf) | |
216 | { | |
217 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | |
218 | ||
219 | switch (data->mode) { | |
220 | case REGULATOR_MODE_FAST: | |
221 | return sprintf(buf, "fast\n"); | |
222 | case REGULATOR_MODE_NORMAL: | |
223 | return sprintf(buf, "normal\n"); | |
224 | case REGULATOR_MODE_IDLE: | |
225 | return sprintf(buf, "idle\n"); | |
226 | case REGULATOR_MODE_STANDBY: | |
227 | return sprintf(buf, "standby\n"); | |
228 | default: | |
229 | return sprintf(buf, "unknown\n"); | |
230 | } | |
231 | } | |
232 | ||
233 | static ssize_t set_mode(struct device *dev, struct device_attribute *attr, | |
234 | const char *buf, size_t count) | |
235 | { | |
236 | struct virtual_consumer_data *data = dev_get_drvdata(dev); | |
237 | unsigned int mode; | |
238 | int ret; | |
239 | ||
9485397a AM |
240 | /* |
241 | * sysfs_streq() doesn't need the \n's, but we add them so the strings | |
242 | * will be shared with show_mode(), above. | |
243 | */ | |
aa61d558 | 244 | if (sysfs_streq(buf, "fast\n")) |
c080909e | 245 | mode = REGULATOR_MODE_FAST; |
aa61d558 | 246 | else if (sysfs_streq(buf, "normal\n")) |
c080909e | 247 | mode = REGULATOR_MODE_NORMAL; |
aa61d558 | 248 | else if (sysfs_streq(buf, "idle\n")) |
c080909e | 249 | mode = REGULATOR_MODE_IDLE; |
aa61d558 | 250 | else if (sysfs_streq(buf, "standby\n")) |
c080909e MB |
251 | mode = REGULATOR_MODE_STANDBY; |
252 | else { | |
253 | dev_err(dev, "Configuring invalid mode\n"); | |
254 | return count; | |
255 | } | |
256 | ||
257 | mutex_lock(&data->lock); | |
258 | ret = regulator_set_mode(data->regulator, mode); | |
259 | if (ret == 0) | |
260 | data->mode = mode; | |
261 | else | |
262 | dev_err(dev, "Failed to configure mode: %d\n", ret); | |
263 | mutex_unlock(&data->lock); | |
264 | ||
265 | return count; | |
266 | } | |
267 | ||
268 | static DEVICE_ATTR(min_microvolts, 0666, show_min_uV, set_min_uV); | |
269 | static DEVICE_ATTR(max_microvolts, 0666, show_max_uV, set_max_uV); | |
270 | static DEVICE_ATTR(min_microamps, 0666, show_min_uA, set_min_uA); | |
271 | static DEVICE_ATTR(max_microamps, 0666, show_max_uA, set_max_uA); | |
272 | static DEVICE_ATTR(mode, 0666, show_mode, set_mode); | |
273 | ||
4cf95663 DT |
274 | static struct attribute *regulator_virtual_attributes[] = { |
275 | &dev_attr_min_microvolts.attr, | |
276 | &dev_attr_max_microvolts.attr, | |
277 | &dev_attr_min_microamps.attr, | |
278 | &dev_attr_max_microamps.attr, | |
279 | &dev_attr_mode.attr, | |
280 | NULL | |
c080909e MB |
281 | }; |
282 | ||
4cf95663 DT |
283 | static const struct attribute_group regulator_virtual_attr_group = { |
284 | .attrs = regulator_virtual_attributes, | |
285 | }; | |
286 | ||
287 | static int __devinit regulator_virtual_probe(struct platform_device *pdev) | |
c080909e MB |
288 | { |
289 | char *reg_id = pdev->dev.platform_data; | |
290 | struct virtual_consumer_data *drvdata; | |
4cf95663 | 291 | int ret; |
c080909e MB |
292 | |
293 | drvdata = kzalloc(sizeof(struct virtual_consumer_data), GFP_KERNEL); | |
4cf95663 | 294 | if (drvdata == NULL) |
72b86876 | 295 | return -ENOMEM; |
c080909e MB |
296 | |
297 | mutex_init(&drvdata->lock); | |
298 | ||
299 | drvdata->regulator = regulator_get(&pdev->dev, reg_id); | |
300 | if (IS_ERR(drvdata->regulator)) { | |
301 | ret = PTR_ERR(drvdata->regulator); | |
d61c3d56 MB |
302 | dev_err(&pdev->dev, "Failed to obtain supply '%s': %d\n", |
303 | reg_id, ret); | |
c080909e MB |
304 | goto err; |
305 | } | |
306 | ||
4cf95663 DT |
307 | ret = sysfs_create_group(&pdev->dev.kobj, |
308 | ®ulator_virtual_attr_group); | |
309 | if (ret != 0) { | |
310 | dev_err(&pdev->dev, | |
311 | "Failed to create attribute group: %d\n", ret); | |
312 | goto err_regulator; | |
c080909e MB |
313 | } |
314 | ||
315 | drvdata->mode = regulator_get_mode(drvdata->regulator); | |
316 | ||
317 | platform_set_drvdata(pdev, drvdata); | |
318 | ||
319 | return 0; | |
320 | ||
72b86876 MB |
321 | err_regulator: |
322 | regulator_put(drvdata->regulator); | |
c080909e | 323 | err: |
c080909e MB |
324 | kfree(drvdata); |
325 | return ret; | |
326 | } | |
327 | ||
4cf95663 | 328 | static int __devexit regulator_virtual_remove(struct platform_device *pdev) |
c080909e MB |
329 | { |
330 | struct virtual_consumer_data *drvdata = platform_get_drvdata(pdev); | |
c080909e | 331 | |
4cf95663 DT |
332 | sysfs_remove_group(&pdev->dev.kobj, ®ulator_virtual_attr_group); |
333 | ||
c080909e MB |
334 | if (drvdata->enabled) |
335 | regulator_disable(drvdata->regulator); | |
336 | regulator_put(drvdata->regulator); | |
337 | ||
338 | kfree(drvdata); | |
339 | ||
4cf95663 DT |
340 | platform_set_drvdata(pdev, NULL); |
341 | ||
c080909e MB |
342 | return 0; |
343 | } | |
344 | ||
345 | static struct platform_driver regulator_virtual_consumer_driver = { | |
4cf95663 DT |
346 | .probe = regulator_virtual_probe, |
347 | .remove = __devexit_p(regulator_virtual_remove), | |
c080909e MB |
348 | .driver = { |
349 | .name = "reg-virt-consumer", | |
4cf95663 | 350 | .owner = THIS_MODULE, |
c080909e MB |
351 | }, |
352 | }; | |
353 | ||
c080909e MB |
354 | static int __init regulator_virtual_consumer_init(void) |
355 | { | |
356 | return platform_driver_register(®ulator_virtual_consumer_driver); | |
357 | } | |
358 | module_init(regulator_virtual_consumer_init); | |
359 | ||
360 | static void __exit regulator_virtual_consumer_exit(void) | |
361 | { | |
362 | platform_driver_unregister(®ulator_virtual_consumer_driver); | |
363 | } | |
364 | module_exit(regulator_virtual_consumer_exit); | |
365 | ||
366 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | |
367 | MODULE_DESCRIPTION("Virtual regulator consumer"); | |
368 | MODULE_LICENSE("GPL"); | |
38c53c89 | 369 | MODULE_ALIAS("platform:reg-virt-consumer"); |