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

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

power: qcom: Add the QPNP QGAUGE (QG) driver



The QG driver adds support for battery gauging and reporting
the battery specific parameters to userspace. The driver comprises
of the below files:

qpnp-qg.c: Core QG driver
qg-soc.c: SOC (state of charge) scaling operations
qg-util.c: Common functions
qg-battery-profile.c: Parsing battery profile from DT
qg-profile-lib.c: Operations on battery profiles
qg-sdam.c: SDAM interface for QG

This commit also adds the below UAPI files,

qg.h: QG data shared between kernel and userspace QG modules
qg-profile.c: Profile data exchange between kernel and userspace.

CRs-Fixed: 2190528
Change-Id: I97f9d051692ed659030136a622e62009da55b462
Signed-off-by: default avatarAnirudh Ghayal <aghayal@codeaurora.org>
parent 350a47b2
Loading
Loading
Loading
Loading
+201 −0
Original line number Diff line number Diff line
Qualcomm Techonologies, Inc. QPNP PMIC QGAUGE (QG) Device

QPNP PMIC QGAUGE device provides the ability to gauge the State-of-Charge
of the battery. It provides an interface to the clients to read various
battery related parameters.

=======================
Required Node Structure
=======================

Qgauge device must be described in two level of nodes. The first level
describes the properties of the Qgauge device and the second level
describes the peripherals managed/used of the module.

====================================
First Level Node - QGAUGE device
====================================

- compatible
	Usage:      required
	Value type: <string>
	Definition: Should be "qcom,qpnp-qg".

- qcom,pmic-revid
	Usage:      required
	Value type: <phandle>
	Definition: Should specify the phandle of PMIC revid module. This is
		    used to identify the PMIC subtype.

- qcom,qg-vadc
	Usage:      required
	Value type: <phandle>
	Definition: Phandle for the VADC node, it is used for BATT_ID and
		    BATT_THERM readings.

- qcom,vbatt-empty-mv
	Usage:      optional
	Value type: <u32>
	Definition: The battery voltage threshold (in mV) at which the
		    vbatt-empty interrupt fires. The SOC is forced to 0
		    when this interrupt fires. If not specified, the
		    default value is 3200 mV.

- qcom,vbatt-cutoff-mv
	Usage:      optional
	Value type: <u32>
	Definition: The battery voltage threshold (in mV) at which the
		    the Qgauge algorithm converges to 0 SOC. If not specified
		    the default value is 3400 mV.

- qcom,vbatt-low-mv
	Usage:      optional
	Value type: <u32>
	Definition: The battery voltage threshold (in mV) at which the
		    the VBAT_LOW interrupt fires. Software can take necessary
		    the action when this interrupt fires. If not specified
		    the default value is 3500 mV.

- qcom,qg-iterm-ma
	Usage:      optional
	Value type: <u32>
	Definition: The battery current (in mA) at which the the QG algorithm
		    converges the SOC to 100% during charging and can be used to
		    terminate charging. If not specified, the default value is
		    100mA.

- qcom,delta-soc
	Usage:      optional
	Value type: <u32>
	Definition: The SOC percentage increase at which the SOC is
		    periodically reported to the userspace. If not specified,
		    the value defaults to 1%.

- qcom,s2-fifo-length
	Usage:      optional
	Value type: <u32>
	Definition: The total number if FIFO samples which need to be filled up
		    in S2 state of QG to fire the FIFO DONE interrupt.
		    Minimum value = 1 Maximum Value = 8. If not specified,
		    the default value is 5.

- qcom,s2-acc-length
	Usage:      optional
	Value type: <u32>
	Definition: The number of distinct V & I samples to be accumulated
		    in each FIFO in the S2 state of QG.
		    Minimum Value = 0 Maximum Value = 256. If not specified,
		    the default value is 128.

- qcom,s2-acc-interval-ms
	Usage:      optional
	Value type: <u32>
	Definition: The time (in ms) between each of the V & I samples being
		    accumulated in FIFO.
		    Minimum Value = 0 ms Maximum Value = 2550 ms. If not
		    specified the default value is 100 ms.

- qcom,ocv-timer-expiry-min
	Usage:      optional
	Value type: <u32>
	Definition: The maximum time (in minutes) for the QG to transition from
		    S3 to S2 state.
		    Minimum Value = 2 min Maximum Value = 30 min. If not
		    specified the hardware default is set to 14 min.

