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

Commit 6963bf1a authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "power: qpnp-fg-gen4: add support for SOH based profile loading"

parents acef4a22 b4b7f7fa
Loading
Loading
Loading
Loading
+132 −1
Original line number Diff line number Diff line
/* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
/* Copyright (c) 2013-2019, 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
@@ -18,6 +18,7 @@
#include <linux/module.h>
#include <linux/types.h>
#include <linux/batterydata-lib.h>
#include <linux/of_batterydata.h>
#include <linux/power_supply.h>

static int of_batterydata_read_lut(const struct device_node *np,
@@ -475,6 +476,136 @@ struct device_node *of_batterydata_get_best_aged_profile(
	return best_node;
}

int of_batterydata_get_aged_profile_count(
		const struct device_node *batterydata_node,
		int batt_id_kohm, int *count)
{
	struct device_node *node;
	int id_range_pct, i = 0, rc = 0, limit = 0, delta = 0;
	bool in_range = false;
	u32 batt_id;

	/* read battery id range percentage for best profile */
	rc = of_property_read_u32(batterydata_node,
			"qcom,batt-id-range-pct", &id_range_pct);
	if (rc) {
		if (rc == -EINVAL) {
			id_range_pct = 0;
		} else {
			pr_err("failed to read battery id range\n");
			return -ENXIO;
		}
	}

	for_each_available_child_of_node(batterydata_node, node) {
		if (!of_find_property(node, "qcom,batt-age-level", NULL))
			continue;

		if (!of_find_property(node, "qcom,soh-range", NULL))
			continue;

		rc = of_property_read_u32(node, "qcom,batt-id-kohm", &batt_id);
		if (rc)
			continue;

		delta = abs(batt_id_kohm - batt_id);
		limit = (batt_id_kohm * id_range_pct) / 100;
		in_range = (delta <= limit);

		if (!in_range) {
			pr_debug("not in range batt_id: %d\n", batt_id);
			continue;
		}

		i++;
	}

	if (i <= 1) {
		pr_err("Less number of profiles to support SOH\n");
		return -EINVAL;
	}

	*count = i;
	return 0;
}

int of_batterydata_read_soh_aged_profiles(
		const struct device_node *batterydata_node,
		int batt_id_kohm, struct soh_range *soh_data)
{
	struct device_node *node;
	u32 val, temp[2], i = 0;
	int rc, batt_id, id_range_pct, limit = 0, delta = 0;
	bool in_range = false;

	if (!batterydata_node || !soh_data)
		return -ENODEV;

	/* read battery id range percentage for best profile */
	rc = of_property_read_u32(batterydata_node,
			"qcom,batt-id-range-pct", &id_range_pct);
	if (rc) {
		if (rc == -EINVAL) {
			id_range_pct = 0;
		} else {
			pr_err("failed to read battery id range\n");
			return -ENXIO;
		}
	}

	for_each_available_child_of_node(batterydata_node, node) {
		rc = of_property_read_u32(node, "qcom,batt-age-level", &val);
		if (rc)
			continue;

		rc = of_property_read_u32(node, "qcom,batt-id-kohm", &batt_id);
		if (rc)
			continue;

		delta = abs(batt_id_kohm - batt_id);
		limit = (batt_id_kohm * id_range_pct) / 100;
		in_range = (delta <= limit);

		if (!in_range) {
			pr_debug("not in range batt_id: %d\n", batt_id);
			continue;
		}

		if (!of_find_property(node, "qcom,soh-range", NULL))
			continue;

		rc = of_property_count_elems_of_size(node, "qcom,soh-range",
						sizeof(u32));
		if (rc != 2) {
			pr_err("Incorrect element size for qcom,soh-range, rc=%d\n",
				rc);
			return -EINVAL;
		}

		rc = of_property_read_u32_array(node, "qcom,soh-range", temp,
						2);
		if (rc < 0) {
			pr_err("Error in reading qcom,soh-range, rc=%d\n", rc);
			return rc;
		}

		if (temp[0] > 100 || temp[1] > 100 || (temp[0] > temp[1])) {
			pr_err("Incorrect SOH range [%d %d]\n", temp[0],
				temp[1]);
			return -ERANGE;
		}

		pr_debug("batt_age_level: %d soh: [%d %d]\n", val, temp[0],
			temp[1]);
		soh_data[i].batt_age_level = val;
		soh_data[i].soh_min = temp[0];
		soh_data[i].soh_max = temp[1];
		i++;
	}

	return 0;
}

