Commit | Line | Data |
---|---|---|
4a11b59d AV |
1 | /* |
2 | * Universal power supply monitor class | |
3 | * | |
4 | * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> | |
5 | * Copyright © 2004 Szabolcs Gyurko | |
6 | * Copyright © 2003 Ian Molton <spyro@f2s.com> | |
7 | * | |
8 | * Modified: 2004, Oct Szabolcs Gyurko | |
9 | * | |
10 | * You may use this code as per GPL version 2 | |
11 | */ | |
12 | ||
13 | #include <linux/module.h> | |
14 | #include <linux/types.h> | |
15 | #include <linux/init.h> | |
5f487cd3 | 16 | #include <linux/slab.h> |
4a11b59d | 17 | #include <linux/device.h> |
d36240d2 | 18 | #include <linux/notifier.h> |
4a11b59d AV |
19 | #include <linux/err.h> |
20 | #include <linux/power_supply.h> | |
3be330bf | 21 | #include <linux/thermal.h> |
4a11b59d AV |
22 | #include "power_supply.h" |
23 | ||
ff3417e7 | 24 | /* exported for the APM Power driver, APM emulation */ |
4a11b59d | 25 | struct class *power_supply_class; |
ff3417e7 | 26 | EXPORT_SYMBOL_GPL(power_supply_class); |
4a11b59d | 27 | |
d36240d2 PR |
28 | ATOMIC_NOTIFIER_HEAD(power_supply_notifier); |
29 | EXPORT_SYMBOL_GPL(power_supply_notifier); | |
30 | ||
5f487cd3 AV |
31 | static struct device_type power_supply_dev_type; |
32 | ||
5e0848c6 RK |
33 | static bool __power_supply_is_supplied_by(struct power_supply *supplier, |
34 | struct power_supply *supply) | |
35 | { | |
36 | int i; | |
37 | ||
38 | if (!supply->supplied_from && !supplier->supplied_to) | |
39 | return false; | |
40 | ||
41 | /* Support both supplied_to and supplied_from modes */ | |
42 | if (supply->supplied_from) { | |
297d716f | 43 | if (!supplier->desc->name) |
5e0848c6 RK |
44 | return false; |
45 | for (i = 0; i < supply->num_supplies; i++) | |
297d716f | 46 | if (!strcmp(supplier->desc->name, supply->supplied_from[i])) |
5e0848c6 RK |
47 | return true; |
48 | } else { | |
297d716f | 49 | if (!supply->desc->name) |
5e0848c6 RK |
50 | return false; |
51 | for (i = 0; i < supplier->num_supplicants; i++) | |
297d716f | 52 | if (!strcmp(supplier->supplied_to[i], supply->desc->name)) |
5e0848c6 RK |
53 | return true; |
54 | } | |
55 | ||
56 | return false; | |
57 | } | |
58 | ||
443cad92 DY |
59 | static int __power_supply_changed_work(struct device *dev, void *data) |
60 | { | |
e80cf421 | 61 | struct power_supply *psy = data; |
443cad92 | 62 | struct power_supply *pst = dev_get_drvdata(dev); |
443cad92 | 63 | |
5e0848c6 | 64 | if (__power_supply_is_supplied_by(psy, pst)) { |
297d716f KK |
65 | if (pst->desc->external_power_changed) |
66 | pst->desc->external_power_changed(pst); | |
5e0848c6 RK |
67 | } |
68 | ||
443cad92 DY |
69 | return 0; |
70 | } | |
71 | ||
4a11b59d AV |
72 | static void power_supply_changed_work(struct work_struct *work) |
73 | { | |
948dcf96 | 74 | unsigned long flags; |
4a11b59d AV |
75 | struct power_supply *psy = container_of(work, struct power_supply, |
76 | changed_work); | |
4a11b59d | 77 | |
297d716f | 78 | dev_dbg(&psy->dev, "%s\n", __func__); |
4a11b59d | 79 | |
948dcf96 | 80 | spin_lock_irqsave(&psy->changed_lock, flags); |
061f3806 VK |
81 | /* |
82 | * Check 'changed' here to avoid issues due to race between | |
83 | * power_supply_changed() and this routine. In worst case | |
84 | * power_supply_changed() can be called again just before we take above | |
85 | * lock. During the first call of this routine we will mark 'changed' as | |
86 | * false and it will stay false for the next call as well. | |
87 | */ | |
88 | if (likely(psy->changed)) { | |
948dcf96 ZM |
89 | psy->changed = false; |
90 | spin_unlock_irqrestore(&psy->changed_lock, flags); | |
91 | class_for_each_device(power_supply_class, NULL, psy, | |
92 | __power_supply_changed_work); | |
93 | power_supply_update_leds(psy); | |
d36240d2 PR |
94 | atomic_notifier_call_chain(&power_supply_notifier, |
95 | PSY_EVENT_PROP_CHANGED, psy); | |
297d716f | 96 | kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE); |
948dcf96 ZM |
97 | spin_lock_irqsave(&psy->changed_lock, flags); |
98 | } | |
061f3806 | 99 | |
948dcf96 | 100 | /* |
061f3806 VK |
101 | * Hold the wakeup_source until all events are processed. |
102 | * power_supply_changed() might have called again and have set 'changed' | |
103 | * to true. | |
948dcf96 | 104 | */ |
061f3806 | 105 | if (likely(!psy->changed)) |
297d716f | 106 | pm_relax(&psy->dev); |
948dcf96 | 107 | spin_unlock_irqrestore(&psy->changed_lock, flags); |
4a11b59d AV |
108 | } |
109 | ||
110 | void power_supply_changed(struct power_supply *psy) | |
111 | { | |
948dcf96 ZM |
112 | unsigned long flags; |
113 | ||
297d716f | 114 | dev_dbg(&psy->dev, "%s\n", __func__); |
4a11b59d | 115 | |
948dcf96 ZM |
116 | spin_lock_irqsave(&psy->changed_lock, flags); |
117 | psy->changed = true; | |
297d716f | 118 | pm_stay_awake(&psy->dev); |
948dcf96 | 119 | spin_unlock_irqrestore(&psy->changed_lock, flags); |
4a11b59d | 120 | schedule_work(&psy->changed_work); |
4a11b59d | 121 | } |
ff3417e7 | 122 | EXPORT_SYMBOL_GPL(power_supply_changed); |
4a11b59d | 123 | |
f6e0b081 RK |
124 | #ifdef CONFIG_OF |
125 | #include <linux/of.h> | |
126 | ||
127 | static int __power_supply_populate_supplied_from(struct device *dev, | |
128 | void *data) | |
129 | { | |
e80cf421 | 130 | struct power_supply *psy = data; |
f6e0b081 RK |
131 | struct power_supply *epsy = dev_get_drvdata(dev); |
132 | struct device_node *np; | |
133 | int i = 0; | |
134 | ||
135 | do { | |
136 | np = of_parse_phandle(psy->of_node, "power-supplies", i++); | |
137 | if (!np) | |
a0f93b42 | 138 | break; |
f6e0b081 RK |
139 | |
140 | if (np == epsy->of_node) { | |
297d716f KK |
141 | dev_info(&psy->dev, "%s: Found supply : %s\n", |
142 | psy->desc->name, epsy->desc->name); | |
143 | psy->supplied_from[i-1] = (char *)epsy->desc->name; | |
f6e0b081 | 144 | psy->num_supplies++; |
2054d6e9 | 145 | of_node_put(np); |
f6e0b081 RK |
146 | break; |
147 | } | |
2054d6e9 | 148 | of_node_put(np); |
f6e0b081 RK |
149 | } while (np); |
150 | ||
151 | return 0; | |
152 | } | |
153 | ||
154 | static int power_supply_populate_supplied_from(struct power_supply *psy) | |
155 | { | |
156 | int error; | |
157 | ||
158 | error = class_for_each_device(power_supply_class, NULL, psy, | |
159 | __power_supply_populate_supplied_from); | |
160 | ||
297d716f | 161 | dev_dbg(&psy->dev, "%s %d\n", __func__, error); |
f6e0b081 RK |
162 | |
163 | return error; | |
164 | } | |
165 | ||
166 | static int __power_supply_find_supply_from_node(struct device *dev, | |
167 | void *data) | |
168 | { | |
e80cf421 | 169 | struct device_node *np = data; |
f6e0b081 RK |
170 | struct power_supply *epsy = dev_get_drvdata(dev); |
171 | ||
585b0087 | 172 | /* returning non-zero breaks out of class_for_each_device loop */ |
f6e0b081 | 173 | if (epsy->of_node == np) |
585b0087 | 174 | return 1; |
f6e0b081 RK |
175 | |
176 | return 0; | |
177 | } | |
178 | ||
179 | static int power_supply_find_supply_from_node(struct device_node *supply_node) | |
180 | { | |
181 | int error; | |
f6e0b081 RK |
182 | |
183 | /* | |
585b0087 VK |
184 | * class_for_each_device() either returns its own errors or values |
185 | * returned by __power_supply_find_supply_from_node(). | |
186 | * | |
187 | * __power_supply_find_supply_from_node() will return 0 (no match) | |
188 | * or 1 (match). | |
189 | * | |
190 | * We return 0 if class_for_each_device() returned 1, -EPROBE_DEFER if | |
191 | * it returned 0, or error as returned by it. | |
f6e0b081 RK |
192 | */ |
193 | error = class_for_each_device(power_supply_class, NULL, supply_node, | |
194 | __power_supply_find_supply_from_node); | |
195 | ||
585b0087 | 196 | return error ? (error == 1 ? 0 : error) : -EPROBE_DEFER; |
f6e0b081 RK |
197 | } |
198 | ||
199 | static int power_supply_check_supplies(struct power_supply *psy) | |
200 | { | |
201 | struct device_node *np; | |
202 | int cnt = 0; | |
203 | ||
204 | /* If there is already a list honor it */ | |
205 | if (psy->supplied_from && psy->num_supplies > 0) | |
206 | return 0; | |
207 | ||
208 | /* No device node found, nothing to do */ | |
209 | if (!psy->of_node) | |
210 | return 0; | |
211 | ||
212 | do { | |
213 | int ret; | |
214 | ||
215 | np = of_parse_phandle(psy->of_node, "power-supplies", cnt++); | |
216 | if (!np) | |
a0f93b42 | 217 | break; |
f6e0b081 RK |
218 | |
219 | ret = power_supply_find_supply_from_node(np); | |
8468b029 VK |
220 | of_node_put(np); |
221 | ||
f6e0b081 | 222 | if (ret) { |
297d716f | 223 | dev_dbg(&psy->dev, "Failed to find supply!\n"); |
f5b89aff | 224 | return ret; |
f6e0b081 RK |
225 | } |
226 | } while (np); | |
227 | ||
f9c85486 VK |
228 | /* Missing valid "power-supplies" entries */ |
229 | if (cnt == 1) | |
230 | return 0; | |
231 | ||
f6e0b081 | 232 | /* All supplies found, allocate char ** array for filling */ |
297d716f | 233 | psy->supplied_from = devm_kzalloc(&psy->dev, sizeof(psy->supplied_from), |
f6e0b081 RK |
234 | GFP_KERNEL); |
235 | if (!psy->supplied_from) { | |
297d716f | 236 | dev_err(&psy->dev, "Couldn't allocate memory for supply list\n"); |
f6e0b081 RK |
237 | return -ENOMEM; |
238 | } | |
239 | ||
297d716f KK |
240 | *psy->supplied_from = devm_kzalloc(&psy->dev, |
241 | sizeof(char *) * (cnt - 1), | |
f6e0b081 RK |
242 | GFP_KERNEL); |
243 | if (!*psy->supplied_from) { | |
297d716f | 244 | dev_err(&psy->dev, "Couldn't allocate memory for supply list\n"); |
f6e0b081 RK |
245 | return -ENOMEM; |
246 | } | |
247 | ||
248 | return power_supply_populate_supplied_from(psy); | |
249 | } | |
250 | #else | |
251 | static inline int power_supply_check_supplies(struct power_supply *psy) | |
252 | { | |
253 | return 0; | |
254 | } | |
255 | #endif | |
256 | ||
443cad92 | 257 | static int __power_supply_am_i_supplied(struct device *dev, void *data) |
4a11b59d AV |
258 | { |
259 | union power_supply_propval ret = {0,}; | |
e80cf421 | 260 | struct power_supply *psy = data; |
443cad92 | 261 | struct power_supply *epsy = dev_get_drvdata(dev); |
443cad92 | 262 | |
5e0848c6 | 263 | if (__power_supply_is_supplied_by(epsy, psy)) |
297d716f KK |
264 | if (!epsy->desc->get_property(epsy, POWER_SUPPLY_PROP_ONLINE, |
265 | &ret)) | |
1c42a389 | 266 | return ret.intval; |
5e0848c6 | 267 | |
443cad92 DY |
268 | return 0; |
269 | } | |
270 | ||
271 | int power_supply_am_i_supplied(struct power_supply *psy) | |
272 | { | |
273 | int error; | |
274 | ||
93562b53 | 275 | error = class_for_each_device(power_supply_class, NULL, psy, |
443cad92 | 276 | __power_supply_am_i_supplied); |
4a11b59d | 277 | |
297d716f | 278 | dev_dbg(&psy->dev, "%s %d\n", __func__, error); |
4a11b59d | 279 | |
443cad92 | 280 | return error; |
4a11b59d | 281 | } |
ff3417e7 | 282 | EXPORT_SYMBOL_GPL(power_supply_am_i_supplied); |
4a11b59d | 283 | |
942ed161 MG |
284 | static int __power_supply_is_system_supplied(struct device *dev, void *data) |
285 | { | |
286 | union power_supply_propval ret = {0,}; | |
287 | struct power_supply *psy = dev_get_drvdata(dev); | |
2530daa1 | 288 | unsigned int *count = data; |
942ed161 | 289 | |
2530daa1 | 290 | (*count)++; |
297d716f KK |
291 | if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) |
292 | if (!psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, | |
293 | &ret)) | |
942ed161 | 294 | return ret.intval; |
1c42a389 | 295 | |
942ed161 MG |
296 | return 0; |
297 | } | |
298 | ||
299 | int power_supply_is_system_supplied(void) | |
300 | { | |
301 | int error; | |
2530daa1 | 302 | unsigned int count = 0; |
942ed161 | 303 | |
2530daa1 | 304 | error = class_for_each_device(power_supply_class, NULL, &count, |
942ed161 MG |
305 | __power_supply_is_system_supplied); |
306 | ||
2530daa1 JD |
307 | /* |
308 | * If no power class device was found at all, most probably we are | |
309 | * running on a desktop system, so assume we are on mains power. | |
310 | */ | |
311 | if (count == 0) | |
312 | return 1; | |
313 | ||
942ed161 MG |
314 | return error; |
315 | } | |
ff3417e7 | 316 | EXPORT_SYMBOL_GPL(power_supply_is_system_supplied); |
942ed161 | 317 | |
e5f5ccb6 DM |
318 | int power_supply_set_battery_charged(struct power_supply *psy) |
319 | { | |
bc154056 | 320 | if (atomic_read(&psy->use_cnt) >= 0 && |
297d716f KK |
321 | psy->desc->type == POWER_SUPPLY_TYPE_BATTERY && |
322 | psy->desc->set_charged) { | |
323 | psy->desc->set_charged(psy); | |
e5f5ccb6 DM |
324 | return 0; |
325 | } | |
326 | ||
327 | return -EINVAL; | |
328 | } | |
329 | EXPORT_SYMBOL_GPL(power_supply_set_battery_charged); | |
330 | ||
9f3b795a | 331 | static int power_supply_match_device_by_name(struct device *dev, const void *data) |
e5f5ccb6 DM |
332 | { |
333 | const char *name = data; | |
334 | struct power_supply *psy = dev_get_drvdata(dev); | |
335 | ||
297d716f | 336 | return strcmp(psy->desc->name, name) == 0; |
e5f5ccb6 DM |
337 | } |
338 | ||
9f3b795a | 339 | struct power_supply *power_supply_get_by_name(const char *name) |
e5f5ccb6 DM |
340 | { |
341 | struct device *dev = class_find_device(power_supply_class, NULL, name, | |
342 | power_supply_match_device_by_name); | |
343 | ||
344 | return dev ? dev_get_drvdata(dev) : NULL; | |
345 | } | |
346 | EXPORT_SYMBOL_GPL(power_supply_get_by_name); | |
347 | ||
abce9770 SR |
348 | #ifdef CONFIG_OF |
349 | static int power_supply_match_device_node(struct device *dev, const void *data) | |
350 | { | |
351 | return dev->parent && dev->parent->of_node == data; | |
352 | } | |
353 | ||
354 | struct power_supply *power_supply_get_by_phandle(struct device_node *np, | |
355 | const char *property) | |
356 | { | |
357 | struct device_node *power_supply_np; | |
358 | struct device *dev; | |
359 | ||
360 | power_supply_np = of_parse_phandle(np, property, 0); | |
361 | if (!power_supply_np) | |
362 | return ERR_PTR(-ENODEV); | |
363 | ||
364 | dev = class_find_device(power_supply_class, NULL, power_supply_np, | |
365 | power_supply_match_device_node); | |
366 | ||
367 | of_node_put(power_supply_np); | |
368 | ||
369 | return dev ? dev_get_drvdata(dev) : NULL; | |
370 | } | |
371 | EXPORT_SYMBOL_GPL(power_supply_get_by_phandle); | |
372 | #endif /* CONFIG_OF */ | |
373 | ||
bc154056 KK |
374 | int power_supply_get_property(struct power_supply *psy, |
375 | enum power_supply_property psp, | |
376 | union power_supply_propval *val) | |
377 | { | |
378 | if (atomic_read(&psy->use_cnt) <= 0) | |
379 | return -ENODEV; | |
380 | ||
297d716f | 381 | return psy->desc->get_property(psy, psp, val); |
bc154056 KK |
382 | } |
383 | EXPORT_SYMBOL_GPL(power_supply_get_property); | |
384 | ||
385 | int power_supply_set_property(struct power_supply *psy, | |
386 | enum power_supply_property psp, | |
387 | const union power_supply_propval *val) | |
388 | { | |
297d716f | 389 | if (atomic_read(&psy->use_cnt) <= 0 || !psy->desc->set_property) |
bc154056 KK |
390 | return -ENODEV; |
391 | ||
297d716f | 392 | return psy->desc->set_property(psy, psp, val); |
bc154056 KK |
393 | } |
394 | EXPORT_SYMBOL_GPL(power_supply_set_property); | |
395 | ||
396 | int power_supply_property_is_writeable(struct power_supply *psy, | |
397 | enum power_supply_property psp) | |
398 | { | |
297d716f KK |
399 | if (atomic_read(&psy->use_cnt) <= 0 || |
400 | !psy->desc->property_is_writeable) | |
bc154056 KK |
401 | return -ENODEV; |
402 | ||
297d716f | 403 | return psy->desc->property_is_writeable(psy, psp); |
bc154056 KK |
404 | } |
405 | EXPORT_SYMBOL_GPL(power_supply_property_is_writeable); | |
406 | ||
407 | void power_supply_external_power_changed(struct power_supply *psy) | |
408 | { | |
297d716f KK |
409 | if (atomic_read(&psy->use_cnt) <= 0 || |
410 | !psy->desc->external_power_changed) | |
bc154056 KK |
411 | return; |
412 | ||
297d716f | 413 | psy->desc->external_power_changed(psy); |
bc154056 KK |
414 | } |
415 | EXPORT_SYMBOL_GPL(power_supply_external_power_changed); | |
416 | ||
83516651 JF |
417 | int power_supply_powers(struct power_supply *psy, struct device *dev) |
418 | { | |
297d716f | 419 | return sysfs_create_link(&psy->dev.kobj, &dev->kobj, "powers"); |
83516651 JF |
420 | } |
421 | EXPORT_SYMBOL_GPL(power_supply_powers); | |
422 | ||
5f487cd3 AV |
423 | static void power_supply_dev_release(struct device *dev) |
424 | { | |
297d716f | 425 | struct power_supply *psy = container_of(dev, struct power_supply, dev); |
5f487cd3 | 426 | pr_debug("device: '%s': %s\n", dev_name(dev), __func__); |
297d716f | 427 | kfree(psy); |
5f487cd3 AV |
428 | } |
429 | ||
d36240d2 PR |
430 | int power_supply_reg_notifier(struct notifier_block *nb) |
431 | { | |
432 | return atomic_notifier_chain_register(&power_supply_notifier, nb); | |
433 | } | |
434 | EXPORT_SYMBOL_GPL(power_supply_reg_notifier); | |
435 | ||
436 | void power_supply_unreg_notifier(struct notifier_block *nb) | |
437 | { | |
438 | atomic_notifier_chain_unregister(&power_supply_notifier, nb); | |
439 | } | |
440 | EXPORT_SYMBOL_GPL(power_supply_unreg_notifier); | |
441 | ||
3be330bf JT |
442 | #ifdef CONFIG_THERMAL |
443 | static int power_supply_read_temp(struct thermal_zone_device *tzd, | |
444 | unsigned long *temp) | |
445 | { | |
446 | struct power_supply *psy; | |
447 | union power_supply_propval val; | |
448 | int ret; | |
449 | ||
450 | WARN_ON(tzd == NULL); | |
451 | psy = tzd->devdata; | |
297d716f | 452 | ret = psy->desc->get_property(psy, POWER_SUPPLY_PROP_TEMP, &val); |
3be330bf JT |
453 | |
454 | /* Convert tenths of degree Celsius to milli degree Celsius. */ | |
455 | if (!ret) | |
456 | *temp = val.intval * 100; | |
457 | ||
458 | return ret; | |
459 | } | |
460 | ||
461 | static struct thermal_zone_device_ops psy_tzd_ops = { | |
462 | .get_temp = power_supply_read_temp, | |
463 | }; | |
464 | ||
465 | static int psy_register_thermal(struct power_supply *psy) | |
466 | { | |
467 | int i; | |
468 | ||
297d716f | 469 | if (psy->desc->no_thermal) |
a69d82b9 KK |
470 | return 0; |
471 | ||
3be330bf | 472 | /* Register battery zone device psy reports temperature */ |
297d716f KK |
473 | for (i = 0; i < psy->desc->num_properties; i++) { |
474 | if (psy->desc->properties[i] == POWER_SUPPLY_PROP_TEMP) { | |
475 | psy->tzd = thermal_zone_device_register(psy->desc->name, | |
476 | 0, 0, psy, &psy_tzd_ops, NULL, 0, 0); | |
9d2410c7 | 477 | return PTR_ERR_OR_ZERO(psy->tzd); |
3be330bf JT |
478 | } |
479 | } | |
480 | return 0; | |
481 | } | |
482 | ||
483 | static void psy_unregister_thermal(struct power_supply *psy) | |
484 | { | |
485 | if (IS_ERR_OR_NULL(psy->tzd)) | |
486 | return; | |
487 | thermal_zone_device_unregister(psy->tzd); | |
488 | } | |
952aeeb3 RP |
489 | |
490 | /* thermal cooling device callbacks */ | |
491 | static int ps_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd, | |
492 | unsigned long *state) | |
493 | { | |
494 | struct power_supply *psy; | |
495 | union power_supply_propval val; | |
496 | int ret; | |
497 | ||
498 | psy = tcd->devdata; | |
297d716f | 499 | ret = psy->desc->get_property(psy, |
952aeeb3 RP |
500 | POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, &val); |
501 | if (!ret) | |
502 | *state = val.intval; | |
503 | ||
504 | return ret; | |
505 | } | |
506 | ||
507 | static int ps_get_cur_chrage_cntl_limit(struct thermal_cooling_device *tcd, | |
508 | unsigned long *state) | |
509 | { | |
510 | struct power_supply *psy; | |
511 | union power_supply_propval val; | |
512 | int ret; | |
513 | ||
514 | psy = tcd->devdata; | |
297d716f | 515 | ret = psy->desc->get_property(psy, |
952aeeb3 RP |
516 | POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val); |
517 | if (!ret) | |
518 | *state = val.intval; | |
519 | ||
520 | return ret; | |
521 | } | |
522 | ||
523 | static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, | |
524 | unsigned long state) | |
525 | { | |
526 | struct power_supply *psy; | |
527 | union power_supply_propval val; | |
528 | int ret; | |
529 | ||
530 | psy = tcd->devdata; | |
531 | val.intval = state; | |
297d716f | 532 | ret = psy->desc->set_property(psy, |
952aeeb3 RP |
533 | POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val); |
534 | ||
535 | return ret; | |
536 | } | |
537 | ||
538 | static struct thermal_cooling_device_ops psy_tcd_ops = { | |
539 | .get_max_state = ps_get_max_charge_cntl_limit, | |
540 | .get_cur_state = ps_get_cur_chrage_cntl_limit, | |
541 | .set_cur_state = ps_set_cur_charge_cntl_limit, | |
542 | }; | |
543 | ||
544 | static int psy_register_cooler(struct power_supply *psy) | |
545 | { | |
546 | int i; | |
547 | ||
548 | /* Register for cooling device if psy can control charging */ | |
297d716f KK |
549 | for (i = 0; i < psy->desc->num_properties; i++) { |
550 | if (psy->desc->properties[i] == | |
952aeeb3 RP |
551 | POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT) { |
552 | psy->tcd = thermal_cooling_device_register( | |
297d716f | 553 | (char *)psy->desc->name, |
952aeeb3 | 554 | psy, &psy_tcd_ops); |
9d2410c7 | 555 | return PTR_ERR_OR_ZERO(psy->tcd); |
952aeeb3 RP |
556 | } |
557 | } | |
558 | return 0; | |
559 | } | |
560 | ||
561 | static void psy_unregister_cooler(struct power_supply *psy) | |
562 | { | |
563 | if (IS_ERR_OR_NULL(psy->tcd)) | |
564 | return; | |
565 | thermal_cooling_device_unregister(psy->tcd); | |
566 | } | |
3be330bf JT |
567 | #else |
568 | static int psy_register_thermal(struct power_supply *psy) | |
569 | { | |
570 | return 0; | |
571 | } | |
572 | ||
573 | static void psy_unregister_thermal(struct power_supply *psy) | |
574 | { | |
575 | } | |
952aeeb3 RP |
576 | |
577 | static int psy_register_cooler(struct power_supply *psy) | |
578 | { | |
579 | return 0; | |
580 | } | |
581 | ||
582 | static void psy_unregister_cooler(struct power_supply *psy) | |
583 | { | |
584 | } | |
3be330bf JT |
585 | #endif |
586 | ||
297d716f KK |
587 | static struct power_supply *__must_check |
588 | __power_supply_register(struct device *parent, | |
589 | const struct power_supply_desc *desc, | |
2dc9215d KK |
590 | const struct power_supply_config *cfg, |
591 | bool ws) | |
4a11b59d | 592 | { |
5f487cd3 | 593 | struct device *dev; |
297d716f | 594 | struct power_supply *psy; |
5f487cd3 | 595 | int rc; |
4a11b59d | 596 | |
297d716f KK |
597 | psy = kzalloc(sizeof(*psy), GFP_KERNEL); |
598 | if (!psy) | |
599 | return ERR_PTR(-ENOMEM); | |
600 | ||
601 | dev = &psy->dev; | |
4a11b59d | 602 | |
5f487cd3 | 603 | device_initialize(dev); |
4a11b59d | 604 | |
5f487cd3 AV |
605 | dev->class = power_supply_class; |
606 | dev->type = &power_supply_dev_type; | |
607 | dev->parent = parent; | |
608 | dev->release = power_supply_dev_release; | |
609 | dev_set_drvdata(dev, psy); | |
297d716f | 610 | psy->desc = desc; |
bc154056 | 611 | atomic_inc(&psy->use_cnt); |
2dc9215d KK |
612 | if (cfg) { |
613 | psy->drv_data = cfg->drv_data; | |
614 | psy->of_node = cfg->of_node; | |
615 | psy->supplied_to = cfg->supplied_to; | |
616 | psy->num_supplicants = cfg->num_supplicants; | |
617 | } | |
5f487cd3 | 618 | |
297d716f | 619 | rc = dev_set_name(dev, "%s", desc->name); |
80c6463e SK |
620 | if (rc) |
621 | goto dev_set_name_failed; | |
622 | ||
97774672 LPC |
623 | INIT_WORK(&psy->changed_work, power_supply_changed_work); |
624 | ||
f6e0b081 RK |
625 | rc = power_supply_check_supplies(psy); |
626 | if (rc) { | |
627 | dev_info(dev, "Not all required supplies found, defer probe\n"); | |
628 | goto check_supplies_failed; | |
629 | } | |
630 | ||
948dcf96 | 631 | spin_lock_init(&psy->changed_lock); |
9113e260 | 632 | rc = device_init_wakeup(dev, ws); |
948dcf96 ZM |
633 | if (rc) |
634 | goto wakeup_init_failed; | |
635 | ||
5f487cd3 | 636 | rc = device_add(dev); |
4a11b59d | 637 | if (rc) |
5f487cd3 AV |
638 | goto device_add_failed; |
639 | ||
3be330bf JT |
640 | rc = psy_register_thermal(psy); |
641 | if (rc) | |
642 | goto register_thermal_failed; | |
643 | ||
952aeeb3 RP |
644 | rc = psy_register_cooler(psy); |
645 | if (rc) | |
646 | goto register_cooler_failed; | |
647 | ||
4a11b59d AV |
648 | rc = power_supply_create_triggers(psy); |
649 | if (rc) | |
650 | goto create_triggers_failed; | |
651 | ||
652 | power_supply_changed(psy); | |
653 | ||
297d716f | 654 | return psy; |
4a11b59d AV |
655 | |
656 | create_triggers_failed: | |
952aeeb3 RP |
657 | psy_unregister_cooler(psy); |
658 | register_cooler_failed: | |
3be330bf JT |
659 | psy_unregister_thermal(psy); |
660 | register_thermal_failed: | |
3a2dbd61 | 661 | device_del(dev); |
5f487cd3 | 662 | device_add_failed: |
80c6463e | 663 | wakeup_init_failed: |
f6e0b081 | 664 | check_supplies_failed: |
80c6463e | 665 | dev_set_name_failed: |
3a2dbd61 | 666 | put_device(dev); |
297d716f | 667 | return ERR_PTR(rc); |
4a11b59d | 668 | } |
9113e260 | 669 | |
297d716f KK |
670 | /** |
671 | * power_supply_register() - Register new power supply | |
672 | * @parent: Device to be a parent of power supply's device | |
673 | * @desc: Description of power supply, must be valid through whole | |
674 | * lifetime of this power supply | |
675 | * @cfg: Run-time specific configuration accessed during registering, | |
676 | * may be NULL | |
677 | * | |
678 | * Return: A pointer to newly allocated power_supply on success | |
679 | * or ERR_PTR otherwise. | |
680 | * Use power_supply_unregister() on returned power_supply pointer to release | |
681 | * resources. | |
682 | */ | |
683 | struct power_supply *__must_check power_supply_register(struct device *parent, | |
684 | const struct power_supply_desc *desc, | |
2dc9215d | 685 | const struct power_supply_config *cfg) |
9113e260 | 686 | { |
297d716f | 687 | return __power_supply_register(parent, desc, cfg, true); |
9113e260 | 688 | } |
ff3417e7 | 689 | EXPORT_SYMBOL_GPL(power_supply_register); |
4a11b59d | 690 | |
297d716f KK |
691 | /** |
692 | * power_supply_register() - Register new non-waking-source power supply | |
693 | * @parent: Device to be a parent of power supply's device | |
694 | * @desc: Description of power supply, must be valid through whole | |
695 | * lifetime of this power supply | |
696 | * @cfg: Run-time specific configuration accessed during registering, | |
697 | * may be NULL | |
698 | * | |
699 | * Return: A pointer to newly allocated power_supply on success | |
700 | * or ERR_PTR otherwise. | |
701 | * Use power_supply_unregister() on returned power_supply pointer to release | |
702 | * resources. | |
703 | */ | |
704 | struct power_supply *__must_check | |
705 | power_supply_register_no_ws(struct device *parent, | |
706 | const struct power_supply_desc *desc, | |
2dc9215d | 707 | const struct power_supply_config *cfg) |
9113e260 | 708 | { |
297d716f | 709 | return __power_supply_register(parent, desc, cfg, false); |
9113e260 ZR |
710 | } |
711 | EXPORT_SYMBOL_GPL(power_supply_register_no_ws); | |
712 | ||
5d8a4219 N |
713 | static void devm_power_supply_release(struct device *dev, void *res) |
714 | { | |
715 | struct power_supply **psy = res; | |
716 | ||
717 | power_supply_unregister(*psy); | |
718 | } | |
719 | ||
297d716f KK |
720 | /** |
721 | * power_supply_register() - Register managed power supply | |
722 | * @parent: Device to be a parent of power supply's device | |
723 | * @desc: Description of power supply, must be valid through whole | |
724 | * lifetime of this power supply | |
725 | * @cfg: Run-time specific configuration accessed during registering, | |
726 | * may be NULL | |
727 | * | |
728 | * Return: A pointer to newly allocated power_supply on success | |
729 | * or ERR_PTR otherwise. | |
730 | * The returned power_supply pointer will be automatically unregistered | |
731 | * on driver detach. | |
732 | */ | |
733 | struct power_supply *__must_check | |
734 | devm_power_supply_register(struct device *parent, | |
735 | const struct power_supply_desc *desc, | |
2dc9215d | 736 | const struct power_supply_config *cfg) |
5d8a4219 | 737 | { |
297d716f KK |
738 | struct power_supply **ptr, *psy; |
739 | ||
740 | ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL); | |
5d8a4219 N |
741 | |
742 | if (!ptr) | |
297d716f KK |
743 | return ERR_PTR(-ENOMEM); |
744 | psy = __power_supply_register(parent, desc, cfg, true); | |
745 | if (IS_ERR(psy)) { | |
5d8a4219 | 746 | devres_free(ptr); |
297d716f | 747 | } else { |
5d8a4219 N |
748 | *ptr = psy; |
749 | devres_add(parent, ptr); | |
750 | } | |
297d716f | 751 | return psy; |
5d8a4219 N |
752 | } |
753 | EXPORT_SYMBOL_GPL(devm_power_supply_register); | |
754 | ||
297d716f KK |
755 | /** |
756 | * power_supply_register() - Register managed non-waking-source power supply | |
757 | * @parent: Device to be a parent of power supply's device | |
758 | * @desc: Description of power supply, must be valid through whole | |
759 | * lifetime of this power supply | |
760 | * @cfg: Run-time specific configuration accessed during registering, | |
761 | * may be NULL | |
762 | * | |
763 | * Return: A pointer to newly allocated power_supply on success | |
764 | * or ERR_PTR otherwise. | |
765 | * The returned power_supply pointer will be automatically unregistered | |
766 | * on driver detach. | |
767 | */ | |
768 | struct power_supply *__must_check | |
769 | devm_power_supply_register_no_ws(struct device *parent, | |
770 | const struct power_supply_desc *desc, | |
2dc9215d | 771 | const struct power_supply_config *cfg) |
5d8a4219 | 772 | { |
297d716f KK |
773 | struct power_supply **ptr, *psy; |
774 | ||
775 | ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL); | |
5d8a4219 N |
776 | |
777 | if (!ptr) | |
297d716f KK |
778 | return ERR_PTR(-ENOMEM); |
779 | psy = __power_supply_register(parent, desc, cfg, false); | |
780 | if (IS_ERR(psy)) { | |
5d8a4219 | 781 | devres_free(ptr); |
297d716f | 782 | } else { |
5d8a4219 N |
783 | *ptr = psy; |
784 | devres_add(parent, ptr); | |
785 | } | |
297d716f | 786 | return psy; |
5d8a4219 N |
787 | } |
788 | EXPORT_SYMBOL_GPL(devm_power_supply_register_no_ws); | |
789 | ||
297d716f KK |
790 | /** |
791 | * power_supply_unregister() - Remove this power supply from system | |
792 | * @psy: Pointer to power supply to unregister | |
793 | * | |
794 | * Remove this power supply from the system. The resources of power supply | |
795 | * will be freed here or on last power_supply_put() call. | |
796 | */ | |
4a11b59d AV |
797 | void power_supply_unregister(struct power_supply *psy) |
798 | { | |
bc154056 | 799 | WARN_ON(atomic_dec_return(&psy->use_cnt)); |
bc51e7ff | 800 | cancel_work_sync(&psy->changed_work); |
297d716f | 801 | sysfs_remove_link(&psy->dev.kobj, "powers"); |
4a11b59d | 802 | power_supply_remove_triggers(psy); |
952aeeb3 | 803 | psy_unregister_cooler(psy); |
3be330bf | 804 | psy_unregister_thermal(psy); |
297d716f KK |
805 | device_init_wakeup(&psy->dev, false); |
806 | device_unregister(&psy->dev); | |
4a11b59d | 807 | } |
ff3417e7 | 808 | EXPORT_SYMBOL_GPL(power_supply_unregister); |
4a11b59d | 809 | |
e44ea364 KK |
810 | void *power_supply_get_drvdata(struct power_supply *psy) |
811 | { | |
812 | return psy->drv_data; | |
813 | } | |
814 | EXPORT_SYMBOL_GPL(power_supply_get_drvdata); | |
815 | ||
4a11b59d AV |
816 | static int __init power_supply_class_init(void) |
817 | { | |
818 | power_supply_class = class_create(THIS_MODULE, "power_supply"); | |
819 | ||
820 | if (IS_ERR(power_supply_class)) | |
821 | return PTR_ERR(power_supply_class); | |
822 | ||
823 | power_supply_class->dev_uevent = power_supply_uevent; | |
5f487cd3 | 824 | power_supply_init_attrs(&power_supply_dev_type); |
4a11b59d AV |
825 | |
826 | return 0; | |
827 | } | |
828 | ||
829 | static void __exit power_supply_class_exit(void) | |
830 | { | |
831 | class_destroy(power_supply_class); | |
4a11b59d AV |
832 | } |
833 | ||
4a11b59d AV |
834 | subsys_initcall(power_supply_class_init); |
835 | module_exit(power_supply_class_exit); | |
836 | ||
837 | MODULE_DESCRIPTION("Universal power supply monitor class"); | |
838 | MODULE_AUTHOR("Ian Molton <spyro@f2s.com>, " | |
839 | "Szabolcs Gyurko, " | |
840 | "Anton Vorontsov <cbou@mail.ru>"); | |
841 | MODULE_LICENSE("GPL"); |