CHERRY-PICK: charger-manager: Poll battery health in normal state
authorChanwoo Choi <cw00.choi@samsung.com>
Sat, 5 May 2012 13:24:10 +0000 (06:24 -0700)
committerChromeBot <chrome-bot@google.com>
Wed, 9 Jan 2013 19:48:50 +0000 (11:48 -0800)
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>
(cherry picked from commit 6d193b13aee372996e45c34865aa37c3c603b228
in branch charger-manager-for-next of
git://git.infradead.org/users/kmpark/linux-2.6-samsung)
BUG=chrome-os-partner:10617
TEST=build and boot to kernel prompt on snow
Change-Id: Iab84dc96254dae448cd18d3ba4458c48203b3d13
Reviewed-on: https://gerrit.chromium.org/gerrit/40157
Reviewed-by: Sameer Nanda <snanda@chromium.org>
Commit-Queue: Simon Glass <sjg@chromium.org>
Tested-by: Simon Glass <sjg@chromium.org>
Documentation/power/charger-manager.txt
drivers/power/charger-manager.c
include/linux/power/charger-manager.h

index fdcca99..9b38633 100644 (file)
@@ -44,6 +44,12 @@ Charger Manager supports the following:
        Normally, the platform will need to resume and suspend some devices
        that are used by Charger Manager.
 
+* Support for premature full-battery event handling
+       If the battery voltage drops by "fullbatt_vchkdrop_uV" after
+       "fullbatt_vchkdrop_ms" from the full-battery event, the framework
+       restarts charging. This check is also performed while suspended by
+       setting wakeup time accordingly and using suspend_again.
+
 2. Global Charger-Manager Data related with suspend_again
 ========================================================
 In order to setup Charger Manager with suspend-again feature
@@ -55,7 +61,7 @@ if there are multiple batteries. If there are multiple batteries, the
 multiple instances of Charger Manager share the same charger_global_desc
 and it will manage in-suspend monitoring for all instances of Charger Manager.
 
-The user needs to provide all the two entries properly in order to activate
+The user needs to provide all the three entries properly in order to activate
 in-suspend monitoring:
 
 struct charger_global_desc {
@@ -74,6 +80,11 @@ bool (*rtc_only_wakeup)(void);
        same struct. If there is any other wakeup source triggered the
        wakeup, it should return false. If the "rtc" is the only wakeup
        reason, it should return true.
+
+bool assume_timer_stops_in_suspend;
+       : if true, Charger Manager assumes that
+       the timer (CM uses jiffies as timer) stops during suspend. Then, CM
+       assumes that the suspend-duration is same as the alarm length.
 };
 
 3. How to setup suspend_again
@@ -111,6 +122,16 @@ enum polling_modes polling_mode;
          CM_POLL_CHARGING_ONLY: poll this battery if and only if the
                                 battery is being charged.
 
+unsigned int fullbatt_vchkdrop_ms;
+unsigned int fullbatt_vchkdrop_uV;
+       : If both have non-zero values, Charger Manager will check the
+       battery voltage drop fullbatt_vchkdrop_ms after the battery is fully
+       charged. If the voltage drop is over fullbatt_vchkdrop_uV, Charger
+       Manager will try to recharge the battery by disabling and enabling
+       chargers. Recharge with voltage drop condition only (without delay
+       condition) is needed to be implemented with hardware interrupts from
+       fuel gauges or charger devices/chips.
+
 unsigned int fullbatt_uV;
        : If specified with a non-zero value, Charger Manager assumes
        that the battery is full (capacity = 100) if the battery is not being
@@ -122,6 +143,8 @@ unsigned int polling_interval_ms;
        this battery every polling_interval_ms or more frequently.
 
 enum data_source battery_present;
+       : CM_BATTERY_PRESENT: assume that the battery exists.
+       CM_NO_BATTERY: assume that the battery does not exists.
        CM_FUEL_GAUGE: get battery presence information from fuel gauge.
        CM_CHARGER_STAT: get battery presence from chargers.
 
index 9eca9f1..959062d 100644 (file)
@@ -57,6 +57,12 @@ static bool cm_suspended;
 static bool cm_rtc_set;
 static unsigned long cm_suspend_duration_ms;
 
+/* About normal (not suspended) monitoring */
+static unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */
+static unsigned long next_polling; /* Next appointed polling time */
+static struct workqueue_struct *cm_wq; /* init at driver add */
+static struct delayed_work cm_monitor_work; /* init at driver add */
+
 /* Global charger-manager description */
 static struct charger_global_desc *g_desc; /* init with setup_charger_manager */
 
@@ -71,6 +77,11 @@ static bool is_batt_present(struct charger_manager *cm)
        int i, ret;
 
        switch (cm->desc->battery_present) {
+       case CM_BATTERY_PRESENT:
+               present = true;
+               break;
+       case CM_NO_BATTERY:
+               break;
        case CM_FUEL_GAUGE:
                ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
                                POWER_SUPPLY_PROP_PRESENT, &val);
@@ -278,6 +289,26 @@ static int try_charger_enable(struct charger_manager *cm, bool enable)
        return err;
 }
 
