1 /* 2 * nvec_power: power supply driver for a NVIDIA compliant embedded controller 3 * 4 * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net> 5 * 6 * Authors: Ilya Petrov <ilya.muromec@gmail.com> 7 * Marc Dietrich <marvin24@gmx.de> 8 * 9 * This file is subject to the terms and conditions of the GNU General Public 10 * License. See the file "COPYING" in the main directory of this archive 11 * for more details. 12 * 13 */ 14 15 #include <linux/module.h> 16 #include <linux/platform_device.h> 17 #include <linux/err.h> 18 #include <linux/power_supply.h> 19 #include <linux/slab.h> 20 #include <linux/workqueue.h> 21 #include <linux/delay.h> 22 23 #include "nvec.h" 24 25 #define GET_SYSTEM_STATUS 0x00 26 27 struct nvec_power { 28 struct notifier_block notifier; 29 struct delayed_work poller; 30 struct nvec_chip *nvec; 31 int on; 32 int bat_present; 33 int bat_status; 34 int bat_voltage_now; 35 int bat_current_now; 36 int bat_current_avg; 37 int time_remain; 38 int charge_full_design; 39 int charge_last_full; 40 int critical_capacity; 41 int capacity_remain; 42 int bat_temperature; 43 int bat_cap; 44 int bat_type_enum; 45 char bat_manu[30]; 46 char bat_model[30]; 47 char bat_type[30]; 48 }; 49 50 enum { 51 SLOT_STATUS, 52 VOLTAGE, 53 TIME_REMAINING, 54 CURRENT, 55 AVERAGE_CURRENT, 56 AVERAGING_TIME_INTERVAL, 57 CAPACITY_REMAINING, 58 LAST_FULL_CHARGE_CAPACITY, 59 DESIGN_CAPACITY, 60 CRITICAL_CAPACITY, 61 TEMPERATURE, 62 MANUFACTURER, 63 MODEL, 64 TYPE, 65 }; 66 67 enum { 68 AC, 69 BAT, 70 }; 71 72 struct bat_response { 73 u8 event_type; 74 u8 length; 75 u8 sub_type; 76 u8 status; 77 /* payload */ 78 union { 79 char plc[30]; 80 u16 plu; 81 s16 pls; 82 }; 83 }; 84 85 static struct power_supply *nvec_bat_psy; 86 static struct power_supply *nvec_psy; 87 88 static int nvec_power_notifier(struct notifier_block *nb, 89 unsigned long event_type, void *data) 90 { 91 struct nvec_power *power = 92 container_of(nb, struct nvec_power, notifier); 93 struct bat_response *res = data; 94 95 if (event_type != NVEC_SYS) 96 return NOTIFY_DONE; 97 98 if (res->sub_type == 0) { 99 if (power->on != res->plu) { 100 power->on = res->plu; 101 power_supply_changed(nvec_psy); 102 } 103 return NOTIFY_STOP; 104 } 105 return NOTIFY_OK; 106 } 107 108 static const int bat_init[] = { 109 LAST_FULL_CHARGE_CAPACITY, DESIGN_CAPACITY, CRITICAL_CAPACITY, 110 MANUFACTURER, MODEL, TYPE, 111 }; 112 113 static void get_bat_mfg_data(struct nvec_power *power) 114 { 115 int i; 116 char buf[] = { NVEC_BAT, SLOT_STATUS }; 117 118 for (i = 0; i < ARRAY_SIZE(bat_init); i++) { 119 buf[1] = bat_init[i]; 120 nvec_write_async(power->nvec, buf, 2); 121 } 122 } 123 124 static int nvec_power_bat_notifier(struct notifier_block *nb, 125 unsigned long event_type, void *data) 126 { 127 struct nvec_power *power = 128 container_of(nb, struct nvec_power, notifier); 129 struct bat_response *res = data; 130 int status_changed = 0; 131 132 if (event_type != NVEC_BAT) 133 return NOTIFY_DONE; 134 135 switch (res->sub_type) { 136 case SLOT_STATUS: 137 if (res->plc[0] & 1) { 138 if (power->bat_present == 0) { 139 status_changed = 1; 140 get_bat_mfg_data(power); 141 } 142 143 power->bat_present = 1; 144 145 switch ((res->plc[0] >> 1) & 3) { 146 case 0: 147 power->bat_status = 148 POWER_SUPPLY_STATUS_NOT_CHARGING; 149 break; 150 case 1: 151 power->bat_status = 152 POWER_SUPPLY_STATUS_CHARGING; 153 break; 154 case 2: 155 power->bat_status = 156 POWER_SUPPLY_STATUS_DISCHARGING; 157 break; 158 default: 159 power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; 160 } 161 } else { 162 if (power->bat_present == 1) 163 status_changed = 1; 164 165 power->bat_present = 0; 166 power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; 167 } 168 power->bat_cap = res->plc[1]; 169 if (status_changed) 170 power_supply_changed(nvec_bat_psy); 171 break; 172 case VOLTAGE: 173 power->bat_voltage_now = res->plu * 1000; 174 break; 175 case TIME_REMAINING: 176 power->time_remain = res->plu * 3600; 177 break; 178 case CURRENT: 179 power->bat_current_now = res->pls * 1000; 180 break; 181 case AVERAGE_CURRENT: 182 power->bat_current_avg = res->pls * 1000; 183 break; 184 case CAPACITY_REMAINING: 185 power->capacity_remain = res->plu * 1000; 186 break; 187 case LAST_FULL_CHARGE_CAPACITY: 188 power->charge_last_full = res->plu * 1000; 189 break; 190 case DESIGN_CAPACITY: 191 power->charge_full_design = res->plu * 1000; 192 break; 193 case CRITICAL_CAPACITY: 194 power->critical_capacity = res->plu * 1000; 195 break; 196 case TEMPERATURE: 197 power->bat_temperature = res->plu - 2732; 198 break; 199 case MANUFACTURER: 200 memcpy(power->bat_manu, &res->plc, res->length - 2); 201 power->bat_model[res->length - 2] = '\0'; 202 break; 203 case MODEL: 204 memcpy(power->bat_model, &res->plc, res->length - 2); 205 power->bat_model[res->length - 2] = '\0'; 206 break; 207 case TYPE: 208 memcpy(power->bat_type, &res->plc, res->length - 2); 209 power->bat_type[res->length - 2] = '\0'; 210 /* 211 * This differs a little from the spec fill in more if you find 212 * some. 213 */ 214 if (!strncmp(power->bat_type, "Li", 30)) 215 power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_LION; 216 else 217 power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; 218 break; 219 default: 220 return NOTIFY_STOP; 221 } 222 223 return NOTIFY_STOP; 224 } 225 226 static int nvec_power_get_property(struct power_supply *psy, 227 enum power_supply_property psp, 228 union power_supply_propval *val) 229 { 230 struct nvec_power *power = dev_get_drvdata(psy->dev.parent); 231 232 switch (psp) { 233 case POWER_SUPPLY_PROP_ONLINE: 234 val->intval = power->on; 235 break; 236 default: 237 return -EINVAL; 238 } 239 return 0; 240 } 241 242 static int nvec_battery_get_property(struct power_supply *psy, 243 enum power_supply_property psp, 244 union power_supply_propval *val) 245 { 246 struct nvec_power *power = dev_get_drvdata(psy->dev.parent); 247 248 switch (psp) { 249 case POWER_SUPPLY_PROP_STATUS: 250 val->intval = power->bat_status; 251 break; 252 case POWER_SUPPLY_PROP_CAPACITY: 253 val->intval = power->bat_cap; 254 break; 255 case POWER_SUPPLY_PROP_PRESENT: 256 val->intval = power->bat_present; 257 break; 258 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 259 val->intval = power->bat_voltage_now; 260 break; 261 case POWER_SUPPLY_PROP_CURRENT_NOW: 262 val->intval = power->bat_current_now; 263 break; 264 case POWER_SUPPLY_PROP_CURRENT_AVG: 265 val->intval = power->bat_current_avg; 266 break; 267 case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: 268 val->intval = power->time_remain; 269 break; 270 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: 271 val->intval = power->charge_full_design; 272 break; 273 case POWER_SUPPLY_PROP_CHARGE_FULL: 274 val->intval = power->charge_last_full; 275 break; 276 case POWER_SUPPLY_PROP_CHARGE_EMPTY: 277 val->intval = power->critical_capacity; 278 break; 279 case POWER_SUPPLY_PROP_CHARGE_NOW: 280 val->intval = power->capacity_remain; 281 break; 282 case POWER_SUPPLY_PROP_TEMP: 283 val->intval = power->bat_temperature; 284 break; 285 case POWER_SUPPLY_PROP_MANUFACTURER: 286 val->strval = power->bat_manu; 287 break; 288 case POWER_SUPPLY_PROP_MODEL_NAME: 289 val->strval = power->bat_model; 290 break; 291 case POWER_SUPPLY_PROP_TECHNOLOGY: 292 val->intval = power->bat_type_enum; 293 break; 294 default: 295 return -EINVAL; 296 } 297 return 0; 298 } 299 300 static enum power_supply_property nvec_power_props[] = { 301 POWER_SUPPLY_PROP_ONLINE, 302 }; 303 304 static enum power_supply_property nvec_battery_props[] = { 305 POWER_SUPPLY_PROP_STATUS, 306 POWER_SUPPLY_PROP_PRESENT, 307 POWER_SUPPLY_PROP_CAPACITY, 308 POWER_SUPPLY_PROP_VOLTAGE_NOW, 309 POWER_SUPPLY_PROP_CURRENT_NOW, 310 #ifdef EC_FULL_DIAG 311 POWER_SUPPLY_PROP_CURRENT_AVG, 312 POWER_SUPPLY_PROP_TEMP, 313 POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, 314 #endif 315 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 316 POWER_SUPPLY_PROP_CHARGE_FULL, 317 POWER_SUPPLY_PROP_CHARGE_EMPTY, 318 POWER_SUPPLY_PROP_CHARGE_NOW, 319 POWER_SUPPLY_PROP_MANUFACTURER, 320 POWER_SUPPLY_PROP_MODEL_NAME, 321 POWER_SUPPLY_PROP_TECHNOLOGY, 322 }; 323 324 static char *nvec_power_supplied_to[] = { 325 "battery", 326 }; 327 328 static const struct power_supply_desc nvec_bat_psy_desc = { 329 .name = "battery", 330 .type = POWER_SUPPLY_TYPE_BATTERY, 331 .properties = nvec_battery_props, 332 .num_properties = ARRAY_SIZE(nvec_battery_props), 333 .get_property = nvec_battery_get_property, 334 }; 335 336 static const struct power_supply_desc nvec_psy_desc = { 337 .name = "ac", 338 .type = POWER_SUPPLY_TYPE_MAINS, 339 .properties = nvec_power_props, 340 .num_properties = ARRAY_SIZE(nvec_power_props), 341 .get_property = nvec_power_get_property, 342 }; 343 344 static int counter; 345 static int const bat_iter[] = { 346 SLOT_STATUS, VOLTAGE, CURRENT, CAPACITY_REMAINING, 347 #ifdef EC_FULL_DIAG 348 AVERAGE_CURRENT, TEMPERATURE, TIME_REMAINING, 349 #endif 350 }; 351 352 static void nvec_power_poll(struct work_struct *work) 353 { 354 char buf[] = { NVEC_SYS, GET_SYSTEM_STATUS }; 355 struct nvec_power *power = container_of(work, struct nvec_power, 356 poller.work); 357 358 if (counter >= ARRAY_SIZE(bat_iter)) 359 counter = 0; 360 361 /* AC status via sys req */ 362 nvec_write_async(power->nvec, buf, 2); 363 msleep(100); 364 365 /* 366 * Select a battery request function via round robin doing it all at 367 * once seems to overload the power supply. 368 */ 369 buf[0] = NVEC_BAT; 370 buf[1] = bat_iter[counter++]; 371 nvec_write_async(power->nvec, buf, 2); 372 373 schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(5000)); 374 }; 375 376 static int nvec_power_probe(struct platform_device *pdev) 377 { 378 struct power_supply **psy; 379 const struct power_supply_desc *psy_desc; 380 struct nvec_power *power; 381 struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); 382 struct power_supply_config psy_cfg = {}; 383 384 power = devm_kzalloc(&pdev->dev, sizeof(struct nvec_power), GFP_NOWAIT); 385 if (!power) 386 return -ENOMEM; 387 388 dev_set_drvdata(&pdev->dev, power); 389 power->nvec = nvec; 390 391 switch (pdev->id) { 392 case AC: 393 psy = &nvec_psy; 394 psy_desc = &nvec_psy_desc; 395 psy_cfg.supplied_to = nvec_power_supplied_to; 396 psy_cfg.num_supplicants = ARRAY_SIZE(nvec_power_supplied_to); 397 398 power->notifier.notifier_call = nvec_power_notifier; 399 400 INIT_DELAYED_WORK(&power->poller, nvec_power_poll); 401 schedule_delayed_work(&power->poller, msecs_to_jiffies(5000)); 402 break; 403 case BAT: 404 psy = &nvec_bat_psy; 405 psy_desc = &nvec_bat_psy_desc; 406 407 power->notifier.notifier_call = nvec_power_bat_notifier; 408 break; 409 default: 410 return -ENODEV; 411 } 412 413 nvec_register_notifier(nvec, &power->notifier, NVEC_SYS); 414 415 if (pdev->id == BAT) 416 get_bat_mfg_data(power); 417 418 *psy = power_supply_register(&pdev->dev, psy_desc, &psy_cfg); 419 420 return PTR_ERR_OR_ZERO(*psy); 421 } 422 423 static int nvec_power_remove(struct platform_device *pdev) 424 { 425 struct nvec_power *power = platform_get_drvdata(pdev); 426 427 cancel_delayed_work_sync(&power->poller); 428 nvec_unregister_notifier(power->nvec, &power->notifier); 429 switch (pdev->id) { 430 case AC: 431 power_supply_unregister(nvec_psy); 432 break; 433 case BAT: 434 power_supply_unregister(nvec_bat_psy); 435 } 436 437 return 0; 438 } 439 440 static struct platform_driver nvec_power_driver = { 441 .probe = nvec_power_probe, 442 .remove = nvec_power_remove, 443 .driver = { 444 .name = "nvec-power", 445 } 446 }; 447 448 module_platform_driver(nvec_power_driver); 449 450 MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>"); 451 MODULE_LICENSE("GPL"); 452 MODULE_DESCRIPTION("NVEC battery and AC driver"); 453 MODULE_ALIAS("platform:nvec-power"); 454