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

Commit a1cdac13 authored by Xiaozhe Shi's avatar Xiaozhe Shi Committed by Abhijeet Dharmapurikar
Browse files

power: qpnp-smbcharger: handle very weak chargers



The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.

Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.

Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).

If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.

Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.

CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: default avatarXiaozhe Shi <xiaozhes@codeaurora.org>
parent 132c08ff
Loading
Loading
Loading
Loading
+121 −37
Original line number Diff line number Diff line
@@ -168,6 +168,7 @@ struct smbchg_chip {
	bool				usb_ov_det;
	bool				otg_pulse_skip_en;
	const char			*battery_type;
	bool				very_weak_charger;

	/* jeita and temperature */
	bool				batt_hot;
@@ -1152,6 +1153,10 @@ enum enable_reason {
	 * charger does not accidentally try to charge from the external supply.
	 */
	REASON_OTG = BIT(5),
	/*
	 * the charger is very weak, do not draw any current from it
	 */
	REASON_WEAK_CHARGER = BIT(6),
};

enum battchg_enable_reason {
@@ -1179,7 +1184,9 @@ static void smbchg_usb_update_online_work(struct work_struct *work)
				struct smbchg_chip,
				usb_set_online_work);
	bool user_enabled = (chip->usb_suspended & REASON_USER) == 0;
	int online = user_enabled && chip->usb_present;
	int online;

	online = user_enabled && chip->usb_present && !chip->very_weak_charger;

	mutex_lock(&chip->usb_set_online_lock);
	if (chip->usb_online != online) {
@@ -3026,9 +3033,11 @@ static int smbchg_aicl_config(struct smbchg_chip *chip)
		pr_err("Couldn't write to DC_AICL_CFG rc=%d\n", rc);
		return rc;
	}
	if (!chip->very_weak_charger) {
		rc = smbchg_hw_aicl_rerun_en(chip, true);
		if (rc)
			pr_err("Couldn't enable AICL rerun rc= %d\n", rc);
	}
	return rc;
}