+/**
+ * try_charger_restart - Restart charging.
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Restart charging by turning off and on the charger.
+ */
+static int try_charger_restart(struct charger_manager *cm)
+{
+       int err;
+
+       if (cm->emergency_stop)
+               return -EAGAIN;
+
+       err = try_charger_enable(cm, false);
+       if (err)
+               return err;
+
+       return try_charger_enable(cm, true);
+}
+
 /**
  * uevent_notify - Let users know something has changed.
  * @cm: the Charger Manager representing the battery.
@@ -333,6 +364,46 @@ static void uevent_notify(struct charger_manager *cm, const char *event)
        dev_info(cm->dev, event);
 }
 
+/**
+ * fullbatt_vchk - Check voltage drop some times after "FULL" event.
+ * @work: the work_struct appointing the function
+ *
+ * If a user has designated "fullbatt_vchkdrop_ms/uV" values with
+ * charger_desc, Charger Manager checks voltage drop after the battery
+ * "FULL" event. It checks whether the voltage has dropped more than
+ * fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms.
+ */
+static void fullbatt_vchk(struct work_struct *work)
+{
+       struct delayed_work *dwork = to_delayed_work(work);
+       struct charger_manager *cm = container_of(dwork,
+                       struct charger_manager, fullbatt_vchk_work);
+       struct charger_desc *desc = cm->desc;
+       int batt_uV, err, diff;
+
+       /* remove the appointment for fullbatt_vchk */
+       cm->fullbatt_vchk_jiffies_at = 0;
+
+       if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms)
+               return;
+
+       err = get_batt_uV(cm, &batt_uV);
+       if (err) {
+               dev_err(cm->dev, "%s: get_batt_uV error(%d).\n", __func__, err);
+               return;
+       }
+
+       diff = cm->fullbatt_vchk_uV;
+       diff -= batt_uV;
+
+       dev_dbg(cm->dev, "VBATT dropped %duV after full-batt.\n", diff);
+
+       if (diff > desc->fullbatt_vchkdrop_uV) {
+               try_charger_restart(cm);
+               uevent_notify(cm, "Recharge");
+       }
+}
+
 /**
  * _cm_monitor - Monitor the temperature and return true for exceptions.
  * @cm: the Charger Manager representing the battery.
@@ -392,6 +463,68 @@ static bool cm_monitor(void)
        return stop;
 }
 
+/**
+ * _setup_polling - Setup the next instance of polling.
+ * @work: work_struct of the function _setup_polling.
+ */
+static void _setup_polling(struct work_struct *work)
+{
+       unsigned long min = ULONG_MAX;
+       struct charger_manager *cm;
+       bool keep_polling = false;
+       unsigned long _next_polling;
+
+       mutex_lock(&cm_list_mtx);
+
+       list_for_each_entry(cm, &cm_list, entry) {
+               if (is_polling_required(cm) && cm->desc->polling_interval_ms) {
+                       keep_polling = true;
+
+                       if (min > cm->desc->polling_interval_ms)
+                               min = cm->desc->polling_interval_ms;
+               }
+       }
+
+       polling_jiffy = msecs_to_jiffies(min);
+       if (polling_jiffy <= CM_JIFFIES_SMALL)
+               polling_jiffy = CM_JIFFIES_SMALL + 1;
+
+       if (!keep_polling)
+               polling_jiffy = ULONG_MAX;
+       if (polling_jiffy == ULONG_MAX)
+               goto out;
+
+       WARN(cm_wq == NULL, "charger-manager: workqueue not initialized"
+                           ". try it later. %s\n", __func__);
+
+       _next_polling = jiffies + polling_jiffy;
+
+       if (!delayed_work_pending(&cm_monitor_work) ||
+           (delayed_work_pending(&cm_monitor_work) &&
+            time_after(next_polling, _next_polling))) {
+               cancel_delayed_work_sync(&cm_monitor_work);
+               next_polling = jiffies + polling_jiffy;
+               queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy);
+       }
+
+out:
+       mutex_unlock(&cm_list_mtx);
+}
+static DECLARE_WORK(setup_polling, _setup_polling);
+
+/**
+ * cm_monitor_poller - The Monitor / Poller.
+ * @work: work_struct of the function cm_monitor_poller
+ *
+ * During non-suspended state, cm_monitor_poller is used to poll and monitor
+ * the batteries.
+ */
+static void cm_monitor_poller(struct work_struct *work)
+{
+       cm_monitor();
+       schedule_work(&setup_polling);
+}
+
 static int charger_get_property(struct power_supply *psy,
                enum power_supply_property psp,
                union power_supply_propval *val)