- qcom,ocv-tol-threshold-uv
	Usage:      optional
	Value type: <u32>
	Definition: The OCV detection error tolerance (in uV). The maximum
		    voltage allowed between 2 VBATT readings in the S3 state
		    to qualify for a valid OCV.
		    Minimum Value = 0 uV Maximum Value = 12262 uV  Step = 195 uV

- qcom,s3-entry-fifo-length
	Usage:      optional
	Value type: <u32>
	Definition: The minimum number if FIFO samples which have to qualify the
		    S3 IBAT entry threshold (qcom,s3-entry-ibat-ua) for QG
		    to enter into S3 state.
		    Minimum Value = 1 Maximum Value = 8. The hardware default
		    is configured to 3.

- qcom,s3-entry-ibat-ua
	Usage:      optional
	Value type: <u32>
	Definition: The battery current (in uA) for the QG to enter into the S3
		    state. The QG algorithm enters into S3 if the battery
		    current is lower than this threshold consecutive for
		    the FIFO length specified in 'qcom,s3-entry-fifo-length'.
		    Minimum Value = 0 uA Maximum Value = 155550 uA
		    Step = 610 uA.

- qcom,s3-exit-ibat-ua
	Usage:      optional
	Value type: <u32>
	Definition: The battery current (in uA) for the QG to exit S3 state.
		    If the battery current is higher than this threshold QG
		    exists S3 state.
		    Minimum Value = 0 uA Maximum Value = 155550 uA
		    Step = 610 uA.

- qcom,rbat-conn-mohm
	Usage:      optional
	Value type: <u32>
	Definition: Resistance of the battery connectors in mOhms.

==========================================================
Second Level Nodes - Peripherals managed by QGAUGE driver
==========================================================
- reg
	Usage:      required
	Value type: <prop-encoded-array>
	Definition: Addresses and sizes for the specified peripheral

- interrupts
	Usage:      optional
	Value type: <prop-encoded-array>
	Definition: Interrupt mapping as per the interrupt encoding

- interrupt-names
	Usage:      optional
	Value type: <stringlist>
	Definition: Interrupt names.  This list must match up 1-to-1 with the
		    interrupts specified in the 'interrupts' property.

========
Example
========

pmi632_qg: qpnp,qg {
	compatible = "qcom,qpnp-qg";
	qcom,pmic-revid = <&pmi632_revid>;
	qcom,qg-vadc = <&pmi632_vadc>;
	qcom,vbatt-empty-mv = <3200>;
	qcom,vbatt-low-mv = <3500>;
	qcom,vbatt-cutoff-mv = <3400>;
	qcom,qg-iterm-ma = <100>;

	qcom,qgauge@4800 {
		status = "okay";
		reg = <0x4800 0x100>;
		interrupts = <0x2 0x48 0x0 IRQ_TYPE_EDGE_BOTH>,
			     <0x2 0x48 0x1 IRQ_TYPE_EDGE_BOTH>,
			     <0x2 0x48 0x2 IRQ_TYPE_EDGE_BOTH>,
			     <0x2 0x48 0x4 IRQ_TYPE_EDGE_BOTH>,
			     <0x2 0x48 0x5 IRQ_TYPE_EDGE_BOTH>,
			     <0x2 0x48 0x6 IRQ_TYPE_EDGE_BOTH>;
		interrupt-names = "qg-batt-missing",
				  "qg-vbat-low",
				  "qg-vbat-empty",
				  "qg-fifo-done",
				  "qg-good-ocv",
				  "qg-fsm-state-chg",
				  "qg-event";
	};

	qcom,qg-sdam@b000 {
		status = "okay";
		reg = <0xb000 0x100>;
	};
};
+9 −0
Original line number Diff line number Diff line
@@ -115,4 +115,13 @@ config QPNP_TYPEC
	  USB power-delivery. The driver adds support to report these type-C
	  parameters via the power-supply framework.

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.

