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

Commit c9fbe1f6 authored by Xiaozhe Shi's avatar Xiaozhe Shi
Browse files

power: qpnp-smbcharger: add workaround for UVLO resets



When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.

The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.

When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.

The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.

If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.

There are a few limitations to this work around.

	1. If the system load is less than 100mA when the input charge
	   path is removed, there may still be a droop on VPH power due
	   to the battery FET still being open. However, this should not
	   cause an UVLO reset because the system load will not cause a
	   large enough droop to cause a UVLO reset.

	2. If the input is removed at the exact instant a charger error
	   occurs (battery goes missing, battery becomes overtemp,
	   battery becomes overvoltage), and the interrupt does not get
	   serviced before the input is removed, an UVLO reset can still
	   happen. However, this is a hardware limitation and there is
	   no solution for this at this time.

	3. Currently, the workaround for the DC input charge path is not
	   supported. Until this is implemented, DC charging and wipower
	   will be impacted.

CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: default avatarXiaozhe Shi <xiaozhes@codeaurora.org>
parent 4a43df8b
Loading
Loading
Loading
Loading
+81 −0
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ struct smbchg_chip {
	bool				bmd_algo_disabled;
	bool				soft_vfloat_comp_disabled;
	bool				chg_enabled;
	bool				low_icl_wa_on;
	struct parallel_usb_cfg		parallel;

	/* status variables */
@@ -117,6 +118,7 @@ struct smbchg_chip {
	int				src_detect_irq;
	int				aicl_done_irq;
	int				chg_inhibit_irq;
	int				chg_error_irq;

	/* psy */
	struct power_supply		*usb_psy;
@@ -944,6 +946,12 @@ static int smbchg_set_usb_current_max(struct smbchg_chip *chip,
	} else {
		rc = smbchg_usb_en(chip, true, REASON_USB);
	}

	if (chip->low_icl_wa_on) {
		chip->usb_max_current_ma = current_ma;
		pr_debug("low_icl_wa on, ignoring the usb current setting\n");
		goto out;
	}
	if (current_ma < CURRENT_150_MA) {
		/* force 100mA */
		rc = smbchg_sec_masked_write(chip,
@@ -996,6 +1004,36 @@ out:
	return rc;
}

static int smbchg_low_icl_wa_check(struct smbchg_chip *chip)
{
	int rc = 0;
	bool enable = (get_prop_batt_status(chip)
		!= POWER_SUPPLY_STATUS_CHARGING);

	mutex_lock(&chip->current_change_lock);
	pr_debug("low icl %s -> %s\n", chip->low_icl_wa_on ? "on" : "off",
			enable ? "on" : "off");
	if (enable == chip->low_icl_wa_on)
		goto out;

	chip->low_icl_wa_on = enable;
	if (enable) {
		rc = smbchg_sec_masked_write(chip,
					chip->usb_chgpth_base + CHGPTH_CFG,
					CFG_USB_2_3_SEL_BIT, CFG_USB_2);
		rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
					USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
					USBIN_LIMITED_MODE | USB51_100MA);
		if (rc)
			pr_err("could not set low current limit: %d\n", rc);
	} else {
		rc = smbchg_set_usb_current_max(chip, chip->usb_max_current_ma);
	}
out:
	mutex_unlock(&chip->current_change_lock);
	return rc;
}

/*
 * set the dc charge path's maximum allowed current draw
 * that may be limited by the system's thermal level
@@ -1449,6 +1487,7 @@ static irqreturn_t batt_hot_handler(int irq, void *_chip)
	smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
	chip->batt_hot = !!(reg & HOT_BAT_HARD_BIT);
	pr_debug("triggered: 0x%02x\n", reg);
	smbchg_low_icl_wa_check(chip);
	if (chip->psy_registered)
		power_supply_changed(&chip->batt_psy);
	return IRQ_HANDLED;
@@ -1462,6 +1501,7 @@ static irqreturn_t batt_cold_handler(int irq, void *_chip)
	smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
	chip->batt_cold = !!(reg & COLD_BAT_HARD_BIT);
	pr_debug("triggered: 0x%02x\n", reg);
	smbchg_low_icl_wa_check(chip);
	if (chip->psy_registered)
		power_supply_changed(&chip->batt_psy);
	return IRQ_HANDLED;
@@ -1512,11 +1552,24 @@ static irqreturn_t vbat_low_handler(int irq, void *_chip)
	return IRQ_HANDLED;
}

static irqreturn_t chg_error_handler(int irq, void *_chip)
{
	struct smbchg_chip *chip = _chip;

	pr_debug("chg-error triggered\n");
	smbchg_low_icl_wa_check(chip);
	if (chip->psy_registered)
		power_supply_changed(&chip->batt_psy);

	return IRQ_HANDLED;
}

static irqreturn_t fastchg_handler(int irq, void *_chip)
{
	struct smbchg_chip *chip = _chip;

	pr_debug("p2f triggered\n");
	smbchg_low_icl_wa_check(chip);
	if (chip->psy_registered)
		power_supply_changed(&chip->batt_psy);

@@ -1861,11 +1914,29 @@ static int chg_time[] = {
#define RCHG_LVL_BIT			BIT(0)
#define CFG_AFVC			0xF5
#define VFLOAT_COMP_ENABLE_MASK		SMB_MASK(2, 0)
#define TR_RID_REG			0xFA
#define FG_INPUT_FET_DELAY_BIT		BIT(3)
#define TRIM_OPTIONS_7_0		0xF6
#define INPUT_MISSING_POLLER_EN_BIT	BIT(3)
static int smbchg_hw_init(struct smbchg_chip *chip)
{
	int rc, i;
	u8 reg;

	rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + TR_RID_REG,
			FG_INPUT_FET_DELAY_BIT, FG_INPUT_FET_DELAY_BIT);
	if (rc < 0) {
		pr_err("Couldn't disable fg input fet delay rc=%d\n", rc);
		return rc;
	}

	rc = smbchg_sec_masked_write(chip, chip->misc_base + TRIM_OPTIONS_7_0,
			INPUT_MISSING_POLLER_EN_BIT, 0);
	if (rc < 0) {
		pr_err("Couldn't disable input missing poller rc=%d\n", rc);
		return rc;
	}

	/*
	 * force using current from the register i.e. ignore auto
	 * power source detect (APSD) mA ratings
@@ -2008,6 +2079,13 @@ static int smbchg_hw_init(struct smbchg_chip *chip)
		return rc;
	}

	smbchg_low_icl_wa_check(chip);

	/*
	 * The charger needs 20 milliseconds to go into battery supplementary
	 * mode. Sleep here until we are sure it takes into effect.
	 */
	msleep(20);
	smbchg_usb_en(chip, chip->chg_enabled, REASON_USER);
	smbchg_dc_en(chip, chip->chg_enabled, REASON_USER);
	/* resume threshold */
@@ -2310,6 +2388,8 @@ static int smbchg_request_irqs(struct smbchg_chip *chip)

		switch (subtype) {
		case SMBCHG_CHGR_SUBTYPE:
			REQUEST_IRQ(chip, spmi_resource, chip->chg_error_irq,
					"chg-error", chg_error_handler, rc);
			REQUEST_IRQ(chip, spmi_resource, chip->taper_irq,
					"chg-taper-thr", taper_handler, rc);
			REQUEST_IRQ(chip, spmi_resource, chip->chg_term_irq,
@@ -2321,6 +2401,7 @@ static int smbchg_request_irqs(struct smbchg_chip *chip)
			REQUEST_IRQ(chip, spmi_resource, chip->fastchg_irq,
					"chg-p2f-thr", fastchg_handler, rc);
			enable_irq_wake(chip->chg_term_irq);
			enable_irq_wake(chip->chg_error_irq);
			enable_irq_wake(chip->fastchg_irq);
			break;
		case SMBCHG_BAT_IF_SUBTYPE: