diff options
| author | Chanwoo Choi <cw00.choi@samsung.com> | 2012-05-05 09:24:10 -0400 |
|---|---|---|
| committer | Anton Vorontsov <anton.vorontsov@linaro.org> | 2012-05-05 22:48:50 -0400 |
| commit | d829dc75bafb10754f35fb8895e5143d20267b04 (patch) | |
| tree | 4cff2aa07dcf7c15ef931e3b9ab20a2d84fcf68d /drivers/power | |
| parent | 34298d40e5853bc195c9db012fc1ddccac9b6f7f (diff) | |
charger-manager: Poll battery health in normal state
Charger-Manager needs to check battery health in normal state
as well as suspend-to-RAM state. When the battery is fully charged,
Charger-Manager needs to determine when the chargers restart charging.
This patch allows Charger-Manager to monitor battery health in normal
state and handle operation for chargers after battery is fully charged.
Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: Donggeun Kim <dg77.kim@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Anton Vorontsov <anton.vorontsov@linaro.org>
Diffstat (limited to 'drivers/power')
| -rw-r--r-- | drivers/power/charger-manager.c | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 9eca9f1ff0ea..959062d16bac 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c | |||
| @@ -57,6 +57,12 @@ static bool cm_suspended; | |||
| 57 | static bool cm_rtc_set; | 57 | static bool cm_rtc_set; |
| 58 | static unsigned long cm_suspend_duration_ms; | 58 | static unsigned long cm_suspend_duration_ms; |
| 59 | 59 | ||
| 60 | /* About normal (not suspended) monitoring */ | ||
| 61 | static unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */ | ||
| 62 | static unsigned long next_polling; /* Next appointed polling time */ | ||
| 63 | static struct workqueue_struct *cm_wq; /* init at driver add */ | ||
| 64 | static struct delayed_work cm_monitor_work; /* init at driver add */ | ||
| 65 | |||
| 60 | /* Global charger-manager description */ | 66 | /* Global charger-manager description */ |
| 61 | static struct charger_global_desc *g_desc; /* init with setup_charger_manager */ | 67 | static struct charger_global_desc *g_desc; /* init with setup_charger_manager */ |
| 62 | 68 | ||
| @@ -71,6 +77,11 @@ static bool is_batt_present(struct charger_manager *cm) | |||
| 71 | int i, ret; | 77 | int i, ret; |
| 72 | 78 | ||
| 73 | switch (cm->desc->battery_present) { | 79 | switch (cm->desc->battery_present) { |
| 80 | case CM_BATTERY_PRESENT: | ||
| 81 | present = true; | ||
| 82 | break; | ||
| 83 | case CM_NO_BATTERY: | ||
| 84 | break; | ||
| 74 | case CM_FUEL_GAUGE: | 85 | case CM_FUEL_GAUGE: |
| 75 | ret = cm->fuel_gauge->get_property(cm->fuel_gauge, | 86 | ret = cm->fuel_gauge->get_property(cm->fuel_gauge, |
| 76 | POWER_SUPPLY_PROP_PRESENT, &val); | 87 | POWER_SUPPLY_PROP_PRESENT, &val); |
| @@ -279,6 +290,26 @@ static int try_charger_enable(struct charger_manager *cm, bool enable) | |||
| 279 | } | 290 | } |
| 280 | 291 | ||
| 281 | /** | 292 | /** |
| 293 | * try_charger_restart - Restart charging. | ||
| 294 | * @cm: the Charger Manager representing the battery. | ||
| 295 | * | ||
| 296 | * Restart charging by turning off and on the charger. | ||
| 297 | */ | ||
| 298 | static int try_charger_restart(struct charger_manager *cm) | ||
| 299 | { | ||
| 300 | int err; | ||
| 301 | |||
| 302 | if (cm->emergency_stop) | ||
| 303 | return -EAGAIN; | ||
| 304 | |||
| 305 | err = try_charger_enable(cm, false); | ||
| 306 | if (err) | ||
| 307 | return err; | ||
| 308 | |||
| 309 | return try_charger_enable(cm, true); | ||
| 310 | } | ||
| 311 | |||
| 312 | /** | ||
| 282 | * uevent_notify - Let users know something has changed. | 313 | * uevent_notify - Let users know something has changed. |
| 283 | * @cm: the Charger Manager representing the battery. | 314 | * @cm: the Charger Manager representing the battery. |
| 284 | * @event: the event string. | 315 | * @event: the event string. |
| @@ -334,6 +365,46 @@ static void uevent_notify(struct charger_manager *cm, const char *event) | |||
| 334 | } | 365 | } |
| 335 | 366 | ||
| 336 | /** | 367 | /** |
| 368 | * fullbatt_vchk - Check voltage drop some times after "FULL" event. | ||
| 369 | * @work: the work_struct appointing the function | ||
| 370 | * | ||
| 371 | * If a user has designated "fullbatt_vchkdrop_ms/uV" values with | ||
| 372 | * charger_desc, Charger Manager checks voltage drop after the battery | ||
| 373 | * "FULL" event. It checks whether the voltage has dropped more than | ||
| 374 | * fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms. | ||
| 375 | */ | ||
| 376 | static void fullbatt_vchk(struct work_struct *work) | ||
| 377 | { | ||
| 378 | struct delayed_work *dwork = to_delayed_work(work); | ||
| 379 | struct charger_manager *cm = container_of(dwork, | ||
| 380 | struct charger_manager, fullbatt_vchk_work); | ||
| 381 | struct charger_desc *desc = cm->desc; | ||
| 382 | int batt_uV, err, diff; | ||
| 383 | |||
| 384 | /* remove the appointment for fullbatt_vchk */ | ||
| 385 | cm->fullbatt_vchk_jiffies_at = 0; | ||
| 386 | |||
| 387 | if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) | ||
| 388 | return; | ||
| 389 | |||
| 390 | err = get_batt_uV(cm, &batt_uV); | ||
| 391 | if (err) { | ||
| 392 | dev_err(cm->dev, "%s: get_batt_uV error(%d).\n", __func__, err); | ||
| 393 | return; | ||
| 394 | } | ||
| 395 | |||
| 396 | diff = cm->fullbatt_vchk_uV; | ||
| 397 | diff -= batt_uV; | ||
| 398 | |||
| 399 | dev_dbg(cm->dev, "VBATT dropped %duV after full-batt.\n", diff); | ||
| 400 | |||
| 401 | if (diff > desc->fullbatt_vchkdrop_uV) { | ||
| 402 | try_charger_restart(cm); | ||
| 403 | uevent_notify(cm, "Recharge"); | ||
| 404 | } | ||
| 405 | } | ||
| 406 | |||
| 407 | /** | ||
| 337 | * _cm_monitor - Monitor the temperature and return true for exceptions. | 408 | * _cm_monitor - Monitor the temperature and return true for exceptions. |
| 338 | * @cm: the Charger Manager representing the battery. | 409 | * @cm: the Charger Manager representing the battery. |
| 339 | * | 410 | * |
| @@ -392,6 +463,68 @@ static bool cm_monitor(void) | |||
| 392 | return stop; | 463 | return stop; |
| 393 | } | 464 | } |
| 394 | 465 | ||
| 466 | /** | ||
| 467 | * _setup_polling - Setup the next instance of polling. | ||
| 468 | * @work: work_struct of the function _setup_polling. | ||
| 469 | */ | ||
| 470 | static void _setup_polling(struct work_struct *work) | ||
| 471 | { | ||
| 472 | unsigned long min = ULONG_MAX; | ||
| 473 | struct charger_manager *cm; | ||
| 474 | bool keep_polling = false; | ||
| 475 | unsigned long _next_polling; | ||
| 476 | |||
| 477 | mutex_lock(&cm_list_mtx); | ||
| 478 | |||
| 479 | list_for_each_entry(cm, &cm_list, entry) { | ||
| 480 | if (is_polling_required(cm) && cm->desc->polling_interval_ms) { | ||
| 481 | keep_polling = true; | ||
| 482 | |||
| 483 | if (min > cm->desc->polling_interval_ms) | ||
| 484 | min = cm->desc->polling_interval_ms; | ||
| 485 | } | ||
| 486 | } | ||
| 487 | |||
| 488 | polling_jiffy = msecs_to_jiffies(min); | ||
| 489 | if (polling_jiffy <= CM_JIFFIES_SMALL) | ||
| 490 | polling_jiffy = CM_JIFFIES_SMALL + 1; | ||
| 491 | |||
| 492 | if (!keep_polling) | ||
| 493 | polling_jiffy = ULONG_MAX; | ||
| 494 | if (polling_jiffy == ULONG_MAX) | ||
| 495 | goto out; | ||
| 496 | |||
| 497 | WARN(cm_wq == NULL, "charger-manager: workqueue not initialized" | ||
| 498 | ". try it later. %s\n", __func__); | ||
| 499 | |||
| 500 | _next_polling = jiffies + polling_jiffy; | ||
| 501 | |||
| 502 | if (!delayed_work_pending(&cm_monitor_work) || | ||
| 503 | (delayed_work_pending(&cm_monitor_work) && | ||
| 504 | time_after(next_polling, _next_polling))) { | ||
| 505 | cancel_delayed_work_sync(&cm_monitor_work); | ||
| 506 | next_polling = jiffies + polling_jiffy; | ||
| 507 | queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy); | ||
| 508 | } | ||
| 509 | |||
| 510 | out: | ||
| 511 | mutex_unlock(&cm_list_mtx); | ||
| 512 | } | ||
| 513 | static DECLARE_WORK(setup_polling, _setup_polling); | ||
| 514 | |||
| 515 | /** | ||
| 516 | * cm_monitor_poller - The Monitor / Poller. | ||
| 517 | * @work: work_struct of the function cm_monitor_poller | ||
| 518 | * | ||
| 519 | * During non-suspended state, cm_monitor_poller is used to poll and monitor | ||
| 520 | * the batteries. | ||
| 521 | */ | ||
| 522 | static void cm_monitor_poller(struct work_struct *work) | ||
| 523 | { | ||
| 524 | cm_monitor(); | ||
| 525 | schedule_work(&setup_polling); | ||
| 526 | } | ||
| 527 | |||
| 395 | static int charger_get_property(struct power_supply *psy, | 528 | static int charger_get_property(struct power_supply *psy, |
| 396 | enum power_supply_property psp, | 529 | enum power_supply_property psp, |
| 397 | union power_supply_propval *val) | 530 | union power_supply_propval *val) |
| @@ -613,6 +746,21 @@ static bool cm_setup_timer(void) | |||
| 613 | mutex_lock(&cm_list_mtx); | 746 | mutex_lock(&cm_list_mtx); |
| 614 | 747 | ||
| 615 | list_for_each_entry(cm, &cm_list, entry) { | 748 | list_for_each_entry(cm, &cm_list, entry) { |
| 749 | unsigned int fbchk_ms = 0; | ||
| 750 | |||
| 751 | /* fullbatt_vchk is required. setup timer for that */ | ||
| 752 | if (cm->fullbatt_vchk_jiffies_at) { | ||
| 753 | fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at | ||
| 754 | - jiffies); | ||
| 755 | if (time_is_before_eq_jiffies( | ||
| 756 | cm->fullbatt_vchk_jiffies_at) || | ||
| 757 | msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) { | ||
| 758 | fullbatt_vchk(&cm->fullbatt_vchk_work.work); | ||
| 759 | fbchk_ms = 0; | ||
| 760 | } | ||
| 761 | } | ||
| 762 | CM_MIN_VALID(wakeup_ms, fbchk_ms); | ||
| 763 | |||
| 616 | /* Skip if polling is not required for this CM */ | 764 | /* Skip if polling is not required for this CM */ |
| 617 | if (!is_polling_required(cm) && !cm->emergency_stop) | 765 | if (!is_polling_required(cm) && !cm->emergency_stop) |
| 618 | continue; | 766 | continue; |
| @@ -672,6 +820,23 @@ static bool cm_setup_timer(void) | |||
| 672 | return false; | 820 | return false; |
| 673 | } | 821 | } |
| 674 | 822 | ||
| 823 | static void _cm_fbchk_in_suspend(struct charger_manager *cm) | ||
| 824 | { | ||
| 825 | unsigned long jiffy_now = jiffies; | ||
| 826 | |||
| 827 | if (!cm->fullbatt_vchk_jiffies_at) | ||
| 828 | return; | ||
| 829 | |||
| 830 | if (g_desc && g_desc->assume_timer_stops_in_suspend) | ||
| 831 | jiffy_now += msecs_to_jiffies(cm_suspend_duration_ms); | ||
| 832 | |||
| 833 | /* Execute now if it's going to be executed not too long after */ | ||
| 834 | jiffy_now += CM_JIFFIES_SMALL; | ||
| 835 | |||
| 836 | if (time_after_eq(jiffy_now, cm->fullbatt_vchk_jiffies_at)) | ||
| 837 | fullbatt_vchk(&cm->fullbatt_vchk_work.work); | ||
| 838 | } | ||
| 839 | |||
| 675 | /** | 840 | /** |
| 676 | * cm_suspend_again - Determine whether suspend again or not | 841 | * cm_suspend_again - Determine whether suspend again or not |
| 677 | * | 842 | * |
| @@ -693,6 +858,8 @@ bool cm_suspend_again(void) | |||
| 693 | ret = true; | 858 | ret = true; |
| 694 | mutex_lock(&cm_list_mtx); | 859 | mutex_lock(&cm_list_mtx); |
| 695 | list_for_each_entry(cm, &cm_list, entry) { | 860 | list_for_each_entry(cm, &cm_list, entry) { |
| 861 | _cm_fbchk_in_suspend(cm); | ||
| 862 | |||
| 696 | if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) || | 863 | if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) || |
| 697 | cm->status_save_batt != is_batt_present(cm)) { | 864 | cm->status_save_batt != is_batt_present(cm)) { |
| 698 | ret = false; | 865 | ret = false; |
| @@ -796,6 +963,21 @@ static int charger_manager_probe(struct platform_device *pdev) | |||
| 796 | memcpy(cm->desc, desc, sizeof(struct charger_desc)); | 963 | memcpy(cm->desc, desc, sizeof(struct charger_desc)); |
| 797 | cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ | 964 | cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ |
| 798 | 965 | ||
| 966 | /* | ||
| 967 | * The following two do not need to be errors. | ||
| 968 | * Users may intentionally ignore those two features. | ||
| 969 | */ | ||
| 970 | if (desc->fullbatt_uV == 0) { | ||
| 971 | dev_info(&pdev->dev, "Ignoring full-battery voltage threshold" | ||
| 972 | " as it is not supplied."); | ||
| 973 | } | ||
| 974 | if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) { | ||
| 975 | dev_info(&pdev->dev, "Disabling full-battery voltage drop " | ||
| 976 | "checking mechanism as it is not supplied."); | ||
| 977 | desc->fullbatt_vchkdrop_ms = 0; | ||
| 978 | desc->fullbatt_vchkdrop_uV = 0; | ||
| 979 | } | ||
| 980 | |||
| 799 | if (!desc->charger_regulators || desc->num_charger_regulators < 1) { | 981 | if (!desc->charger_regulators || desc->num_charger_regulators < 1) { |
| 800 | ret = -EINVAL; | 982 | ret = -EINVAL; |
| 801 | dev_err(&pdev->dev, "charger_regulators undefined.\n"); | 983 | dev_err(&pdev->dev, "charger_regulators undefined.\n"); |
| @@ -903,6 +1085,8 @@ static int charger_manager_probe(struct platform_device *pdev) | |||
| 903 | cm->charger_psy.num_properties++; | 1085 | cm->charger_psy.num_properties++; |
| 904 | } | 1086 | } |
| 905 | 1087 | ||
| 1088 | INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); | ||
| 1089 | |||
| 906 | ret = power_supply_register(NULL, &cm->charger_psy); | 1090 | ret = power_supply_register(NULL, &cm->charger_psy); |
| 907 | if (ret) { | 1091 | if (ret) { |
| 908 | dev_err(&pdev->dev, "Cannot register charger-manager with" | 1092 | dev_err(&pdev->dev, "Cannot register charger-manager with" |
| @@ -928,6 +1112,8 @@ static int charger_manager_probe(struct platform_device *pdev) | |||
| 928 | list_add(&cm->entry, &cm_list); | 1112 | list_add(&cm->entry, &cm_list); |
| 929 | mutex_unlock(&cm_list_mtx); | 1113 | mutex_unlock(&cm_list_mtx); |
| 930 | 1114 | ||
| 1115 | schedule_work(&setup_polling); | ||
| 1116 | |||
| 931 | return 0; | 1117 | return 0; |
| 932 | 1118 | ||
| 933 | err_chg_enable: | 1119 | err_chg_enable: |
| @@ -958,9 +1144,17 @@ static int __devexit charger_manager_remove(struct platform_device *pdev) | |||
| 958 | list_del(&cm->entry); | 1144 | list_del(&cm->entry); |
| 959 | mutex_unlock(&cm_list_mtx); | 1145 | mutex_unlock(&cm_list_mtx); |
| 960 | 1146 | ||
| 1147 | if (work_pending(&setup_polling)) | ||
| 1148 | cancel_work_sync(&setup_polling); | ||
| 1149 | if (delayed_work_pending(&cm_monitor_work)) | ||
| 1150 | cancel_delayed_work_sync(&cm_monitor_work); | ||
| 1151 | |||
| 961 | regulator_bulk_free(desc->num_charger_regulators, | 1152 | regulator_bulk_free(desc->num_charger_regulators, |
| 962 | desc->charger_regulators); | 1153 | desc->charger_regulators); |
| 963 | power_supply_unregister(&cm->charger_psy); | 1154 | power_supply_unregister(&cm->charger_psy); |
| 1155 | |||
| 1156 | try_charger_enable(cm, false); | ||
| 1157 | |||
| 964 | kfree(cm->charger_psy.properties); | 1158 | kfree(cm->charger_psy.properties); |
| 965 | kfree(cm->charger_stat); | 1159 | kfree(cm->charger_stat); |
| 966 | kfree(cm->desc); | 1160 | kfree(cm->desc); |
| @@ -1000,6 +1194,8 @@ static int cm_suspend_prepare(struct device *dev) | |||
| 1000 | cm_suspended = true; | 1194 | cm_suspended = true; |
| 1001 | } | 1195 | } |
| 1002 | 1196 | ||
| 1197 | if (delayed_work_pending(&cm->fullbatt_vchk_work)) | ||
| 1198 | cancel_delayed_work(&cm->fullbatt_vchk_work); | ||
| 1003 | cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm); | 1199 | cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm); |
| 1004 | cm->status_save_batt = is_batt_present(cm); | 1200 | cm->status_save_batt = is_batt_present(cm); |
| 1005 | 1201 | ||
| @@ -1027,6 +1223,33 @@ static void cm_suspend_complete(struct device *dev) | |||
| 1027 | cm_rtc_set = false; | 1223 | cm_rtc_set = false; |
| 1028 | } | 1224 | } |
| 1029 | 1225 | ||
| 1226 | /* Re-enqueue delayed work (fullbatt_vchk_work) */ | ||
| 1227 | if (cm->fullbatt_vchk_jiffies_at) { | ||
| 1228 | unsigned long delay = 0; | ||
| 1229 | unsigned long now = jiffies + CM_JIFFIES_SMALL; | ||
| 1230 | |||
| 1231 | if (time_after_eq(now, cm->fullbatt_vchk_jiffies_at)) { | ||
| 1232 | delay = (unsigned long)((long)now | ||
| 1233 | - (long)(cm->fullbatt_vchk_jiffies_at)); | ||
| 1234 | delay = jiffies_to_msecs(delay); | ||
| 1235 | } else { | ||
| 1236 | delay = 0; | ||
| 1237 | } | ||
| 1238 | |||
| 1239 | /* | ||
| 1240 | * Account for cm_suspend_duration_ms if | ||
| 1241 | * assume_timer_stops_in_suspend is active | ||
| 1242 | */ | ||
| 1243 | if (g_desc && g_desc->assume_timer_stops_in_suspend) { | ||
| 1244 | if (delay > cm_suspend_duration_ms) | ||
| 1245 | delay -= cm_suspend_duration_ms; | ||
| 1246 | else | ||
| 1247 | delay = 0; | ||
| 1248 | } | ||
| 1249 | |||
| 1250 | queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, | ||
| 1251 | msecs_to_jiffies(delay)); | ||
| 1252 | } | ||
| 1030 | uevent_notify(cm, NULL); | 1253 | uevent_notify(cm, NULL); |
| 1031 | } | 1254 | } |
| 1032 | 1255 | ||
| @@ -1048,12 +1271,18 @@ static struct platform_driver charger_manager_driver = { | |||
| 1048 | 1271 | ||
| 1049 | static int __init charger_manager_init(void) | 1272 | static int __init charger_manager_init(void) |
| 1050 | { | 1273 | { |
| 1274 | cm_wq = create_freezable_workqueue("charger_manager"); | ||
| 1275 | INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller); | ||
| 1276 | |||
| 1051 | return platform_driver_register(&charger_manager_driver); | 1277 | return platform_driver_register(&charger_manager_driver); |
| 1052 | } | 1278 | } |
| 1053 | late_initcall(charger_manager_init); | 1279 | late_initcall(charger_manager_init); |
| 1054 | 1280 | ||
| 1055 | static void __exit charger_manager_cleanup(void) | 1281 | static void __exit charger_manager_cleanup(void) |
| 1056 | { | 1282 | { |
| 1283 | destroy_workqueue(cm_wq); | ||
| 1284 | cm_wq = NULL; | ||
| 1285 | |||
| 1057 | platform_driver_unregister(&charger_manager_driver); | 1286 | platform_driver_unregister(&charger_manager_driver); |
| 1058 | } | 1287 | } |
| 1059 | module_exit(charger_manager_cleanup); | 1288 | module_exit(charger_manager_cleanup); |