endmenu
+1 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ obj-$(CONFIG_SMB1355_SLAVE_CHARGER) += smb1355-charger.o pmic-voter.o
obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o battery.o
obj-$(CONFIG_QPNP_SMB2)		+= step-chg-jeita.o battery.o qpnp-smb2.o smb-lib.o pmic-voter.o storm-watch.o
obj-$(CONFIG_SMB138X_CHARGER)	+= step-chg-jeita.o smb138x-charger.o smb-lib.o pmic-voter.o storm-watch.o battery.o
obj-$(CONFIG_QPNP_QG)		+= qpnp-qg.o pmic-voter.o qg-util.o qg-soc.o qg-sdam.o qg-battery-profile.o qg-profile-lib.o
obj-$(CONFIG_QPNP_QNOVO)	+= qpnp-qnovo.o battery.o
obj-$(CONFIG_QPNP_TYPEC)	+= qpnp-typec.o
obj-$(CONFIG_QPNP_SMB5)		+= step-chg-jeita.o battery.o qpnp-smb5.o smb5-lib.o pmic-voter.o storm-watch.o
+503 −0
Original line number Diff line number Diff line
/* Copyright (c) 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
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

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

#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <uapi/linux/qg-profile.h>
#include "qg-profile-lib.h"
#include "qg-defs.h"

struct qg_battery_data {
	/* battery-data class node */
	dev_t				dev_no;
	struct class			*battery_class;
	struct device			*battery_device;
	struct cdev			battery_cdev;

	/* profile */
	struct device_node		*profile_node;
	struct profile_table_data	profile[TABLE_MAX];
};

struct tables {
	int table_index;
	char *table_name;
};

static struct tables table[] = {
	{TABLE_SOC_OCV1, "qcom,pc-temp-v1-lut"},
	{TABLE_SOC_OCV2, "qcom,pc-temp-v2-lut"},
	{TABLE_FCC1, "qcom,fcc1-temp-lut"},
	{TABLE_FCC2, "qcom,fcc2-temp-lut"},
	{TABLE_Z1, "qcom,pc-temp-z1-lut"},
	{TABLE_Z2, "qcom,pc-temp-z2-lut"},
	{TABLE_Z3, "qcom,pc-temp-z3-lut"},
	{TABLE_Z4, "qcom,pc-temp-z4-lut"},
	{TABLE_Z5, "qcom,pc-temp-z5-lut"},
	{TABLE_Z6, "qcom,pc-temp-z6-lut"},
	{TABLE_Y1, "qcom,pc-temp-y1-lut"},
	{TABLE_Y2, "qcom,pc-temp-y2-lut"},
	{TABLE_Y3, "qcom,pc-temp-y3-lut"},
	{TABLE_Y4, "qcom,pc-temp-y4-lut"},
	{TABLE_Y5, "qcom,pc-temp-y5-lut"},
	{TABLE_Y6, "qcom,pc-temp-y6-lut"},
};

static struct qg_battery_data *the_battery;

static int qg_battery_data_open(struct inode *inode, struct file *file)
{
	struct qg_battery_data *battery = container_of(inode->i_cdev,
				struct qg_battery_data, battery_cdev);

	file->private_data = battery;

	return 0;
}

