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

Commit cfd89499 authored by Anirudh Ghayal's avatar Anirudh Ghayal Committed by Rajeswari Konda
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>
Signed-off-by: default avatarChinkit Kumar,Kirti Kumar Parmar <parma@codeaurora.org>
parent d26b7fdf
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -458,6 +458,12 @@ First Level Node - FG Gen3 device
	Value type: <u32>
	Definition: The delay in ms for FG to enable BMD after reading RID.

- 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
==========================================================
+17 −0
Original line number Diff line number Diff line
@@ -24,9 +24,11 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/alarmtimer.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string_helpers.h>
#include <linux/types.h>
#include <linux/uaccess.h>
@@ -51,6 +53,7 @@
#define SRAM_WRITE		"fg_sram_write"
#define PROFILE_LOAD		"fg_profile_load"
#define TTF_PRIMING		"fg_ttf_priming"
#define FG_ESR_VOTER		"fg_esr_voter"

/* Delta BSOC irq votable reasons */
#define DELTA_BSOC_IRQ_VOTER	"fg_delta_bsoc_irq"
@@ -107,6 +110,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,
@@ -273,6 +281,7 @@ struct fg_dt_props {
	bool	hold_soc_while_full;
	bool	linearize_soc;
	bool	auto_recharge_soc;
	bool	use_esr_sw;
	int	cutoff_volt_mv;
	int	empty_volt_mv;
	int	vbatt_low_thr_mv;
@@ -440,17 +449,21 @@ 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 ttf		ttf;
	struct mutex		bus_lock;
	struct mutex		sram_rw_lock;
	struct mutex		charge_full_lock;
	struct mutex		qnovo_esr_ctrl_lock;
	spinlock_t		awake_lock;
	spinlock_t		suspend_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;
@@ -482,6 +495,7 @@ struct fg_chip {
	bool			esr_flt_cold_temp_en;
	bool			slope_limit_en;
	bool			use_ima_single_mode;
	bool			usb_present;
	bool			use_dma;
	bool			qnovo_enable;
	bool			suspended;
@@ -489,6 +503,7 @@ struct fg_chip {
	struct completion	soc_ready;
	struct delayed_work	profile_load_work;
	struct work_struct	status_change_work;
	struct work_struct	esr_sw_work;
	struct delayed_work	ttf_work;
	struct delayed_work	sram_dump_work;
	struct delayed_work	pl_enable_work;
@@ -561,4 +576,6 @@ extern int fg_circ_buf_avg(struct fg_circ_buf *buf, int *avg);
extern int fg_circ_buf_median(struct fg_circ_buf *buf, int *median);
extern int fg_lerp(const struct fg_pt *pts, size_t tablesize, s32 input,
			s32 *output);
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
@@ -999,3 +999,27 @@ int fg_debugfs_create(struct fg_chip *chip)
	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);
}
+178 −3
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
#include <linux/ktime.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <linux/alarmtimer.h>
#include <linux/of_platform.h>
#include <linux/of_batterydata.h>
#include <linux/platform_device.h>
@@ -2754,6 +2756,142 @@ static const char *fg_get_cycle_count(struct fg_chip *chip)
	return buf;
}

#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,
@@ -2771,6 +2909,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) {
@@ -2836,7 +2978,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 int fg_bp_params_config(struct fg_chip *chip)
@@ -3157,7 +3299,7 @@ static void profile_load_work(struct work_struct *work)
	schedule_delayed_work(&chip->pl_enable_work, msecs_to_jiffies(5000));
	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);
	}
}
@@ -4075,7 +4217,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);
	}

@@ -4413,6 +4555,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;
}

@@ -5350,6 +5504,8 @@ static int fg_parse_dt(struct fg_chip *chip)
			chip->dt.bmd_en_delay_ms = temp;
	}

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

	return 0;
}

@@ -5498,12 +5654,14 @@ static int fg_gen3_probe(struct platform_device *pdev)
	mutex_init(&chip->ttf.lock);
	mutex_init(&chip->charge_full_lock);
	mutex_init(&chip->qnovo_esr_ctrl_lock);
	spin_lock_init(&chip->awake_lock);
	spin_lock_init(&chip->suspend_lock);
	init_completion(&chip->soc_update);
	init_completion(&chip->soc_ready);
	INIT_DELAYED_WORK(&chip->profile_load_work, profile_load_work);
	INIT_DELAYED_WORK(&chip->pl_enable_work, pl_enable_work);
	INIT_WORK(&chip->status_change_work, status_change_work);
	INIT_WORK(&chip->esr_sw_work, fg_esr_sw_work);
	INIT_DELAYED_WORK(&chip->ttf_work, ttf_work);
	INIT_DELAYED_WORK(&chip->sram_dump_work, sram_dump_work);
	INIT_WORK(&chip->esr_filter_work, esr_filter_work);
@@ -5526,6 +5684,23 @@ static int fg_gen3_probe(struct platform_device *pdev)
		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 */
	}

	/* Register the power supply */
	fg_psy_cfg.drv_data = chip;
	fg_psy_cfg.of_node = NULL;