Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d71d65af authored by Subbaraman Narayanamurthy's avatar Subbaraman Narayanamurthy
Browse files

power: qpnp-fg: add support to detect empty battery based on vbatt-low irq



On some platforms, the empty soc interrupt fires randomly and it
cannot be trusted to report battery empty condition. Use Vbatt
low interrupt along with delta soc interrupt to determine whether
battery is really empty.

This option of using vbatt-low interrupt to detect empty battery
is supported via "qcom,fg-use-vbat-low-empty-soc" property.

CRs-Fixed: 977670
Change-Id: I757a1f3c7c66d33d88495348f5dce40cb05cfa64
Signed-off-by: default avatarSubbaraman Narayanamurthy <subbaram@codeaurora.org>
parent 9dc32790
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -250,6 +250,11 @@ Parent node optional properties:
					If qcom,fg-dischg-voltage-gain-ctrl is
					set, then this property should be
					specified to apply the gain setting.
- qcom,fg-use-vbat-low-empty-soc:	A boolean property to specify whether
					vbatt-low interrupt is used to handle
					empty battery condition. If this is
					not specified, empty battery condition
					is detected by empty-soc interrupt.

qcom,fg-soc node required properties:
- reg : offset and length of the PMIC peripheral register map.
+128 −38
Original line number Diff line number Diff line
@@ -481,6 +481,7 @@ struct fg_chip {
	bool			bad_batt_detection_en;
	bool			bcl_lpm_disabled;
	bool			charging_disabled;
	bool			use_vbat_low_empty_soc;
	struct delayed_work	update_jeita_setting;
	struct delayed_work	update_sram_data;
	struct delayed_work	update_temp_work;
@@ -1717,14 +1718,37 @@ out:
	return rc;
}

#define VBATT_LOW_STS_BIT BIT(2)
static int fg_get_vbatt_status(struct fg_chip *chip, bool *vbatt_low_sts)
{
	int rc = 0;
	u8 fg_batt_sts;

	rc = fg_read(chip, &fg_batt_sts, INT_RT_STS(chip->batt_base), 1);
	if (rc)
		pr_err("spmi read failed: addr=%03X, rc=%d\n",
				INT_RT_STS(chip->batt_base), rc);
	else
		*vbatt_low_sts = !!(fg_batt_sts & VBATT_LOW_STS_BIT);

	return rc;
}

