power: pm8921-charger: enable battery alarm

During UVLO events the charger hardware may lock up
if it is in hardware clock switching mode.

To fix this add a battery alarm with a lower threshold
that disables hardware clock switching.

When the system is awake the resume callback ensures
that the charger is running off of the 19.2 MhZ clock
which does not allow the charger to lock up. Therefore
if the battery alarm wakes up the device before hitting
UVLO the charger hardware will not be in hardware clock
switching mode.

CRs-Fixed: 374607
Signed-off-by: David Keitel <dkeitel@codeaurora.org>
Signed-off-by: Ajay Dudani <adudani@codeaurora.org>

Change-Id: I1bb20b2e7ff1a11f032b12b4abd4804ed130fe4a
diff --git a/drivers/power/pm8921-charger.c b/drivers/power/pm8921-charger.c
index 89786e1..1fddf53 100644
--- a/drivers/power/pm8921-charger.c
+++ b/drivers/power/pm8921-charger.c
@@ -27,6 +27,7 @@
 #include <linux/workqueue.h>
 #include <linux/debugfs.h>
 #include <linux/slab.h>
+#include <linux/mfd/pm8xxx/batt-alarm.h>
 
 #include <mach/msm_xo.h>
 #include <mach/msm_hsusb.h>
@@ -118,6 +119,13 @@
 	int			batt_state;
 };
 
+static int pm8921_battery_gauge_alarm_notify(struct notifier_block *nb,
+					  unsigned long status, void *unused);
+
+static struct notifier_block alarm_notifier = {
+	.notifier_call = pm8921_battery_gauge_alarm_notify,
+};
+
 static struct fsm_state_to_batt_status map[] = {
 	{FSM_STATE_OFF_0, POWER_SUPPLY_STATUS_UNKNOWN},
 	{FSM_STATE_BATFETDET_START_12, POWER_SUPPLY_STATUS_UNKNOWN},
@@ -208,6 +216,7 @@
  * @max_voltage_mv:		the max volts the batt should be charged up to
  * @min_voltage_mv:		the min battery voltage before turning the FETon
  * @uvd_voltage_mv:		(PM8917 only) the falling UVD threshold voltage
+ * @alarm_voltage_mv:		the battery alarm voltage
  * @cool_temp_dc:		the cool temp threshold in deciCelcius
  * @warm_temp_dc:		the warm temp threshold in deciCelcius
  * @resume_voltage_delta:	the voltage delta from vdd max at which the
@@ -228,6 +237,7 @@
 	unsigned int			max_voltage_mv;
 	unsigned int			min_voltage_mv;
 	unsigned int			uvd_voltage_mv;
+	unsigned int			alarm_voltage_mv;
 	int				cool_temp_dc;
 	int				warm_temp_dc;
 	unsigned int			temp_check_period;
@@ -252,6 +262,8 @@
 	bool				ext_charging;
 	bool				ext_charge_done;
 	bool				iusb_fine_res;
+	bool				dc_unplug_check;
+	bool				disable_hw_clock_switching;
 	DECLARE_BITMAP(enabled_irqs, PM_CHG_MAX_INTS);
 	struct work_struct		battery_id_valid_work;
 	int64_t				batt_id_min;
@@ -1989,6 +2001,75 @@
 	return get_prop_batt_temp(the_chip);
 }
 
