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

Commit 0e4e7eab authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "power: fg-util: add median filter for circular buffer"

parents f473715d 175907d3
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -297,6 +297,8 @@ static struct device_attribute power_supply_attrs[] = {
	POWER_SUPPLY_ATTR(hw_current_max),
	POWER_SUPPLY_ATTR(real_type),
	POWER_SUPPLY_ATTR(pr_swap),
	POWER_SUPPLY_ATTR(cc_step),
	POWER_SUPPLY_ATTR(cc_step_sel),
	/* Local extensions of type int64_t */
	POWER_SUPPLY_ATTR(charge_counter_ext),
	/* Properties of type `const char *' */
+10 −1
Original line number Diff line number Diff line
@@ -81,6 +81,8 @@

#define BATT_THERM_NUM_COEFFS		3

#define MAX_CC_STEPS			20

/* Debug flag definitions */
enum fg_debug_flag {
	FG_IRQ			= BIT(0), /* Show interrupts */
@@ -309,11 +311,16 @@ struct fg_irq_info {
};

struct fg_circ_buf {
	int	arr[20];
	int	arr[10];
	int	size;
	int	head;
};

struct fg_cc_step_data {
	int arr[MAX_CC_STEPS];
	int sel;
};

struct fg_pt {
	s32 x;
	s32 y;
@@ -374,6 +381,7 @@ struct fg_chip {
	struct fg_cyc_ctr_data	cyc_ctr;
	struct notifier_block	nb;
	struct fg_cap_learning  cl;
	struct fg_cc_step_data	cc_step;
	struct mutex		bus_lock;
	struct mutex		sram_rw_lock;
	struct mutex		batt_avg_lock;
@@ -475,5 +483,6 @@ extern bool is_qnovo_en(struct fg_chip *chip);
extern void fg_circ_buf_add(struct fg_circ_buf *, int);
extern void fg_circ_buf_clr(struct fg_circ_buf *);
extern int fg_circ_buf_avg(struct fg_circ_buf *, int *);
extern int fg_circ_buf_median(struct fg_circ_buf *, int *);
extern int fg_lerp(const struct fg_pt *, size_t, s32, s32 *);
#endif
+34 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
 * GNU General Public License for more details.
 */

#include <linux/sort.h>
#include "fg-core.h"

void fg_circ_buf_add(struct fg_circ_buf *buf, int val)
@@ -39,6 +40,39 @@ int fg_circ_buf_avg(struct fg_circ_buf *buf, int *avg)
	return 0;
}

static int cmp_int(const void *a, const void *b)
{
	return *(int *)a - *(int *)b;
}

int fg_circ_buf_median(struct fg_circ_buf *buf, int *median)
{
	int *temp;

	if (buf->size == 0)
		return -ENODATA;

	if (buf->size == 1) {
		*median = buf->arr[0];
		return 0;
	}

	temp = kmalloc_array(buf->size, sizeof(*temp), GFP_KERNEL);
	if (!temp)
		return -ENOMEM;

	memcpy(temp, buf->arr, buf->size * sizeof(*temp));
	sort(temp, buf->size, sizeof(*temp), cmp_int, NULL);

	if (buf->size % 2)
		*median = temp[buf->size / 2];
	else
		*median = (temp[buf->size / 2 - 1] + temp[buf->size / 2]) / 2;

	kfree(temp);
	return 0;
}

int fg_lerp(const struct fg_pt *pts, size_t tablesize, s32 input, s32 *output)
{
	int i;
+127 −0
Original line number Diff line number Diff line
@@ -75,6 +75,8 @@
#define ESR_TIMER_CHG_MAX_OFFSET	0
#define ESR_TIMER_CHG_INIT_WORD		18
#define ESR_TIMER_CHG_INIT_OFFSET	2
#define ESR_EXTRACTION_ENABLE_WORD	19
#define ESR_EXTRACTION_ENABLE_OFFSET	0
#define PROFILE_LOAD_WORD		24
#define PROFILE_LOAD_OFFSET		0
#define ESR_RSLOW_DISCHG_WORD		34
@@ -3033,6 +3035,89 @@ static int fg_esr_validate(struct fg_chip *chip)
	return 0;
}

static int fg_force_esr_meas(struct fg_chip *chip)
{
	int rc;
	int esr_uohms;

	/* force esr extraction enable */
	rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD,
			ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), BIT(0),
			FG_IMA_DEFAULT);
	if (rc < 0) {
		pr_err("failed to enable esr extn rc=%d\n", rc);
		return rc;
	}

	rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip),
			LD_REG_CTRL_BIT, 0);
	if (rc < 0) {
		pr_err("Error in configuring qnovo_cfg rc=%d\n", rc);
		return rc;
	}

	rc = fg_masked_write(chip, BATT_INFO_TM_MISC1(chip),
			ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT,
			ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT);
	if (rc < 0) {
		pr_err("Error in configuring force ESR rc=%d\n", rc);
		return rc;
	}

	/* wait 1.5 seconds for hw to measure ESR */
	msleep(1500);
	rc = fg_masked_write(chip, BATT_INFO_TM_MISC1(chip),
			ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT,
			0);
	if (rc < 0) {
		pr_err("Error in restoring force ESR rc=%d\n", rc);
		return rc;
	}

	rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip),
			LD_REG_CTRL_BIT, LD_REG_CTRL_BIT);
	if (rc < 0) {
		pr_err("Error in restoring qnovo_cfg rc=%d\n", rc);
		return rc;
	}

	/* force esr extraction disable */
	rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD,
			ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), 0,
			FG_IMA_DEFAULT);
	if (rc < 0) {
		pr_err("failed to disable esr extn rc=%d\n", rc);
		return rc;
	}

	fg_get_battery_resistance(chip, &esr_uohms);
	fg_dbg(chip, FG_STATUS, "ESR uohms = %d\n", esr_uohms);

	return rc;
}

