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

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

Merge "power: supply: qcom: Add snapshot of SMB5 charger driver"

parents 85a774ce 58bfbabe
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -93,3 +93,4 @@ obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
obj-$(CONFIG_CHARGER_BD70528)	+= bd70528-charger.o
obj-$(CONFIG_CHARGER_WILCO)	+= wilco-charger.o
obj-$(CONFIG_QTI_BATTERY_CHARGER)	+= qti_battery_charger.o
obj-$(CONFIG_QCOM_POWER_SUPPLY)		+= qcom/
+31 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0-only

menuconfig QCOM_POWER_SUPPLY
	tristate "Support for Qualcomm Technologies, Inc. power supply"
	depends on ARCH_QCOM

if QCOM_POWER_SUPPLY

config QPNP_QG
	bool "QPNP Qgauge driver"
	depends on MFD_SPMI_PMIC
	help
	  Say Y here to enable the Qualcomm Technologies, Inc. QGauge driver
	  which uses the periodic sampling of the battery voltage and current
	  to determine the battery state-of-charge (SOC) and supports other
	  battery management features.

config QPNP_SMB5
	tristate "SMB5 Battery Charger"
	depends on MFD_SPMI_PMIC
	help
	  Say Y to enables support for the SMB5 charging peripheral.
	  The QPNP SMB5 charger driver supports the charger peripheral
	  present in the chip.
	  The power supply framework is used to communicate battery and
	  usb properties to userspace and other driver consumers such
	  as fuel gauge, USB, and USB-PD.
	  VBUS and VCONN regulators are registered for supporting OTG,
	  and powered Type-C cables respectively.

endif
+4 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0-only

obj-$(CONFIG_QPNP_QG)	+= qpnp-qg.o battery-profile-loader.o pmic-voter.o qg-util.o qg-soc.o qg-sdam.o qg-battery-profile.o qg-profile-lib.o fg-alg.o
obj-$(CONFIG_QPNP_SMB5)	+= step-chg-jeita.o battery.o qpnp-smb5.o smb5-lib.o pmic-voter.o storm-watch.o schgm-flash.o
+336 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved.
 */

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

#include <linux/err.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/power_supply.h>
#include "battery-profile-loader.h"

static int of_batterydata_read_batt_id_kohm(const struct device_node *np,
				const char *propname, struct batt_ids *batt_ids)
{
	struct property *prop;
	const __be32 *data;
	int num, i, *id_kohm = batt_ids->kohm;

	prop = of_find_property(np, "qcom,batt-id-kohm", NULL);
	if (!prop) {
		pr_err("%s: No battery id resistor found\n", np->name);
		return -EINVAL;
	} else if (!prop->value) {
		pr_err("%s: No battery id resistor value found, np->name\n",
						np->name);
		return -ENODATA;
	} else if (prop->length > MAX_BATT_ID_NUM * sizeof(__be32)) {
		pr_err("%s: Too many battery id resistors\n", np->name);
		return -EINVAL;
	}

	num = prop->length/sizeof(__be32);
	batt_ids->num = num;
	data = prop->value;
	for (i = 0; i < num; i++)
		*id_kohm++ = be32_to_cpup(data++);

	return 0;
}

struct device_node *of_batterydata_get_best_profile(
		const struct device_node *batterydata_container_node,
		int batt_id_kohm, const char *batt_type)
{
	struct batt_ids batt_ids;
	struct device_node *node, *best_node = NULL;
	const char *battery_type = NULL;
	int delta = 0, best_delta = 0, best_id_kohm = 0, id_range_pct,
		i = 0, rc = 0, limit = 0;
	bool in_range = false;

	/* read battery id range percentage for best profile */
	rc = of_property_read_u32(batterydata_container_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 ERR_PTR(-ENXIO);
		}
	}

	/*
	 * Find the battery data with a battery id resistor closest to this one
	 */
	for_each_child_of_node(batterydata_container_node, node) {
		if (batt_type != NULL) {
			rc = of_property_read_string(node, "qcom,battery-type",
							&battery_type);
			if (!rc && strcmp(battery_type, batt_type) == 0) {
				best_node = node;
				best_id_kohm = batt_id_kohm;
				break;
			}
		} else {
			rc = of_batterydata_read_batt_id_kohm(node,
							"qcom,batt-id-kohm",
							&batt_ids);
			if (rc)
				continue;
			for (i = 0; i < batt_ids.num; i++) {
				delta = abs(batt_ids.kohm[i] - batt_id_kohm);
				limit = (batt_ids.kohm[i] * id_range_pct) / 100;
				in_range = (delta <= limit);
				/*
				 * Check if the delta is the lowest one
				 * and also if the limits are in range
				 * before selecting the best node.
				 */
				if ((delta < best_delta || !best_node)
					&& in_range) {
					best_node = node;
					best_delta = delta;
					best_id_kohm = batt_ids.kohm[i];
				}
			}
		}
	}

	if (best_node == NULL) {
		pr_err("No battery data found\n");
		return best_node;
	}

	/* check that profile id is in range of the measured batt_id */
	if (abs(best_id_kohm - batt_id_kohm) >
			((best_id_kohm * id_range_pct) / 100)) {
		pr_err("out of range: profile id %d batt id %d pct %d\n",
			best_id_kohm, batt_id_kohm, id_range_pct);
		return NULL;
	}

	rc = of_property_read_string(best_node, "qcom,battery-type",
							&battery_type);
	if (!rc)
		pr_info("%s found\n", battery_type);
	else
		pr_info("%s found\n", best_node->name);

	return best_node;
}