@@ -613,6 +746,21 @@ static bool cm_setup_timer(void)
        mutex_lock(&cm_list_mtx);
 
        list_for_each_entry(cm, &cm_list, entry) {
+               unsigned int fbchk_ms = 0;
+
+               /* fullbatt_vchk is required. setup timer for that */
+               if (cm->fullbatt_vchk_jiffies_at) {
+                       fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at
+                                                   - jiffies);
+                       if (time_is_before_eq_jiffies(
+                               cm->fullbatt_vchk_jiffies_at) ||
+                               msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) {
+                               fullbatt_vchk(&cm->fullbatt_vchk_work.work);
+                               fbchk_ms = 0;
+                       }
+               }
+               CM_MIN_VALID(wakeup_ms, fbchk_ms);
+
                /* Skip if polling is not required for this CM */
                if (!is_polling_required(cm) && !cm->emergency_stop)
                        continue;
@@ -672,6 +820,23 @@ static bool cm_setup_timer(void)
        return false;
 }
 
+static void _cm_fbchk_in_suspend(struct charger_manager *cm)
+{
+       unsigned long jiffy_now = jiffies;
+
+       if (!cm->fullbatt_vchk_jiffies_at)
+               return;
+
+       if (g_desc && g_desc->assume_timer_stops_in_suspend)
+               jiffy_now += msecs_to_jiffies(cm_suspend_duration_ms);
+
+       /* Execute now if it's going to be executed not too long after */
+       jiffy_now += CM_JIFFIES_SMALL;
+
+       if (time_after_eq(jiffy_now, cm->fullbatt_vchk_jiffies_at))
+               fullbatt_vchk(&cm->fullbatt_vchk_work.work);
+}
+
 /**
  * cm_suspend_again - Determine whether suspend again or not
  *
@@ -693,6 +858,8 @@ bool cm_suspend_again(void)
        ret = true;
        mutex_lock(&cm_list_mtx);
        list_for_each_entry(cm, &cm_list, entry) {
+               _cm_fbchk_in_suspend(cm);
+
                if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) ||
                    cm->status_save_batt != is_batt_present(cm)) {
                        ret = false;
@@ -796,6 +963,21 @@ static int charger_manager_probe(struct platform_device *pdev)
        memcpy(cm->desc, desc, sizeof(struct charger_desc));
        cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */
 
+       /*
+        * The following two do not need to be errors.
+        * Users may intentionally ignore those two features.
+        */
+       if (desc->fullbatt_uV == 0) {
+               dev_info(&pdev->dev, "Ignoring full-battery voltage threshold"
+                                       " as it is not supplied.");
+       }
+       if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) {
+               dev_info(&pdev->dev, "Disabling full-battery voltage drop "
+                               "checking mechanism as it is not supplied.");
+               desc->fullbatt_vchkdrop_ms = 0;
+               desc->fullbatt_vchkdrop_uV = 0;
+       }
+
        if (!desc->charger_regulators || desc->num_charger_regulators < 1) {
                ret = -EINVAL;
                dev_err(&pdev->dev, "charger_regulators undefined.\n");
@@ -903,6 +1085,8 @@ static int charger_manager_probe(struct platform_device *pdev)
                cm->charger_psy.num_properties++;
        }
 
+       INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
+
        ret = power_supply_register(NULL, &cm->charger_psy);
        if (ret) {
                dev_err(&pdev->dev, "Cannot register charger-manager with"
@@ -928,6 +1112,8 @@ static int charger_manager_probe(struct platform_device *pdev)
        list_add(&cm->entry, &cm_list);
        mutex_unlock(&cm_list_mtx);
 
+       schedule_work(&setup_polling);
+
        return 0;
 
 err_chg_enable:
@@ -958,9 +1144,17 @@ static int __devexit charger_manager_remove(struct platform_device *pdev)
        list_del(&cm->entry);
        mutex_unlock(&cm_list_mtx);
 
+       if (work_pending(&setup_polling))
+               cancel_work_sync(&setup_polling);
+       if (delayed_work_pending(&cm_monitor_work))
+               cancel_delayed_work_sync(&cm_monitor_work);
+
        regulator_bulk_free(desc->num_charger_regulators,
                            desc->charger_regulators);
        power_supply_unregister(&cm->charger_psy);
+
+       try_charger_enable(cm, false);
+
        kfree(cm->charger_psy.properties);
        kfree(cm->charger_stat);
        kfree(cm->desc);
@@ -1000,6 +1194,8 @@ static int cm_suspend_prepare(struct device *dev)
                cm_suspended = true;
        }
 
+       if (delayed_work_pending(&cm->fullbatt_vchk_work))
+               cancel_delayed_work(&cm->fullbatt_vchk_work);
        cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm);
        cm->status_save_batt = is_batt_present(cm);
 