static int fg_prepare_for_qnovo(struct fg_chip *chip, int qnovo_enable)
{
	int rc;

	/* force esr extraction disable when qnovo enables */
	rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD,
			ESR_EXTRACTION_ENABLE_OFFSET,
			BIT(0), qnovo_enable ? 0 : BIT(0),
			FG_IMA_DEFAULT);
	if (rc < 0)
		pr_err("Error in configuring esr extraction rc=%d\n", rc);

	rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip),
			LD_REG_CTRL_BIT,
			qnovo_enable ? LD_REG_CTRL_BIT : 0);
	if (rc < 0) {
		pr_err("Error in configuring qnovo_cfg rc=%d\n", rc);
		return rc;
	}
	fg_dbg(chip, FG_STATUS, "Prepared for Qnovo\n");
	return 0;
}
/* PSY CALLBACKS STAY HERE */

static int fg_psy_get_property(struct power_supply *psy,
@@ -3109,6 +3194,19 @@ static int fg_psy_get_property(struct power_supply *psy,
	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
		rc = fg_get_sram_prop(chip, FG_SRAM_VBATT_FULL, &pval->intval);
		break;
	case POWER_SUPPLY_PROP_CC_STEP:
		if ((chip->cc_step.sel >= 0) &&
					(chip->cc_step.sel < MAX_CC_STEPS)) {
			pval->intval = chip->cc_step.arr[chip->cc_step.sel];
		} else {
			pr_err("cc_step_sel is out of bounds [0, %d]\n",
				chip->cc_step.sel);
			return -EINVAL;
		}
		break;
	case POWER_SUPPLY_PROP_CC_STEP_SEL:
		pval->intval = chip->cc_step.sel;
		break;
	default:
		pr_err("unsupported property %d\n", psp);
		rc = -EINVAL;
@@ -3141,6 +3239,31 @@ static int fg_psy_set_property(struct power_supply *psy,
	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
		rc = fg_set_constant_chg_voltage(chip, pval->intval);
		break;
	case POWER_SUPPLY_PROP_RESISTANCE:
		rc = fg_force_esr_meas(chip);
		break;
	case POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE:
		rc = fg_prepare_for_qnovo(chip, pval->intval);
		break;
	case POWER_SUPPLY_PROP_CC_STEP:
		if ((chip->cc_step.sel >= 0) &&
					(chip->cc_step.sel < MAX_CC_STEPS)) {
			chip->cc_step.arr[chip->cc_step.sel] = pval->intval;
		} else {
			pr_err("cc_step_sel is out of bounds [0, %d]\n",
				chip->cc_step.sel);
			return -EINVAL;
		}
		break;
	case POWER_SUPPLY_PROP_CC_STEP_SEL:
		if ((pval->intval >= 0) && (pval->intval < MAX_CC_STEPS)) {
			chip->cc_step.sel = pval->intval;
		} else {
			pr_err("cc_step_sel is out of bounds [0, %d]\n",
				pval->intval);
			return -EINVAL;
		}
		break;
	default:
		break;
	}
@@ -3154,6 +3277,8 @@ static int fg_property_is_writeable(struct power_supply *psy,
	switch (psp) {
	case POWER_SUPPLY_PROP_CYCLE_COUNT_ID:
	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
	case POWER_SUPPLY_PROP_CC_STEP:
	case POWER_SUPPLY_PROP_CC_STEP_SEL:
		return 1;
	default:
		break;
@@ -3214,6 +3339,8 @@ static enum power_supply_property fg_psy_props[] = {
	POWER_SUPPLY_PROP_SOC_REPORTING_READY,
	POWER_SUPPLY_PROP_DEBUG_BATTERY,
	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
	POWER_SUPPLY_PROP_CC_STEP,
	POWER_SUPPLY_PROP_CC_STEP_SEL,
};

static const struct power_supply_desc fg_psy_desc = {
+330 −43
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include <linux/of_irq.h>
#include <linux/qpnp/qpnp-revid.h>
#include <linux/pmic-voter.h>
#include <linux/delay.h>

#define QNOVO_REVISION1		0x00
#define QNOVO_REVISION2		0x01
@@ -114,6 +115,17 @@
#define OK_TO_QNOVO_VOTER	"ok_to_qnovo_voter"

#define QNOVO_VOTER		"qnovo_voter"
#define FG_AVAILABLE_VOTER	"FG_AVAILABLE_VOTER"
#define QNOVO_OVERALL_VOTER	"QNOVO_OVERALL_VOTER"
#define QNI_PT_VOTER		"QNI_PT_VOTER"
#define ESR_VOTER		"ESR_VOTER"

#define HW_OK_TO_QNOVO_VOTER	"HW_OK_TO_QNOVO_VOTER"
#define CHG_READY_VOTER		"CHG_READY_VOTER"
#define USB_READY_VOTER		"USB_READY_VOTER"
#define DC_READY_VOTER		"DC_READY_VOTER"

#define PT_RESTART_VOTER	"PT_RESTART_VOTER"

struct qnovo_dt_props {
	bool			external_rsense;
@@ -127,6 +139,10 @@ struct qnovo {
	struct qnovo_dt_props	dt;
	struct device		*dev;
	struct votable		*disable_votable;
	struct votable		*pt_dis_votable;
	struct votable		*not_ok_to_qnovo_votable;
	struct votable		*chg_ready_votable;
	struct votable		*awake_votable;
	struct class		qnovo_class;
	struct pmic_revid_data	*pmic_rev_id;
	u32			wa_flags;
@@ -138,10 +154,18 @@ struct qnovo {
	s64			v_gain_mega;
	struct notifier_block	nb;
	struct power_supply	*batt_psy;
	struct power_supply	*bms_psy;
	struct power_supply	*usb_psy;
	struct power_supply	*dc_psy;
	struct work_struct	status_change_work;
	int			fv_uV_request;
	int			fcc_uA_request;
	bool			ok_to_qnovo;
	int			usb_present;
	int			dc_present;
	struct delayed_work	usb_debounce_work;
	struct delayed_work	dc_debounce_work;

	struct delayed_work	ptrain_restart_work;
};

static int debug_mask;
@@ -229,6 +253,39 @@ static bool is_batt_available(struct qnovo *chip)
	return true;
}

static bool is_fg_available(struct qnovo *chip)
{
	if (!chip->bms_psy)
		chip->bms_psy = power_supply_get_by_name("bms");

	if (!chip->bms_psy)
		return false;

	return true;
}

static bool is_usb_available(struct qnovo *chip)
{
	if (!chip->usb_psy)
		chip->usb_psy = power_supply_get_by_name("usb");

	if (!chip->usb_psy)
		return false;

	return true;
}

static bool is_dc_available(struct qnovo *chip)
{
	if (!chip->dc_psy)
		chip->dc_psy = power_supply_get_by_name("dc");

	if (!chip->dc_psy)
		return false;

	return true;
}

static int qnovo_batt_psy_update(struct qnovo *chip, bool disable)
{
	union power_supply_propval pval = {0};
@@ -281,10 +338,86 @@ static int qnovo_disable_cb(struct votable *votable, void *data, int disable,
		return -EINVAL;
	}

	/*
	 * fg must be available for enable FG_AVAILABLE_VOTER
	 * won't enable it otherwise
	 */

	if (is_fg_available(chip))
		power_supply_set_property(chip->bms_psy,
				POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE,
				&pval);

	vote(chip->pt_dis_votable, QNOVO_OVERALL_VOTER, disable, 0);
	rc = qnovo_batt_psy_update(chip, disable);
	return rc;
}

static int pt_dis_votable_cb(struct votable *votable, void *data, int disable,
					const char *client)
{
	struct qnovo *chip = data;
	int rc;

	if (disable) {
		cancel_delayed_work_sync(&chip->ptrain_restart_work);
		vote(chip->awake_votable, PT_RESTART_VOTER, false, 0);
	}

	rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT,
				 (bool)disable ? 0 : QNOVO_PTRAIN_EN_BIT);
	if (rc < 0) {
		dev_err(chip->dev, "Couldn't %s pulse train rc=%d\n",
			(bool)disable ? "disable" : "enable", rc);
		return rc;
	}

	if (!disable) {
		vote(chip->awake_votable, PT_RESTART_VOTER, true, 0);
		schedule_delayed_work(&chip->ptrain_restart_work,
				msecs_to_jiffies(20));
	}

	return 0;
}

static int not_ok_to_qnovo_cb(struct votable *votable, void *data,
					int not_ok_to_qnovo,
					const char *client)
{
	struct qnovo *chip = data;

	vote(chip->disable_votable, OK_TO_QNOVO_VOTER, not_ok_to_qnovo, 0);
	if (not_ok_to_qnovo)
		vote(chip->disable_votable, USER_VOTER, true, 0);

	kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
	return 0;
}

static int chg_ready_cb(struct votable *votable, void *data, int ready,
					const char *client)
{
	struct qnovo *chip = data;

	vote(chip->not_ok_to_qnovo_votable, CHG_READY_VOTER, !ready, 0);

	return 0;
}

static int awake_cb(struct votable *votable, void *data, int awake,
					const char *client)
{
	struct qnovo *chip = data;

	if (awake)
		pm_stay_awake(chip->dev);
	else
		pm_relax(chip->dev);

	return 0;
}

static int qnovo_parse_dt(struct qnovo *chip)
{
	struct device_node *node = chip->dev->of_node;
@@ -626,8 +759,9 @@ static ssize_t ok_to_qnovo_show(struct class *c, struct class_attribute *attr,
			char *buf)
{
	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
	int val = get_effective_result(chip->not_ok_to_qnovo_votable);

	return snprintf(buf, PAGE_SIZE, "%d\n", chip->ok_to_qnovo);
	return snprintf(buf, PAGE_SIZE, "%d\n", !val);
}

static ssize_t qnovo_enable_show(struct class *c, struct class_attribute *attr,
@@ -656,21 +790,10 @@ static ssize_t qnovo_enable_store(struct class *c, struct class_attribute *attr,
static ssize_t pt_enable_show(struct class *c, struct class_attribute *attr,
			char *ubuf)
{
	int i = attr - qnovo_attributes;
	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
	u8 buf[2] = {0, 0};
	u16 regval;
	int rc;

	rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
	if (rc < 0) {
		pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
		return -EINVAL;
	}
	regval = buf[1] << 8 | buf[0];
	int val = get_effective_result(chip->pt_dis_votable);

	return snprintf(ubuf, PAGE_SIZE, "%d\n",
				(int)(regval & QNOVO_PTRAIN_EN_BIT));
	return snprintf(ubuf, PAGE_SIZE, "%d\n", !val);
}

static ssize_t pt_enable_store(struct class *c, struct class_attribute *attr,
@@ -678,21 +801,12 @@ static ssize_t pt_enable_store(struct class *c, struct class_attribute *attr,
{
	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
	unsigned long val;
	int rc = 0;

	if (get_effective_result(chip->disable_votable))
		return -EINVAL;

	if (kstrtoul(ubuf, 0, &val))
		return -EINVAL;

	rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT,
				 (bool)val ? QNOVO_PTRAIN_EN_BIT : 0);
	if (rc < 0) {
		dev_err(chip->dev, "Couldn't %s pulse train rc=%d\n",
			(bool)val ? "enable" : "disable", rc);
		return rc;
	}
	/* val being 0, userspace wishes to disable pt so vote true */
	vote(chip->pt_dis_votable, QNI_PT_VOTER, val ? false : true, 0);

	return count;
}
@@ -1116,41 +1230,146 @@ static int qnovo_update_status(struct qnovo *chip)
{
	u8 val = 0;
	int rc;
	bool ok_to_qnovo;
	bool changed = false;
	bool hw_ok_to_qnovo;

	rc = qnovo_read(chip, QNOVO_ERROR_STS2, &val, 1);
	if (rc < 0) {
		pr_err("Couldn't read error sts rc = %d\n", rc);
		ok_to_qnovo = false;
		hw_ok_to_qnovo = false;
	} else {
		/*
		 * For CV mode keep qnovo enabled, userspace is expected to
		 * disable it after few runs
		 */
		ok_to_qnovo = (val == ERR_CV_MODE || val == 0) ? true : false;
		hw_ok_to_qnovo = (val == ERR_CV_MODE || val == 0) ?
			true : false;
	}

	if (chip->ok_to_qnovo ^ ok_to_qnovo) {
	vote(chip->not_ok_to_qnovo_votable, HW_OK_TO_QNOVO_VOTER,
					!hw_ok_to_qnovo, 0);
	return 0;
}

		vote(chip->disable_votable, OK_TO_QNOVO_VOTER, !ok_to_qnovo, 0);
		if (!ok_to_qnovo)
			vote(chip->disable_votable, USER_VOTER, true, 0);
static void usb_debounce_work(struct work_struct *work)
{
	struct qnovo *chip = container_of(work,
				struct qnovo, usb_debounce_work.work);

		chip->ok_to_qnovo = ok_to_qnovo;
		changed = true;
	vote(chip->chg_ready_votable, USB_READY_VOTER, true, 0);
	vote(chip->awake_votable, USB_READY_VOTER, false, 0);
}

	return changed;
static void dc_debounce_work(struct work_struct *work)
{
	struct qnovo *chip = container_of(work,
				struct qnovo, dc_debounce_work.work);

	vote(chip->chg_ready_votable, DC_READY_VOTER, true, 0);
	vote(chip->awake_votable, DC_READY_VOTER, false, 0);
}

#define DEBOUNCE_MS 15000  /* 15 seconds */
static void status_change_work(struct work_struct *work)
{
	struct qnovo *chip = container_of(work,
			struct qnovo, status_change_work);
	union power_supply_propval pval;
	bool usb_present = false, dc_present = false;
	int rc;

	if (qnovo_update_status(chip))
		kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
	if (is_fg_available(chip))
		vote(chip->disable_votable, FG_AVAILABLE_VOTER, false, 0);

	if (is_usb_available(chip)) {
		rc = power_supply_get_property(chip->usb_psy,
				POWER_SUPPLY_PROP_PRESENT, &pval);
		usb_present = (rc < 0) ? 0 : pval.intval;
	}

	if (chip->usb_present && !usb_present) {
		/* removal */
		chip->usb_present = 0;
		cancel_delayed_work_sync(&chip->usb_debounce_work);
		vote(chip->awake_votable, USB_READY_VOTER, false, 0);
		vote(chip->chg_ready_votable, USB_READY_VOTER, false, 0);
	} else if (!chip->usb_present && usb_present) {
		/* insertion */
		chip->usb_present = 1;
		vote(chip->awake_votable, USB_READY_VOTER, true, 0);
		schedule_delayed_work(&chip->usb_debounce_work,
				msecs_to_jiffies(DEBOUNCE_MS));
	}

	if (is_dc_available(chip)) {
		rc = power_supply_get_property(chip->dc_psy,
			POWER_SUPPLY_PROP_PRESENT,
			&pval);
		dc_present = (rc < 0) ? 0 : pval.intval;
	}

	if (usb_present)
		dc_present = 0;

	if (chip->dc_present && !dc_present) {
		/* removal */
		chip->dc_present = 0;
		cancel_delayed_work_sync(&chip->dc_debounce_work);
		vote(chip->awake_votable, DC_READY_VOTER, false, 0);
		vote(chip->chg_ready_votable, DC_READY_VOTER, false, 0);
	} else if (!chip->dc_present && dc_present) {
		/* insertion */
		chip->dc_present = 1;
		vote(chip->awake_votable, DC_READY_VOTER, true, 0);
		schedule_delayed_work(&chip->dc_debounce_work,
				msecs_to_jiffies(DEBOUNCE_MS));
	}

	qnovo_update_status(chip);
}

static void ptrain_restart_work(struct work_struct *work)
{
	struct qnovo *chip = container_of(work,
				struct qnovo, ptrain_restart_work.work);
	u8 pt_t1, pt_t2;
	int rc;

	rc = qnovo_read(chip, QNOVO_PTTIME_STS, &pt_t1, 1);
	if (rc < 0) {
		dev_err(chip->dev, "Couldn't read QNOVO_PTTIME_STS rc = %d\n",
			rc);
		goto clean_up;
	}

	/* pttime increments every 2 seconds */
	msleep(2100);

	rc = qnovo_read(chip, QNOVO_PTTIME_STS, &pt_t2, 1);
	if (rc < 0) {
		dev_err(chip->dev, "Couldn't read QNOVO_PTTIME_STS rc = %d\n",
			rc);
		goto clean_up;
	}

	if (pt_t1 != pt_t2)
		goto clean_up;

	/* Toggle pt enable to restart pulse train */
	rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, 0);
	if (rc < 0) {
		dev_err(chip->dev, "Couldn't disable pulse train rc=%d\n", rc);
		goto clean_up;
	}
	msleep(1000);
	rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT,
				QNOVO_PTRAIN_EN_BIT);
	if (rc < 0) {
		dev_err(chip->dev, "Couldn't enable pulse train rc=%d\n", rc);
		goto clean_up;
	}

clean_up:
	vote(chip->awake_votable, PT_RESTART_VOTER, false, 0);
}

static int qnovo_notifier_call(struct notifier_block *nb,
@@ -1162,7 +1381,10 @@ static int qnovo_notifier_call(struct notifier_block *nb,
	if (ev != PSY_EVENT_PROP_CHANGED)
		return NOTIFY_OK;

	if (strcmp(psy->desc->name, "battery") == 0)
	if (strcmp(psy->desc->name, "battery") == 0
		|| strcmp(psy->desc->name, "bms") == 0
		|| strcmp(psy->desc->name, "usb") == 0
		|| strcmp(psy->desc->name, "dc") == 0)
		schedule_work(&chip->status_change_work);

	return NOTIFY_OK;
@@ -1171,7 +1393,23 @@ static int qnovo_notifier_call(struct notifier_block *nb,
static irqreturn_t handle_ptrain_done(int irq, void *data)
{
	struct qnovo *chip = data;
	union power_supply_propval pval = {0};

	/*
	 * hw resets pt_en bit once ptrain_done triggers.
	 * vote on behalf of QNI to disable it such that
	 * once QNI enables it, the votable state changes
	 * and the callback that sets it is indeed invoked
	 */
	vote(chip->pt_dis_votable, QNI_PT_VOTER, true, 0);

	vote(chip->pt_dis_votable, ESR_VOTER, true, 0);
	if (is_fg_available(chip))
		power_supply_set_property(chip->bms_psy,
				POWER_SUPPLY_PROP_RESISTANCE,
				&pval);

	vote(chip->pt_dis_votable, ESR_VOTER, false, 0);
	qnovo_update_status(chip);
	kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
	return IRQ_HANDLED;
@@ -1186,6 +1424,11 @@ static int qnovo_hw_init(struct qnovo *chip)
	u8 val;

	vote(chip->disable_votable, USER_VOTER, true, 0);
	vote(chip->disable_votable, FG_AVAILABLE_VOTER, true, 0);

	vote(chip->pt_dis_votable, QNI_PT_VOTER, true, 0);
	vote(chip->pt_dis_votable, QNOVO_OVERALL_VOTER, true, 0);
	vote(chip->pt_dis_votable, ESR_VOTER, false, 0);

	val = 0;
	rc = qnovo_write(chip, QNOVO_STRM_CTRL, &val, 1);
@@ -1349,12 +1592,45 @@ static int qnovo_probe(struct platform_device *pdev)
		goto cleanup;
	}

	chip->pt_dis_votable = create_votable("QNOVO_PT_DIS", VOTE_SET_ANY,
					pt_dis_votable_cb, chip);
	if (IS_ERR(chip->pt_dis_votable)) {
		rc = PTR_ERR(chip->pt_dis_votable);
		goto destroy_disable_votable;
	}

	chip->not_ok_to_qnovo_votable = create_votable("QNOVO_NOT_OK",
					VOTE_SET_ANY,
					not_ok_to_qnovo_cb, chip);
	if (IS_ERR(chip->not_ok_to_qnovo_votable)) {
		rc = PTR_ERR(chip->not_ok_to_qnovo_votable);
		goto destroy_pt_dis_votable;
	}

	chip->chg_ready_votable = create_votable("QNOVO_CHG_READY",
					VOTE_SET_ANY,
					chg_ready_cb, chip);
	if (IS_ERR(chip->chg_ready_votable)) {
		rc = PTR_ERR(chip->chg_ready_votable);
		goto destroy_not_ok_to_qnovo_votable;
	}

	chip->awake_votable = create_votable("QNOVO_AWAKE", VOTE_SET_ANY,
					awake_cb, chip);
	if (IS_ERR(chip->awake_votable)) {
		rc = PTR_ERR(chip->awake_votable);
		goto destroy_chg_ready_votable;
	}

	INIT_WORK(&chip->status_change_work, status_change_work);
	INIT_DELAYED_WORK(&chip->dc_debounce_work, dc_debounce_work);
	INIT_DELAYED_WORK(&chip->usb_debounce_work, usb_debounce_work);
	INIT_DELAYED_WORK(&chip->ptrain_restart_work, ptrain_restart_work);

	rc = qnovo_hw_init(chip);
	if (rc < 0) {
		pr_err("Couldn't initialize hardware rc=%d\n", rc);
		goto destroy_votable;
		goto destroy_awake_votable;
	}

	rc = qnovo_register_notifier(chip);
@@ -1390,7 +1666,15 @@ static int qnovo_probe(struct platform_device *pdev)

unreg_notifier:
	power_supply_unreg_notifier(&chip->nb);
destroy_votable:
destroy_awake_votable:
	destroy_votable(chip->awake_votable);
destroy_chg_ready_votable:
	destroy_votable(chip->chg_ready_votable);
destroy_not_ok_to_qnovo_votable:
	destroy_votable(chip->not_ok_to_qnovo_votable);
destroy_pt_dis_votable:
	destroy_votable(chip->pt_dis_votable);
destroy_disable_votable:
	destroy_votable(chip->disable_votable);
cleanup:
	platform_set_drvdata(pdev, NULL);
@@ -1403,6 +1687,9 @@ static int qnovo_remove(struct platform_device *pdev)

	class_unregister(&chip->qnovo_class);
	power_supply_unreg_notifier(&chip->nb);
	destroy_votable(chip->chg_ready_votable);
	destroy_votable(chip->not_ok_to_qnovo_votable);
	destroy_votable(chip->pt_dis_votable);
	destroy_votable(chip->disable_votable);
	platform_set_drvdata(pdev, NULL);
	return 0;
Loading