int of_batterydata_read_data(struct device_node *batterydata_container_node,
				struct bms_battery_data *batt_data,
				int batt_id_uv)
+118 −0
Original line number Diff line number Diff line
@@ -794,6 +794,124 @@ int cap_learning_init(struct cap_learning *cl)
	return 0;
}

/* SOH based profile loading */

/**
 * soh_get_batt_age_level -
 * @sp: SOH profile object
 * @soh: SOH level
 * @batt_age_level: Battery age level if exists for the SOH passed
 *
 */
static int soh_get_batt_age_level(struct soh_profile *sp, int soh,
				int *batt_age_level)
{
	struct soh_range *range = sp->soh_data;
	int i;

	for (i = 0; i < sp->profile_count; i++) {
		if (is_between(range[i].soh_min, range[i].soh_max, soh)) {
			*batt_age_level = range[i].batt_age_level;
			return 0;
		}
	}

	return -ENOENT;
}

/**
 * soh_profile_update -
 * @sp: SOH profile object
 * @new_soh: SOH level that is updated and notified to FG/QG driver
 *
 * FG/QG have to call this whenever SOH is notified by the userspace.
 *
 */
int soh_profile_update(struct soh_profile *sp, int new_soh)
{
	union power_supply_propval pval = {0, };
	int rc, batt_age_level = 0;

	if (!sp->bms_psy)
		return -ENODEV;

	if (new_soh <= 0)
		return 0;

	if (new_soh != sp->last_soh)
		pr_debug("SOH changed from %d to %d\n", sp->last_soh, new_soh);

	if (sp->last_soh <= 0) {
		sp->last_soh = new_soh;
		pr_debug("SOH initialized to %d\n", sp->last_soh);
	}

	rc = soh_get_batt_age_level(sp, new_soh, &batt_age_level);
	if (rc < 0)
		return rc;

	if (batt_age_level != sp->last_batt_age_level) {
		pval.intval = batt_age_level;
		rc = power_supply_set_property(sp->bms_psy,
			POWER_SUPPLY_PROP_BATT_AGE_LEVEL, &pval);
		if (rc < 0) {
			pr_err("Couldn't set batt_age_level rc=%d\n", rc);
			return rc;
		}

		sp->last_batt_age_level = batt_age_level;
		pr_info("Batt_age_level set to %d for SOH %d\n",
			batt_age_level, new_soh);
	}

	return 0;
}

/**
 * soh_profile_init -
 * @dev: Device node of FG/QG
 * @sp: SOH profile object
 *
 * FG/QG have to call this after parsing battery profile node and multiple
 * profile load feature is enabled. SOH profile object should have atleast
 * the power supply of FG/QG and battery profile node. SOH specific range
 * data is allocated by this function.
 *
 */
int soh_profile_init(struct device *dev, struct soh_profile *sp)
{
	int rc, profile_count = 0;

	if (!dev || !sp || !sp->bp_node || !sp->bms_psy)
		return -ENODEV;

	rc = of_batterydata_get_aged_profile_count(sp->bp_node,
				sp->batt_id_kohms, &profile_count);
	if (rc < 0) {
		pr_err("Couldn't get profile count rc=%d\n", rc);
		return rc;
	}

	sp->soh_data = devm_kcalloc(dev, profile_count, sizeof(*sp->soh_data),
				GFP_KERNEL);
	if (!sp->soh_data)
		return -ENOMEM;

	rc = of_batterydata_read_soh_aged_profiles(sp->bp_node,
				sp->batt_id_kohms, sp->soh_data);
	if (rc < 0) {
		pr_err("Couldn't read SOH data for profile loading, rc=%d\n",
			rc);
		devm_kfree(dev, sp->soh_data);
		return rc;
	}

	sp->profile_count = profile_count;
	sp->last_soh = -EINVAL;
	sp->initialized = true;
	return 0;
}

/* Time to full/empty algorithm  helper functions */

static void ttf_circ_buf_add(struct ttf_circ_buf *buf, int val)
+14 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#ifndef __FG_ALG_H__
#define __FG_ALG_H__