static long qg_battery_data_ioctl(struct file *file, unsigned int cmd,
						unsigned long arg)
{
	struct qg_battery_data *battery = file->private_data;
	struct battery_params __user *bp_user =
				(struct battery_params __user *)arg;
	struct battery_params bp;
	int rc = 0, soc, ocv_uv, fcc_mah, var, slope;

	if (!battery->profile_node) {
		pr_err("Battery data not set!\n");
		return -EINVAL;
	}

	if (!bp_user) {
		pr_err("Invalid battery-params user pointer\n");
		return -EINVAL;
	}

	if (copy_from_user(&bp, bp_user, sizeof(bp))) {
		pr_err("Failed in copy_from_user\n");
		return -EFAULT;
	}

	switch (cmd) {
	case BPIOCXSOC:
		if (bp.table_index != TABLE_SOC_OCV1 &&
				bp.table_index != TABLE_SOC_OCV2) {
			pr_err("Invalid table index %d for SOC-OCV lookup\n",
					bp.table_index);
			rc = -EINVAL;
		} else {
			/* OCV is passed as deci-uV  - 10^-4 V */
			soc = interpolate_soc(&battery->profile[bp.table_index],
					bp.batt_temp, UV_TO_DECIUV(bp.ocv_uv));
			soc = CAP(QG_MIN_SOC, QG_MAX_SOC, soc);
			rc = put_user(soc, &bp_user->soc);
			if (rc < 0) {
				pr_err("BPIOCXSOC: Failed rc=%d\n", rc);
				goto ret_err;
			}
			pr_debug("BPIOCXSOC: lut=%s ocv=%d batt_temp=%d soc=%d\n",
					battery->profile[bp.table_index].name,
					bp.ocv_uv, bp.batt_temp, soc);
		}
		break;
	case BPIOCXOCV:
		if (bp.table_index != TABLE_SOC_OCV1 &&
				bp.table_index != TABLE_SOC_OCV2) {
			pr_err("Invalid table index %d for SOC-OCV lookup\n",
					bp.table_index);
			rc = -EINVAL;
		} else {
			ocv_uv = interpolate_var(
					&battery->profile[bp.table_index],
					bp.batt_temp, bp.soc);
			ocv_uv = DECIUV_TO_UV(ocv_uv);
			ocv_uv = CAP(QG_MIN_OCV_UV, QG_MAX_OCV_UV, ocv_uv);
			rc = put_user(ocv_uv, &bp_user->ocv_uv);
			if (rc < 0) {
				pr_err("BPIOCXOCV: Failed rc=%d\n", rc);
				goto ret_err;
			}
			pr_debug("BPIOCXOCV: lut=%s ocv=%d batt_temp=%d soc=%d\n",
					battery->profile[bp.table_index].name,
					ocv_uv, bp.batt_temp, bp.soc);
		}
		break;
	case BPIOCXFCC:
		if (bp.table_index != TABLE_FCC1 &&
				bp.table_index != TABLE_FCC2) {
			pr_err("Invalid table index %d for FCC lookup\n",
					bp.table_index);
			rc = -EINVAL;
		} else {
			fcc_mah = interpolate_single_row_lut(
					&battery->profile[bp.table_index],
					bp.batt_temp, DEGC_SCALE);
			fcc_mah = CAP(QG_MIN_FCC_MAH, QG_MAX_FCC_MAH, fcc_mah);
			rc = put_user(fcc_mah, &bp_user->fcc_mah);
			if (rc) {
				pr_err("BPIOCXFCC: Failed rc=%d\n", rc);
				goto ret_err;
			}
			pr_debug("BPIOCXFCC: lut=%s batt_temp=%d fcc_mah=%d\n",
					battery->profile[bp.table_index].name,
					bp.batt_temp, fcc_mah);
		}
		break;
	case BPIOCXVAR:
		if (bp.table_index < TABLE_Z1 || bp.table_index > TABLE_MAX) {
			pr_err("Invalid table index %d for VAR lookup\n",
					bp.table_index);
			rc = -EINVAL;
		} else {
			var = interpolate_var(&battery->profile[bp.table_index],
					bp.batt_temp, bp.soc);
			var = CAP(QG_MIN_VAR, QG_MAX_VAR, var);
			rc = put_user(var, &bp_user->var);
			if (rc < 0) {
				pr_err("BPIOCXVAR: Failed rc=%d\n", rc);
				goto ret_err;
			}
			pr_debug("BPIOCXVAR: lut=%s var=%d batt_temp=%d soc=%d\n",
					battery->profile[bp.table_index].name,
					var, bp.batt_temp, bp.soc);
		}
		break;
	case BPIOCXSLOPE:
		if (bp.table_index != TABLE_SOC_OCV1 &&
				bp.table_index != TABLE_SOC_OCV2) {
			pr_err("Invalid table index %d for Slope lookup\n",
					bp.table_index);
			rc = -EINVAL;
		} else {
			slope = interpolate_slope(
					&battery->profile[bp.table_index],
					bp.batt_temp, bp.soc);
			slope = CAP(QG_MIN_SLOPE, QG_MAX_SLOPE, slope);
			rc = put_user(slope, &bp_user->slope);
			if (rc) {
				pr_err("BPIOCXSLOPE: Failed rc=%d\n", rc);
				goto ret_err;
			}
			pr_debug("BPIOCXSLOPE: lut=%s soc=%d batt_temp=%d slope=%d\n",
					battery->profile[bp.table_index].name,
					bp.soc, bp.batt_temp, slope);
		}
		break;
	default:
		pr_err("IOCTL %d not supported\n", cmd);
		rc = -EINVAL;
	}
ret_err:
	return rc;
}

static int qg_battery_data_release(struct inode *inode, struct file *file)
{
	pr_debug("battery_data device closed\n");

	return 0;
}

static const struct file_operations qg_battery_data_fops = {
	.owner = THIS_MODULE,
	.open = qg_battery_data_open,
	.unlocked_ioctl = qg_battery_data_ioctl,
	.compat_ioctl = qg_battery_data_ioctl,
	.release = qg_battery_data_release,
};

