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

Commit 7b9c8bc4 authored by David Keitel's avatar David Keitel
Browse files

power: qpnp-fg: add battery profile loading support



The battery profile for the PMI8994 fuel gauge peripheral
is represented as a 128 byte long binary blob which
is specified in the batterydata devicetree file for
a given battery.

Upon probe the devicetree data is parsed and written
into the corresponding SRAM offset using a delayed
workqueue.

If no such property is present in the referenced battery
data file, the default OTP profiles will be used by the
fuel gauge and may introduce accuracy issues.

Change-Id: I2605d69265908f0c06d5b47c1dfe87e94c743380
Signed-off-by: default avatarDavid Keitel <dkeitel@codeaurora.org>
parent 55ca3fcf
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -27,6 +27,11 @@ Profile data node required properties:
			module when the ID resistance of some battery modules goes across
			several ranges.
- qcom,battery-type : A string indicating the type of battery.
- qcom,fg-profile-data : An array of hexadecimal values used to configure more
			complex fuel gauge peripherals which have a large amount
			of coefficients used in hardware state machines and thus
			influencing the final output of the state of charge read
			by software.

Profile data node required subnodes:
- qcom,fcc-temp-lut : An 1-dimensional lookup table node that encodes
+2 −0
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ Parent node optional properties:
- qcom,cool-bat-decidegc:		Cool battery temperature in decidegC.
- qcom,hot-bat-decidegc:		Hot battery temperature in decidegC.
- qcom,cold-bat-decidegc:		Cold battery temperature in decidegC.
- qcom,use-otp-profile:			Specify this flag to avoid RAM loading
					any battery profile.

