s3c_adc_battery.c (11325B)
1// SPDX-License-Identifier: GPL-2.0 2// 3// iPAQ h1930/h1940/rx1950 battery controller driver 4// Copyright (c) Vasily Khoruzhick 5// Based on h1940_battery.c by Arnaud Patard 6 7#include <linux/interrupt.h> 8#include <linux/platform_device.h> 9#include <linux/power_supply.h> 10#include <linux/leds.h> 11#include <linux/gpio/consumer.h> 12#include <linux/err.h> 13#include <linux/timer.h> 14#include <linux/jiffies.h> 15#include <linux/s3c_adc_battery.h> 16#include <linux/errno.h> 17#include <linux/init.h> 18#include <linux/module.h> 19 20#include <linux/soc/samsung/s3c-adc.h> 21 22#define BAT_POLL_INTERVAL 10000 /* ms */ 23#define JITTER_DELAY 500 /* ms */ 24 25struct s3c_adc_bat { 26 struct power_supply *psy; 27 struct s3c_adc_client *client; 28 struct s3c_adc_bat_pdata *pdata; 29 struct gpio_desc *charge_finished; 30 int volt_value; 31 int cur_value; 32 unsigned int timestamp; 33 int level; 34 int status; 35 int cable_plugged:1; 36}; 37 38static struct delayed_work bat_work; 39 40static void s3c_adc_bat_ext_power_changed(struct power_supply *psy) 41{ 42 schedule_delayed_work(&bat_work, 43 msecs_to_jiffies(JITTER_DELAY)); 44} 45 46static int gather_samples(struct s3c_adc_client *client, int num, int channel) 47{ 48 int value, i; 49 50 /* default to 1 if nothing is set */ 51 if (num < 1) 52 num = 1; 53 54 value = 0; 55 for (i = 0; i < num; i++) 56 value += s3c_adc_read(client, channel); 57 value /= num; 58 59 return value; 60} 61 62static enum power_supply_property s3c_adc_backup_bat_props[] = { 63 POWER_SUPPLY_PROP_VOLTAGE_NOW, 64 POWER_SUPPLY_PROP_VOLTAGE_MIN, 65 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 66}; 67 68static int s3c_adc_backup_bat_get_property(struct power_supply *psy, 69 enum power_supply_property psp, 70 union power_supply_propval *val) 71{ 72 struct s3c_adc_bat *bat = power_supply_get_drvdata(psy); 73 74 if (!bat) { 75 dev_err(&psy->dev, "%s: no battery infos ?!\n", __func__); 76 return -EINVAL; 77 } 78 79 if (bat->volt_value < 0 || 80 jiffies_to_msecs(jiffies - bat->timestamp) > 81 BAT_POLL_INTERVAL) { 82 bat->volt_value = gather_samples(bat->client, 83 bat->pdata->backup_volt_samples, 84 bat->pdata->backup_volt_channel); 85 bat->volt_value *= bat->pdata->backup_volt_mult; 86 bat->timestamp = jiffies; 87 } 88 89 switch (psp) { 90 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 91 val->intval = bat->volt_value; 92 return 0; 93 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 94 val->intval = bat->pdata->backup_volt_min; 95 return 0; 96 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: 97 val->intval = bat->pdata->backup_volt_max; 98 return 0; 99 default: 100 return -EINVAL; 101 } 102} 103 104static const struct power_supply_desc backup_bat_desc = { 105 .name = "backup-battery", 106 .type = POWER_SUPPLY_TYPE_BATTERY, 107 .properties = s3c_adc_backup_bat_props, 108 .num_properties = ARRAY_SIZE(s3c_adc_backup_bat_props), 109 .get_property = s3c_adc_backup_bat_get_property, 110 .use_for_apm = 1, 111}; 112 113static struct s3c_adc_bat backup_bat; 114 115static enum power_supply_property s3c_adc_main_bat_props[] = { 116 POWER_SUPPLY_PROP_STATUS, 117 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 118 POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, 119 POWER_SUPPLY_PROP_CHARGE_NOW, 120 POWER_SUPPLY_PROP_VOLTAGE_NOW, 121 POWER_SUPPLY_PROP_CURRENT_NOW, 122}; 123 124static int calc_full_volt(int volt_val, int cur_val, int impedance) 125{ 126 return volt_val + cur_val * impedance / 1000; 127} 128 129static int charge_finished(struct s3c_adc_bat *bat) 130{ 131 return gpiod_get_value(bat->charge_finished); 132} 133 134static int s3c_adc_bat_get_property(struct power_supply *psy, 135 enum power_supply_property psp, 136 union power_supply_propval *val) 137{ 138 struct s3c_adc_bat *bat = power_supply_get_drvdata(psy); 139 140 int new_level; 141 int full_volt; 142 const struct s3c_adc_bat_thresh *lut; 143 unsigned int lut_size; 144 145 if (!bat) { 146 dev_err(&psy->dev, "no battery infos ?!\n"); 147 return -EINVAL; 148 } 149 150 lut = bat->pdata->lut_noac; 151 lut_size = bat->pdata->lut_noac_cnt; 152 153 if (bat->volt_value < 0 || bat->cur_value < 0 || 154 jiffies_to_msecs(jiffies - bat->timestamp) > 155 BAT_POLL_INTERVAL) { 156 bat->volt_value = gather_samples(bat->client, 157 bat->pdata->volt_samples, 158 bat->pdata->volt_channel) * bat->pdata->volt_mult; 159 bat->cur_value = gather_samples(bat->client, 160 bat->pdata->current_samples, 161 bat->pdata->current_channel) * bat->pdata->current_mult; 162 bat->timestamp = jiffies; 163 } 164 165 if (bat->cable_plugged && 166 (!bat->charge_finished || 167 !charge_finished(bat))) { 168 lut = bat->pdata->lut_acin; 169 lut_size = bat->pdata->lut_acin_cnt; 170 } 171 172 new_level = 100000; 173 full_volt = calc_full_volt((bat->volt_value / 1000), 174 (bat->cur_value / 1000), bat->pdata->internal_impedance); 175 176 if (full_volt < calc_full_volt(lut->volt, lut->cur, 177 bat->pdata->internal_impedance)) { 178 lut_size--; 179 while (lut_size--) { 180 int lut_volt1; 181 int lut_volt2; 182 183 lut_volt1 = calc_full_volt(lut[0].volt, lut[0].cur, 184 bat->pdata->internal_impedance); 185 lut_volt2 = calc_full_volt(lut[1].volt, lut[1].cur, 186 bat->pdata->internal_impedance); 187 if (full_volt < lut_volt1 && full_volt >= lut_volt2) { 188 new_level = (lut[1].level + 189 (lut[0].level - lut[1].level) * 190 (full_volt - lut_volt2) / 191 (lut_volt1 - lut_volt2)) * 1000; 192 break; 193 } 194 new_level = lut[1].level * 1000; 195 lut++; 196 } 197 } 198 199 bat->level = new_level; 200 201 switch (psp) { 202 case POWER_SUPPLY_PROP_STATUS: 203 if (!bat->charge_finished) 204 val->intval = bat->level == 100000 ? 205 POWER_SUPPLY_STATUS_FULL : bat->status; 206 else 207 val->intval = bat->status; 208 return 0; 209 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: 210 val->intval = 100000; 211 return 0; 212 case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: 213 val->intval = 0; 214 return 0; 215 case POWER_SUPPLY_PROP_CHARGE_NOW: 216 val->intval = bat->level; 217 return 0; 218 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 219 val->intval = bat->volt_value; 220 return 0; 221 case POWER_SUPPLY_PROP_CURRENT_NOW: 222 val->intval = bat->cur_value; 223 return 0; 224 default: 225 return -EINVAL; 226 } 227} 228 229static const struct power_supply_desc main_bat_desc = { 230 .name = "main-battery", 231 .type = POWER_SUPPLY_TYPE_BATTERY, 232 .properties = s3c_adc_main_bat_props, 233 .num_properties = ARRAY_SIZE(s3c_adc_main_bat_props), 234 .get_property = s3c_adc_bat_get_property, 235 .external_power_changed = s3c_adc_bat_ext_power_changed, 236 .use_for_apm = 1, 237}; 238 239static struct s3c_adc_bat main_bat; 240 241static void s3c_adc_bat_work(struct work_struct *work) 242{ 243 struct s3c_adc_bat *bat = &main_bat; 244 int is_charged; 245 int is_plugged; 246 static int was_plugged; 247 248 is_plugged = power_supply_am_i_supplied(bat->psy); 249 bat->cable_plugged = is_plugged; 250 if (is_plugged != was_plugged) { 251 was_plugged = is_plugged; 252 if (is_plugged) { 253 if (bat->pdata->enable_charger) 254 bat->pdata->enable_charger(); 255 bat->status = POWER_SUPPLY_STATUS_CHARGING; 256 } else { 257 if (bat->pdata->disable_charger) 258 bat->pdata->disable_charger(); 259 bat->status = POWER_SUPPLY_STATUS_DISCHARGING; 260 } 261 } else { 262 if (bat->charge_finished && is_plugged) { 263 is_charged = charge_finished(&main_bat); 264 if (is_charged) { 265 if (bat->pdata->disable_charger) 266 bat->pdata->disable_charger(); 267 bat->status = POWER_SUPPLY_STATUS_FULL; 268 } else { 269 if (bat->pdata->enable_charger) 270 bat->pdata->enable_charger(); 271 bat->status = POWER_SUPPLY_STATUS_CHARGING; 272 } 273 } 274 } 275 276 power_supply_changed(bat->psy); 277} 278 279static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id) 280{ 281 schedule_delayed_work(&bat_work, 282 msecs_to_jiffies(JITTER_DELAY)); 283 return IRQ_HANDLED; 284} 285 286static int s3c_adc_bat_probe(struct platform_device *pdev) 287{ 288 struct s3c_adc_client *client; 289 struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; 290 struct power_supply_config psy_cfg = {}; 291 struct gpio_desc *gpiod; 292 int ret; 293 294 client = s3c_adc_register(pdev, NULL, NULL, 0); 295 if (IS_ERR(client)) { 296 dev_err(&pdev->dev, "cannot register adc\n"); 297 return PTR_ERR(client); 298 } 299 300 platform_set_drvdata(pdev, client); 301 302 gpiod = devm_gpiod_get_optional(&pdev->dev, "charge-status", GPIOD_IN); 303 if (IS_ERR(gpiod)) { 304 /* Could be probe deferral etc */ 305 ret = PTR_ERR(gpiod); 306 dev_err(&pdev->dev, "no GPIO %d\n", ret); 307 return ret; 308 } 309 310 main_bat.client = client; 311 main_bat.pdata = pdata; 312 main_bat.charge_finished = gpiod; 313 main_bat.volt_value = -1; 314 main_bat.cur_value = -1; 315 main_bat.cable_plugged = 0; 316 main_bat.status = POWER_SUPPLY_STATUS_DISCHARGING; 317 psy_cfg.drv_data = &main_bat; 318 319 main_bat.psy = power_supply_register(&pdev->dev, &main_bat_desc, &psy_cfg); 320 if (IS_ERR(main_bat.psy)) { 321 ret = PTR_ERR(main_bat.psy); 322 goto err_reg_main; 323 } 324 if (pdata->backup_volt_mult) { 325 const struct power_supply_config backup_psy_cfg 326 = { .drv_data = &backup_bat, }; 327 328 backup_bat.client = client; 329 backup_bat.pdata = pdev->dev.platform_data; 330 backup_bat.charge_finished = gpiod; 331 backup_bat.volt_value = -1; 332 backup_bat.psy = power_supply_register(&pdev->dev, 333 &backup_bat_desc, 334 &backup_psy_cfg); 335 if (IS_ERR(backup_bat.psy)) { 336 ret = PTR_ERR(backup_bat.psy); 337 goto err_reg_backup; 338 } 339 } 340 341 INIT_DELAYED_WORK(&bat_work, s3c_adc_bat_work); 342 343 if (gpiod) { 344 ret = request_irq(gpiod_to_irq(gpiod), 345 s3c_adc_bat_charged, 346 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 347 "battery charged", NULL); 348 if (ret) 349 goto err_irq; 350 } 351 352 if (pdata->init) { 353 ret = pdata->init(); 354 if (ret) 355 goto err_platform; 356 } 357 358 dev_info(&pdev->dev, "successfully loaded\n"); 359 device_init_wakeup(&pdev->dev, 1); 360 361 /* Schedule timer to check current status */ 362 schedule_delayed_work(&bat_work, 363 msecs_to_jiffies(JITTER_DELAY)); 364 365 return 0; 366 367err_platform: 368 if (gpiod) 369 free_irq(gpiod_to_irq(gpiod), NULL); 370err_irq: 371 if (pdata->backup_volt_mult) 372 power_supply_unregister(backup_bat.psy); 373err_reg_backup: 374 power_supply_unregister(main_bat.psy); 375err_reg_main: 376 return ret; 377} 378 379static int s3c_adc_bat_remove(struct platform_device *pdev) 380{ 381 struct s3c_adc_client *client = platform_get_drvdata(pdev); 382 struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; 383 384 power_supply_unregister(main_bat.psy); 385 if (pdata->backup_volt_mult) 386 power_supply_unregister(backup_bat.psy); 387 388 s3c_adc_release(client); 389 390 if (main_bat.charge_finished) 391 free_irq(gpiod_to_irq(main_bat.charge_finished), NULL); 392 393 cancel_delayed_work_sync(&bat_work); 394 395 if (pdata->exit) 396 pdata->exit(); 397 398 return 0; 399} 400 401#ifdef CONFIG_PM 402static int s3c_adc_bat_suspend(struct platform_device *pdev, 403 pm_message_t state) 404{ 405 if (main_bat.charge_finished) { 406 if (device_may_wakeup(&pdev->dev)) 407 enable_irq_wake( 408 gpiod_to_irq(main_bat.charge_finished)); 409 else { 410 disable_irq(gpiod_to_irq(main_bat.charge_finished)); 411 main_bat.pdata->disable_charger(); 412 } 413 } 414 415 return 0; 416} 417 418static int s3c_adc_bat_resume(struct platform_device *pdev) 419{ 420 if (main_bat.charge_finished) { 421 if (device_may_wakeup(&pdev->dev)) 422 disable_irq_wake( 423 gpiod_to_irq(main_bat.charge_finished)); 424 else 425 enable_irq(gpiod_to_irq(main_bat.charge_finished)); 426 } 427 428 /* Schedule timer to check current status */ 429 schedule_delayed_work(&bat_work, 430 msecs_to_jiffies(JITTER_DELAY)); 431 432 return 0; 433} 434#else 435#define s3c_adc_bat_suspend NULL 436#define s3c_adc_bat_resume NULL 437#endif 438 439static struct platform_driver s3c_adc_bat_driver = { 440 .driver = { 441 .name = "s3c-adc-battery", 442 }, 443 .probe = s3c_adc_bat_probe, 444 .remove = s3c_adc_bat_remove, 445 .suspend = s3c_adc_bat_suspend, 446 .resume = s3c_adc_bat_resume, 447}; 448 449module_platform_driver(s3c_adc_bat_driver); 450 451MODULE_AUTHOR("Vasily Khoruzhick <anarsoul@gmail.com>"); 452MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver"); 453MODULE_LICENSE("GPL");