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

Commit ff47cb40 authored by Anirudh Ghayal's avatar Anirudh Ghayal
Browse files

power: qpnp-fg-gen3: Add support for software based ESR during charging



On wearable platforms where the FCC supported by the battery is lower
than 300mA (lowest possible FCC for HW ESR) the HW based ESR pulsing
cannot be used.

Add support for SW based ESR where the SW periodically reduces
FCC and enables the FG to extract an ESR. This feature can be
enabled by the DT property  "qcom,fg-use-sw-esr".

Change-Id: Iaf9e1336438eb6f0c75032f99b7def8970508d33
Signed-off-by: default avatarAnirudh Ghayal <aghayal@codeaurora.org>
parent 38ec0494
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -385,6 +385,12 @@ First Level Node - FG Gen3 device
		    property "qcom,slope-limit-temp-threshold" to make dynamic
		    slope limit adjustment functional.

- qcom,fg-use-sw-esr
	Usage:      optional
	Value type: <empty>
	Definition: A boolean property when defined uses software based
		    ESR during charging.

==========================================================
Second Level Nodes - Peripherals managed by FG Gen3 driver
==========================================================
+18 −1
Original line number Diff line number Diff line
/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -24,8 +24,10 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/spmi.h>
#include <linux/alarmtimer.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string_helpers.h>
#include <linux/types.h>
#include <linux/uaccess.h>
@@ -50,6 +52,7 @@
#define SRAM_WRITE		"fg_sram_write"
#define PROFILE_LOAD		"fg_profile_load"
#define DELTA_SOC		"fg_delta_soc"
#define FG_ESR_VOTER		"fg_esr_voter"

/* Delta BSOC irq votable reasons */
#define DELTA_BSOC_IRQ_VOTER	"fg_delta_bsoc_irq"
@@ -94,6 +97,11 @@ enum fg_debug_flag {
	FG_TTF			= BIT(8), /* Show time to full */
};

enum awake_reasons {
	FG_SW_ESR_WAKE = BIT(0),
	FG_STATUS_NOTIFY_WAKE = BIT(1),
};

/* SRAM access */
enum sram_access_flags {
	FG_IMA_DEFAULT	= 0,
@@ -233,6 +241,7 @@ struct fg_dt_props {
	bool	force_load_profile;
	bool	hold_soc_while_full;
	bool	auto_recharge_soc;
	bool	use_esr_sw;
	int	cutoff_volt_mv;
	int	empty_volt_mv;
	int	vbatt_low_thr_mv;
@@ -375,15 +384,19 @@ struct fg_chip {
	struct fg_cyc_ctr_data	cyc_ctr;
	struct notifier_block	nb;
	struct fg_cap_learning  cl;
	struct alarm            esr_sw_timer;
	struct mutex		bus_lock;
	struct mutex		sram_rw_lock;
	struct mutex		batt_avg_lock;
	struct mutex		charge_full_lock;
	spinlock_t		awake_lock;
	u32			batt_soc_base;
	u32			batt_info_base;
	u32			mem_if_base;
	u32			rradc_base;
	u32			wa_flags;
	u32			esr_wakeup_ms;
	u32			awake_status;
	int			batt_id_ohms;
	int			ki_coeff_full_soc;
	int			charge_status;
@@ -410,11 +423,13 @@ struct fg_chip {
	bool			esr_flt_cold_temp_en;
	bool			slope_limit_en;
	bool			use_ima_single_mode;
	bool			usb_present;
	struct completion	soc_update;
	struct completion	soc_ready;
	struct delayed_work	profile_load_work;
	struct work_struct	status_change_work;
	struct work_struct	cycle_count_work;
	struct work_struct	esr_sw_work;
	struct delayed_work	batt_avg_work;
	struct delayed_work	sram_dump_work;
	struct fg_circ_buf	ibatt_circ_buf;
@@ -477,4 +492,6 @@ 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_lerp(const struct fg_pt *, size_t, s32, s32 *);
void fg_stay_awake(struct fg_chip *chip, int awake_reason);
void fg_relax(struct fg_chip *chip, int awake_reason);
#endif
+25 −1
Original line number Diff line number Diff line
/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -935,3 +935,27 @@ err_remove_fs:
	debugfs_remove_recursive(chip->dfs_root);
	return -ENOMEM;
}

void fg_stay_awake(struct fg_chip *chip, int awake_reason)
{
	spin_lock(&chip->awake_lock);

	if (!chip->awake_status)
		pm_stay_awake(chip->dev);

	chip->awake_status |= awake_reason;

	spin_unlock(&chip->awake_lock);
}

void fg_relax(struct fg_chip *chip, int awake_reason)
{
	spin_lock(&chip->awake_lock);

	chip->awake_status &= ~awake_reason;

	if (!chip->awake_status)
		pm_relax(chip->dev);

	spin_unlock(&chip->awake_lock);
}
+193 −4
Original line number Diff line number Diff line
/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -16,6 +16,8 @@
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spmi.h>
#include <linux/spinlock.h>
#include <linux/alarmtimer.h>
#include <linux/of_batterydata.h>
#include <linux/iio/consumer.h>
#include <linux/qpnp/qpnp-revid.h>
@@ -74,6 +76,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
@@ -1175,6 +1179,18 @@ static bool batt_psy_initialized(struct fg_chip *chip)
	return true;
}

static bool usb_psy_initialized(struct fg_chip *chip)
{
	if (chip->usb_psy)
		return true;

	chip->usb_psy = power_supply_get_by_name("usb");
	if (!chip->usb_psy)
		return false;

	return true;
}

static bool is_parallel_charger_available(struct fg_chip *chip)
{
	if (!chip->parallel_psy)
@@ -2149,6 +2165,142 @@ static void fg_batt_avg_update(struct fg_chip *chip)
							msecs_to_jiffies(2000));
}

#define ESR_SW_FCC_UA				100000	/* 100mA */
#define ESR_EXTRACTION_ENABLE_MASK		BIT(0)
static void fg_esr_sw_work(struct work_struct *work)
{
	struct fg_chip *chip = container_of(work,
			struct fg_chip, esr_sw_work);
	union power_supply_propval pval = {0, };
	int rc, esr_uohms = 0;

	vote(chip->awake_votable, FG_ESR_VOTER, true, 0);
	/*
	 * Enable ESR extraction just before we reduce the FCC
	 * to make sure that FG extracts the ESR. Disable ESR
	 * extraction after FCC reduction is complete to prevent
	 * any further HW pulses.
	 */
	rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD,
			ESR_EXTRACTION_ENABLE_OFFSET,
			ESR_EXTRACTION_ENABLE_MASK, 0x1, FG_IMA_DEFAULT);
	if (rc < 0) {
		pr_err("Failed to enable ESR extraction rc=%d\n", rc);
		goto done;
	}

