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); |