power: pm8921-bms: fix shutting down early at cold temperature

At cold temperatures the battery resistance is high. This high resistance
translates to a high unusable charge(UUC) value and causes early 0% state
of charge report. The phone survives for only about 20 minutes from a fully
charged state.

Fix this by slowly increasing/decreasing the UUC value as the battery
discharges/charges respectively. Remember the last uuc reported and that
forms the base from which we change it in steps.

Change-Id: I9c09bc8274de1c7b1a1fcae586653dd9e91e161f
Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org>
diff --git a/drivers/power/pm8921-bms.c b/drivers/power/pm8921-bms.c
index aa1a589..90f48f4 100644
--- a/drivers/power/pm8921-bms.c
+++ b/drivers/power/pm8921-bms.c
@@ -132,6 +132,9 @@
 	unsigned int		rconn_mohm;
 	struct mutex		last_ocv_uv_mutex;
 	int			last_ocv_uv;
+	int			last_cc_uah; /* used for Iavg calc for UUC */
+	struct timeval		t;
+	int			last_uuc_uah;
 };
 
 static struct pm8921_bms_chip *the_chip;
@@ -966,6 +969,8 @@
 		convert_vbatt_raw_to_uv(chip, usb_chg,
 			raw->last_good_ocv_raw, &raw->last_good_ocv_uv);
 		chip->last_ocv_uv = raw->last_good_ocv_uv;
+		/* forget the old cc value upon ocv */
+		chip->last_cc_uah = 0;
 	} else {
 		raw->last_good_ocv_uv = chip->last_ocv_uv;
 	}
@@ -1147,20 +1152,128 @@
 	*val = cc_uah;
 }
 
-static int calculate_unusable_charge_uah(struct pm8921_bms_chip *chip,
-				int rbatt, int fcc_uah,
-				int batt_temp, int chargecycles)
+static int calculate_uuc_uah_at_given_current(struct pm8921_bms_chip *chip,
+				 int batt_temp, int chargecycles,
+				int rbatt, int fcc_uah, int i_ma)
 {
-	int voltage_unusable_uv, pc_unusable;
+	int unusable_uv, pc_unusable, uuc;
 
-	/* calculate unusable charge */
-	voltage_unusable_uv = (rbatt * chip->i_test)
-						+ (chip->v_failure * 1000);
-	pc_unusable = calculate_pc(chip, voltage_unusable_uv,
-						batt_temp, chargecycles);
-	pr_debug("rbatt = %umilliOhms unusable_v =%d unusable_pc = %d\n",
-			rbatt, voltage_unusable_uv, pc_unusable);
-	return (fcc_uah * pc_unusable) / 100;
+	/* calculate unusable charge with itest */
+	unusable_uv = (rbatt * i_ma) + (chip->v_failure * 1000);
+	pc_unusable = calculate_pc(chip, unusable_uv, batt_temp, chargecycles);
+	uuc = (fcc_uah * pc_unusable) / 100;
+	pr_debug("For i_ma = %d, unusable_uv = %d unusable_pc = %d uuc = %d\n",
+					i_ma, unusable_uv, pc_unusable, uuc);
+	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)
+{
+	struct timeval now;
+	int delta_time_s;
+	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;
+
+	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);
+	} else {
+		/* uuc calculation for the first time */
+		delta_time_s = 0;
+		firsttime = 1;
+	}
+
+	if (delta_time_s != 0)
+		iavg_ua = div_s64((s64)delta_cc_uah * 3600, delta_time_s);
+	else
+		iavg_ua = 0;
+
+	iavg_ma = iavg_ua/1000;
+
+	pr_debug("t.tv_sec = %d, now.tv_sec = %d\n", (int)chip->t.tv_sec,
+				(int)now.tv_sec);
+
+	pr_debug("delta_time_s = %d iavg_ma = %d\n", delta_time_s, iavg_ma);
+
+	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;
+	}
+
+	/* calculate unusable charge with itest */
+	uuc_uah_itest = calculate_uuc_uah_at_given_current(chip,
+					batt_temp, chargecycles,
+					rbatt, fcc_uah, chip->i_test);
+
+	pr_debug("itest = %d uuc_itest = %d\n", chip->i_test, uuc_uah_itest);
+
+	/* calculate unusable charge with iavg */
+	iavg_ma = max(0, iavg_ma);
+	uuc_uah_iavg = calculate_uuc_uah_at_given_current(chip,
+					batt_temp, chargecycles,
+					rbatt, fcc_uah, iavg_ma);
+	pr_debug("iavg = %d uuc_iavg = %d\n", iavg_ma, uuc_uah_iavg);
+
+	if (firsttime) {
+		if (cc_uah < chip->last_cc_uah)
+			chip->last_uuc_uah = uuc_uah_itest;
+		else
+			chip->last_uuc_uah = uuc_uah_iavg;
+		pr_debug("firsttime uuc_prev = %d\n", chip->last_uuc_uah);
+	}
+
+	uuc_now = min(uuc_uah_itest, uuc_uah_iavg);
+
+	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;
+
+	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;
+
+	return uuc_reported;
 }
 
 /* calculate remainging charge at the time of ocv */
@@ -1211,7 +1324,9 @@
 	*rbatt = get_rbatt(chip, soc_rbatt, batt_temp);
 
 	*unusable_charge_uah = calculate_unusable_charge_uah(chip, *rbatt,
-					*fcc_uah, batt_temp, chargecycles);
+					*fcc_uah, *cc_uah, soc_rbatt,
+					batt_temp,
+					chargecycles);
 	pr_debug("UUC = %uuAh\n", *unusable_charge_uah);
 }
 
@@ -1745,6 +1860,11 @@
 
 		the_chip->last_ocv_uv = the_chip->max_voltage_uv;
 		raw.last_good_ocv_uv = the_chip->max_voltage_uv;
+		/*
+		 * since we are treating this as an ocv event
+		 * forget the old cc value
+		 */
+		the_chip->last_cc_uah = 0;
 		pr_debug("EOC ocv_reading = 0x%x cc = 0x%x\n",
 				the_chip->ocv_reading_at_100,
 				the_chip->cc_reading_at_100);