	/* delay for 1 FG cycle to complete */
	msleep(1500);

	/* for FCC to 100mA */
	pval.intval = ESR_SW_FCC_UA;
	rc = power_supply_set_property(chip->batt_psy,
			POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
			&pval);
	if (rc < 0) {
		pr_err("Failed to set FCC to 100mA rc=%d\n", rc);
		goto done;
	}

	/* delay for ESR readings */
	msleep(3000);

	/* FCC to 0 (removes vote) */
	pval.intval = 0;
	rc = power_supply_set_property(chip->batt_psy,
			POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
			&pval);
	if (rc < 0) {
		pr_err("Failed to remove FCC vote rc=%d\n", rc);
		goto done;
	}

	fg_get_sram_prop(chip, FG_SRAM_ESR, &esr_uohms);
	fg_dbg(chip, FG_STATUS, "SW ESR done ESR=%d\n", esr_uohms);

	/* restart the alarm timer */
	alarm_start_relative(&chip->esr_sw_timer,
		ms_to_ktime(chip->esr_wakeup_ms));
done:
	rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD,
			ESR_EXTRACTION_ENABLE_OFFSET,
			ESR_EXTRACTION_ENABLE_MASK, 0x0, FG_IMA_DEFAULT);
	if (rc < 0)
		pr_err("Failed to disable ESR extraction rc=%d\n", rc);


	vote(chip->awake_votable, FG_ESR_VOTER, false, 0);
	fg_relax(chip, FG_SW_ESR_WAKE);
}

static enum alarmtimer_restart
	fg_esr_sw_timer(struct alarm *alarm, ktime_t now)
{
	struct fg_chip *chip = container_of(alarm,
			struct fg_chip, esr_sw_timer);

	if (!chip->usb_present)
		return ALARMTIMER_NORESTART;

	fg_stay_awake(chip, FG_SW_ESR_WAKE);
	schedule_work(&chip->esr_sw_work);

	return ALARMTIMER_NORESTART;
}

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

	if (!chip->dt.use_esr_sw)
		return 0;

	if (!usb_psy_initialized(chip))
		return 0;

	rc = power_supply_get_property(chip->usb_psy,
			POWER_SUPPLY_PROP_PRESENT, &prop);
	if (rc < 0) {
		pr_err("Error in reading usb-status rc = %d\n", rc);
		return rc;
	}

	if (chip->usb_present != prop.intval) {
		chip->usb_present = prop.intval;
		fg_dbg(chip, FG_STATUS, "USB status changed=%d\n",
						chip->usb_present);
		/* cancel any pending work */
		alarm_cancel(&chip->esr_sw_timer);
		cancel_work_sync(&chip->esr_sw_work);

		if (chip->usb_present) {
			/* disable ESR extraction across the charging cycle */
			rc = fg_sram_masked_write(chip,
					ESR_EXTRACTION_ENABLE_WORD,
					ESR_EXTRACTION_ENABLE_OFFSET,
					ESR_EXTRACTION_ENABLE_MASK,
					0x0, FG_IMA_DEFAULT);
			if (rc < 0)
				return rc;
			/* wake up early for the first ESR on insertion */
			alarm_start_relative(&chip->esr_sw_timer,
				ms_to_ktime(chip->esr_wakeup_ms / 2));
		} else {
			/* enable ESR extraction on removal */
			rc = fg_sram_masked_write(chip,
					ESR_EXTRACTION_ENABLE_WORD,
					ESR_EXTRACTION_ENABLE_OFFSET,
					ESR_EXTRACTION_ENABLE_MASK,
					0x1, FG_IMA_DEFAULT);
			if (rc < 0)
				return rc;
		}
	}

	return 0;
}