static int get_length(struct device_node *node,
			int *length, char *prop_name, bool ignore_null)
{
	struct property *prop;

	prop = of_find_property(node, prop_name, NULL);
	if (!prop) {
		if (ignore_null) {
			*length = 1;
			return 0;
		}
		pr_err("Failed to find %s property\n", prop_name);
		return -ENODATA;
	} else if (!prop->value) {
		pr_err("Failed to find value for %s property\n", prop_name);
		return -ENODATA;
	}

	*length = prop->length / sizeof(u32);

	return 0;
}

static int qg_parse_battery_profile(struct qg_battery_data *battery)
{
	int i, j, k, rows = 0, cols = 0, lut_length = 0, rc = 0;
	struct device_node *node;
	struct property *prop;
	const __be32 *data;

	for (i = 0; i < TABLE_MAX; i++) {
		node = of_find_node_by_name(battery->profile_node,
						table[i].table_name);
		if (!node) {
			pr_err("%s table not found\n", table[i].table_name);
			rc = -ENODEV;
			goto cleanup;
		}

		rc = get_length(node, &cols, "qcom,lut-col-legend", false);
		if (rc < 0) {
			pr_err("Failed to get col-length for %s table rc=%d\n",
				table[i].table_name, rc);
			goto cleanup;
		}

		rc = get_length(node, &rows, "qcom,lut-row-legend", true);
		if (rc < 0) {
			pr_err("Failed to get row-length for %s table rc=%d\n",
				table[i].table_name, rc);
			goto cleanup;
		}

		rc = get_length(node, &lut_length, "qcom,lut-data", false);
		if (rc < 0) {
			pr_err("Failed to get lut-length for %s table rc=%d\n",
				table[i].table_name, rc);
			goto cleanup;
		}

		if (lut_length != cols * rows) {
			pr_err("Invalid lut-length for %s table\n",
					table[i].table_name);
			rc = -EINVAL;
			goto cleanup;
		}

		battery->profile[i].name = kzalloc(strlen(table[i].table_name)
						+ 1, GFP_KERNEL);
		if (!battery->profile[i].name) {
			rc = -ENOMEM;
			goto cleanup;
		}

		strlcpy(battery->profile[i].name, table[i].table_name,
						strlen(table[i].table_name));
		battery->profile[i].rows = rows;
		battery->profile[i].cols = cols;

		if (rows != 1) {
			battery->profile[i].row_entries = kcalloc(rows,
				sizeof(*battery->profile[i].row_entries),
				GFP_KERNEL);
			if (!battery->profile[i].row_entries) {
				rc = -ENOMEM;
				goto cleanup;
			}
		}

		battery->profile[i].col_entries = kcalloc(cols,
				sizeof(*battery->profile[i].col_entries),
				GFP_KERNEL);
		if (!battery->profile[i].col_entries) {
			rc = -ENOMEM;
			goto cleanup;
		}

		battery->profile[i].data = kcalloc(rows,
				sizeof(*battery->profile[i].data), GFP_KERNEL);
		if (!battery->profile[i].data) {
			rc = -ENOMEM;
			goto cleanup;
		}

		for (j = 0; j < rows; j++) {
			battery->profile[i].data[j] = kcalloc(cols,
				sizeof(**battery->profile[i].data),
				GFP_KERNEL);
			if (!battery->profile[i].data[j]) {
				rc = -ENOMEM;
				goto cleanup;
			}
		}

		/* read profile data */
		rc = of_property_read_u32_array(node, "qcom,lut-col-legend",
					battery->profile[i].col_entries, cols);
		if (rc < 0) {
			pr_err("Failed to read cols values for table %s rc=%d\n",
					table[i].table_name, rc);
			goto cleanup;
		}

		if (rows != 1) {
			rc = of_property_read_u32_array(node,
					"qcom,lut-row-legend",
					battery->profile[i].row_entries, rows);
			if (rc < 0) {
				pr_err("Failed to read row values for table %s rc=%d\n",
						table[i].table_name, rc);
				goto cleanup;
			}
		}

		prop = of_find_property(node, "qcom,lut-data", NULL);
		if (!prop) {
			pr_err("Failed to find lut-data\n");
			rc = -EINVAL;
			goto cleanup;
		}
		data = prop->value;
		for (j = 0; j < rows; j++) {
			for (k = 0; k < cols; k++)
				battery->profile[i].data[j][k] =
						be32_to_cpup(data++);
		}

		pr_debug("Profile table %s parsed rows=%d cols=%d\n",
			battery->profile[i].name, battery->profile[i].rows,
			battery->profile[i].cols);
	}

	return 0;

cleanup:
	for (; i >= 0; i++) {
		kfree(battery->profile[i].name);
		kfree(battery->profile[i].row_entries);
		kfree(battery->profile[i].col_entries);
		for (j = 0; j < battery->profile[i].rows; j++) {
			if (battery->profile[i].data)
				kfree(battery->profile[i].data[j]);
		}
		kfree(battery->profile[i].data);
	}
	return rc;
}

