power: pm8921-bms: fine tune the uuc scaling algorithm
For every soc calculations there are three unusable charge (uuc)
values.
uuc_uah_itest:
The uuc value in micro amp hour (uah) based on the max current
(itest). Typically this value is very high and changes as the
battery resistance changes
uuc_uah_iavg:
The uuc value based on the average current (iavg). Typically
this is lower than uuc_itest
uuc_reported:
The reported uuc based on state of charge (SOC) of the battery
which chooses a stepsize value and approaches towards
uuc_uah_itest or uuc_uah_iavg based on whether the device is
being charged.
The expectation is that the reported uuc increase as the battery looses SOC
and decrease as SOC approaches full condition.
To help us coming up with a satisfactory algorithm the following
parameters are introduced.
uuc_multiplier:
The multiplier used in stepsize calculation
uuc_iavg_div:
The divisor to use in calculation of stepsize when there
is a significant jump in average current from the
previous attempt
uuc_min_step_size:
The minimum stepsize to use if the calculated stepsize is very less
Also, in order to ignore the high bootup time load a timer is introduced
which makes the algorithm ignore iavg values upon bootup.
The algorithm also uses the previous uuc based on iavg in
"uuc_uah_iavg_prev". This aids in checking if there was a significant
increase in average current from the previous time uuc was calculated.
The algorithm keeps track of the previous reported uuc in last_uuc_uah.
Change-Id: I360fd74e7fd2c939dd67023c128b2d70da923e9a
Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org>
diff --git a/drivers/power/pm8921-bms.c b/drivers/power/pm8921-bms.c
index 85389d0..304dc6b 100644
--- a/drivers/power/pm8921-bms.c
+++ b/drivers/power/pm8921-bms.c
@@ -132,11 +132,14 @@
unsigned int rconn_mohm;
struct mutex last_ocv_uv_mutex;
int last_ocv_uv;
- int last_cc_uah; /* used for Iavg calc for UUC */
+ int last_cc_uah;
struct timeval t;
int last_uuc_uah;
int enable_fcc_learning;
int shutdown_soc;
+ int timer_uuc_expired;
+ struct delayed_work uuc_timer_work;
+ int uuc_uah_iavg_prev;
};
static int shutdown_soc_invalid;
@@ -1153,49 +1156,128 @@
return uuc;
}
-/* soc_rbatt when uuc_reported should be equal to uuc_now */
-#define SOC_RBATT_CHG 80
-#define SOC_RBATT_DISCHG 10
-static int calculate_unusable_charge_uah(struct pm8921_bms_chip *chip,
- int rbatt, int fcc_uah, int cc_uah,
- int soc_rbatt, int batt_temp, int chargecycles)
+#define SOC_RBATT_CHG 70
+#define SOC_RBATT_DISCHG 20
+
+static int uuc_iavg_div = 150;
+module_param(uuc_iavg_div, int, 0644);
+
+static int uuc_min_step_size = 120;
+module_param(uuc_min_step_size, int, 0644);
+
+static int uuc_multiplier = 1000;
+module_param(uuc_multiplier, int, 0644);
+
+#define UUC_TIMER_MS 120000
+
+static void uuc_timer_work(struct work_struct *work)
{
- struct timeval now;
- int delta_time_s;
+ struct pm8921_bms_chip *chip = container_of(work,
+ struct pm8921_bms_chip, uuc_timer_work.work);
+
+ pr_debug("UUC Timer expired\n");
+ /* indicates the system is done with the high load during bootup */
+ chip->timer_uuc_expired = 1;
+}
+
+static void calculate_iavg_ua(struct pm8921_bms_chip *chip, int cc_uah,
+ int *iavg_ua, int *delta_time_us)
+{
int delta_cc_uah;
- int iavg_ua, iavg_ma;
- int uuc_uah_itest, uuc_uah_iavg, uuc_now, uuc_reported;
- s64 stepsize = 0;
- int firsttime = 0;
+ struct timeval now;
delta_cc_uah = cc_uah - chip->last_cc_uah;
do_gettimeofday(&now);
if (chip->t.tv_sec != 0) {
- delta_time_s = (now.tv_sec - chip->t.tv_sec);
+ *delta_time_us = (now.tv_sec - chip->t.tv_sec) * USEC_PER_SEC
+ + now.tv_usec - chip->t.tv_usec;
} else {
- /* uuc calculation for the first time */
- delta_time_s = 0;
- firsttime = 1;
+ /* calculation for the first time */
+ *delta_time_us = 0;
}
- if (delta_time_s != 0)
- iavg_ua = div_s64((s64)delta_cc_uah * 3600, delta_time_s);
+ if (*delta_time_us != 0)
+ *iavg_ua = div_s64((s64)delta_cc_uah * 3600 * 1000000,
+ *delta_time_us);
else
- iavg_ua = 0;
+ *iavg_ua = 0;
- iavg_ma = iavg_ua/1000;
+ pr_debug("t.tv_sec = %d, now.tv_sec = %d delta_us = %d iavg_ua = %d\n",
+ (int)chip->t.tv_sec, (int)now.tv_sec,
+ *delta_time_us, (int)*iavg_ua);
+ /* remember cc_uah */
+ chip->last_cc_uah = cc_uah;
- pr_debug("t.tv_sec = %d, now.tv_sec = %d\n", (int)chip->t.tv_sec,
- (int)now.tv_sec);
+ /* remember this time */
+ chip->t = now;
+}
- pr_debug("delta_time_s = %d iavg_ma = %d\n", delta_time_s, iavg_ma);
+#define UUC_IAVG_THRESHOLD_UAH 50000
+static int scale_unusable_charge_uah(struct pm8921_bms_chip *chip,
+ bool charging, int uuc_uah_iavg, int uuc_uah_itest,
+ int uuc_uah_iavg_prev)
+{
+ int stepsize = 0;
+ int delta_uuc = 0;
+ int uuc_reported = 0;
- if (iavg_ma == 0) {
- pr_debug("Iavg = 0 returning last uuc = %d\n",
- chip->last_uuc_uah);
- uuc_reported = chip->last_uuc_uah;
- goto out;
+ if (charging) {
+ stepsize = max(uuc_min_step_size,
+ uuc_multiplier * (SOC_RBATT_CHG - last_soc));
+ /*
+ * set the delta only if uuc is decreasing. If it has increased
+ * simply report the last uuc since we don't want to report a
+ * higher uuc as charging progresses
+ */
+ if (chip->last_uuc_uah > uuc_uah_iavg)
+ delta_uuc = (chip->last_uuc_uah - uuc_uah_iavg)
+ / stepsize;
+ uuc_reported = chip->last_uuc_uah - delta_uuc;
+ } else {
+ stepsize = max(uuc_min_step_size,
+ uuc_multiplier * (last_soc - SOC_RBATT_DISCHG));
+ if (uuc_uah_itest > uuc_uah_iavg) {
+ if ((uuc_uah_iavg > uuc_uah_iavg_prev
+ + UUC_IAVG_THRESHOLD_UAH)
+ && chip->timer_uuc_expired)
+ /*
+ * there is a big jump in iavg current way past
+ * the bootup increase uuc to this high iavg
+ * based uuc in steps
+ */
+ delta_uuc = (uuc_uah_iavg - uuc_uah_iavg_prev)
+ / uuc_iavg_div;
+ else
+ /* increase uuc towards itest based uuc */
+ delta_uuc = (uuc_uah_itest - uuc_uah_iavg)
+ / stepsize;
+ } else {
+ /*
+ * the iavg based uuc was higher than itest based
+ * uuc. This means that iavg > itest. Itest represents
+ * the max current drawn from the device at anytime.
+ * If we find iavg > itest, ignore iavg and simply step
+ * up the uuc based on itest
+ */
+ delta_uuc = uuc_uah_itest / stepsize;
+ }
+ uuc_reported = min(uuc_uah_itest,
+ chip->last_uuc_uah + delta_uuc);
}
+ pr_debug("uuc_prev = %d stepsize = %d d_uuc = %d uuc_reported = %d\n",
+ chip->last_uuc_uah, (int)stepsize, delta_uuc,
+ uuc_reported);
+ return uuc_reported;
+}
+
+static int calculate_unusable_charge_uah(struct pm8921_bms_chip *chip,
+ int rbatt, int fcc_uah, int cc_uah,
+ int soc_rbatt, int batt_temp, int chargecycles,
+ int iavg_ua)
+{
+ int uuc_uah_itest, uuc_uah_iavg, uuc_reported;
+ static int firsttime = 1;
+ int iavg_ma = iavg_ua / 1000;
/* calculate unusable charge with itest */
uuc_uah_itest = calculate_uuc_uah_at_given_current(chip,
@@ -1212,6 +1294,8 @@
pr_debug("iavg = %d uuc_iavg = %d\n", iavg_ma, uuc_uah_iavg);
if (firsttime) {
+ chip->uuc_uah_iavg_prev = uuc_uah_iavg;
+
if (cc_uah < chip->last_cc_uah)
chip->last_uuc_uah = uuc_uah_itest;
else
@@ -1219,45 +1303,21 @@
pr_debug("firsttime uuc_prev = %d\n", chip->last_uuc_uah);
}
- uuc_now = min(uuc_uah_itest, uuc_uah_iavg);
+ uuc_reported = scale_unusable_charge_uah(chip,
+ cc_uah < chip->last_cc_uah,
+ uuc_uah_iavg, uuc_uah_itest,
+ chip->uuc_uah_iavg_prev);
- uuc_reported = -EINVAL;
- if (cc_uah < chip->last_cc_uah) {
- /* charging */
- if (uuc_now < chip->last_uuc_uah) {
- stepsize = max(1, (SOC_RBATT_CHG - soc_rbatt));
- /* uuc_reported = uuc_prev + deltauuc / stepsize */
- uuc_reported = div_s64 (stepsize * chip->last_uuc_uah
- + (uuc_now - chip->last_uuc_uah),
- stepsize);
- uuc_reported = max(0, uuc_reported);
- }
- } else {
- if (uuc_now > chip->last_uuc_uah) {
- stepsize = max(1, (soc_rbatt - SOC_RBATT_DISCHG));
- /* uuc_reported = uuc_prev + deltauuc / stepsize */
- uuc_reported = div_s64 (stepsize * chip->last_uuc_uah
- + (uuc_now - chip->last_uuc_uah),
- stepsize);
- uuc_reported = max(0, uuc_reported);
- }
- }
- if (uuc_reported == -EINVAL)
- uuc_reported = chip->last_uuc_uah;
+ /* remember the last uuc_uah_iavg */
+ chip->uuc_uah_iavg_prev = uuc_uah_iavg;
- pr_debug("uuc_now = %d uuc_prev = %d stepsize = %d uuc_reported = %d\n",
- uuc_now, chip->last_uuc_uah, (int)stepsize,
- uuc_reported);
-
-out:
/* remember the reported uuc */
chip->last_uuc_uah = uuc_reported;
- /* remember cc_uah */
- chip->last_cc_uah = cc_uah;
-
- /* remember this time */
- chip->t = now;
+ if (firsttime == 1) {
+ /* uuc calculation for the first time is done */
+ firsttime = 0;
+ }
return uuc_reported;
}
@@ -1283,7 +1343,9 @@
int *unusable_charge_uah,
int *remaining_charge_uah,
int *cc_uah,
- int *rbatt)
+ int *rbatt,
+ int *iavg_ua,
+ int *delta_time_us)
{
int soc_rbatt;
@@ -1309,10 +1371,11 @@
soc_rbatt = 0;
*rbatt = get_rbatt(chip, soc_rbatt, batt_temp);
+ calculate_iavg_ua(chip, *cc_uah, iavg_ua, delta_time_us);
+
*unusable_charge_uah = calculate_unusable_charge_uah(chip, *rbatt,
*fcc_uah, *cc_uah, soc_rbatt,
- batt_temp,
- chargecycles);
+ batt_temp, chargecycles, *iavg_ua);
pr_debug("UUC = %uuAh\n", *unusable_charge_uah);
}
@@ -1326,13 +1389,17 @@
int cc_uah;
int real_fcc_uah;
int rbatt;
+ int iavg_ua;
+ int delta_time_us;
calculate_soc_params(chip, raw, batt_temp, chargecycles,
&fcc_uah,
&unusable_charge_uah,
&remaining_charge_uah,
&cc_uah,
- &rbatt);
+ &rbatt,
+ &iavg_ua,
+ &delta_time_us);
real_fcc_uah = remaining_charge_uah - cc_uah;
*ret_fcc_uah = fcc_uah;
@@ -1522,13 +1589,17 @@
int cc_uah;
int rbatt;
int shutdown_adjusted_soc;
+ int iavg_ua;
+ int delta_time_us;
calculate_soc_params(chip, raw, batt_temp, chargecycles,
&fcc_uah,
&unusable_charge_uah,
&remaining_charge_uah,
&cc_uah,
- &rbatt);
+ &rbatt,
+ &iavg_ua,
+ &delta_time_us);
/* calculate remaining usable charge */
remaining_usable_charge_uah = remaining_charge_uah
@@ -1743,6 +1814,8 @@
int remaining_charge_uah;
int cc_uah;
int rbatt;
+ int iavg_ua;
+ int delta_time_us;
if (!the_chip) {
pr_err("called before initialization\n");
@@ -1768,7 +1841,9 @@
&unusable_charge_uah,
&remaining_charge_uah,
&cc_uah,
- &rbatt);
+ &rbatt,
+ &iavg_ua,
+ &delta_time_us);
mutex_unlock(&the_chip->last_ocv_uv_mutex);
return rbatt;
@@ -2704,6 +2779,10 @@
pm8921_bms_enable_irq(chip, PM8921_BMS_GOOD_OCV);
pm8921_bms_enable_irq(chip, PM8921_BMS_OCV_FOR_R);
+ INIT_DELAYED_WORK(&chip->uuc_timer_work, uuc_timer_work);
+ schedule_delayed_work(&chip->uuc_timer_work,
+ msecs_to_jiffies(UUC_TIMER_MS));
+
get_battery_uvolts(chip, &vbatt);
pr_info("OK battery_capacity_at_boot=%d volt = %d ocv = %d\n",
pm8921_bms_get_percent_charge(),