struct device_node *of_batterydata_get_best_aged_profile(
		const struct device_node *batterydata_container_node,
		int batt_id_kohm, int batt_age_level, int *avail_age_level)
{
	struct batt_ids batt_ids;
	struct device_node *node, *best_node = NULL;
	const char *battery_type = NULL;
	int delta = 0, best_id_kohm = 0, id_range_pct, i = 0, rc = 0, limit = 0;
	u32 val;
	bool in_range = false;

	/* read battery id range percentage for best profile */
	rc = of_property_read_u32(batterydata_container_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 ERR_PTR(-ENXIO);
		}
	}

	/*
	 * Find the battery data with a battery id resistor closest to this one
	 */
	for_each_available_child_of_node(batterydata_container_node, node) {
		val = 0;
		of_property_read_u32(node, "qcom,batt-age-level", &val);
		rc = of_batterydata_read_batt_id_kohm(node,
						"qcom,batt-id-kohm", &batt_ids);
		if (rc)
			continue;
		for (i = 0; i < batt_ids.num; i++) {
			delta = abs(batt_ids.kohm[i] - batt_id_kohm);
			limit = (batt_ids.kohm[i] * id_range_pct) / 100;
			in_range = (delta <= limit);

			/*
			 * Check if the battery aging level matches and the
			 * limits are in range before selecting the best node.
			 */
			if ((batt_age_level == val || !best_node) && in_range) {
				best_node = node;
				best_id_kohm = batt_ids.kohm[i];
				*avail_age_level = val;
				break;
			}
		}
	}

	if (best_node == NULL) {
		pr_err("No battery data found\n");
		return best_node;
	}

	/* check that profile id is in range of the measured batt_id */
	if (abs(best_id_kohm - batt_id_kohm) >
			((best_id_kohm * id_range_pct) / 100)) {
		pr_err("out of range: profile id %d batt id %d pct %d\n",
			best_id_kohm, batt_id_kohm, id_range_pct);
		return NULL;
	}

	rc = of_property_read_string(best_node, "qcom,battery-type",
							&battery_type);
	if (!rc)
		pr_info("%s age level %d found\n", battery_type,
			*avail_age_level);
	else
		pr_info("%s age level %d found\n", best_node->name,
			*avail_age_level);

	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;
}

MODULE_LICENSE("GPL v2");
+89 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (c) 2013-2014, 2016-2020 The Linux Foundation. All rights reserved.
 */

#ifndef __BATTERY_PROFILE_LOADER_H
#define __BATTERY_PROFILE_LOADER_H

#include <linux/of.h>

#define MAX_BATT_ID_NUM		4
#define DEGC_SCALE		10

struct batt_ids {
	int kohm[MAX_BATT_ID_NUM];
	int num;
};

/**
 * 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;
};

/**
 * of_batterydata_get_best_profile() - Find matching battery data device node
 * @batterydata_container_node: pointer to the battery-data container device
 *		node containing the profile nodes.
 * @batt_id_kohm: Battery ID in KOhms for which we want to find the profile.
 * @batt_type: Battery type which we want to force load the profile.
 *
 * This routine returns a device_node pointer to the closest match battery data
 * from device tree based on the battery id reading.
 */
struct device_node *of_batterydata_get_best_profile(
		const struct device_node *batterydata_container_node,
		int batt_id_kohm, const char *batt_type);

/**
 * of_batterydata_get_best_aged_profile() - Find best aged battery profile
 * @batterydata_container_node: pointer to the battery-data container device
 *		node containing the profile nodes.
 * @batt_id_kohm: Battery ID in KOhms for which we want to find the profile.
 * @batt_age_level: Battery age level.
 * @avail_age_level: Available battery age level.
 *
 * This routine returns a device_node pointer to the closest match battery data
 * from device tree based on the battery id reading and age level.
 */
struct device_node *of_batterydata_get_best_aged_profile(
		const struct device_node *batterydata_container_node,
		int batt_id_kohm, int batt_age_level, int *avail_age_level);

/**
 * of_batterydata_get_aged_profile_count() - Gets the number of aged profiles
 * @batterydata_node: pointer to the battery-data container device
 *		node containing the profile nodes.
 * @batt_id_kohm: Battery ID in KOhms for which we want to find the profile.
 * @count: Number of aged profiles available to support SOH based profile
 * loading.
 *
 * This routine returns zero if valid number of aged profiles are available.
 */
int of_batterydata_get_aged_profile_count(
		const struct device_node *batterydata_node,
		int batt_id_kohm, int *count);

/**
 * of_batterydata_read_soh_aged_profiles() - Reads the data from aged profiles
 * @batterydata_node: pointer to the battery-data container device
 *		node containing the profile nodes.
 * @batt_id_kohm: Battery ID in KOhms for which we want to find the profile.
 * @soh_data: SOH data from the profile if it is found to be valid.
 *
 * This routine returns zero if SOH data of aged profiles is valid.
 */
int of_batterydata_read_soh_aged_profiles(
		const struct device_node *batterydata_node,
		int batt_id_kohm, struct soh_range *soh_data);

#endif /* __BATTERY_PROFILE_LOADER_H */
Loading