max1720x_battery: switch RC version for MixCap issue

MixCap increased abnormally during FG empty detection, switch to RC1
and disable learning if soc < 20% or temperature under 5 degC

Bug: 213425610
Signed-off-by: Jenny Ho <[email protected]>
Change-Id: I6dbeb332d2046b04978c6168a869f1c553c21028
diff --git a/max1720x_battery.c b/max1720x_battery.c
index 6cb2b5a..94e6d12 100644
--- a/max1720x_battery.c
+++ b/max1720x_battery.c
@@ -100,6 +100,17 @@
 	int start_vfsoc;
 };
 
+struct max1720x_rc_switch {
+	struct delayed_work switch_work;
+	bool available;
+	bool enable;
+	int soc;
+	int temp;
+	u16 rc1_tempco;
+	u16 rc2_tempco;
+	u16 rc2_learncfg;
+};
+
 #define DEFAULT_BATTERY_ID		0
 #define DEFAULT_BATTERY_ID_RETRIES	5
 
@@ -247,6 +258,7 @@
 	int bhi_fcn_count;
 	int bhi_acim;
 
+	struct max1720x_rc_switch rc_switch;
 };
 
 #define MAX1720_EMPTY_VOLTAGE(profile, temp, cycle) \
@@ -1065,6 +1077,39 @@
 
 static const DEVICE_ATTR_RO(resistance);
 
+static ssize_t rc_switch_enable_store(struct device *dev, struct device_attribute *attr,
+				      const char *buff, size_t count)
+{
+	struct power_supply *psy = container_of(dev, struct power_supply, dev);
+	struct max1720x_chip *chip = power_supply_get_drvdata(psy);
+	bool curr_enable = chip->rc_switch.enable;
+	int ret;
+
+	if (kstrtobool(buff, &chip->rc_switch.enable))
+		return -EINVAL;
+
+	/* Set back to original INI setting when disable */
+	if (curr_enable == true && chip->rc_switch.enable == false) {
+		ret = REGMAP_WRITE(&chip->regmap, MAX_M5_LEARNCFG, chip->rc_switch.rc2_learncfg);
+		dev_info(chip->dev, "Disable RC switch, recover to learncfg %#x. ret=%d",
+			 chip->rc_switch.rc2_learncfg, ret);
+	}
+
+	mod_delayed_work(system_wq, &chip->rc_switch.switch_work, 0);
+
+	return count;
+}
+
+static ssize_t rc_switch_enable_show(struct device *dev,
+				     struct device_attribute *attr, char *buff)
+{
+	struct power_supply *psy = container_of(dev, struct power_supply, dev);
+	struct max1720x_chip *chip = power_supply_get_drvdata(psy);
+
+	return scnprintf(buff, PAGE_SIZE, "%d\n", chip->rc_switch.enable);
+}
+
+static const DEVICE_ATTR_RW(rc_switch_enable);
 
 /* lsb 1/256, race with max1720x_model_work()  */
 static int max1720x_get_capacity_raw(struct max1720x_chip *chip, u16 *data)
@@ -2482,6 +2527,7 @@
 {
 	u16 data, repsoc, vfsoc, avcap, repcap, fullcap, fullcaprep;
 	u16 fullcapnom, qh0, qh, dqacc, dpacc, qresidual, fstat;
+	u16 learncfg, tempco;
 	int ret = 0, charge_counter = -1;
 
 	ret = REGMAP_READ(&chip->regmap, MAX1720X_REPSOC, &data);
@@ -2540,6 +2586,14 @@
 	if (ret < 0)
 		return ret;
 
+	ret = REGMAP_READ(&chip->regmap, MAX1720X_LEARNCFG, &learncfg);
+	if (ret < 0)
+		return ret;
+
+	ret = REGMAP_READ(&chip->regmap, MAX1720X_TEMPCO, &tempco);
+	if (ret < 0)
+		return ret;
+
 	ret = max1720x_update_battery_qh_based_capacity(chip);
 	if (ret == 0)
 		charge_counter = reg_to_capacity_uah(chip->current_capacity, chip);
@@ -2547,13 +2601,14 @@
 	gbms_logbuffer_prlog(chip->monitor_log, LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
 			     "%s %02X:%04X %02X:%04X %02X:%04X %02X:%04X %02X:%04X"
 			     " %02X:%04X %02X:%04X %02X:%04X %02X:%04X %02X:%04X"
-			     " %02X:%04X %02X:%04X %02X:%04X CC:%d",
+			     " %02X:%04X %02X:%04X %02X:%04X %02X:%04X %02X:%04X CC:%d",
 			     chip->max1720x_psy_desc.name, MAX1720X_REPSOC, data, MAX1720X_VFSOC,
 			     vfsoc, MAX1720X_AVCAP, avcap, MAX1720X_REPCAP, repcap,
 			     MAX1720X_FULLCAP, fullcap, MAX1720X_FULLCAPREP, fullcaprep,
 			     MAX1720X_FULLCAPNOM, fullcapnom, MAX1720X_QH0, qh0,
 			     MAX1720X_QH, qh, MAX1720X_DQACC, dqacc, MAX1720X_DPACC, dpacc,
 			     MAX1720X_QRESIDUAL, qresidual, MAX1720X_FSTAT, fstat,
+			     MAX1720X_LEARNCFG, learncfg, MAX1720X_TEMPCO, tempco,
 			     charge_counter);
 
 	chip->pre_repsoc = repsoc;
@@ -4291,6 +4346,146 @@
 	mutex_unlock(&chip->model_lock);
 }
 
