power_supply_core.c revision 50125a9b27dd09e9afdc1b8712ba0b3859886c68
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> 16#include <linux/slab.h> 17#include <linux/device.h> 18#include <linux/err.h> 19#include <linux/power_supply.h> 20#include <linux/thermal.h> 21#include "power_supply.h" 22 23/* exported for the APM Power driver, APM emulation */ 24struct class *power_supply_class; 25EXPORT_SYMBOL_GPL(power_supply_class); 26 27static struct device_type power_supply_dev_type; 28 29static int __power_supply_changed_work(struct device *dev, void *data) 30{ 31 struct power_supply *psy = (struct power_supply *)data; 32 struct power_supply *pst = dev_get_drvdata(dev); 33 int i; 34 35 for (i = 0; i < psy->num_supplicants; i++) 36 if (!strcmp(psy->supplied_to[i], pst->name)) { 37 if (pst->external_power_changed) 38 pst->external_power_changed(pst); 39 } 40 return 0; 41} 42 43static void power_supply_changed_work(struct work_struct *work) 44{ 45 struct power_supply *psy = container_of(work, struct power_supply, 46 changed_work); 47 48 dev_dbg(psy->dev, "%s\n", __func__); 49 50 class_for_each_device(power_supply_class, NULL, psy, 51 __power_supply_changed_work); 52 53 power_supply_update_leds(psy); 54 55 kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); 56} 57 58void power_supply_changed(struct power_supply *psy) 59{ 60 dev_dbg(psy->dev, "%s\n", __func__); 61 62 schedule_work(&psy->changed_work); 63} 64EXPORT_SYMBOL_GPL(power_supply_changed); 65 66static int __power_supply_am_i_supplied(struct device *dev, void *data) 67{ 68 union power_supply_propval ret = {0,}; 69 struct power_supply *psy = (struct power_supply *)data; 70 struct power_supply *epsy = dev_get_drvdata(dev); 71 int i; 72 73 for (i = 0; i < epsy->num_supplicants; i++) { 74 if (!strcmp(epsy->supplied_to[i], psy->name)) { 75 if (epsy->get_property(epsy, 76 POWER_SUPPLY_PROP_ONLINE, &ret)) 77 continue; 78 if (ret.intval) 79 return ret.intval; 80 } 81 } 82 return 0; 83} 84 85int power_supply_am_i_supplied(struct power_supply *psy) 86{ 87 int error; 88 89 error = class_for_each_device(power_supply_class, NULL, psy, 90 __power_supply_am_i_supplied); 91 92 dev_dbg(psy->dev, "%s %d\n", __func__, error); 93 94 return error; 95} 96EXPORT_SYMBOL_GPL(power_supply_am_i_supplied); 97 98static int __power_supply_is_system_supplied(struct device *dev, void *data) 99{ 100 union power_supply_propval ret = {0,}; 101 struct power_supply *psy = dev_get_drvdata(dev); 102 unsigned int *count = data; 103 104 (*count)++; 105 if (psy->type != POWER_SUPPLY_TYPE_BATTERY) { 106 if (psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &ret)) 107 return 0; 108 if (ret.intval) 109 return ret.intval; 110 } 111 return 0; 112} 113 114int power_supply_is_system_supplied(void) 115{ 116 int error; 117 unsigned int count = 0; 118 119 error = class_for_each_device(power_supply_class, NULL, &count, 120 __power_supply_is_system_supplied); 121 122 /* 123 * If no power class device was found at all, most probably we are 124 * running on a desktop system, so assume we are on mains power. 125 */ 126 if (count == 0) 127 return 1; 128 129 return error; 130} 131EXPORT_SYMBOL_GPL(power_supply_is_system_supplied); 132 133int power_supply_set_battery_charged(struct power_supply *psy) 134{ 135 if (psy->type == POWER_SUPPLY_TYPE_BATTERY && psy->set_charged) { 136 psy->set_charged(psy); 137 return 0; 138 } 139 140 return -EINVAL; 141} 142EXPORT_SYMBOL_GPL(power_supply_set_battery_charged); 143 144static int power_supply_match_device_by_name(struct device *dev, void *data) 145{ 146 const char *name = data; 147 struct power_supply *psy = dev_get_drvdata(dev); 148 149 return strcmp(psy->name, name) == 0; 150} 151 152struct power_supply *power_supply_get_by_name(char *name) 153{ 154 struct device *dev = class_find_device(power_supply_class, NULL, name, 155 power_supply_match_device_by_name); 156 157 return dev ? dev_get_drvdata(dev) : NULL; 158} 159EXPORT_SYMBOL_GPL(power_supply_get_by_name); 160 161int power_supply_powers(struct power_supply *psy, struct device *dev) 162{ 163 return sysfs_create_link(&psy->dev->kobj, &dev->kobj, "powers"); 164} 165EXPORT_SYMBOL_GPL(power_supply_powers); 166 167static void power_supply_dev_release(struct device *dev) 168{ 169 pr_debug("device: '%s': %s\n", dev_name(dev), __func__); 170 kfree(dev); 171} 172 173#ifdef CONFIG_THERMAL 174static int power_supply_read_temp(struct thermal_zone_device *tzd, 175 unsigned long *temp) 176{ 177 struct power_supply *psy; 178 union power_supply_propval val; 179 int ret; 180 181 WARN_ON(tzd == NULL); 182 psy = tzd->devdata; 183 ret = psy->get_property(psy, POWER_SUPPLY_PROP_TEMP, &val); 184 185 /* Convert tenths of degree Celsius to milli degree Celsius. */ 186 if (!ret) 187 *temp = val.intval * 100; 188 189 return ret; 190} 191 192static struct thermal_zone_device_ops psy_tzd_ops = { 193 .get_temp = power_supply_read_temp, 194}; 195 196static int psy_register_thermal(struct power_supply *psy) 197{ 198 int i; 199 200 /* Register battery zone device psy reports temperature */ 201 for (i = 0; i < psy->num_properties; i++) { 202 if (psy->properties[i] == POWER_SUPPLY_PROP_TEMP) { 203 psy->tzd = thermal_zone_device_register(psy->name, 0, 0, 204 psy, &psy_tzd_ops, NULL, 0, 0); 205 if (IS_ERR(psy->tzd)) 206 return PTR_ERR(psy->tzd); 207 break; 208 } 209 } 210 return 0; 211} 212 213static void psy_unregister_thermal(struct power_supply *psy) 214{ 215 if (IS_ERR_OR_NULL(psy->tzd)) 216 return; 217 thermal_zone_device_unregister(psy->tzd); 218} 219#else 220static int psy_register_thermal(struct power_supply *psy) 221{ 222 return 0; 223} 224 225static void psy_unregister_thermal(struct power_supply *psy) 226{ 227} 228#endif 229 230int power_supply_register(struct device *parent, struct power_supply *psy) 231{ 232 struct device *dev; 233 int rc; 234 235 dev = kzalloc(sizeof(*dev), GFP_KERNEL); 236 if (!dev) 237 return -ENOMEM; 238 239 device_initialize(dev); 240 241 dev->class = power_supply_class; 242 dev->type = &power_supply_dev_type; 243 dev->parent = parent; 244 dev->release = power_supply_dev_release; 245 dev_set_drvdata(dev, psy); 246 psy->dev = dev; 247 248 INIT_WORK(&psy->changed_work, power_supply_changed_work); 249 250 rc = kobject_set_name(&dev->kobj, "%s", psy->name); 251 if (rc) 252 goto kobject_set_name_failed; 253 254 rc = device_add(dev); 255 if (rc) 256 goto device_add_failed; 257 258 rc = psy_register_thermal(psy); 259 if (rc) 260 goto register_thermal_failed; 261 262 rc = power_supply_create_triggers(psy); 263 if (rc) 264 goto create_triggers_failed; 265 266 power_supply_changed(psy); 267 268 goto success; 269 270create_triggers_failed: 271 psy_unregister_thermal(psy); 272register_thermal_failed: 273 device_del(dev); 274kobject_set_name_failed: 275device_add_failed: 276 put_device(dev); 277success: 278 return rc; 279} 280EXPORT_SYMBOL_GPL(power_supply_register); 281 282void power_supply_unregister(struct power_supply *psy) 283{ 284 cancel_work_sync(&psy->changed_work); 285 sysfs_remove_link(&psy->dev->kobj, "powers"); 286 power_supply_remove_triggers(psy); 287 psy_unregister_thermal(psy); 288 device_unregister(psy->dev); 289} 290EXPORT_SYMBOL_GPL(power_supply_unregister); 291 292static int __init power_supply_class_init(void) 293{ 294 power_supply_class = class_create(THIS_MODULE, "power_supply"); 295 296 if (IS_ERR(power_supply_class)) 297 return PTR_ERR(power_supply_class); 298 299 power_supply_class->dev_uevent = power_supply_uevent; 300 power_supply_init_attrs(&power_supply_dev_type); 301 302 return 0; 303} 304 305static void __exit power_supply_class_exit(void) 306{ 307 class_destroy(power_supply_class); 308} 309 310subsys_initcall(power_supply_class_init); 311module_exit(power_supply_class_exit); 312 313MODULE_DESCRIPTION("Universal power supply monitor class"); 314MODULE_AUTHOR("Ian Molton <spyro@f2s.com>, " 315 "Szabolcs Gyurko, " 316 "Anton Vorontsov <cbou@mail.ru>"); 317MODULE_LICENSE("GPL"); 318