static void status_change_work(struct work_struct *work)
{
	struct fg_chip *chip = container_of(work,
@@ -2166,6 +2318,10 @@ static void status_change_work(struct work_struct *work)
		goto out;
	}

	rc = fg_config_esr_sw(chip);
	if (rc < 0)
		pr_err("Failed to config SW ESR rc=%d\n", rc);

	rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS,
			&prop);
	if (rc < 0) {
@@ -2234,7 +2390,7 @@ static void status_change_work(struct work_struct *work)
out:
	fg_dbg(chip, FG_STATUS, "charge_status:%d charge_type:%d charge_done:%d\n",
		chip->charge_status, chip->charge_type, chip->charge_done);
	pm_relax(chip->dev);
	fg_relax(chip, FG_STATUS_NOTIFY_WAKE);
}

static void restore_cycle_counter(struct fg_chip *chip)
@@ -2629,7 +2785,7 @@ out:
	chip->soc_reporting_ready = true;
	vote(chip->awake_votable, PROFILE_LOAD, false, 0);
	if (!work_pending(&chip->status_change_work)) {
		pm_stay_awake(chip->dev);
		fg_stay_awake(chip, FG_STATUS_NOTIFY_WAKE);
		schedule_work(&chip->status_change_work);
	}
}
@@ -3197,7 +3353,7 @@ static int fg_notifier_cb(struct notifier_block *nb,
		 * We cannot vote for awake votable here as that takes
		 * a mutex lock and this is executed in an atomic context.
		 */
		pm_stay_awake(chip->dev);
		fg_stay_awake(chip, FG_STATUS_NOTIFY_WAKE);
		schedule_work(&chip->status_change_work);
	}

@@ -3500,6 +3656,18 @@ static int fg_hw_init(struct fg_chip *chip)
		}
	}

	if (chip->dt.use_esr_sw) {
		/* Enable ESR extraction explicitly */
		rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD,
				ESR_EXTRACTION_ENABLE_OFFSET,
				ESR_EXTRACTION_ENABLE_MASK,
				0x1, FG_IMA_DEFAULT);
		if (rc < 0) {
			pr_err("Error in enabling ESR extraction rc=%d\n", rc);
			return rc;
		}
	}

	return 0;
}

@@ -4374,6 +4542,8 @@ static int fg_parse_dt(struct fg_chip *chip)
			chip->dt.esr_meas_curr_ma = temp;
	}

	chip->dt.use_esr_sw = of_property_read_bool(node, "qcom,fg-use-sw-esr");

	return 0;
}

@@ -4482,11 +4652,13 @@ static int fg_gen3_probe(struct spmi_device *spmi)
	mutex_init(&chip->cl.lock);
	mutex_init(&chip->batt_avg_lock);
	mutex_init(&chip->charge_full_lock);
	spin_lock_init(&chip->awake_lock);
	init_completion(&chip->soc_update);
	init_completion(&chip->soc_ready);
	INIT_DELAYED_WORK(&chip->profile_load_work, profile_load_work);
	INIT_WORK(&chip->status_change_work, status_change_work);
	INIT_WORK(&chip->cycle_count_work, cycle_count_work);
	INIT_WORK(&chip->esr_sw_work, fg_esr_sw_work);
	INIT_DELAYED_WORK(&chip->batt_avg_work, batt_avg_work);
	INIT_DELAYED_WORK(&chip->sram_dump_work, sram_dump_work);
	dev_set_drvdata(&spmi->dev, chip);
@@ -4505,6 +4677,23 @@ static int fg_gen3_probe(struct spmi_device *spmi)
		goto exit;
	}

	if (chip->dt.use_esr_sw) {
		if (alarmtimer_get_rtcdev()) {
			alarm_init(&chip->esr_sw_timer, ALARM_BOOTTIME,
				fg_esr_sw_timer);
		} else {
			pr_err("Failed to get esw_sw alarm-timer\n");
			/* RTC always registers, hence defer until it passes */
			rc = -EPROBE_DEFER;
			goto exit;
		}
		if (chip->dt.esr_timer_charging[TIMER_MAX] != -EINVAL)
			chip->esr_wakeup_ms =
				chip->dt.esr_timer_charging[TIMER_MAX] * 1460;
		else
			chip->esr_wakeup_ms = 140000;	/* 140 seconds */
	}

	chip->fg_psy.name = "bms";
	chip->fg_psy.type = POWER_SUPPLY_TYPE_BMS;
	chip->fg_psy.properties = fg_psy_props;