+
+static int max17201_init_rc_switch(struct max1720x_chip *chip)
+{
+	int ret = 0;
+
+	if (chip->gauge_type != MAX_M5_GAUGE_TYPE)
+		return -EINVAL;
+
+	chip->rc_switch.enable = of_property_read_bool(chip->dev->of_node, "maxim,rc-enable");
+
+	ret = of_property_read_u32(chip->dev->of_node, "maxim,rc-soc", &chip->rc_switch.soc);
+	if (ret < 0)
+		return ret;
+
+	ret = of_property_read_u32(chip->dev->of_node, "maxim,rc-temp", &chip->rc_switch.temp);
+	if (ret < 0)
+		return ret;
+
+	ret = of_property_read_u16(chip->batt_node, "maxim,rc1-tempco", &chip->rc_switch.rc1_tempco);
+	if (ret < 0)
+		return ret;
+
+	/* Same as INI value */
+	ret = of_property_read_u16(chip->batt_node, "maxim,rc2-tempco", &chip->rc_switch.rc2_tempco);
+	if (ret < 0)
+		return ret;
+
+	/* Same as INI value */
+	ret = of_property_read_u16(chip->batt_node, "maxim,rc2-learncfg", &chip->rc_switch.rc2_learncfg);
+	if (ret < 0)
+		return ret;
+
+	chip->rc_switch.available = true;
+
+	dev_warn(chip->dev, "rc_switch: enable:%d soc/temp:%d/%d tempco_rc1/rc2:%#x/%#x\n",
+		 chip->rc_switch.enable, chip->rc_switch.soc, chip->rc_switch.temp,
+		 chip->rc_switch.rc1_tempco, chip->rc_switch.rc2_tempco);
+
+	if (chip->rc_switch.enable)
+		schedule_delayed_work(&chip->rc_switch.switch_work, msecs_to_jiffies(60 * 1000));
+
+	return 0;
+}
+
+#define RC_WORK_TIME_MS	60 * 1000
+#define RC_WORK_TIME_QUICK_MS	5 * 1000
+static void max1720x_rc_work(struct work_struct *work)
+{
+	struct max1720x_chip *chip = container_of(work, struct max1720x_chip,
+						  rc_switch.switch_work.work);
+	int interval = RC_WORK_TIME_MS;
+	u16 data, learncfg;
+	bool to_rc1, to_rc2;
+	int ret, soc, temp;
+
+	if (!chip->rc_switch.available || !chip->rc_switch.enable)
+		return;
+
+	/* Read SOC */
+	ret = REGMAP_READ(&chip->regmap, MAX_M5_REPSOC, &data);
+	if (ret < 0)
+		goto reschedule;
+
+	soc = (data >> 8) & 0x00FF;
+
+	/* Read Temperature */
+	ret = max17x0x_reg_read(&chip->regmap, MAX17X0X_TAG_temp, &data);
+	if (ret < 0)
+		goto reschedule;
+
+	temp = reg_to_deci_deg_cel(data);
+
+	/* Read LearnCfg */
+	ret = REGMAP_READ(&chip->regmap, MAX_M5_LEARNCFG, &learncfg);
+	if (ret < 0)
+		goto reschedule;
+
+	/* Disable LearnCfg.LearnTCO */
+	if (learncfg & MAX_M5_LEARNCFG_LEARNTCO_CLEAR) {
+		learncfg = MAX_M5_LEARNCFG_LEARNTCO_CLR(learncfg);
+		ret = REGMAP_WRITE(&chip->regmap, MAX_M5_LEARNCFG, learncfg);
+		if (ret < 0)
+			dev_warn(chip->dev, "Unable to clear LearnTCO\n");
+	}
+
+	to_rc1 = soc < chip->rc_switch.soc || temp < chip->rc_switch.temp;
+	to_rc2 = soc >= chip->rc_switch.soc && temp >= chip->rc_switch.temp;
+
+	if (to_rc1 && ((learncfg & MAX_M5_LEARNCFG_RC_VER) == MAX_M5_LEARNCFG_RC2)) {
+		/*
+		 * 1: set LearnCfg.LearnRComp = 0
+		 * 2: load TempCo value from RC1 INI file
+		 * 3: set LearnCfg.RCx = 0
+		 */
+		learncfg = MAX_M5_LEARNCFG_LEARNRCOMP_CLR(learncfg);
+		ret = REGMAP_WRITE(&chip->regmap, MAX_M5_LEARNCFG, learncfg);
+
+		if (ret == 0)
+			ret = REGMAP_WRITE(&chip->regmap, MAX_M5_TEMPCO, chip->rc_switch.rc1_tempco);
+
+		learncfg = MAX_M5_LEARNCFG_RC_VER_CLR(learncfg);
+		if (ret == 0)
+			ret = REGMAP_WRITE(&chip->regmap, MAX_M5_LEARNCFG, learncfg);
+
+		gbms_logbuffer_prlog(chip->monitor_log, LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
+				     "%s to RC1. ret=%d soc=%d temp=%d tempco=0x%x, learncfg=0x%x",
+				     __func__, ret, soc, temp, chip->rc_switch.rc1_tempco, learncfg);
+
+	} else if (to_rc2 && ((learncfg & MAX_M5_LEARNCFG_RC_VER) == MAX_M5_LEARNCFG_RC1)) {
+		/*
+		 * 1: load LearnCfg.LearnRComp from RC2 INI value
+		 * 2: load TempCo value from RC2 INI value
+		 * 3: set LearnCfg.RCx = 1
+		 */
+
+		learncfg |= (chip->rc_switch.rc2_learncfg & MAX_M5_LEARNCFG_LEARNRCOMP);
+		ret = REGMAP_WRITE(&chip->regmap, MAX_M5_LEARNCFG, learncfg);
+
+		if (ret == 0)
+			ret = REGMAP_WRITE(&chip->regmap, MAX_M5_TEMPCO, chip->rc_switch.rc2_tempco);
+
+		learncfg = MAX_M5_LEARNCFG_RC_VER_SET(learncfg);
+		if (ret == 0)
+			ret = REGMAP_WRITE(&chip->regmap, MAX_M5_LEARNCFG, learncfg);
+
+		gbms_logbuffer_prlog(chip->monitor_log, LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
+				     "%s to RC2. ret=%d soc=%d temp=%d tempco=0x%x, learncfg=0x%x",
+				     __func__, ret, soc, temp, chip->rc_switch.rc2_tempco, learncfg);
+	}
+
+reschedule:
+	if (ret != 0) {
+		interval = RC_WORK_TIME_QUICK_MS;
+		gbms_logbuffer_prlog(chip->monitor_log, LOGLEVEL_WARNING, 0, LOGLEVEL_INFO,
+				     "%s didn't finish. ret=%d", __func__, ret);
+	}
+
+	mod_delayed_work(system_wq, &chip->rc_switch.switch_work, msecs_to_jiffies(interval));
+}
+
 static int read_chip_property_u32(const struct max1720x_chip *chip,
 				  char *property, u32 *data32)
 {
@@ -4671,6 +4866,13 @@
 			 ddata->ini_tempco);
 	}
 
