Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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