#define SOC_EMPTY	BIT(3)
static bool fg_is_batt_empty(struct fg_chip *chip)
{
	u8 fg_soc_sts;
	int rc;
	bool vbatt_low_sts;

	rc = fg_read(chip, &fg_soc_sts,
				 INT_RT_STS(chip->soc_base), 1);
	if (chip->use_vbat_low_empty_soc) {
		if (fg_get_vbatt_status(chip, &vbatt_low_sts))
			return false;

		return vbatt_low_sts;
	}

	rc = fg_read(chip, &fg_soc_sts, INT_RT_STS(chip->soc_base), 1);
	if (rc) {
		pr_err("spmi read failed: addr=%03X, rc=%d\n",
				INT_RT_STS(chip->soc_base), rc);
@@ -2270,18 +2294,6 @@ static int fg_set_resume_soc(struct fg_chip *chip, u8 threshold)
	return rc;
}

#define VBATT_LOW_STS_BIT BIT(2)
static int fg_get_vbatt_status(struct fg_chip *chip, bool *vbatt_low_sts)
{
	int rc = 0;
	u8 fg_batt_sts;

	rc = fg_read(chip, &fg_batt_sts, INT_RT_STS(chip->batt_base), 1);
	if (!rc)
		*vbatt_low_sts = !!(fg_batt_sts & VBATT_LOW_STS_BIT);
	return rc;
}

#define BATT_CYCLE_NUMBER_REG		0x5E8
#define BATT_CYCLE_OFFSET		0
static void restore_cycle_counter(struct fg_chip *chip)
@@ -3517,7 +3529,8 @@ static void status_change_work(struct work_struct *work)
	}
	if (chip->status == POWER_SUPPLY_STATUS_FULL ||
			chip->status == POWER_SUPPLY_STATUS_CHARGING) {
		if (!chip->vbat_low_irq_enabled) {
		if (!chip->vbat_low_irq_enabled &&
				!chip->use_vbat_low_empty_soc) {
			enable_irq(chip->batt_irq[VBATT_LOW].irq);
			enable_irq_wake(chip->batt_irq[VBATT_LOW].irq);
			chip->vbat_low_irq_enabled = true;
@@ -3525,7 +3538,8 @@ static void status_change_work(struct work_struct *work)
		if (!!(chip->wa_flag & PULSE_REQUEST_WA) && capacity == 100)
			fg_configure_soc(chip);
	} else if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) {
		if (chip->vbat_low_irq_enabled) {
		if (chip->vbat_low_irq_enabled &&
				!chip->use_vbat_low_empty_soc) {
			disable_irq_wake(chip->batt_irq[VBATT_LOW].irq);
			disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq);
			chip->vbat_low_irq_enabled = false;
@@ -4018,21 +4032,40 @@ static bool is_first_est_done(struct fg_chip *chip)
	return (fg_soc_sts & SOC_FIRST_EST_DONE) ? true : false;
}

#define FG_EMPTY_DEBOUNCE_MS	1500
static irqreturn_t fg_vbatt_low_handler(int irq, void *_chip)
{
	struct fg_chip *chip = _chip;
	int rc;
	bool vbatt_low_sts;

	if (fg_debug_mask & FG_IRQS)
		pr_info("vbatt-low triggered\n");

	if (chip->status == POWER_SUPPLY_STATUS_CHARGING) {
		rc = fg_get_vbatt_status(chip, &vbatt_low_sts);
		if (rc) {
			pr_err("error in reading vbatt_status, rc:%d\n", rc);
	/* handle empty soc based on vbatt-low interrupt */
	if (chip->use_vbat_low_empty_soc) {
		if (fg_get_vbatt_status(chip, &vbatt_low_sts))
			goto out;

		if (vbatt_low_sts) {
			if (fg_debug_mask & FG_IRQS)
				pr_info("Vbatt is low\n");
			disable_irq_wake(chip->batt_irq[VBATT_LOW].irq);
			disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq);
			chip->vbat_low_irq_enabled = false;
			fg_stay_awake(&chip->empty_check_wakeup_source);
			schedule_delayed_work(&chip->check_empty_work,
				msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS));
		} else {
			if (fg_debug_mask & FG_IRQS)
				pr_info("Vbatt is high\n");
			chip->soc_empty = false;
		}
		goto out;
	}

	if (chip->status == POWER_SUPPLY_STATUS_CHARGING) {
		if (fg_get_vbatt_status(chip, &vbatt_low_sts))
			goto out;
		if (!vbatt_low_sts && chip->vbat_low_irq_enabled) {
			if (fg_debug_mask & FG_IRQS)
				pr_info("disabling vbatt_low irq\n");
@@ -4121,7 +4154,7 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip)
{
	struct fg_chip *chip = _chip;
	u8 soc_rt_sts;
	int rc;
	int rc, msoc;

	rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1);
	if (rc) {
@@ -4142,6 +4175,15 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip)
		schedule_work(&chip->slope_limiter_work);
	}

	if (chip->use_vbat_low_empty_soc) {
		msoc = get_monotonic_soc_raw(chip);
		if (msoc == 0 || chip->soc_empty) {
			fg_stay_awake(&chip->empty_check_wakeup_source);
			schedule_delayed_work(&chip->check_empty_work,
				msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS));
		}
	}

	schedule_work(&chip->battery_age_work);

	if (chip->power_supply_registered)
@@ -4180,7 +4222,6 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip)
	return IRQ_HANDLED;
}

#define FG_EMPTY_DEBOUNCE_MS	1500
static irqreturn_t fg_empty_soc_irq_handler(int irq, void *_chip)
{
	struct fg_chip *chip = _chip;
@@ -5390,14 +5431,41 @@ static void check_empty_work(struct work_struct *work)
	struct fg_chip *chip = container_of(work,
				struct fg_chip,
				check_empty_work.work);
	bool vbatt_low_sts;
	int msoc;

	/* handle empty soc based on vbatt-low interrupt */
	if (chip->use_vbat_low_empty_soc) {
		if (fg_get_vbatt_status(chip, &vbatt_low_sts))
			goto out;

		msoc = get_monotonic_soc_raw(chip);

	if (fg_is_batt_empty(chip)) {
		if (fg_debug_mask & FG_STATUS)
			pr_info("Vbatt_low: %d, msoc: %d\n", vbatt_low_sts,
				msoc);
		if (vbatt_low_sts || (msoc == 0))
			chip->soc_empty = true;
		else
			chip->soc_empty = false;

		if (chip->power_supply_registered)
			power_supply_changed(&chip->bms_psy);

		if (!chip->vbat_low_irq_enabled) {
			enable_irq(chip->batt_irq[VBATT_LOW].irq);
			enable_irq_wake(chip->batt_irq[VBATT_LOW].irq);
			chip->vbat_low_irq_enabled = true;
		}
	} else if (fg_is_batt_empty(chip)) {
		if (fg_debug_mask & FG_STATUS)
			pr_info("EMPTY SOC high\n");
		chip->soc_empty = true;
		if (chip->power_supply_registered)
			power_supply_changed(&chip->bms_psy);
	}

out:
	fg_relax(&chip->empty_check_wakeup_source);
}

@@ -5932,6 +6000,9 @@ static int fg_of_init(struct fg_chip *chip)
		}
	}

	chip->use_vbat_low_empty_soc = of_property_read_bool(node,
					"qcom,fg-use-vbat-low-empty-soc");

	return rc;
}