#include <linux/of_batterydata.h>
#include "step-chg-jeita.h"

#define BUCKET_COUNT		8
@@ -136,6 +137,17 @@ struct ttf {
	int (*awake_voter)(void *data, bool vote);
};

struct soh_profile {
	struct device_node *bp_node;
	struct power_supply *bms_psy;
	struct soh_range *soh_data;
	int batt_id_kohms;
	int profile_count;
	int last_soh;
	int last_batt_age_level;
	bool initialized;
};

int restore_cycle_count(struct cycle_counter *counter);
void clear_cycle_count(struct cycle_counter *counter);
void cycle_count_update(struct cycle_counter *counter, int batt_soc,
@@ -154,5 +166,7 @@ void ttf_update(struct ttf *ttf, bool input_present);
int ttf_get_time_to_empty(struct ttf *ttf, int *val);
int ttf_get_time_to_full(struct ttf *ttf, int *val);
int ttf_tte_init(struct ttf *ttf);
int soh_profile_init(struct device *dev, struct soh_profile *sp);
int soh_profile_update(struct soh_profile *sp, int soh);

#endif
+30 −5
Original line number Diff line number Diff line
@@ -256,6 +256,7 @@ struct fg_gen4_chip {
	struct cycle_counter	*counter;
	struct cap_learning	*cl;
	struct ttf		*ttf;
	struct soh_profile	*sp;
	struct device_node	*pbs_dev;
	struct nvmem_device	*fg_nvmem;
	struct votable		*delta_esr_irq_en_votable;
@@ -1628,13 +1629,35 @@ static int fg_gen4_get_batt_profile(struct fg_dev *fg)
		return -ENODATA;
	}

	if (chip->dt.multi_profile_load &&
		chip->batt_age_level != avail_age_level) {
	if (chip->dt.multi_profile_load) {
		if (chip->batt_age_level != avail_age_level) {
			fg_dbg(fg, FG_STATUS, "Batt_age_level %d doesn't exist, using %d\n",
				chip->batt_age_level, avail_age_level);
			chip->batt_age_level = avail_age_level;
		}

		if (!chip->sp)
			chip->sp = devm_kzalloc(fg->dev, sizeof(*chip->sp),
						GFP_KERNEL);
		if (!chip->sp)
			return -ENOMEM;

		if (!chip->sp->initialized) {
			chip->sp->batt_id_kohms = fg->batt_id_ohms / 1000;
			chip->sp->last_batt_age_level = chip->batt_age_level;
			chip->sp->bp_node = batt_node;
			chip->sp->bms_psy = fg->fg_psy;
			rc = soh_profile_init(fg->dev, chip->sp);
			if (rc < 0) {
				devm_kfree(fg->dev, chip->sp);
				chip->sp = NULL;
			} else {
				fg_dbg(fg, FG_STATUS, "SOH profile count: %d\n",
					chip->sp->profile_count);
			}
		}
	}

	rc = of_property_read_string(profile_node, "qcom,battery-type",
			&fg->bp.batt_type_str);
	if (rc < 0) {
@@ -4351,6 +4374,8 @@ static int fg_psy_set_property(struct power_supply *psy,
		break;
	case POWER_SUPPLY_PROP_SOH:
		chip->soh = pval->intval;
		if (chip->sp)
			soh_profile_update(chip->sp, chip->soh);
		break;
	case POWER_SUPPLY_PROP_CLEAR_SOH:
		if (chip->first_profile_load && !pval->intval) {
+15 −1
Original line number Diff line number Diff line
/* Copyright (c) 2012-2015, 2017 The Linux Foundation. All rights reserved.
/* Copyright (c) 2012-2015, 2017, 2019 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
@@ -144,6 +144,20 @@ struct bms_battery_data {
	const char		*battery_type;
};

/**
 * struct soh_range -
 * @batt_age_level:	Battery age level (e.g. 0, 1 etc.,)
 * @soh_min:		Minimum SOH (state of health) level that this battery
 *			profile can support.
 * @soh_max:		Maximum SOH (state of health) level that this battery
 *			profile can support.
 */
struct soh_range {
	int	batt_age_level;
	int	soh_min;
	int	soh_max;
};

#define is_between(left, right, value) \
		(((left) >= (right) && (left) >= (value) \
			&& (value) >= (right)) \
Loading