@@ -1027,6 +1223,33 @@ static void cm_suspend_complete(struct device *dev)
                cm_rtc_set = false;
        }
 
+       /* Re-enqueue delayed work (fullbatt_vchk_work) */
+       if (cm->fullbatt_vchk_jiffies_at) {
+               unsigned long delay = 0;
+               unsigned long now = jiffies + CM_JIFFIES_SMALL;
+
+               if (time_after_eq(now, cm->fullbatt_vchk_jiffies_at)) {
+                       delay = (unsigned long)((long)now
+                               - (long)(cm->fullbatt_vchk_jiffies_at));
+                       delay = jiffies_to_msecs(delay);
+               } else {
+                       delay = 0;
+               }
+
+               /*
+                * Account for cm_suspend_duration_ms if
+                * assume_timer_stops_in_suspend is active
+                */
+               if (g_desc && g_desc->assume_timer_stops_in_suspend) {
+                       if (delay > cm_suspend_duration_ms)
+                               delay -= cm_suspend_duration_ms;
+                       else
+                               delay = 0;
+               }
+
+               queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work,
+                                  msecs_to_jiffies(delay));
+       }
        uevent_notify(cm, NULL);
 }
 
@@ -1048,12 +1271,18 @@ static struct platform_driver charger_manager_driver = {
 
 static int __init charger_manager_init(void)
 {
+       cm_wq = create_freezable_workqueue("charger_manager");
+       INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller);
+
        return platform_driver_register(&charger_manager_driver);
 }
 late_initcall(charger_manager_init);
 
 static void __exit charger_manager_cleanup(void)
 {
+       destroy_workqueue(cm_wq);
+       cm_wq = NULL;
+
        platform_driver_unregister(&charger_manager_driver);
 }
 module_exit(charger_manager_cleanup);
index 4f75e53..baa299a 100644 (file)
@@ -18,6 +18,8 @@
 #include <linux/power_supply.h>
 
 enum data_source {
+       CM_BATTERY_PRESENT,
+       CM_NO_BATTERY,
        CM_FUEL_GAUGE,
        CM_CHARGER_STAT,
 };
@@ -38,11 +40,18 @@ enum polling_modes {
  *     rtc_only_wakeup() returning false.
  *     If the RTC given to CM is the only wakeup reason,
  *     rtc_only_wakeup should return true.
+ * @assume_timer_stops_in_suspend:
+ *     Assume that the jiffy timer stops in suspend-to-RAM.
+ *     When enabled, CM does not rely on jiffies value in
+ *     suspend_again and assumes that jiffies value does not
+ *     change during suspend.
  */
 struct charger_global_desc {
        char *rtc_name;
 
        bool (*rtc_only_wakeup)(void);
+
+       bool assume_timer_stops_in_suspend;
 };
 
 /**
@@ -50,6 +59,11 @@ struct charger_global_desc {
  * @psy_name: the name of power-supply-class for charger manager
  * @polling_mode:
  *     Determine which polling mode will be used
+ * @fullbatt_vchkdrop_ms:
+ * @fullbatt_vchkdrop_uV:
+ *     Check voltage drop after the battery is fully charged.
+ *     If it has dropped more than fullbatt_vchkdrop_uV after
+ *     fullbatt_vchkdrop_ms, CM will restart charging.
  * @fullbatt_uV: voltage in microvolt
  *     If it is not being charged and VBATT >= fullbatt_uV,
  *     it is assumed to be full.
@@ -76,6 +90,8 @@ struct charger_desc {
        enum polling_modes polling_mode;
        unsigned int polling_interval_ms;
 
+       unsigned int fullbatt_vchkdrop_ms;
+       unsigned int fullbatt_vchkdrop_uV;
        unsigned int fullbatt_uV;
 
        enum data_source battery_present;
@@ -101,6 +117,11 @@ struct charger_desc {
  * @fuel_gauge: power_supply for fuel gauge
  * @charger_stat: array of power_supply for chargers
  * @charger_enabled: the state of charger
+ * @fullbatt_vchk_jiffies_at:
+ *     jiffies at the time full battery check will occur.
+ * @fullbatt_vchk_uV: voltage in microvolt
+ *     criteria for full battery
+ * @fullbatt_vchk_work: work queue for full battery check
  * @emergency_stop:
  *     When setting true, stop charging
  * @last_temp_mC: the measured temperature in milli-Celsius
@@ -121,6 +142,10 @@ struct charger_manager {
 
        bool charger_enabled;
 
+       unsigned long fullbatt_vchk_jiffies_at;
+       unsigned int fullbatt_vchk_uV;
+       struct delayed_work fullbatt_vchk_work;
+
        int emergency_stop;
        int last_temp_mC;