@@ -3053,11 +3062,14 @@ static void smbchg_aicl_deglitch_wa_en(struct smbchg_chip *chip, bool en)
			pr_err("Couldn't write to DC_AICL_CFG rc=%d\n", rc);
			return;
		}
		if (!chip->very_weak_charger) {
			rc = smbchg_hw_aicl_rerun_en(chip, true);
			if (rc) {
			pr_err("Couldn't enable AICL rerun rc= %d\n", rc);
				pr_err("Couldn't enable AICL rerun rc= %d\n",
						rc);
				return;
			}
		}
		pr_smb(PR_STATUS, "AICL deglitch set to short\n");
	} else if (!en && chip->aicl_deglitch_short) {
		rc = smbchg_sec_masked_write(chip,
@@ -4197,8 +4209,9 @@ static void handle_usb_removal(struct smbchg_chip *chip)
	int rc;

	chip->apsd_rerun = false;
	pr_smb(PR_STATUS, "triggered\n");
	smbchg_aicl_deglitch_wa_check(chip);
	if (chip->force_aicl_rerun) {
	if (chip->force_aicl_rerun && !chip->very_weak_charger) {
		rc = smbchg_hw_aicl_rerun_en(chip, true);
		if (rc)
			pr_err("Error enabling AICL rerun rc= %d\n",
@@ -4274,6 +4287,7 @@ static void handle_usb_insertion(struct smbchg_chip *chip)
	int rc;
	char *usb_type_name = "null";

	pr_smb(PR_STATUS, "triggered\n");
	/* usb inserted */
	read_usb_type(chip, &usb_type_name, &usb_supply_type);
	pr_smb(PR_STATUS,
@@ -4301,8 +4315,15 @@ static void handle_usb_insertion(struct smbchg_chip *chip)
		power_supply_set_present(chip->usb_psy, chip->usb_present);
		/* Notify the USB psy if OV condition is not present */
		if (!chip->usb_ov_det) {
			/*
			 * Note that this could still be a very weak charger
			 * if the handle_usb_insertion was triggered from
			 * the falling edge of an USBIN_OV interrupt
			 */
			rc = power_supply_set_health_state(chip->usb_psy,
					POWER_SUPPLY_HEALTH_GOOD);
					chip->very_weak_charger
					? POWER_SUPPLY_HEALTH_UNSPEC_FAILURE
					: POWER_SUPPLY_HEALTH_GOOD);
			if (rc)
				pr_smb(PR_STATUS,
					"usb psy does not allow updating prop %d rc = %d\n",
@@ -4457,11 +4478,14 @@ out:
 * @chip: pointer to smbchg_chip chip
 * @rt_stat: the status bit indicating chg insertion/removal
 */
#define ICL_MODE_MASK		SMB_MASK(5, 4)
#define ICL_MODE_HIGH_CURRENT	0
static irqreturn_t usbin_uv_handler(int irq, void *_chip)
{
	struct smbchg_chip *chip = _chip;
	int aicl_level = smbchg_get_aicl_level_ma(chip);
	int rc;
	bool unused;
	u8 reg;

	rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + RT_STS, 1);
@@ -4479,6 +4503,39 @@ static irqreturn_t usbin_uv_handler(int irq, void *_chip)
	if (chip->apsd_rerun_ignore_uv_irq)
		goto out;

	if ((reg & USBIN_UV_BIT) && (reg & USBIN_SRC_DET_BIT)) {
		pr_smb(PR_STATUS, "Very weak charger detected\n");
		chip->very_weak_charger = true;
		rc = smbchg_read(chip, &reg,
				chip->usb_chgpth_base + ICL_STS_2_REG, 1);
		if (rc) {
			dev_err(chip->dev, "Could not read usb icl sts 2: %d\n",
					rc);
			goto out;
		}
		if ((reg & ICL_MODE_MASK) != ICL_MODE_HIGH_CURRENT) {
			/*
			 * If AICL is not even enabled, this is either an
			 * SDP or a grossly out of spec charger. Do not
			 * draw any current from it.
			 */
			rc = smbchg_primary_usb_en(chip, false,
					REASON_WEAK_CHARGER, &unused);
			if (rc)
				pr_err("could not disable charger: %d", rc);
		} else if ((chip->aicl_deglitch_short || chip->force_aicl_rerun)
			&& aicl_level == usb_current_table[0]) {
			rc = smbchg_hw_aicl_rerun_en(chip, false);
			if (rc)
				pr_err("could not enable aicl reruns: %d", rc);
		}
		rc = power_supply_set_health_state(chip->usb_psy,
				POWER_SUPPLY_HEALTH_UNSPEC_FAILURE);
		if (rc)
			pr_err("Couldn't set health on usb psy rc:%d\n", rc);
		schedule_work(&chip->usb_set_online_work);
	}

	smbchg_wipower_check(chip);
out:
	return IRQ_HANDLED;
@@ -4495,8 +4552,9 @@ out:
static irqreturn_t src_detect_handler(int irq, void *_chip)
{
	struct smbchg_chip *chip = _chip;
	bool usb_present = is_usb_present(chip);
	bool usb_present = is_usb_present(chip), unused;
	bool src_detect = is_src_detect_high(chip);
	int rc;

	pr_smb(PR_STATUS,
		"%s chip->usb_present = %d usb_present = %d src_detect = %d apsd_rerun_ignore_uv_irq=%d\n",
@@ -4524,6 +4582,11 @@ static irqreturn_t src_detect_handler(int irq, void *_chip)
	if (src_detect) {
		update_usb_status(chip, usb_present, chip->apsd_rerun);
	} else {
		chip->very_weak_charger = false;
		rc = smbchg_primary_usb_en(chip, true,
				REASON_WEAK_CHARGER, &unused);
		if (rc)
			pr_err("could not enable charger: %d", rc);
		update_usb_status(chip, 0, false);
		chip->aicl_irq_count = 0;
	}
@@ -4618,22 +4681,15 @@ close_time:

#define AICL_IRQ_LIMIT_SECONDS	60
#define AICL_IRQ_LIMIT_COUNT	25
/**
 * aicl_done_handler() - called when the usb AICL algorithm is finished
 *			and a current is set.
 */
static irqreturn_t aicl_done_handler(int irq, void *_chip)
static void increment_aicl_count(struct smbchg_chip *chip)
{
	struct smbchg_chip *chip = _chip;
	bool usb_present = is_usb_present(chip);
	bool bad_charger = false;
	int rc;
	u8 reg;
	long elapsed_seconds;
	unsigned long now_seconds;

	pr_smb(PR_INTERRUPT, "Aicl triggered icl:%d c:%d dgltch:%d first:%ld\n",
			smbchg_get_aicl_level_ma(chip),
	pr_smb(PR_INTERRUPT, "aicl count c:%d dgltch:%d first:%ld\n",
			chip->aicl_irq_count, chip->aicl_deglitch_short,
			chip->first_aicl_seconds);

@@ -4647,34 +4703,46 @@ static irqreturn_t aicl_done_handler(int irq, void *_chip)
	if (chip->aicl_deglitch_short || chip->force_aicl_rerun) {
		if (!chip->aicl_irq_count)
			get_current_time(&chip->first_aicl_seconds);
		get_current_time(&now_seconds);
		elapsed_seconds = now_seconds
				- chip->first_aicl_seconds;

		if (elapsed_seconds > AICL_IRQ_LIMIT_SECONDS) {
			pr_smb(PR_INTERRUPT,
				"resetting: elp:%ld first:%ld now:%ld c=%d\n",
				elapsed_seconds, chip->first_aicl_seconds,
				now_seconds, chip->aicl_irq_count);
			chip->aicl_irq_count = 1;
			get_current_time(&chip->first_aicl_seconds);
			return;
		}
		chip->aicl_irq_count++;

		if (chip->aicl_irq_count > AICL_IRQ_LIMIT_COUNT) {
			get_current_time(&now_seconds);
			elapsed_seconds = now_seconds
					- chip->first_aicl_seconds;
			pr_smb(PR_INTERRUPT, "elp:%ld first:%ld now:%ld c=%d\n",
				elapsed_seconds, chip->first_aicl_seconds,
				now_seconds, chip->aicl_irq_count);
			if (elapsed_seconds <= AICL_IRQ_LIMIT_SECONDS) {
			pr_smb(PR_INTERRUPT, "Disable AICL rerun\n");
			/*
			 * Disable AICL rerun since many interrupts were
			 * triggered in a short time
			 */
				rc = smbchg_sec_masked_write(chip,
					chip->misc_base + MISC_TRIM_OPT_15_8,
					AICL_RERUN_MASK, AICL_RERUN_OFF);
			chip->very_weak_charger = true;
			rc = smbchg_hw_aicl_rerun_en(chip, false);
			if (rc)
					pr_err("Couldn't turn off AICL rerun rc:%d\n",
						rc);
				pr_err("could not enable aicl reruns: %d", rc);
			bad_charger = true;
			}
			chip->aicl_irq_count = 0;
		} else if ((get_prop_charge_type(chip) ==
				POWER_SUPPLY_CHARGE_TYPE_FAST) &&
					(reg & AICL_SUSP_BIT)) {
			/*
			 * If the AICL_SUSP_BIT is on, then AICL reruns have
			 * already been disabled. Set the very weak charger
			 * flag so that the driver reports a bad charger
			 * and does not reenable AICL reruns.
			 */
			chip->very_weak_charger = true;
			bad_charger = true;
		}
		if (bad_charger) {
@@ -4683,9 +4751,25 @@ static irqreturn_t aicl_done_handler(int irq, void *_chip)
			if (rc)
				pr_err("Couldn't set health on usb psy rc:%d\n",
					rc);
			schedule_work(&chip->usb_set_online_work);
		}
	}
}

/**
 * aicl_done_handler() - called when the usb AICL algorithm is finished
 *			and a current is set.
 */
static irqreturn_t aicl_done_handler(int irq, void *_chip)
{
	struct smbchg_chip *chip = _chip;
	bool usb_present = is_usb_present(chip);
	int aicl_level = smbchg_get_aicl_level_ma(chip);

	pr_smb(PR_INTERRUPT, "triggered, aicl: %d\n", aicl_level);

	increment_aicl_count(chip);

	if (usb_present)
		smbchg_parallel_usb_check_ok(chip);