int lookup_soc_ocv(u32 *soc, u32 ocv_uv, int batt_temp, bool charging)
{
	u8 table_index = charging ? TABLE_SOC_OCV1 : TABLE_SOC_OCV2;

	if (!the_battery || !the_battery->profile) {
		pr_err("Battery profile not loaded\n");
		return -ENODEV;
	}

	*soc = interpolate_soc(&the_battery->profile[table_index],
				batt_temp, UV_TO_DECIUV(ocv_uv));

	*soc = CAP(0, 100, DIV_ROUND_CLOSEST(*soc, 100));

	return 0;
}

int qg_batterydata_init(struct device_node *profile_node)
{
	int rc = 0;
	struct qg_battery_data *battery;

	battery = kzalloc(sizeof(*battery), GFP_KERNEL);
	if (!battery)
		return -ENOMEM;

	battery->profile_node = profile_node;

	/* char device to access battery-profile data */
	rc = alloc_chrdev_region(&battery->dev_no, 0, 1, "qg_battery");
	if (rc < 0) {
		pr_err("Failed to allocate chrdev rc=%d\n", rc);
		goto free_battery;
	}

	cdev_init(&battery->battery_cdev, &qg_battery_data_fops);
	rc = cdev_add(&battery->battery_cdev, battery->dev_no, 1);
	if (rc) {
		pr_err("Failed to add battery_cdev rc=%d\n", rc);
		goto unregister_chrdev;
	}

	battery->battery_class = class_create(THIS_MODULE, "qg_battery");
	if (IS_ERR_OR_NULL(battery->battery_class)) {
		pr_err("Failed to create qg-battery class\n");
		rc = -ENODEV;
		goto delete_cdev;
	}

	battery->battery_device = device_create(battery->battery_class,
					NULL, battery->dev_no,
					NULL, "qg_battery");
	if (IS_ERR_OR_NULL(battery->battery_device)) {
		pr_err("Failed to create battery_device device\n");
		rc = -ENODEV;
		goto delete_cdev;
	}

	/* parse the battery profile */
	rc = qg_parse_battery_profile(battery);
	if (rc < 0) {
		pr_err("Failed to parse battery profile rc=%d\n", rc);
		goto destroy_device;
	}

	the_battery = battery;

	pr_info("QG Battery-profile loaded, '/dev/qg_battery' created!\n");

	return 0;

destroy_device:
	device_destroy(battery->battery_class, battery->dev_no);
delete_cdev:
	cdev_del(&battery->battery_cdev);
unregister_chrdev:
	unregister_chrdev_region(battery->dev_no, 1);
free_battery:
	kfree(battery);
	return rc;
}

void qg_batterydata_exit(void)
{
	int i, j;

	if (the_battery) {
		/* unregister the device node */
		device_destroy(the_battery->battery_class, the_battery->dev_no);
		cdev_del(&the_battery->battery_cdev);
		unregister_chrdev_region(the_battery->dev_no, 1);

		/* delete all the battery profile memory */
		for (i = 0; i < TABLE_MAX; i++) {
			kfree(the_battery->profile[i].name);
			kfree(the_battery->profile[i].row_entries);
			kfree(the_battery->profile[i].col_entries);
			for (j = 0; j < the_battery->profile[i].rows; j++) {
				if (the_battery->profile[i].data)
					kfree(the_battery->profile[i].data[j]);
			}
			kfree(the_battery->profile[i].data);
		}
	}

	kfree(the_battery);
	the_battery = NULL;
}
+19 −0
Original line number Diff line number Diff line
/* Copyright (c) 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
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#ifndef __QG_BATTERY_PROFILE_H__
#define __QG_BATTERY_PROFILE_H__

int qg_batterydata_init(struct device_node *node);
void qg_batterydata_exit(void);
int lookup_soc_ocv(u32 *soc, u32 ocv_uv, int batt_temp, bool charging);

#endif /* __QG_BATTERY_PROFILE_H__ */
Loading