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

Commit 485ad23c authored by Anirudh Ghayal's avatar Anirudh Ghayal
Browse files

power: qpnp-qg: Add battery hot-swap support in QG



Support battery hot-swap (removal and re-insertion with
USB present). Clear the userspace and SDAM data during
hot-swap.

Change-Id: Iaabf3de46ed72e33980d1e3e386e8ca0ae651c77
Signed-off-by: default avatarAnirudh Ghayal <aghayal@codeaurora.org>
parent 3ae5bd09
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#define QG_TYPE					0x0D

#define QG_STATUS1_REG				0x08
#define QG_OK_BIT				BIT(7)
#define BATTERY_PRESENT_BIT			BIT(0)
#define ESR_MEAS_DONE_BIT			BIT(4)

@@ -32,6 +33,7 @@
#define QG_INT_RT_STS_REG			0x10
#define FIFO_UPDATE_DONE_RT_STS_BIT		BIT(3)
#define VBAT_LOW_INT_RT_STS_BIT			BIT(1)
#define BATTERY_MISSING_INT_RT_STS_BIT		BIT(0)

#define QG_INT_LATCHED_STS_REG			0x18
#define FIFO_UPDATE_DONE_INT_LAT_STS_BIT	BIT(3)
@@ -64,6 +66,12 @@
#define QG_S3_ENTRY_IBAT_THRESHOLD_REG		0x5E
#define QG_S3_EXIT_IBAT_THRESHOLD_REG		0x5F

#define QG_S5_OCV_VALIDATE_MEAS_CTL1_REG	0x60
#define ALLOW_S5_BIT				BIT(7)

#define QG_S7_PON_OCV_MEAS_CTL1_REG		0x64
#define ADC_CONV_DLY_MASK			GENMASK(3, 0)

#define QG_ESR_MEAS_TRIG_REG			0x68
#define HW_ESR_MEAS_START_BIT			BIT(0)

@@ -105,6 +113,7 @@
#define QG_SDAM_ESR_DISCHARGE_DELTA_OFFSET	0x6E /* 4-byte 0x6E-0x71 */
#define QG_SDAM_ESR_CHARGE_SF_OFFSET		0x72 /* 2-byte 0x72-0x73 */
#define QG_SDAM_ESR_DISCHARGE_SF_OFFSET		0x74 /* 2-byte 0x74-0x75 */
#define QG_SDAM_MAX_OFFSET			0xA4

/* Below offset is used by PBS */
#define QG_SDAM_PON_OCV_OFFSET			0xBC /* 2-byte 0xBC-0xBD */
+146 −13
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/pmic-voter.h>
#include <linux/qpnp/qpnp-adc.h>
@@ -1059,19 +1060,12 @@ static void process_udata_work(struct work_struct *work)
		qg_store_esr_params(chip);

	qg_dbg(chip, QG_DEBUG_STATUS, "udata update: batt_soc=%d cc_soc=%d full_soc=%d qg_esr=%d\n",
		chip->batt_soc, chip->cc_soc, chip->full_soc, chip->esr_last);
		(chip->batt_soc != INT_MIN) ? chip->batt_soc : -EINVAL,
		(chip->cc_soc != INT_MIN) ? chip->cc_soc : -EINVAL,
		chip->full_soc, chip->esr_last);
	vote(chip->awake_votable, UDATA_READY_VOTER, false, 0);
}

static irqreturn_t qg_default_irq_handler(int irq, void *data)
{
	struct qpnp_qg *chip = data;

	qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n");

	return IRQ_HANDLED;
}

#define MAX_FIFO_DELTA_PERCENT		10
static irqreturn_t qg_fifo_update_done_handler(int irq, void *data)
{
@@ -1157,6 +1151,11 @@ static irqreturn_t qg_vbat_low_handler(int irq, void *data)
		pr_err("Failed to read RT status, rc=%d\n", rc);
		goto done;
	}
	/* ignore VBAT low if battery is missing */
	if ((status & BATTERY_MISSING_INT_RT_STS_BIT) ||
			chip->battery_missing)
		goto done;

	chip->vbat_low = !!(status & VBAT_LOW_INT_RT_STS_BIT);

	rc = process_rt_fifo_data(chip, chip->vbat_low, false);