qcom,fg-soc node required properties:
- reg : offset and length of the PMIC peripheral register map.
+34 −1
Original line number Diff line number Diff line
/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
/* Copyright (c) 2013-2014, 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
@@ -19,6 +19,39 @@ qcom,mtp-3000mah {
	qcom,chg-term-ua = <200000>;
	qcom,batt-id-kohm = <300>;
	qcom,battery-type = "mtp_3000mah";
	qcom,fg-profile-data = [
		07 88 73 7E
		31 82 CB 7C
		6D 83 E6 76
		74 89 E1 8E
		F3 81 7A 93
		72 AE 1A B3
		67 1B 07 88
		03 7E DB 81
		7B 7C 6F 83
		D7 75 DE 89
		4C 94 34 82
		0F 99 41 B7
		F7 C2 60 14
		07 0B 2C 5B
		28 6E 5C FF
		9A 3E 49 4E
		78 49 18 28
		82 46 19 34
		C5 4F 18 28
		66 72 66 62
		66 72 66 62
		2D 6B 8F 69
		2F 88 65 91
		F5 6E 0C 48
		51 75 67 83
		DF 6D 01 3E
		66 4E 00 82
		0A 32 58 8A
		5C A0 71 0C
		28 00 FF 37
		F0 51 30 03
		01 00 00 00];

	qcom,fcc-temp-lut {
		qcom,lut-col-legend = <(-20) 0 25 40 60>;
+10 −0
Original line number Diff line number Diff line
@@ -555,3 +555,13 @@
		interrupt-names = "silabs_fm_int";
	};
};

/{
	mtp_batterydata: qcom,battery-data {
		#include "batterydata-mtp-3000mah.dtsi"
	};
};

&pmi8994_fg {
	qcom,battery-data = <&mtp_batterydata>;
};
+268 −45
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@

#define pr_fmt(fmt)	"FG: %s: " fmt, __func__

#include <linux/atomic.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/of.h>
@@ -49,6 +50,8 @@
#define MEM_INTF_RD_DATA2	0x4E
#define MEM_INTF_RD_DATA3	0x4F
#define OTP_CFG1		0xE2
#define SOC_BOOT_MOD		0x50
#define SOC_RESTART		0x51

#define REG_OFFSET_PERP_SUBTYPE	0x05

@@ -61,6 +64,9 @@
#define MA_MV_BIT_RES		39
#define MSB_SIGN		BIT(7)
#define IBAT_VBAT_MASK		0x7F
#define NO_OTP_PROF_RELOAD	BIT(6)
#define REDO_FIRST_ESTIMATE	BIT(3)
#define RESTART_GO		BIT(0)

/* SUBTYPE definitions */
#define FG_SOC			0x9
@@ -89,10 +95,10 @@ struct fg_mem_setting {

/* FG_MEMIF setting index */
enum fg_mem_setting_index {
	FG_MEM_JEITA_SOFT_COLD,
	FG_MEM_JEITA_SOFT_HOT,
	FG_MEM_JEITA_HARD_COLD,
	FG_MEM_JEITA_HARD_HOT,
	FG_MEM_SOFT_COLD,
	FG_MEM_SOFT_HOT,
	FG_MEM_HARD_COLD,
	FG_MEM_HARD_HOT,
	FG_MEM_SETTING_MAX,
};

@@ -105,10 +111,10 @@ enum fg_mem_setting_index {

static struct fg_mem_setting settings[FG_MEM_SETTING_MAX] = {
	/*       ID                    Address, Offset, Value*/
	SETTING(JEITA_SOFT_COLD,       0x454,   0,      100),
	SETTING(JEITA_SOFT_HOT,        0x454,   1,      400),
	SETTING(JEITA_HARD_COLD,       0x454,   2,      50),
	SETTING(JEITA_HARD_HOT,        0x454,   3,      450),
	SETTING(SOFT_COLD,       0x454,   0,      100),
	SETTING(SOFT_HOT,        0x454,   1,      400),
	SETTING(HARD_COLD,       0x454,   2,      50),
	SETTING(HARD_HOT,        0x454,   3,      450),
};

static int fg_debug_mask;
@@ -159,13 +165,20 @@ struct fg_chip {
	u16			mem_base;
	u16			vbat_adc_addr;
	u16			ibat_adc_addr;
	atomic_t		memif_user_cnt;
	bool			fast_access;
	struct fg_irq		soc_irq[FG_SOC_IRQ_COUNT];
	struct fg_irq		batt_irq[FG_BATT_IRQ_COUNT];
	struct fg_irq		mem_irq[FG_MEM_IF_IRQ_COUNT];
	struct completion	sram_access;
	struct power_supply	bms_psy;
	struct mutex		rw_lock;
	struct work_struct	batt_profile_init;
	bool			profile_loaded;
	bool			use_otp_profile;
	struct delayed_work	update_jeita_setting;
	char			*batt_profile;
	unsigned int		batt_profile_len;
};

/* FG_MEMIF DEBUGFS structures */
@@ -420,20 +433,23 @@ static int fg_set_ram_addr(struct fg_chip *chip, u16 *address)
static int fg_mem_read(struct fg_chip *chip, u8 *val, u16 address, int len,
		bool keep_access)
{
	int rc = 0;
	int rc = 0, user_cnt = 0;
	u8 *rd_data = val;
	bool otp;

	if (address < RAM_OFFSET)
		otp = 1;

	user_cnt = atomic_add_return(1, &chip->memif_user_cnt);
	if (fg_debug_mask & FG_MEM_DEBUG_READS)
		pr_info("user_cnt %d\n", user_cnt);
	mutex_lock(&chip->rw_lock);
	if (!fg_check_sram_access(chip)) {
		rc = fg_req_and_wait_access(chip, MEM_IF_TIMEOUT_MS);
		if (rc)
			return rc;
			goto out;
	}

	mutex_lock(&chip->rw_lock);
	rc = fg_config_access(chip, 0, (len > 4), otp);
	if (rc)
		goto out;
@@ -461,14 +477,18 @@ static int fg_mem_read(struct fg_chip *chip, u8 *val, u16 address, int len,
			len = 0;
	}

	if (!keep_access) {
out:
	user_cnt = atomic_sub_return(1, &chip->memif_user_cnt);
	if (fg_debug_mask & FG_MEM_DEBUG_READS)
		pr_info("user_cnt %d\n", user_cnt);

	if (!keep_access && (user_cnt == 0) && !rc) {
		rc = fg_masked_write(chip, chip->mem_base + MEM_INTF_CFG,
				RIF_MEM_ACCESS_REQ, 0, 1);
		if (rc)
			pr_err("failed to set mem access bit\n");
	}

out:
	mutex_unlock(&chip->rw_lock);
	return rc;
}
@@ -476,7 +496,7 @@ out:
static int fg_mem_write(struct fg_chip *chip, u8 *val, u16 address,
		unsigned int len, unsigned int offset, bool keep_access)
{
	int rc = 0;
	int rc = 0, user_cnt = 0;
	u8 *wr_data = val;

	if (address < RAM_OFFSET)
@@ -485,13 +505,16 @@ static int fg_mem_write(struct fg_chip *chip, u8 *val, u16 address,
	if (offset > 3)
		return -EINVAL;

	user_cnt = atomic_add_return(1, &chip->memif_user_cnt);
	if (fg_debug_mask & FG_MEM_DEBUG_READS)
		pr_info("user_cnt %d\n", user_cnt);
	mutex_lock(&chip->rw_lock);
	if (!fg_check_sram_access(chip)) {
		rc = fg_req_and_wait_access(chip, MEM_IF_TIMEOUT_MS);
		if (rc)
			return rc;
			goto out;
	}

	mutex_lock(&chip->rw_lock);
	rc = fg_config_access(chip, 1, (len > 4), 0);
	if (rc)
		goto out;
@@ -532,7 +555,11 @@ static int fg_mem_write(struct fg_chip *chip, u8 *val, u16 address,
		}
	}

	if (!keep_access) {
out:
	user_cnt = atomic_sub_return(1, &chip->memif_user_cnt);
	if (fg_debug_mask & FG_MEM_DEBUG_READS)
		pr_info("user_cnt %d\n", user_cnt);
	if (!keep_access && (user_cnt == 0) && !rc) {
		rc = fg_masked_write(chip, chip->mem_base + MEM_INTF_CFG,
				RIF_MEM_ACCESS_REQ, 0, 1);
		if (rc) {
@@ -541,16 +568,48 @@ static int fg_mem_write(struct fg_chip *chip, u8 *val, u16 address,
		}
	}

out:
	mutex_unlock(&chip->rw_lock);
	return 0;
	return rc;
}

static int fg_mem_masked_write(struct fg_chip *chip, u16 addr,
		u8 mask, u8 val, u8 offset)
{
	int rc = 0;
	u8 reg[4];
	char str[DEBUG_PRINT_BUFFER_SIZE];

	rc = fg_mem_read(chip, reg, addr, 4, 1);
	if (rc) {
		pr_err("spmi read failed: addr=%03X, rc=%d\n", addr, rc);
		return rc;
	}

	reg[offset] &= ~mask;
	reg[offset] |= val & mask;

	str[0] = '\0';
	fill_string(str, DEBUG_PRINT_BUFFER_SIZE, reg, 4);
	pr_debug("Writing %s address %03x, offset %d\n", str, addr, offset);

	rc = fg_mem_write(chip, reg, addr, 4, 0, 0);
	if (rc) {
		pr_err("spmi write failed: addr=%03X, rc=%d\n", addr, rc);
		return rc;
	}

	return rc;
}

#define DEFAULT_CAPACITY 50
static int get_prop_capacity(struct fg_chip *chip)
{
	u8 cap[2];
	int rc, capacity = 0, tries = 0;

	if (!chip->profile_loaded && !chip->use_otp_profile)
		return DEFAULT_CAPACITY;

	while (tries < MAX_TRIES_SOC) {
		rc = fg_read(chip, cap,
				chip->soc_base + SOC_MONOTONIC_SOC, 2);
@@ -633,6 +692,10 @@ static int get_prop_voltage_now(struct fg_chip *chip)
#define MAX_TEMP_DEGC	970
static int get_prop_jeita_temp(struct fg_chip *chip, unsigned int type)
{
	if (fg_debug_mask & FG_POWER_SUPPLY)
		pr_info("addr 0x%02X, offset %d\n", settings[type].address,
			settings[type].offset);

	return settings[type].value;
}

@@ -641,7 +704,9 @@ static int set_prop_jeita_temp(struct fg_chip *chip,
{
	int rc = 0;

	pr_debug("addr 0x%02X, offset %d temp%d\n", settings[type].address,
	if (fg_debug_mask & FG_POWER_SUPPLY)
		pr_info("addr 0x%02X, offset %d temp%d\n",
			settings[type].address,
			settings[type].offset, decidegc);

	settings[type].value = decidegc;
@@ -663,10 +728,10 @@ static void update_jeita_setting(struct work_struct *work)
	int i, rc;

	for (i = 0; i < 4; i++)
		reg[i] = (settings[JEITA_SOFT_COLD + i].value / 10) + 30;
		reg[i] = (settings[FG_MEM_SOFT_COLD + i].value / 10) + 30;

	rc = fg_mem_write(chip, reg, settings[JEITA_SOFT_COLD].address, 4,
			settings[JEITA_SOFT_COLD].offset, 0);
	rc = fg_mem_write(chip, reg, settings[FG_MEM_SOFT_COLD].address,
			4, settings[FG_MEM_SOFT_COLD].offset, 0);
	if (rc)
		pr_err("failed to update JEITA setting rc=%d\n", rc);
}
@@ -696,10 +761,10 @@ static int fg_power_get_property(struct power_supply *psy,
		val->intval = get_prop_voltage_now(chip);
		break;
	case POWER_SUPPLY_PROP_COOL_TEMP:
		val->intval = get_prop_jeita_temp(chip, FG_MEM_JEITA_SOFT_COLD);
		val->intval = get_prop_jeita_temp(chip, FG_MEM_SOFT_COLD);
		break;
	case POWER_SUPPLY_PROP_WARM_TEMP:
		val->intval = get_prop_jeita_temp(chip, FG_MEM_JEITA_SOFT_HOT);
		val->intval = get_prop_jeita_temp(chip, FG_MEM_SOFT_HOT);
		break;
	default:
		return -EINVAL;
@@ -718,11 +783,11 @@ static int fg_power_set_property(struct power_supply *psy,
	switch (psp) {
	case POWER_SUPPLY_PROP_COOL_TEMP:
		rc = set_prop_jeita_temp(chip,
				FG_MEM_JEITA_SOFT_COLD, val->intval);
				FG_MEM_SOFT_COLD, val->intval);
		break;
	case POWER_SUPPLY_PROP_WARM_TEMP:
		rc = set_prop_jeita_temp(chip,
				FG_MEM_JEITA_SOFT_HOT, val->intval);
				FG_MEM_SOFT_HOT, val->intval);
		break;
	default:
		return -EINVAL;
@@ -748,7 +813,7 @@ static int fg_property_is_writeable(struct power_supply *psy,
static irqreturn_t fg_mem_avail_irq_handler(int irq, void *_chip)
{
	struct fg_chip *chip = _chip;
	u8 mem_if_sts;
	u8 mem_if_sts, reg;
	int rc;

	rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1);
@@ -760,6 +825,13 @@ static irqreturn_t fg_mem_avail_irq_handler(int irq, void *_chip)
	if (fg_check_sram_access(chip)) {
		if (fg_debug_mask & FG_IRQS)
			pr_info("sram access granted\n");
		if (chip->fast_access) {
			reg = REDO_FIRST_ESTIMATE | RESTART_GO;
			rc = fg_masked_write(chip, chip->soc_base + SOC_RESTART,
				       reg, reg, 1);
			if (rc)
				pr_err("failed to set low latency bit\n");
		}
		complete_all(&chip->sram_access);
	} else {
		if (fg_debug_mask & FG_IRQS)
@@ -774,6 +846,25 @@ static irqreturn_t fg_mem_avail_irq_handler(int irq, void *_chip)
}

static irqreturn_t fg_soc_irq_handler(int irq, void *_chip)
{
	struct fg_chip *chip = _chip;
	u8 soc_rt_sts;
	int rc;

	rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1);
	if (rc) {
		pr_err("spmi read failed: addr=%03X, rc=%d\n",
				INT_RT_STS(chip->soc_base), rc);
	}

	if (fg_debug_mask & FG_IRQS)
		pr_info("triggered 0x%x\n", soc_rt_sts);

	power_supply_changed(&chip->bms_psy);
	return IRQ_HANDLED;
}

static irqreturn_t fg_first_soc_irq_handler(int irq, void *_chip)
{
	struct fg_chip *chip = _chip;

@@ -800,14 +891,121 @@ do { \
				" property rc = %d\n", rc);		\
} while (0)

#define LOW_LATENCY	BIT(6)
static int fg_batt_profile_init(struct fg_chip *chip)
{
	int rc = 0;
	int len;
	struct device_node *node = chip->spmi->dev.of_node;
	struct device_node *batt_node;
	const char *data;
	u8 reg = 0;

	batt_node = of_find_node_by_name(node, "qcom,battery-data");
	if (!batt_node) {
		pr_warn("No available batterydata, using OTP defaults\n");
		return 0;
	}

	for_each_child_of_node(batt_node, node) {
		data = of_get_property(node, "qcom,fg-profile-data", &len);
		if (!data) {
			pr_err("no battery profile loaded\n");
			return 0;
		} else {
			break;
		}
	}

	chip->batt_profile = devm_kzalloc(chip->dev,
			sizeof(char) * len, GFP_KERNEL);
	if (!chip->batt_profile)
		return -ENOMEM;

	memcpy(chip->batt_profile, data, len);

	chip->batt_profile_len = len;

	if (fg_debug_mask & FG_MEM_DEBUG_WRITES)
		print_hex_dump(KERN_ERR, "profile: ", DUMP_PREFIX_NONE, 16, 1,
			chip->batt_profile, chip->batt_profile_len, false);

	reg = NO_OTP_PROF_RELOAD;
	rc = fg_masked_write(chip, chip->soc_base + SOC_BOOT_MOD, reg, reg, 1);
	if (rc) {
		pr_err("failed to set low latency access bit\n");
		return -EIO;
	}

	reg = LOW_LATENCY;
	rc = fg_write(chip, &reg, chip->mem_base + MEM_INTF_CTL, 1);
	if (rc) {
		pr_err("failed to set low latency access bit\n");
		return -EIO;
	}
	chip->fast_access = true;

	rc = fg_mem_write(chip, chip->batt_profile, 0x4C0,
			chip->batt_profile_len, 0, 1);
	if (rc)
		pr_err("failed to write profile rc=%d\n", rc);

	rc = fg_mem_masked_write(chip, 0x53C, 0x1, 0x1, 0);
	if (rc)
		pr_err("failed to write profile rc=%d\n", rc);

	reg = 0;
	rc = fg_write(chip, &reg, chip->mem_base + MEM_INTF_CTL, 1);
	if (rc) {
		pr_err("failed to set low latency access bit\n");
		return -EIO;
	}

	reg = 0;
	rc = fg_write(chip, &reg, chip->soc_base + SOC_RESTART, 1);
	if (rc) {
		pr_err("failed to set low latency access bit\n");
		return -EIO;
	}

	chip->fast_access = false;

	/* wait for 2 seconds prior to restart the fuel gauge */
	msleep(2000);
	reg = 0x19;
	rc = fg_write(chip, &reg, chip->soc_base + SOC_RESTART, 1);
	if (rc) {
		pr_err("failed to set low latency access bit\n");
		return -EIO;
	}

	chip->profile_loaded = true;
	return rc;
}

static void batt_profile_init(struct work_struct *work)
{
	struct fg_chip *chip = container_of(work,
				struct fg_chip,
				batt_profile_init);

	if (fg_batt_profile_init(chip))
		pr_err("failed to update JEITA setting\n");
}

static int fg_of_init(struct fg_chip *chip)
{
	int rc = 0;

	OF_READ_SETTING(FG_MEM_JEITA_SOFT_HOT, "warm-bat-decidegc", rc, 1);
	OF_READ_SETTING(FG_MEM_JEITA_SOFT_COLD, "cool-bat-decidegc", rc, 1);
	OF_READ_SETTING(FG_MEM_JEITA_HARD_HOT, "hot-bat-decidegc", rc, 1);
	OF_READ_SETTING(FG_MEM_JEITA_HARD_COLD, "cold-bat-decidegc", rc, 1);
	OF_READ_SETTING(FG_MEM_SOFT_HOT, "warm-bat-decidegc", rc, 1);
	OF_READ_SETTING(FG_MEM_SOFT_COLD, "cool-bat-decidegc", rc, 1);
	OF_READ_SETTING(FG_MEM_HARD_HOT, "hot-bat-decidegc", rc, 1);
	OF_READ_SETTING(FG_MEM_HARD_COLD, "cold-bat-decidegc", rc, 1);

	/* Get the use-otp-profile property */
	chip->use_otp_profile = of_property_read_bool(
			chip->spmi->dev.of_node,
			"qcom,use-otp-profile");

	return rc;
}
@@ -865,6 +1063,12 @@ static int fg_init_irqs(struct fg_chip *chip)
				pr_err("Unable to get delta-soc irq\n");
				return rc;
			}
			chip->soc_irq[FIRST_EST_DONE].irq = spmi_get_irq_byname(
				chip->spmi, spmi_resource, "first-est-done");
			if (chip->soc_irq[FIRST_EST_DONE].irq < 0) {
				pr_err("Unable to get first-est-done irq\n");
				return rc;
			}

			rc |= devm_request_irq(chip->dev,
				chip->soc_irq[FULL_SOC].irq,
@@ -893,6 +1097,15 @@ static int fg_init_irqs(struct fg_chip *chip)
					chip->soc_irq[DELTA_SOC].irq, rc);
				return rc;
			}
			rc |= devm_request_irq(chip->dev,
				chip->soc_irq[FIRST_EST_DONE].irq,
				fg_first_soc_irq_handler, IRQF_TRIGGER_RISING,
				"first-est-done", chip);
			if (rc < 0) {
				pr_err("Can't request %d delta-soc: %d\n",
					chip->soc_irq[FIRST_EST_DONE].irq, rc);
				return rc;
			}

			enable_irq_wake(chip->soc_irq[FULL_SOC].irq);
			enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq);
@@ -934,6 +1147,7 @@ static int fg_remove(struct spmi_device *spmi)

	mutex_destroy(&chip->rw_lock);
	cancel_delayed_work_sync(&chip->update_jeita_setting);
	cancel_work_sync(&chip->batt_profile_init);
	power_supply_unregister(&chip->bms_psy);
	dev_set_drvdata(&spmi->dev, NULL);
	return 0;
@@ -1327,6 +1541,7 @@ err_remove_fs:
	return -ENOMEM;
}

#define INIT_JEITA_DELAY_MS 1000
static int fg_probe(struct spmi_device *spmi)
{
	struct device *dev = &(spmi->dev);
@@ -1358,14 +1573,10 @@ static int fg_probe(struct spmi_device *spmi)
	mutex_init(&chip->rw_lock);
	INIT_DELAYED_WORK(&chip->update_jeita_setting,
			update_jeita_setting);
	INIT_WORK(&chip->batt_profile_init,
			batt_profile_init);
	init_completion(&chip->sram_access);

	rc = fg_of_init(chip);
	if (rc) {
		pr_err("failed to parse devicetree rc%d\n", rc);
		goto of_init_fail;
	}

	spmi_for_each_container_dev(spmi_resource, spmi) {
		if (!spmi_resource) {
			pr_err("qpnp_chg: spmi resource absent\n");
@@ -1412,6 +1623,12 @@ static int fg_probe(struct spmi_device *spmi)
		}
	}

	rc = fg_of_init(chip);
	if (rc) {
		pr_err("failed to parse devicetree rc%d\n", rc);
		goto of_init_fail;
	}

	chip->bms_psy.name = "bms";
	chip->bms_psy.type = POWER_SUPPLY_TYPE_BMS;
	chip->bms_psy.properties = fg_power_props;
@@ -1442,7 +1659,13 @@ static int fg_probe(struct spmi_device *spmi)
		}
	}

	pr_info("probe success SOC %d\n", get_prop_capacity(chip));
	schedule_delayed_work(
		&chip->update_jeita_setting,
		msecs_to_jiffies(INIT_JEITA_DELAY_MS));
	if (!chip->use_otp_profile)
		schedule_work(&chip->batt_profile_init);

	pr_info("probe success\n");

	return rc;