+	/*
+	 * The RC change is WA for MaxCap increase abnormally b/213425610
+	 */
+	ret = max17201_init_rc_switch(chip);
+	if (ret < 0)
+		chip->rc_switch.available = false;
+
 	/* not needed for FG with NVRAM */
 	ret = max17x0x_handle_dt_shadow_config(chip);
 	if (ret == -EPROBE_DEFER)
@@ -5723,6 +5925,11 @@
 	if (ret)
 		dev_err(dev, "Failed to create gmsr attribute\n");
 
+	/* RC switch enable/disable */
+	ret = device_create_file(&chip->psy->dev, &dev_attr_rc_switch_enable);
+	if (ret)
+		dev_err(dev, "Failed to create rc_switch_enable attribute\n");
+
 	/*
 	 * TODO:
 	 *	POWER_SUPPLY_PROP_CHARGE_FULL_ESTIMATE -> GBMS_TAG_GCFE
@@ -5768,6 +5975,7 @@
 			  batt_ce_capacityfiltered_work);
 	INIT_DELAYED_WORK(&chip->init_work, max1720x_init_work);
 	INIT_DELAYED_WORK(&chip->model_work, max1720x_model_work);
+	INIT_DELAYED_WORK(&chip->rc_switch.switch_work, max1720x_rc_work);
 
 	schedule_delayed_work(&chip->init_work, 0);
 
@@ -5796,6 +6004,7 @@
 	max_m5_free_data(chip->model_data);
 	cancel_delayed_work(&chip->init_work);
 	cancel_delayed_work(&chip->model_work);
+	cancel_delayed_work(&chip->rc_switch.switch_work);
 
 	if (chip->primary->irq)
 		free_irq(chip->primary->irq, chip);