@@ -1173,8 +1172,20 @@ static irqreturn_t qg_vbat_empty_handler(int irq, void *data)
{
	struct qpnp_qg *chip = data;
	u32 ocv_uv = 0;
	int rc;
	u8 status = 0;

	qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n");

	rc = qg_read(chip, chip->qg_base + QG_INT_RT_STS_REG, &status, 1);
	if (rc < 0)
		pr_err("Failed to read RT status rc=%d\n", rc);

	/* ignore VBAT empty if battery is missing */
	if ((status & BATTERY_MISSING_INT_RT_STS_BIT) ||
			chip->battery_missing)
		return IRQ_HANDLED;

	pr_warn("VBATT EMPTY SOC = 0\n");

	chip->catch_up_soc = 0;
@@ -1235,7 +1246,6 @@ static irqreturn_t qg_good_ocv_handler(int irq, void *data)
static struct qg_irq_info qg_irqs[] = {
	[QG_BATT_MISSING_IRQ] = {
		.name		= "qg-batt-missing",
		.handler	= qg_default_irq_handler,
	},
	[QG_VBATT_LOW_IRQ] = {
		.name		= "qg-vbat-low",
@@ -1259,11 +1269,9 @@ static struct qg_irq_info qg_irqs[] = {
	},
	[QG_FSM_STAT_CHG_IRQ] = {
		.name		= "qg-fsm-state-chg",
		.handler	= qg_default_irq_handler,
	},
	[QG_EVENT_IRQ] = {
		.name		= "qg-event",
		.handler	= qg_default_irq_handler,
	},
};

@@ -1960,6 +1968,110 @@ static int qg_usb_status_update(struct qpnp_qg *chip)
	return 0;
}

static int qg_handle_battery_removal(struct qpnp_qg *chip)
{
	int rc, length = QG_SDAM_MAX_OFFSET - QG_SDAM_VALID_OFFSET;
	u8 *data;

	/* clear SDAM */
	data = kcalloc(length, sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	rc = qg_sdam_multibyte_write(QG_SDAM_VALID_OFFSET, data, length);
	if (rc < 0)
		pr_err("Failed to clear SDAM rc=%d\n", rc);

	return rc;
}

#define MAX_QG_OK_RETRIES	20
static int qg_handle_battery_insertion(struct qpnp_qg *chip)
{
	int rc, count = 0;
	u32 ocv_uv = 0, ocv_raw = 0;
	u8 reg = 0;

	do {
		rc = qg_read(chip, chip->qg_base + QG_STATUS1_REG, &reg, 1);
		if (rc < 0) {
			pr_err("Failed to read STATUS1_REG rc=%d\n", rc);
			return rc;
		}

		if (reg & QG_OK_BIT)
			break;

		msleep(200);
		count++;
	} while (count < MAX_QG_OK_RETRIES);

	if (count == MAX_QG_OK_RETRIES) {
		qg_dbg(chip, QG_DEBUG_STATUS, "QG_OK not set!\n");
		return 0;
	}

	/* read S7 PON OCV */
	rc = qg_read_ocv(chip, &ocv_uv, &ocv_raw, S7_PON_OCV);
	if (rc < 0) {
		pr_err("Failed to read PON OCV rc=%d\n", rc);
		return rc;
	}

	qg_dbg(chip, QG_DEBUG_STATUS,
		"S7_OCV on battery insertion = %duV\n", ocv_uv);

	chip->kdata.param[QG_GOOD_OCV_UV].data = ocv_uv;
	chip->kdata.param[QG_GOOD_OCV_UV].valid = true;
	/* clear all the userspace data */
	chip->kdata.param[QG_CLEAR_LEARNT_DATA].data = 1;
	chip->kdata.param[QG_CLEAR_LEARNT_DATA].valid = true;

	vote(chip->awake_votable, GOOD_OCV_VOTER, true, 0);
	/* signal the read thread */
	chip->data_ready = true;
	wake_up_interruptible(&chip->qg_wait_q);

	return 0;
}

static int qg_battery_status_update(struct qpnp_qg *chip)
{
	int rc;
	union power_supply_propval prop = {0, };

	if (!is_batt_available(chip))
		return 0;

	mutex_lock(&chip->data_lock);

	rc = power_supply_get_property(chip->batt_psy,
			POWER_SUPPLY_PROP_PRESENT, &prop);
	if (rc < 0) {
		pr_err("Failed to get battery-present, rc=%d\n", rc);
		goto done;
	}

	if (chip->battery_missing && prop.intval) {
		pr_warn("Battery inserted!\n");
		rc = qg_handle_battery_insertion(chip);
		if (rc < 0)
			pr_err("Failed in battery-insertion rc=%d\n", rc);
	} else if (!chip->battery_missing && !prop.intval) {
		pr_warn("Battery removed!\n");
		rc = qg_handle_battery_removal(chip);
		if (rc < 0)
			pr_err("Failed in battery-removal rc=%d\n", rc);
	}

	chip->battery_missing = !prop.intval;

done:
	mutex_unlock(&chip->data_lock);
	return rc;
}


static void qg_status_change_work(struct work_struct *work)
{
	struct qpnp_qg *chip = container_of(work,
@@ -1972,6 +2084,10 @@ static void qg_status_change_work(struct work_struct *work)
		goto out;
	}

	rc = qg_battery_status_update(chip);
	if (rc < 0)
		pr_err("Failed to process battery status update rc=%d\n", rc);

	rc = power_supply_get_property(chip->batt_psy,
			POWER_SUPPLY_PROP_CHARGE_TYPE, &prop);
	if (rc < 0)
@@ -2549,6 +2665,7 @@ static int qg_set_wa_flags(struct qpnp_qg *chip)
	return 0;
}

#define ADC_CONV_DLY_512MS		0xA
static int qg_hw_init(struct qpnp_qg *chip)
{
	int rc, temp;
@@ -2729,6 +2846,22 @@ static int qg_hw_init(struct qpnp_qg *chip)
		return rc;
	}

	/* disable S5 */
	rc = qg_masked_write(chip, chip->qg_base +
				QG_S5_OCV_VALIDATE_MEAS_CTL1_REG,
				ALLOW_S5_BIT, 0);
	if (rc < 0)
		pr_err("Failed to disable S5 rc=%d\n", rc);

	/* change PON OCV time to 512ms */
	rc = qg_masked_write(chip, chip->qg_base +
				QG_S7_PON_OCV_MEAS_CTL1_REG,
				ADC_CONV_DLY_MASK,
				ADC_CONV_DLY_512MS);
	if (rc < 0)
		pr_err("Failed to reconfigure S7-delay rc=%d\n", rc);


	return 0;
}

+2 −1
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ enum qg {
	QG_ESR_CHARGE_SF,
	QG_ESR_DISCHARGE_SF,
	QG_FULL_SOC,
	QG_RESERVED_8,
	QG_CLEAR_LEARNT_DATA,
	QG_RESERVED_9,
	QG_RESERVED_10,
	QG_MAX,
@@ -32,6 +32,7 @@ enum qg {
#define QG_ESR_CHARGE_SF QG_ESR_CHARGE_SF
#define QG_ESR_DISCHARGE_SF QG_ESR_DISCHARGE_SF
#define QG_FULL_SOC QG_FULL_SOC
#define QG_CLEAR_LEARNT_DATA QG_CLEAR_LEARNT_DATA

struct fifo_data {
	unsigned int			v;