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