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 27struct 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 50enum { 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 67enum { 68 AC, 69 BAT, 70}; 71 72struct 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 85static struct power_supply *nvec_bat_psy; 86static struct power_supply *nvec_psy; 87 88static 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 = (struct bat_response *)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 108static const int bat_init[] = { 109 LAST_FULL_CHARGE_CAPACITY, DESIGN_CAPACITY, CRITICAL_CAPACITY, 110 MANUFACTURER, MODEL, TYPE, 111}; 112 113static 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 124static 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 = (struct bat_response *)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 /* this differs a little from the spec 211 fill in more if you find some */ 212 if (!strncmp(power->bat_type, "Li", 30)) 213 power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_LION; 214 else 215 power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; 216 break; 217 default: 218 return NOTIFY_STOP; 219 } 220 221 return NOTIFY_STOP; 222} 223 224static int nvec_power_get_property(struct power_supply *psy, 225 enum power_supply_property psp, 226 union power_supply_propval *val) 227{ 228 struct nvec_power *power = dev_get_drvdata(psy->dev.parent); 229 230 switch (psp) { 231 case POWER_SUPPLY_PROP_ONLINE: 232 val->intval = power->on; 233 break; 234 default: 235 return -EINVAL; 236 } 237 return 0; 238} 239 240static int nvec_battery_get_property(struct power_supply *psy, 241 enum power_supply_property psp, 242 union power_supply_propval *val) 243{ 244 struct nvec_power *power = dev_get_drvdata(psy->dev.parent); 245 246 switch (psp) { 247 case POWER_SUPPLY_PROP_STATUS: 248 val->intval = power->bat_status; 249 break; 250 case POWER_SUPPLY_PROP_CAPACITY: 251 val->intval = power->bat_cap; 252 break; 253 case POWER_SUPPLY_PROP_PRESENT: 254 val->intval = power->bat_present; 255 break; 256 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 257 val->intval = power->bat_voltage_now; 258 break; 259 case POWER_SUPPLY_PROP_CURRENT_NOW: 260 val->intval = power->bat_current_now; 261 break; 262 case POWER_SUPPLY_PROP_CURRENT_AVG: 263 val->intval = power->bat_current_avg; 264 break; 265 case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: 266 val->intval = power->time_remain; 267 break; 268 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: 269 val->intval = power->charge_full_design; 270 break; 271 case POWER_SUPPLY_PROP_CHARGE_FULL: 272 val->intval = power->charge_last_full; 273 break; 274 case POWER_SUPPLY_PROP_CHARGE_EMPTY: 275 val->intval = power->critical_capacity; 276 break; 277 case POWER_SUPPLY_PROP_CHARGE_NOW: 278 val->intval = power->capacity_remain; 279 break; 280 case POWER_SUPPLY_PROP_TEMP: 281 val->intval = power->bat_temperature; 282 break; 283 case POWER_SUPPLY_PROP_MANUFACTURER: 284 val->strval = power->bat_manu; 285 break; 286 case POWER_SUPPLY_PROP_MODEL_NAME: 287 val->strval = power->bat_model; 288 break; 289 case POWER_SUPPLY_PROP_TECHNOLOGY: 290 val->intval = power->bat_type_enum; 291 break; 292 default: 293 return -EINVAL; 294 } 295 return 0; 296} 297 298static enum power_supply_property nvec_power_props[] = { 299 POWER_SUPPLY_PROP_ONLINE, 300}; 301 302static enum power_supply_property nvec_battery_props[] = { 303 POWER_SUPPLY_PROP_STATUS, 304 POWER_SUPPLY_PROP_PRESENT, 305 POWER_SUPPLY_PROP_CAPACITY, 306 POWER_SUPPLY_PROP_VOLTAGE_NOW, 307 POWER_SUPPLY_PROP_CURRENT_NOW, 308#ifdef EC_FULL_DIAG 309 POWER_SUPPLY_PROP_CURRENT_AVG, 310 POWER_SUPPLY_PROP_TEMP, 311 POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, 312#endif 313 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 314 POWER_SUPPLY_PROP_CHARGE_FULL, 315 POWER_SUPPLY_PROP_CHARGE_EMPTY, 316 POWER_SUPPLY_PROP_CHARGE_NOW, 317 POWER_SUPPLY_PROP_MANUFACTURER, 318 POWER_SUPPLY_PROP_MODEL_NAME, 319 POWER_SUPPLY_PROP_TECHNOLOGY, 320}; 321 322static char *nvec_power_supplied_to[] = { 323 "battery", 324}; 325 326static const struct power_supply_desc nvec_bat_psy_desc = { 327 .name = "battery", 328 .type = POWER_SUPPLY_TYPE_BATTERY, 329 .properties = nvec_battery_props, 330 .num_properties = ARRAY_SIZE(nvec_battery_props), 331 .get_property = nvec_battery_get_property, 332}; 333 334static const struct power_supply_desc nvec_psy_desc = { 335 .name = "ac", 336 .type = POWER_SUPPLY_TYPE_MAINS, 337 .properties = nvec_power_props, 338 .num_properties = ARRAY_SIZE(nvec_power_props), 339 .get_property = nvec_power_get_property, 340}; 341 342static int counter; 343static int const bat_iter[] = { 344 SLOT_STATUS, VOLTAGE, CURRENT, CAPACITY_REMAINING, 345#ifdef EC_FULL_DIAG 346 AVERAGE_CURRENT, TEMPERATURE, TIME_REMAINING, 347#endif 348}; 349 350static void nvec_power_poll(struct work_struct *work) 351{ 352 char buf[] = { NVEC_SYS, GET_SYSTEM_STATUS }; 353 struct nvec_power *power = container_of(work, struct nvec_power, 354 poller.work); 355 356 if (counter >= ARRAY_SIZE(bat_iter)) 357 counter = 0; 358 359/* AC status via sys req */ 360 nvec_write_async(power->nvec, buf, 2); 361 msleep(100); 362 363/* select a battery request function via round robin 364 doing it all at once seems to overload the power supply */ 365 buf[0] = NVEC_BAT; 366 buf[1] = bat_iter[counter++]; 367 nvec_write_async(power->nvec, buf, 2); 368 369 schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(5000)); 370}; 371 372static int nvec_power_probe(struct platform_device *pdev) 373{ 374 struct power_supply **psy; 375 const struct power_supply_desc *psy_desc; 376 struct nvec_power *power; 377 struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); 378 struct power_supply_config psy_cfg = {}; 379 380 power = devm_kzalloc(&pdev->dev, sizeof(struct nvec_power), GFP_NOWAIT); 381 if (!power) 382 return -ENOMEM; 383 384 dev_set_drvdata(&pdev->dev, power); 385 power->nvec = nvec; 386 387 switch (pdev->id) { 388 case AC: 389 psy = &nvec_psy; 390 psy_desc = &nvec_psy_desc; 391 psy_cfg.supplied_to = nvec_power_supplied_to; 392 psy_cfg.num_supplicants = ARRAY_SIZE(nvec_power_supplied_to); 393 394 power->notifier.notifier_call = nvec_power_notifier; 395 396 INIT_DELAYED_WORK(&power->poller, nvec_power_poll); 397 schedule_delayed_work(&power->poller, msecs_to_jiffies(5000)); 398 break; 399 case BAT: 400 psy = &nvec_bat_psy; 401 psy_desc = &nvec_bat_psy_desc; 402 403 power->notifier.notifier_call = nvec_power_bat_notifier; 404 break; 405 default: 406 return -ENODEV; 407 } 408 409 nvec_register_notifier(nvec, &power->notifier, NVEC_SYS); 410 411 if (pdev->id == BAT) 412 get_bat_mfg_data(power); 413 414 *psy = power_supply_register(&pdev->dev, psy_desc, &psy_cfg); 415 416 return PTR_ERR_OR_ZERO(*psy); 417} 418 419static int nvec_power_remove(struct platform_device *pdev) 420{ 421 struct nvec_power *power = platform_get_drvdata(pdev); 422 423 cancel_delayed_work_sync(&power->poller); 424 nvec_unregister_notifier(power->nvec, &power->notifier); 425 switch (pdev->id) { 426 case AC: 427 power_supply_unregister(nvec_psy); 428 break; 429 case BAT: 430 power_supply_unregister(nvec_bat_psy); 431 } 432 433 return 0; 434} 435 436static struct platform_driver nvec_power_driver = { 437 .probe = nvec_power_probe, 438 .remove = nvec_power_remove, 439 .driver = { 440 .name = "nvec-power", 441 } 442}; 443 444module_platform_driver(nvec_power_driver); 445 446MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>"); 447MODULE_LICENSE("GPL"); 448MODULE_DESCRIPTION("NVEC battery and AC driver"); 449MODULE_ALIAS("platform:nvec-power"); 450