nvec_power.c (10509B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * nvec_power: power supply driver for a NVIDIA compliant embedded controller 4 * 5 * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net> 6 * 7 * Authors: Ilya Petrov <ilya.muromec@gmail.com> 8 * Marc Dietrich <marvin24@gmx.de> 9 */ 10 11#include <linux/module.h> 12#include <linux/platform_device.h> 13#include <linux/err.h> 14#include <linux/power_supply.h> 15#include <linux/slab.h> 16#include <linux/workqueue.h> 17#include <linux/delay.h> 18 19#include "nvec.h" 20 21#define GET_SYSTEM_STATUS 0x00 22 23struct nvec_power { 24 struct notifier_block notifier; 25 struct delayed_work poller; 26 struct nvec_chip *nvec; 27 int on; 28 int bat_present; 29 int bat_status; 30 int bat_voltage_now; 31 int bat_current_now; 32 int bat_current_avg; 33 int time_remain; 34 int charge_full_design; 35 int charge_last_full; 36 int critical_capacity; 37 int capacity_remain; 38 int bat_temperature; 39 int bat_cap; 40 int bat_type_enum; 41 char bat_manu[30]; 42 char bat_model[30]; 43 char bat_type[30]; 44}; 45 46enum { 47 SLOT_STATUS, 48 VOLTAGE, 49 TIME_REMAINING, 50 CURRENT, 51 AVERAGE_CURRENT, 52 AVERAGING_TIME_INTERVAL, 53 CAPACITY_REMAINING, 54 LAST_FULL_CHARGE_CAPACITY, 55 DESIGN_CAPACITY, 56 CRITICAL_CAPACITY, 57 TEMPERATURE, 58 MANUFACTURER, 59 MODEL, 60 TYPE, 61}; 62 63enum { 64 AC, 65 BAT, 66}; 67 68struct bat_response { 69 u8 event_type; 70 u8 length; 71 u8 sub_type; 72 u8 status; 73 /* payload */ 74 union { 75 char plc[30]; 76 u16 plu; 77 s16 pls; 78 }; 79}; 80 81static struct power_supply *nvec_bat_psy; 82static struct power_supply *nvec_psy; 83 84static int nvec_power_notifier(struct notifier_block *nb, 85 unsigned long event_type, void *data) 86{ 87 struct nvec_power *power = 88 container_of(nb, struct nvec_power, notifier); 89 struct bat_response *res = data; 90 91 if (event_type != NVEC_SYS) 92 return NOTIFY_DONE; 93 94 if (res->sub_type == 0) { 95 if (power->on != res->plu) { 96 power->on = res->plu; 97 power_supply_changed(nvec_psy); 98 } 99 return NOTIFY_STOP; 100 } 101 return NOTIFY_OK; 102} 103 104static const int bat_init[] = { 105 LAST_FULL_CHARGE_CAPACITY, DESIGN_CAPACITY, CRITICAL_CAPACITY, 106 MANUFACTURER, MODEL, TYPE, 107}; 108 109static void get_bat_mfg_data(struct nvec_power *power) 110{ 111 int i; 112 char buf[] = { NVEC_BAT, SLOT_STATUS }; 113 114 for (i = 0; i < ARRAY_SIZE(bat_init); i++) { 115 buf[1] = bat_init[i]; 116 nvec_write_async(power->nvec, buf, 2); 117 } 118} 119 120static int nvec_power_bat_notifier(struct notifier_block *nb, 121 unsigned long event_type, void *data) 122{ 123 struct nvec_power *power = 124 container_of(nb, struct nvec_power, notifier); 125 struct bat_response *res = data; 126 int status_changed = 0; 127 128 if (event_type != NVEC_BAT) 129 return NOTIFY_DONE; 130 131 switch (res->sub_type) { 132 case SLOT_STATUS: 133 if (res->plc[0] & 1) { 134 if (power->bat_present == 0) { 135 status_changed = 1; 136 get_bat_mfg_data(power); 137 } 138 139 power->bat_present = 1; 140 141 switch ((res->plc[0] >> 1) & 3) { 142 case 0: 143 power->bat_status = 144 POWER_SUPPLY_STATUS_NOT_CHARGING; 145 break; 146 case 1: 147 power->bat_status = 148 POWER_SUPPLY_STATUS_CHARGING; 149 break; 150 case 2: 151 power->bat_status = 152 POWER_SUPPLY_STATUS_DISCHARGING; 153 break; 154 default: 155 power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; 156 } 157 } else { 158 if (power->bat_present == 1) 159 status_changed = 1; 160 161 power->bat_present = 0; 162 power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; 163 } 164 power->bat_cap = res->plc[1]; 165 if (status_changed) 166 power_supply_changed(nvec_bat_psy); 167 break; 168 case VOLTAGE: 169 power->bat_voltage_now = res->plu * 1000; 170 break; 171 case TIME_REMAINING: 172 power->time_remain = res->plu * 3600; 173 break; 174 case CURRENT: 175 power->bat_current_now = res->pls * 1000; 176 break; 177 case AVERAGE_CURRENT: 178 power->bat_current_avg = res->pls * 1000; 179 break; 180 case CAPACITY_REMAINING: 181 power->capacity_remain = res->plu * 1000; 182 break; 183 case LAST_FULL_CHARGE_CAPACITY: 184 power->charge_last_full = res->plu * 1000; 185 break; 186 case DESIGN_CAPACITY: 187 power->charge_full_design = res->plu * 1000; 188 break; 189 case CRITICAL_CAPACITY: 190 power->critical_capacity = res->plu * 1000; 191 break; 192 case TEMPERATURE: 193 power->bat_temperature = res->plu - 2732; 194 break; 195 case MANUFACTURER: 196 memcpy(power->bat_manu, &res->plc, res->length - 2); 197 power->bat_model[res->length - 2] = '\0'; 198 break; 199 case MODEL: 200 memcpy(power->bat_model, &res->plc, res->length - 2); 201 power->bat_model[res->length - 2] = '\0'; 202 break; 203 case TYPE: 204 memcpy(power->bat_type, &res->plc, res->length - 2); 205 power->bat_type[res->length - 2] = '\0'; 206 /* 207 * This differs a little from the spec fill in more if you find 208 * some. 209 */ 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 228 switch (psp) { 229 case POWER_SUPPLY_PROP_ONLINE: 230 val->intval = power->on; 231 break; 232 default: 233 return -EINVAL; 234 } 235 return 0; 236} 237 238static int nvec_battery_get_property(struct power_supply *psy, 239 enum power_supply_property psp, 240 union power_supply_propval *val) 241{ 242 struct nvec_power *power = dev_get_drvdata(psy->dev.parent); 243 244 switch (psp) { 245 case POWER_SUPPLY_PROP_STATUS: 246 val->intval = power->bat_status; 247 break; 248 case POWER_SUPPLY_PROP_CAPACITY: 249 val->intval = power->bat_cap; 250 break; 251 case POWER_SUPPLY_PROP_PRESENT: 252 val->intval = power->bat_present; 253 break; 254 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 255 val->intval = power->bat_voltage_now; 256 break; 257 case POWER_SUPPLY_PROP_CURRENT_NOW: 258 val->intval = power->bat_current_now; 259 break; 260 case POWER_SUPPLY_PROP_CURRENT_AVG: 261 val->intval = power->bat_current_avg; 262 break; 263 case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: 264 val->intval = power->time_remain; 265 break; 266 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: 267 val->intval = power->charge_full_design; 268 break; 269 case POWER_SUPPLY_PROP_CHARGE_FULL: 270 val->intval = power->charge_last_full; 271 break; 272 case POWER_SUPPLY_PROP_CHARGE_EMPTY: 273 val->intval = power->critical_capacity; 274 break; 275 case POWER_SUPPLY_PROP_CHARGE_NOW: 276 val->intval = power->capacity_remain; 277 break; 278 case POWER_SUPPLY_PROP_TEMP: 279 val->intval = power->bat_temperature; 280 break; 281 case POWER_SUPPLY_PROP_MANUFACTURER: 282 val->strval = power->bat_manu; 283 break; 284 case POWER_SUPPLY_PROP_MODEL_NAME: 285 val->strval = power->bat_model; 286 break; 287 case POWER_SUPPLY_PROP_TECHNOLOGY: 288 val->intval = power->bat_type_enum; 289 break; 290 default: 291 return -EINVAL; 292 } 293 return 0; 294} 295 296static enum power_supply_property nvec_power_props[] = { 297 POWER_SUPPLY_PROP_ONLINE, 298}; 299 300static enum power_supply_property nvec_battery_props[] = { 301 POWER_SUPPLY_PROP_STATUS, 302 POWER_SUPPLY_PROP_PRESENT, 303 POWER_SUPPLY_PROP_CAPACITY, 304 POWER_SUPPLY_PROP_VOLTAGE_NOW, 305 POWER_SUPPLY_PROP_CURRENT_NOW, 306#ifdef EC_FULL_DIAG 307 POWER_SUPPLY_PROP_CURRENT_AVG, 308 POWER_SUPPLY_PROP_TEMP, 309 POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, 310#endif 311 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 312 POWER_SUPPLY_PROP_CHARGE_FULL, 313 POWER_SUPPLY_PROP_CHARGE_EMPTY, 314 POWER_SUPPLY_PROP_CHARGE_NOW, 315 POWER_SUPPLY_PROP_MANUFACTURER, 316 POWER_SUPPLY_PROP_MODEL_NAME, 317 POWER_SUPPLY_PROP_TECHNOLOGY, 318}; 319 320static char *nvec_power_supplied_to[] = { 321 "battery", 322}; 323 324static const struct power_supply_desc nvec_bat_psy_desc = { 325 .name = "battery", 326 .type = POWER_SUPPLY_TYPE_BATTERY, 327 .properties = nvec_battery_props, 328 .num_properties = ARRAY_SIZE(nvec_battery_props), 329 .get_property = nvec_battery_get_property, 330}; 331 332static const struct power_supply_desc nvec_psy_desc = { 333 .name = "ac", 334 .type = POWER_SUPPLY_TYPE_MAINS, 335 .properties = nvec_power_props, 336 .num_properties = ARRAY_SIZE(nvec_power_props), 337 .get_property = nvec_power_get_property, 338}; 339 340static int counter; 341static const int bat_iter[] = { 342 SLOT_STATUS, VOLTAGE, CURRENT, CAPACITY_REMAINING, 343#ifdef EC_FULL_DIAG 344 AVERAGE_CURRENT, TEMPERATURE, TIME_REMAINING, 345#endif 346}; 347 348static void nvec_power_poll(struct work_struct *work) 349{ 350 char buf[] = { NVEC_SYS, GET_SYSTEM_STATUS }; 351 struct nvec_power *power = container_of(work, struct nvec_power, 352 poller.work); 353 354 if (counter >= ARRAY_SIZE(bat_iter)) 355 counter = 0; 356 357 /* AC status via sys req */ 358 nvec_write_async(power->nvec, buf, 2); 359 msleep(100); 360 361 /* 362 * Select a battery request function via round robin doing it all at 363 * once seems to overload the power supply. 364 */ 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");