@@ -5980,7 +6051,7 @@ static int fg_init_irqs(struct fg_chip *chip)
			chip->soc_irq[EMPTY_SOC].irq = spmi_get_irq_byname(
					chip->spmi, spmi_resource, "empty-soc");
			if (chip->soc_irq[EMPTY_SOC].irq < 0) {
				pr_err("Unable to get low-soc irq\n");
				pr_err("Unable to get empty-soc irq\n");
				return rc;
			}
			chip->soc_irq[DELTA_SOC].irq = spmi_get_irq_byname(
@@ -6005,16 +6076,22 @@ static int fg_init_irqs(struct fg_chip *chip)
					chip->soc_irq[FULL_SOC].irq, rc);
				return rc;
			}

			if (!chip->use_vbat_low_empty_soc) {
				rc = devm_request_irq(chip->dev,
					chip->soc_irq[EMPTY_SOC].irq,
					fg_empty_soc_irq_handler,
				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
					IRQF_TRIGGER_RISING |
					IRQF_TRIGGER_FALLING,
					"empty-soc", chip);
				if (rc < 0) {
					pr_err("Can't request %d empty-soc: %d\n",
					chip->soc_irq[EMPTY_SOC].irq, rc);
						chip->soc_irq[EMPTY_SOC].irq,
						rc);
					return rc;
				}
			}

			rc = devm_request_irq(chip->dev,
				chip->soc_irq[DELTA_SOC].irq,
				fg_soc_irq_handler, IRQF_TRIGGER_RISING,
@@ -6036,6 +6113,7 @@ static int fg_init_irqs(struct fg_chip *chip)

			enable_irq_wake(chip->soc_irq[DELTA_SOC].irq);
			enable_irq_wake(chip->soc_irq[FULL_SOC].irq);
			if (!chip->use_vbat_low_empty_soc)
				enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq);
			break;
		case FG_MEMIF:
@@ -6098,8 +6176,14 @@ static int fg_init_irqs(struct fg_chip *chip)
					chip->batt_irq[VBATT_LOW].irq, rc);
				return rc;
			}
			disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq);
			if (chip->use_vbat_low_empty_soc) {
				enable_irq_wake(chip->batt_irq[VBATT_LOW].irq);
				chip->vbat_low_irq_enabled = true;
			} else {
				disable_irq_nosync(
					chip->batt_irq[VBATT_LOW].irq);
				chip->vbat_low_irq_enabled = false;
			}
			break;
		case FG_ADC:
			break;
@@ -6633,8 +6717,9 @@ static int fg_common_hw_init(struct fg_chip *chip)

	update_iterm(chip);
	update_cutoff_voltage(chip);
	update_irq_volt_empty(chip);
	update_bcl_thresholds(chip);
	if (!chip->use_vbat_low_empty_soc)
		update_irq_volt_empty(chip);

	resume_soc_raw = settings[FG_MEM_RESUME_SOC].value;
	if (resume_soc_raw > 0) {
@@ -6664,6 +6749,11 @@ static int fg_common_hw_init(struct fg_chip *chip)
		return rc;
	}

	/* Override the voltage threshold for vbatt_low with empty_volt */
	if (chip->use_vbat_low_empty_soc)
		settings[FG_MEM_BATT_LOW].value =
			settings[FG_MEM_IRQ_VOLT_EMPTY].value;

	rc = fg_mem_masked_write(chip, settings[FG_MEM_BATT_LOW].address, 0xFF,
			batt_to_setpoint_8b(settings[FG_MEM_BATT_LOW].value),
			settings[FG_MEM_BATT_LOW].offset);