+static int pm8921_charger_enable_batt_alarm(struct pm8921_chg_chip *chip)
+{
+	int rc = 0;
+
+	rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR);
+	if (!rc)
+		rc = pm8xxx_batt_alarm_enable(
+			PM8XXX_BATT_ALARM_LOWER_COMPARATOR);
+	if (rc) {
+		pr_err("unable to set batt alarm state rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+static int pm8921_charger_configure_batt_alarm(struct pm8921_chg_chip *chip)
+{
+	int rc = 0;
+
+	rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR);
+	if (!rc)
+		rc = pm8xxx_batt_alarm_disable(
+			PM8XXX_BATT_ALARM_LOWER_COMPARATOR);
+	if (rc) {
+		pr_err("unable to set batt alarm state rc=%d\n", rc);
+		return rc;
+	}
+
+	/*
+	 * The batt-alarm driver requires sane values for both min / max,
+	 * regardless of whether they're both activated.
+	 */
+	rc = pm8xxx_batt_alarm_threshold_set(
+			PM8XXX_BATT_ALARM_LOWER_COMPARATOR,
+					chip->alarm_voltage_mv);
+	/* We only handle the lower limit of the battery alarm, thus
+	 * set a high sane maximum.
+	 */
+	if (!rc)
+		rc = pm8xxx_batt_alarm_threshold_set(
+			PM8XXX_BATT_ALARM_UPPER_COMPARATOR, 5000);
+	if (rc) {
+		pr_err("unable to set batt alarm threshold rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = pm8xxx_batt_alarm_hold_time_set(
+				PM8XXX_BATT_ALARM_HOLD_TIME_16_MS);
+	if (rc) {
+		pr_err("unable to set batt alarm hold time rc=%d\n", rc);
+		return rc;
+	}
+
+	/* PWM enabled at 2Hz */
+	rc = pm8xxx_batt_alarm_pwm_rate_set(1, 7, 4);
+	if (rc) {
+		pr_err("unable to set batt alarm pwm rate rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = pm8xxx_batt_alarm_register_notifier(&alarm_notifier);
+	if (rc) {
+		pr_err("unable to register alarm notifier rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
 static void handle_usb_insertion_removal(struct pm8921_chg_chip *chip)
 {
 	int usb_present;
@@ -2124,6 +2205,47 @@
 	}
 }
 
+static int pm8921_battery_gauge_alarm_notify(struct notifier_block *nb,
+		unsigned long status, void *unused)
+{
+	int rc, fsm_state;
+
+	pr_info("status: %lu\n", status);
+
+	/* Check if called before init */
+
+	switch (status) {
+	case 0:
+		pr_err("spurious interrupt\n");
+		break;
+	/* expected case - trip of low threshold */
+	case 1:
+		if (!the_chip) {
+			pr_err("not initialized\n");
+			return -EINVAL;
+		}
+
+		fsm_state = pm_chg_get_fsm_state(the_chip);
+		the_chip->disable_hw_clock_switching = 1;
+
+		rc = pm8xxx_batt_alarm_disable(
+				PM8XXX_BATT_ALARM_UPPER_COMPARATOR);
+		if (!rc)
+			rc = pm8xxx_batt_alarm_disable(
+				PM8XXX_BATT_ALARM_LOWER_COMPARATOR);
+		if (rc)
+			pr_err("unable to set alarm state rc=%d\n", rc);
+		break;
+	case 2:
+		pr_err("trip of high threshold\n");
+		break;
+	default:
+		pr_err("error received\n");
+	};
+
+	return 0;
+}
+
 static void turn_on_ovp_fet(struct pm8921_chg_chip *chip, u16 ovptestreg)
 {
 	u8 temp;
@@ -3029,6 +3151,18 @@
 		return;
 	}
 
+	/* If the disable hw clock switching
+	 * flag was set it can now be unset. Also, re-enable
+	 * the battery alarm to set the flag again when needed
+	 */
+	if (chip->disable_hw_clock_switching) {
+		/* Unset the hw clock switching flag */
+		chip->disable_hw_clock_switching = 0;
+
+		if (pm8921_charger_enable_batt_alarm(chip))
+			pr_err("couldn't set up batt alarm!\n");
+	}
+
 	if (end == CHG_FINISHED) {
 		count++;
 	} else {
@@ -4048,7 +4182,8 @@
 	rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON, 0);
 	if (rc)
 		pr_err("Failed to Force Vref therm off rc=%d\n", rc);
-	pm8921_chg_set_hw_clk_switching(chip);
+	if (!(chip->disable_hw_clock_switching))
+		pm8921_chg_set_hw_clk_switching(chip);
 	return 0;
 }
 
@@ -4131,6 +4266,7 @@
 	chip->ttrkl_time = pdata->ttrkl_time;
 	chip->update_time = pdata->update_time;
 	chip->max_voltage_mv = pdata->max_voltage;
+	chip->alarm_voltage_mv = pdata->alarm_voltage;
 	chip->min_voltage_mv = pdata->min_voltage;
 	chip->uvd_voltage_mv = pdata->uvd_thresh_voltage;
 	chip->resume_voltage_delta = pdata->resume_voltage_delta;
@@ -4250,6 +4386,17 @@
 		}
 	}
 
+	rc = pm8921_charger_configure_batt_alarm(chip);
+	if (rc) {
+		pr_err("Couldn't configure battery alarm! rc=%d\n", rc);
+		goto free_irq;
+	}
+
+	rc = pm8921_charger_enable_batt_alarm(chip);
+	if (rc) {
+		pr_err("Couldn't enable battery alarm! rc=%d\n", rc);
+		goto free_irq;
+	}
 	create_debugfs_entries(chip);
 
 	INIT_WORK(&chip->bms_notify.work, bms_notify);