power: pm8921-charger: add support for invalid battery

Use adc reading on the battery_id resistor to check if a battery
is valid. If it is not disable charging.

Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org>
diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c
index b38f2ec..b2b3fee 100644
--- a/drivers/mfd/pm8921-core.c
+++ b/drivers/mfd/pm8921-core.c
@@ -429,6 +429,8 @@
 		pdata->charger_pdata->charger_cdata.vbat_channel = CHANNEL_VBAT;
 		pdata->charger_pdata->charger_cdata.batt_temp_channel
 						= CHANNEL_BATT_THERM;
+		pdata->charger_pdata->charger_cdata.batt_id_channel
+						= CHANNEL_BATT_ID;
 		charger_cell.platform_data = pdata->charger_pdata;
 		charger_cell.pdata_size =
 				sizeof(struct pm8921_charger_platform_data);
diff --git a/drivers/power/pm8921-charger.c b/drivers/power/pm8921-charger.c
index ec62c83..0677bcc 100644
--- a/drivers/power/pm8921-charger.c
+++ b/drivers/power/pm8921-charger.c
@@ -175,12 +175,16 @@
 	unsigned int		term_current;
 	unsigned int		vbat_channel;
 	unsigned int		batt_temp_channel;
+	unsigned int		batt_id_channel;
 	struct power_supply	usb_psy;
 	struct power_supply	dc_psy;
 	struct power_supply	batt_psy;
 	struct dentry		*dent;
 	struct bms_notify	bms_notify;
 	DECLARE_BITMAP(enabled_irqs, PM_CHG_MAX_INTS);
+	struct work_struct	battery_id_valid_work;
+	int64_t			batt_id_min;
+	int64_t			batt_id_max;
 };
 
 static int charging_disabled;
@@ -439,6 +443,62 @@
 					 temp);
 }
 
+static int64_t read_battery_id(struct pm8921_chg_chip *chip)
+{
+	int rc;
+	struct pm8921_adc_chan_result result;
+
+	rc = pm8921_adc_read(chip->batt_id_channel, &result);
+	if (rc) {
+		pr_err("error reading batt id channel = %d, rc = %d\n",
+					chip->vbat_channel, rc);
+		return rc;
+	}
+	pr_debug("batt_id phy = %lld meas = 0x%llx\n", result.physical,
+						result.measurement);
+	return result.physical;
+}
+
+static int is_battery_valid(struct pm8921_chg_chip *chip)
+{
+	int64_t rc;
+
+	if (chip->batt_id_min == 0 && chip->batt_id_max == 0)
+		return 1;
+
+	rc = read_battery_id(chip);
+	if (rc < 0) {
+		pr_err("error reading batt id channel = %d, rc = %lld\n",
+					chip->vbat_channel, rc);
+		/* assume battery id is valid when adc error happens */
+		return 1;
+	}
+
+	if (rc < chip->batt_id_min || rc > chip->batt_id_max) {
+		pr_err("batt_id phy =%lld is not valid\n", rc);
+		return 0;
+	}
+	return 1;
+}
+
+static void check_battery_valid(struct pm8921_chg_chip *chip)
+{
+	if (is_battery_valid(chip) == 0) {
+		pr_err("batt_id not valid, disbling charging\n");
+		pm_chg_auto_enable(chip, 0);
+	} else {
+		pm_chg_auto_enable(chip, !charging_disabled);
+	}
+}
+
+static void battery_id_valid(struct work_struct *work)
+{
+	struct pm8921_chg_chip *chip = container_of(work,
+				struct pm8921_chg_chip, battery_id_valid_work);
+
+	check_battery_valid(chip);
+}
+
 static void pm8921_chg_enable_irq(struct pm8921_chg_chip *chip, int interrupt)
 {
 	if (!__test_and_set_bit(interrupt, chip->enabled_irqs)) {
@@ -568,7 +628,7 @@
 					chip->vbat_channel, rc);
 		return rc;
 	}
-	pr_debug("mvolts phy = %lld meas = 0x%llx", result.physical,
+	pr_debug("mvolts phy = %lld meas = 0x%llx\n", result.physical,
 						result.measurement);
 	return (int)result.physical;
 }
@@ -977,8 +1037,8 @@
 	struct pm8921_chg_chip *chip = data;
 	int status;
 
-	status = pm_chg_get_rt_status(chip,
-				BATT_INSERTED_IRQ);
+	status = pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ);
+	schedule_work(&chip->battery_id_valid_work);
 	pr_debug("battery present=%d", status);
 	power_supply_changed(&chip->batt_psy);
 	return IRQ_HANDLED;
@@ -1284,6 +1344,8 @@
 		pm8921_bms_charging_began();
 	}
 
+	check_battery_valid(chip);
+
 	pr_debug("usb = %d, dc = %d  batt = %d state=%d\n",
 			chip->usb_present,
 			chip->dc_present,
@@ -1501,6 +1563,15 @@
 		pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xCE);
 		pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xD8);
 		pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xF1);
+
+		/* software workaround for correct battery_id detection */
+		pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_0, 0xFF);
+		pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_1, 0xFF);
+		pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_2, 0xFF);
+		pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_3, 0xFF);
+		pm8xxx_writeb(chip->dev->parent, PSI_CONFIG_STATUS, 0x0D);
+		udelay(100);
+		pm8xxx_writeb(chip->dev->parent, PSI_CONFIG_STATUS, 0x0C);
 	}
 
 	rc = pm_chg_charge_dis(chip, charging_disabled);
@@ -1674,6 +1745,9 @@
 	chip->term_current = pdata->term_current;
 	chip->vbat_channel = pdata->charger_cdata.vbat_channel;
 	chip->batt_temp_channel = pdata->charger_cdata.batt_temp_channel;
+	chip->batt_id_channel = pdata->charger_cdata.batt_id_channel;
+	chip->batt_id_min = pdata->batt_id_min;
+	chip->batt_id_max = pdata->batt_id_max;
 
 	rc = pm8921_chg_hw_init(chip);
 	if (rc) {
@@ -1736,6 +1810,7 @@
 	create_debugfs_entries(chip);
 
 	INIT_WORK(&chip->bms_notify.work, bms_notify);
+	INIT_WORK(&chip->battery_id_valid_work, battery_id_valid);
 	/* determine what state the charger is in */
 	determine_initial_state(chip);
 
diff --git a/include/linux/mfd/pm8xxx/pm8921-charger.h b/include/linux/mfd/pm8xxx/pm8921-charger.h
index 8119bfb..24adc7c 100644
--- a/include/linux/mfd/pm8xxx/pm8921-charger.h
+++ b/include/linux/mfd/pm8xxx/pm8921-charger.h
@@ -20,6 +20,7 @@
 struct pm8xxx_charger_core_data {
 	unsigned int	vbat_channel;
 	unsigned int	batt_temp_channel;
+	unsigned int	batt_id_channel;
 };
 
 /**
@@ -49,6 +50,8 @@
 	unsigned int			resume_voltage;
 	unsigned int			term_current;
 	unsigned int			(*get_batt_capacity_percent) (void);
+	int64_t				batt_id_min;
+	int64_t				batt_id_max;
 };
 
 enum pm8921_charger_source {