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: Anirudh Ghayal <aghayal@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-qg.txt b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-qg.txt
new file mode 100644
index 0000000..3481b80
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-qg.txt
@@ -0,0 +1,201 @@
+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>;
+	};
+};
diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig
index c8e731a..e653475 100644
--- a/drivers/power/supply/qcom/Kconfig
+++ b/drivers/power/supply/qcom/Kconfig
@@ -115,4 +115,13 @@
 	  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
diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile
index 870a67e..6c7fc78 100644
--- a/drivers/power/supply/qcom/Makefile
+++ b/drivers/power/supply/qcom/Makefile
@@ -6,6 +6,7 @@
 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
diff --git a/drivers/power/supply/qcom/qg-battery-profile.c b/drivers/power/supply/qcom/qg-battery-profile.c
new file mode 100644
index 0000000..b86c7f5
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-battery-profile.c
@@ -0,0 +1,503 @@
+/* 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;
+}
diff --git a/drivers/power/supply/qcom/qg-battery-profile.h b/drivers/power/supply/qcom/qg-battery-profile.h
new file mode 100644
index 0000000..143a4f2
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-battery-profile.h
@@ -0,0 +1,19 @@
+/* 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__ */
diff --git a/drivers/power/supply/qcom/qg-core.h b/drivers/power/supply/qcom/qg-core.h
new file mode 100644
index 0000000..aadef7f
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-core.h
@@ -0,0 +1,143 @@
+/* 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_CORE_H__
+#define __QG_CORE_H__
+
+struct qg_batt_props {
+	const char		*batt_type_str;
+	int			float_volt_uv;
+	int			vbatt_full_mv;
+	int			fastchg_curr_ma;
+	int			qg_profile_version;
+};
+
+struct qg_irq_info {
+	const char		*name;
+	const irq_handler_t	handler;
+	const bool		wake;
+	int			irq;
+};
+
+struct qg_dt {
+	int			vbatt_empty_mv;
+	int			vbatt_low_mv;
+	int			vbatt_cutoff_mv;
+	int			iterm_ma;
+	int			s2_fifo_length;
+	int			s2_vbat_low_fifo_length;
+	int			s2_acc_length;
+	int			s2_acc_intvl_ms;
+	int			ocv_timer_expiry_min;
+	int			ocv_tol_threshold_uv;
+	int			s3_entry_fifo_length;
+	int			s3_entry_ibat_ua;
+	int			s3_exit_ibat_ua;
+	int			delta_soc;
+	int			rbat_conn_mohm;
+};
+
+struct qpnp_qg {
+	struct device		*dev;
+	struct pmic_revid_data	*pmic_rev_id;
+	struct regmap		*regmap;
+	struct qpnp_vadc_chip	*vadc_dev;
+	struct power_supply	*qg_psy;
+	struct class		*qg_class;
+	struct device		*qg_device;
+	struct cdev		qg_cdev;
+	dev_t			dev_no;
+	struct work_struct	udata_work;
+	struct work_struct	scale_soc_work;
+	struct work_struct	qg_status_change_work;
+	struct notifier_block	nb;
+	struct mutex		bus_lock;
+	struct mutex		data_lock;
+	struct mutex		soc_lock;
+	wait_queue_head_t	qg_wait_q;
+	struct votable		*awake_votable;
+	struct votable		*vbatt_irq_disable_votable;
+	struct votable		*fifo_irq_disable_votable;
+	struct votable		*good_ocv_irq_disable_votable;
+	u32			qg_base;
+
+	/* local data variables */
+	u32			batt_id_ohm;
+	struct qg_kernel_data	kdata;
+	struct qg_user_data	udata;
+	struct power_supply	*batt_psy;
+	struct power_supply	*usb_psy;
+	struct power_supply	*parallel_psy;
+
+	/* status variable */
+	u32			*debug_mask;
+	bool			profile_loaded;
+	bool			battery_missing;
+	bool			data_ready;
+	bool			suspend_data;
+	bool			vbat_low;
+	bool			charge_done;
+	bool			parallel_enabled;
+	bool			usb_present;
+	int			charge_status;
+	int			charge_type;
+	int			next_wakeup_ms;
+	u32			wa_flags;
+	ktime_t			last_user_update_time;
+	ktime_t			last_fifo_update_time;
+
+	/* soc params */
+	int			catch_up_soc;
+	int			msoc;
+	int			pon_soc;
+	struct alarm		alarm_timer;
+	u32			sdam_data[SDAM_MAX];
+
+	/* DT */
+	struct qg_dt		dt;
+	struct qg_batt_props	bp;
+};
+
+enum ocv_type {
+	PON_OCV,
+	GOOD_OCV,
+};
+
+enum debug_mask {
+	QG_DEBUG_PON		= BIT(0),
+	QG_DEBUG_PROFILE	= BIT(1),
+	QG_DEBUG_DEVICE		= BIT(2),
+	QG_DEBUG_STATUS		= BIT(3),
+	QG_DEBUG_FIFO		= BIT(4),
+	QG_DEBUG_IRQ		= BIT(5),
+	QG_DEBUG_SOC		= BIT(6),
+	QG_DEBUG_PM		= BIT(7),
+	QG_DEBUG_BUS_READ	= BIT(8),
+	QG_DEBUG_BUS_WRITE	= BIT(9),
+};
+
+enum qg_irq {
+	QG_BATT_MISSING_IRQ,
+	QG_VBATT_LOW_IRQ,
+	QG_VBATT_EMPTY_IRQ,
+	QG_FIFO_UPDATE_DONE_IRQ,
+	QG_GOOD_OCV_IRQ,
+	QG_FSM_STAT_CHG_IRQ,
+	QG_EVENT_IRQ,
+	QG_MAX_IRQ,
+};
+
+enum qg_wa_flags {
+	QG_VBAT_LOW_WA = BIT(0),
+};
+
+
+#endif /* __QG_CORE_H__ */
diff --git a/drivers/power/supply/qcom/qg-defs.h b/drivers/power/supply/qcom/qg-defs.h
new file mode 100644
index 0000000..bbbc7ee
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-defs.h
@@ -0,0 +1,48 @@
+/* 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_DEFS_H__
+#define __QG_DEFS_H__
+
+#define qg_dbg(chip, reason, fmt, ...)			\
+	do {							\
+		if (*chip->debug_mask & (reason))		\
+			pr_info(fmt, ##__VA_ARGS__);	\
+		else						\
+			pr_debug(fmt, ##__VA_ARGS__);	\
+	} while (0)
+
+#define is_between(left, right, value) \
+		(((left) >= (right) && (left) >= (value) \
+			&& (value) >= (right)) \
+		|| ((left) <= (right) && (left) <= (value) \
+			&& (value) <= (right)))
+
+#define UDATA_READY_VOTER		"UDATA_READY_VOTER"
+#define FIFO_DONE_VOTER			"FIFO_DONE_VOTER"
+#define FIFO_RT_DONE_VOTER		"FIFO_RT_DONE_VOTER"
+#define SUSPEND_DATA_VOTER		"SUSPEND_DATA_VOTER"
+#define GOOD_OCV_VOTER			"GOOD_OCV_VOTER"
+#define PROFILE_IRQ_DISABLE		"NO_PROFILE_IRQ_DISABLE"
+#define QG_INIT_STATE_IRQ_DISABLE	"QG_INIT_STATE_IRQ_DISABLE"
+
+#define V_RAW_TO_UV(V_RAW)		div_u64(194637ULL * (u64)V_RAW, 1000)
+#define I_RAW_TO_UA(I_RAW)		div_s64(152588LL * (s64)I_RAW, 1000)
+
+#define DEGC_SCALE			10
+#define UV_TO_DECIUV(a)			(a / 100)
+#define DECIUV_TO_UV(a)			(a * 100)
+
+#define CAP(min, max, value)			\
+		((min > value) ? min : ((value > max) ? max : value))
+
+#endif /* __QG_DEFS_H__ */
diff --git a/drivers/power/supply/qcom/qg-profile-lib.c b/drivers/power/supply/qcom/qg-profile-lib.c
new file mode 100644
index 0000000..2af997e
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-profile-lib.c
@@ -0,0 +1,311 @@
+/* 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.
+ */
+
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/ratelimit.h>
+#include "qg-profile-lib.h"
+#include "qg-defs.h"
+
+static int linear_interpolate(int y0, int x0, int y1, int x1, int x)
+{
+	if (y0 == y1 || x == x0)
+		return y0;
+	if (x1 == x0 || x == x1)
+		return y1;
+
+	return y0 + ((y1 - y0) * (x - x0) / (x1 - x0));
+}
+
+int interpolate_single_row_lut(struct profile_table_data *lut,
+						int x, int scale)
+{
+	int i, result;
+	int cols = lut->cols;
+
+	if (x < lut->col_entries[0] * scale) {
+		pr_debug("x %d less than known range return y = %d lut = %s\n",
+					x, lut->data[0][0], lut->name);
+		return lut->data[0][0];
+	}
+
+	if (x > lut->col_entries[cols-1] * scale) {
+		pr_debug("x %d more than known range return y = %d lut = %s\n",
+					x, lut->data[0][cols-1], lut->name);
+		return lut->data[0][cols-1];
+	}
+
+	for (i = 0; i < cols; i++) {
+		if (x <= lut->col_entries[i] * scale)
+			break;
+	}
+
+	if (x == lut->col_entries[i] * scale) {
+		result = lut->data[0][i];
+	} else {
+		result = linear_interpolate(
+			lut->data[0][i-1],
+			lut->col_entries[i-1] * scale,
+			lut->data[0][i],
+			lut->col_entries[i] * scale,
+			x);
+	}
+
+	return result;
+}
+
+int interpolate_soc(struct profile_table_data *lut,
+				int batt_temp, int ocv)
+{
+	int i, j, soc_high, soc_low, soc;
+	int rows = lut->rows;
+	int cols = lut->cols;
+
+	if (batt_temp < lut->col_entries[0] * DEGC_SCALE) {
+		pr_debug("batt_temp %d < known temp range\n", batt_temp);
+		batt_temp = lut->col_entries[0] * DEGC_SCALE;
+	}
+
+	if (batt_temp > lut->col_entries[cols - 1] * DEGC_SCALE) {
+		pr_debug("batt_temp %d > known temp range\n", batt_temp);
+		batt_temp = lut->col_entries[cols - 1] * DEGC_SCALE;
+	}
+
+	for (j = 0; j < cols; j++)
+		if (batt_temp <= lut->col_entries[j] * DEGC_SCALE)
+			break;
+
+	if (batt_temp == lut->col_entries[j] * DEGC_SCALE) {
+		/* found an exact match for temp in the table */
+		if (ocv >= lut->data[0][j])
+			return lut->row_entries[0];
+		if (ocv <= lut->data[rows - 1][j])
+			return lut->row_entries[rows - 1];
+		for (i = 0; i < rows; i++) {
+			if (ocv >= lut->data[i][j]) {
+				if (ocv == lut->data[i][j])
+					return lut->row_entries[i];
+				soc = linear_interpolate(
+					lut->row_entries[i],
+					lut->data[i][j],
+					lut->row_entries[i - 1],
+					lut->data[i - 1][j],
+					ocv);
+				return soc;
+			}
+		}
+	}
+
+	/* batt_temp is within temperature for column j-1 and j */
+	if (ocv >= lut->data[0][j])
+		return lut->row_entries[0];
+	if (ocv <= lut->data[rows - 1][j - 1])
+		return lut->row_entries[rows - 1];
+
+	soc_low = soc_high = 0;
+	for (i = 0; i < rows-1; i++) {
+		if (soc_high == 0 && is_between(lut->data[i][j],
+				lut->data[i+1][j], ocv)) {
+			soc_high = linear_interpolate(
+				lut->row_entries[i],
+				lut->data[i][j],
+				lut->row_entries[i + 1],
+				lut->data[i+1][j],
+				ocv);
+		}
+
+		if (soc_low == 0 && is_between(lut->data[i][j-1],
+				lut->data[i+1][j-1], ocv)) {
+			soc_low = linear_interpolate(
+				lut->row_entries[i],
+				lut->data[i][j-1],
+				lut->row_entries[i + 1],
+				lut->data[i+1][j-1],
+				ocv);
+		}
+
+		if (soc_high && soc_low) {
+			soc = linear_interpolate(
+				soc_low,
+				lut->col_entries[j-1] * DEGC_SCALE,
+				soc_high,
+				lut->col_entries[j] * DEGC_SCALE,
+				batt_temp);
+			return soc;
+		}
+	}
+
+	if (soc_high)
+		return soc_high;
+
+	if (soc_low)
+		return soc_low;
+
+	pr_debug("%d ocv wasn't found for temp %d in the LUT %s returning 100%%\n",
+						ocv, batt_temp, lut->name);
+	return 10000;
+}
+
+int interpolate_var(struct profile_table_data *lut,
+				int batt_temp, int soc)
+{
+	int i, var1, var2, var, rows, cols;
+	int row1 = 0;
+	int row2 = 0;
+
+	rows = lut->rows;
+	cols = lut->cols;
+	if (soc > lut->row_entries[0]) {
+		pr_debug("soc %d greater than known soc ranges for %s lut\n",
+							soc, lut->name);
+		row1 = 0;
+		row2 = 0;
+	} else if (soc < lut->row_entries[rows - 1]) {
+		pr_debug("soc %d less than known soc ranges for %s lut\n",
+							soc, lut->name);
+		row1 = rows - 1;
+		row2 = rows - 1;
+	} else {
+		for (i = 0; i < rows; i++) {
+			if (soc == lut->row_entries[i]) {
+				row1 = i;
+				row2 = i;
+				break;
+			}
+			if (soc > lut->row_entries[i]) {
+				row1 = i - 1;
+				row2 = i;
+				break;
+			}
+		}
+	}
+
+	if (batt_temp < lut->col_entries[0] * DEGC_SCALE)
+		batt_temp = lut->col_entries[0] * DEGC_SCALE;
+	if (batt_temp > lut->col_entries[cols - 1] * DEGC_SCALE)
+		batt_temp = lut->col_entries[cols - 1] * DEGC_SCALE;
+
+	for (i = 0; i < cols; i++)
+		if (batt_temp <= lut->col_entries[i] * DEGC_SCALE)
+			break;
+
+	if (batt_temp == lut->col_entries[i] * DEGC_SCALE) {
+		var = linear_interpolate(
+				lut->data[row1][i],
+				lut->row_entries[row1],
+				lut->data[row2][i],
+				lut->row_entries[row2],
+				soc);
+		return var;
+	}
+
+	var1 = linear_interpolate(
+				lut->data[row1][i - 1],
+				lut->col_entries[i - 1] * DEGC_SCALE,
+				lut->data[row1][i],
+				lut->col_entries[i] * DEGC_SCALE,
+				batt_temp);
+
+	var2 = linear_interpolate(
+				lut->data[row2][i - 1],
+				lut->col_entries[i - 1] * DEGC_SCALE,
+				lut->data[row2][i],
+				lut->col_entries[i] * DEGC_SCALE,
+				batt_temp);
+
+	var = linear_interpolate(
+				var1,
+				lut->row_entries[row1],
+				var2,
+				lut->row_entries[row2],
+				soc);
+
+	return var;
+}
+
+int interpolate_slope(struct profile_table_data *lut,
+					int batt_temp, int soc)
+{
+	int i, ocvrow1, ocvrow2, rows, cols;
+	int row1 = 0;
+	int row2 = 0;
+	int slope;
+
+	rows = lut->rows;
+	cols = lut->cols;
+	if (soc >= lut->row_entries[0]) {
+		pr_debug("soc %d >= max soc range - use the slope at soc=%d for lut %s\n",
+					soc, lut->row_entries[0], lut->name);
+		row1 = 0;
+		row2 = 1;
+	} else if (soc <= lut->row_entries[rows - 1]) {
+		pr_debug("soc %d is <= min soc range - use the slope at soc=%d for lut %s\n",
+				soc, lut->row_entries[rows - 1], lut->name);
+		row1 = rows - 2;
+		row2 = rows - 1;
+	} else {
+		for (i = 0; i < rows; i++) {
+			if (soc >= lut->row_entries[i]) {
+				row1 = i - 1;
+				row2 = i;
+				break;
+			}
+		}
+	}
+
+	if (batt_temp < lut->col_entries[0] * DEGC_SCALE)
+		batt_temp = lut->col_entries[0] * DEGC_SCALE;
+	if (batt_temp > lut->col_entries[cols - 1] * DEGC_SCALE)
+		batt_temp = lut->col_entries[cols - 1] * DEGC_SCALE;
+
+	for (i = 0; i < cols; i++) {
+		if (batt_temp <= lut->col_entries[i] * DEGC_SCALE)
+			break;
+	}
+
+	if (batt_temp == lut->col_entries[i] * DEGC_SCALE) {
+		slope = (lut->data[row1][i] - lut->data[row2][i]);
+		if (slope <= 0) {
+			pr_warn_ratelimited("Slope=%d for soc=%d, using 1\n",
+							slope, soc);
+			slope = 1;
+		}
+		slope *= 10000;
+		slope /= (lut->row_entries[row1] -
+			lut->row_entries[row2]);
+		return slope;
+	}
+	ocvrow1 = linear_interpolate(
+			lut->data[row1][i - 1],
+			lut->col_entries[i - 1] * DEGC_SCALE,
+			lut->data[row1][i],
+			lut->col_entries[i] * DEGC_SCALE,
+			batt_temp);
+
+	ocvrow2 = linear_interpolate(
+			lut->data[row2][i - 1],
+			lut->col_entries[i - 1] * DEGC_SCALE,
+			lut->data[row2][i],
+			lut->col_entries[i] * DEGC_SCALE,
+			batt_temp);
+
+	slope = (ocvrow1 - ocvrow2);
+	if (slope <= 0) {
+		pr_warn_ratelimited("Slope=%d for soc=%d, using 1\n",
+							slope, soc);
+		slope = 1;
+	}
+	slope *= 10000;
+	slope /= (lut->row_entries[row1] - lut->row_entries[row2]);
+
+	return slope;
+}
diff --git a/drivers/power/supply/qcom/qg-profile-lib.h b/drivers/power/supply/qcom/qg-profile-lib.h
new file mode 100644
index 0000000..eb7263d
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-profile-lib.h
@@ -0,0 +1,34 @@
+/* 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_PROFILE_LIB_H__
+#define __QG_PROFILE_LIB_H__
+
+struct profile_table_data {
+	char		*name;
+	int		rows;
+	int		cols;
+	int		*row_entries;
+	int		*col_entries;
+	int		**data;
+};
+
+int interpolate_single_row_lut(struct profile_table_data *lut,
+						int x, int scale);
+int interpolate_soc(struct profile_table_data *lut,
+				int batt_temp, int ocv);
+int interpolate_var(struct profile_table_data *lut,
+				int batt_temp, int soc);
+int interpolate_slope(struct profile_table_data *lut,
+				int batt_temp, int soc);
+
+#endif /*__QG_PROFILE_LIB_H__ */
diff --git a/drivers/power/supply/qcom/qg-reg.h b/drivers/power/supply/qcom/qg-reg.h
new file mode 100644
index 0000000..1f42a8c
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-reg.h
@@ -0,0 +1,89 @@
+/* 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_REG_H__
+#define __QG_REG_H__
+
+#define PERPH_TYPE_REG				0x04
+#define QG_TYPE					0x0D
+
+#define QG_STATUS1_REG				0x08
+#define BATTERY_PRESENT_BIT			BIT(0)
+
+#define QG_STATUS2_REG				0x09
+#define GOOD_OCV_BIT				BIT(1)
+
+#define QG_STATUS3_REG				0x0A
+#define COUNT_FIFO_RT_MASK			GENMASK(3, 0)
+
+#define QG_INT_RT_STS_REG			0x10
+#define FIFO_UPDATE_DONE_RT_STS_BIT		BIT(3)
+#define VBAT_LOW_INT_RT_STS_BIT			BIT(1)
+
+#define QG_INT_LATCHED_STS_REG			0x18
+#define FIFO_UPDATE_DONE_INT_LAT_STS_BIT	BIT(3)
+
+#define QG_DATA_CTL1_REG			0x41
+#define MASTER_HOLD_OR_CLR_BIT			BIT(0)
+
+#define QG_MODE_CTL1_REG			0x43
+#define PARALLEL_IBAT_SENSE_EN_BIT		BIT(7)
+
+#define QG_VBAT_EMPTY_THRESHOLD_REG		0x4B
+#define QG_VBAT_LOW_THRESHOLD_REG		0x4C
+
+#define QG_S2_NORMAL_MEAS_CTL2_REG		0x51
+#define FIFO_LENGTH_MASK			GENMASK(5, 3)
+#define FIFO_LENGTH_SHIFT			3
+#define NUM_OF_ACCUM_MASK			GENMASK(2, 0)
+
+#define QG_S2_NORMAL_MEAS_CTL3_REG		0x52
+
+#define QG_S3_SLEEP_OCV_MEAS_CTL4_REG		0x59
+#define S3_SLEEP_OCV_TIMER_MASK			GENMASK(2, 0)
+
+#define QG_S3_SLEEP_OCV_TREND_CTL2_REG		0x5C
+#define TREND_TOL_MASK				GENMASK(5, 0)
+
+#define QG_S3_SLEEP_OCV_IBAT_CTL1_REG		0x5D
+#define SLEEP_IBAT_QUALIFIED_LENGTH_MASK	GENMASK(2, 0)
+
+#define QG_S3_ENTRY_IBAT_THRESHOLD_REG		0x5E
+#define QG_S3_EXIT_IBAT_THRESHOLD_REG		0x5F
+
+#define QG_S7_PON_OCV_V_DATA0_REG		0x70
+#define QG_S7_PON_OCV_I_DATA0_REG		0x72
+#define QG_S3_GOOD_OCV_V_DATA0_REG		0x74
+#define QG_S3_GOOD_OCV_I_DATA0_REG		0x76
+
+#define QG_V_ACCUM_DATA0_RT_REG			0x88
+#define QG_I_ACCUM_DATA0_RT_REG			0x8B
+#define QG_ACCUM_CNT_RT_REG			0x8E
+
+#define QG_V_FIFO0_DATA0_REG			0x90
+#define QG_I_FIFO0_DATA0_REG			0xA0
+
+#define QG_SOC_MONOTONIC_REG			0xBF
+
+#define QG_LAST_ADC_V_DATA0_REG			0xC0
+#define QG_LAST_ADC_I_DATA0_REG			0xC2
+
+/* SDAM offsets */
+#define QG_SDAM_VALID_OFFSET			0x46
+#define QG_SDAM_SOC_OFFSET			0x47
+#define QG_SDAM_TEMP_OFFSET			0x48
+#define QG_SDAM_RBAT_OFFSET			0x4A
+#define QG_SDAM_OCV_OFFSET			0x4C
+#define QG_SDAM_IBAT_OFFSET			0x50
+#define QG_SDAM_TIME_OFFSET			0x54
+
+#endif
diff --git a/drivers/power/supply/qcom/qg-sdam.c b/drivers/power/supply/qcom/qg-sdam.c
new file mode 100644
index 0000000..65bebab
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-sdam.c
@@ -0,0 +1,219 @@
+/* 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/device.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include "qg-sdam.h"
+#include "qg-reg.h"
+
+static struct qg_sdam *the_chip;
+
+struct qg_sdam_info {
+	char		*name;
+	u32		offset;
+	u32		length;
+};
+
+static struct qg_sdam_info sdam_info[] = {
+	[SDAM_VALID] = {
+		.name	= "VALID",
+		.offset = QG_SDAM_VALID_OFFSET,
+		.length = 1,
+	},
+	[SDAM_SOC] = {
+		.name	= "SOC",
+		.offset = QG_SDAM_SOC_OFFSET,
+		.length = 1,
+	},
+	[SDAM_TEMP] = {
+		.name	= "BATT_TEMP",
+		.offset = QG_SDAM_TEMP_OFFSET,
+		.length = 2,
+	},
+	[SDAM_RBAT_MOHM] = {
+		.name	= "RBAT_MOHM",
+		.offset = QG_SDAM_RBAT_OFFSET,
+		.length = 2,
+	},
+	[SDAM_OCV_UV] = {
+		.name	= "OCV_UV",
+		.offset = QG_SDAM_OCV_OFFSET,
+		.length = 4,
+	},
+	[SDAM_IBAT_UA] = {
+		.name	= "IBAT_UA",
+		.offset = QG_SDAM_IBAT_OFFSET,
+		.length = 4,
+	},
+	[SDAM_TIME_SEC] = {
+		.name	= "TIME_SEC",
+		.offset = QG_SDAM_TIME_OFFSET,
+		.length = 4,
+	},
+};
+
+int qg_sdam_write(u8 param, u32 data)
+{
+	int rc;
+	struct qg_sdam *chip = the_chip;
+	u32 offset;
+	size_t length;
+
+	if (!chip) {
+		pr_err("Invalid sdam-chip pointer\n");
+		return -EINVAL;
+	}
+
+	if (param >= SDAM_MAX) {
+		pr_err("Invalid SDAM param %d\n", param);
+		return -EINVAL;
+	}
+
+	offset = chip->sdam_base + sdam_info[param].offset;
+	length = sdam_info[param].length;
+	rc = regmap_bulk_write(chip->regmap, offset, (u8 *)&data, length);
+	if (rc < 0)
+		pr_err("Failed to write offset=%0x4x param=%d value=%d\n",
+					offset, param, data);
+	else
+		pr_debug("QG SDAM write param=%s value=%d\n",
+					sdam_info[param].name, data);
+
+	return rc;
+}
+
+int qg_sdam_read(u8 param, u32 *data)
+{
+	int rc;
+	struct qg_sdam *chip = the_chip;
+	u32 offset;
+	size_t length;
+
+	if (!chip) {
+		pr_err("Invalid sdam-chip pointer\n");
+		return -EINVAL;
+	}
+
+	if (param >= SDAM_MAX) {
+		pr_err("Invalid SDAM param %d\n", param);
+		return -EINVAL;
+	}
+
+	offset = chip->sdam_base + sdam_info[param].offset;
+	length = sdam_info[param].length;
+	rc = regmap_raw_read(chip->regmap, offset, (u8 *)data, length);
+	if (rc < 0)
+		pr_err("Failed to read offset=%0x4x param=%d\n",
+					offset, param);
+	else
+		pr_debug("QG SDAM read param=%s value=%d\n",
+				sdam_info[param].name, *data);
+
+	return rc;
+}
+
+int qg_sdam_read_all(u32 *sdam_data)
+{
+	int i, rc;
+	struct qg_sdam *chip = the_chip;
+
+	if (!chip) {
+		pr_err("Invalid sdam-chip pointer\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < SDAM_MAX; i++) {
+		rc = qg_sdam_read(i, &sdam_data[i]);
+		if (rc < 0) {
+			pr_err("Failed to read SDAM param=%s rc=%d\n",
+					sdam_info[i].name, rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+int qg_sdam_write_all(u32 *sdam_data)
+{
+	int i, rc;
+	struct qg_sdam *chip = the_chip;
+
+	if (!chip) {
+		pr_err("Invalid sdam-chip pointer\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < SDAM_MAX; i++) {
+		rc = qg_sdam_write(i, sdam_data[i]);
+		if (rc < 0) {
+			pr_err("Failed to write SDAM param=%s rc=%d\n",
+					sdam_info[i].name, rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+int qg_sdam_init(struct device *dev)
+{
+	int rc;
+	u32 base = 0, type = 0;
+	struct qg_sdam *chip;
+	struct device_node *child, *node = dev->of_node;
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return 0;
+
+	chip->regmap = dev_get_regmap(dev->parent, NULL);
+	if (!chip->regmap) {
+		pr_err("Parent regmap is unavailable\n");
+		return -ENXIO;
+	}
+
+	/* get the SDAM base address */
+	for_each_available_child_of_node(node, child) {
+		rc = of_property_read_u32(child, "reg", &base);
+		if (rc < 0) {
+			pr_err("Failed to read base address rc=%d\n", rc);
+			return rc;
+		}
+
+		rc = regmap_read(chip->regmap, base + PERPH_TYPE_REG, &type);
+		if (rc < 0) {
+			pr_err("Failed to read type rc=%d\n", rc);
+			return rc;
+		}
+
+		switch (type) {
+		case SDAM_TYPE:
+			chip->sdam_base = base;
+			break;
+		default:
+			break;
+		}
+	}
+	if (!chip->sdam_base) {
+		pr_err("QG SDAM node not defined\n");
+		return -EINVAL;
+	}
+
+	the_chip = chip;
+
+	return 0;
+}
diff --git a/drivers/power/supply/qcom/qg-sdam.h b/drivers/power/supply/qcom/qg-sdam.h
new file mode 100644
index 0000000..a75ead9
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-sdam.h
@@ -0,0 +1,40 @@
+/* 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_SDAM_H__
+#define __QG_SDAM_H__
+
+#define SDAM_TYPE			0x2E
+
+enum qg_sdam_param {
+	SDAM_VALID,
+	SDAM_SOC,
+	SDAM_TEMP,
+	SDAM_RBAT_MOHM,
+	SDAM_OCV_UV,
+	SDAM_IBAT_UA,
+	SDAM_TIME_SEC,
+	SDAM_MAX,
+};
+
+struct qg_sdam {
+	struct regmap		*regmap;
+	u16			sdam_base;
+};
+
+int qg_sdam_init(struct device *dev);
+int qg_sdam_write(u8 param, u32 data);
+int qg_sdam_read(u8 param, u32 *data);
+int qg_sdam_write_all(u32 *sdam_data);
+int qg_sdam_read_all(u32 *sdam_data);
+
+#endif
diff --git a/drivers/power/supply/qcom/qg-soc.c b/drivers/power/supply/qcom/qg-soc.c
new file mode 100644
index 0000000..4fdd258
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-soc.c
@@ -0,0 +1,226 @@
+/* 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/alarmtimer.h>
+#include <linux/power_supply.h>
+#include <uapi/linux/qg.h>
+#include "qg-sdam.h"
+#include "qg-core.h"
+#include "qg-reg.h"
+#include "qg-util.h"
+#include "qg-defs.h"
+
+#define DEFAULT_UPDATE_TIME_MS			64000
+#define SOC_SCALE_HYST_MS			2000
+static void get_next_update_time(struct qpnp_qg *chip, int *time_ms)
+{
+	int rc = 0, full_time_ms = 0, rt_time_ms = 0;
+
+	*time_ms = DEFAULT_UPDATE_TIME_MS;
+
+	rc = get_fifo_done_time(chip, false, &full_time_ms);
+	if (rc < 0)
+		return;
+
+	rc = get_fifo_done_time(chip, true, &rt_time_ms);
+	if (rc < 0)
+		return;
+
+	*time_ms = full_time_ms - rt_time_ms;
+
+	if (*time_ms < 0)
+		*time_ms = 0;
+
+	qg_dbg(chip, QG_DEBUG_SOC, "SOC scale next-update-time %d secs\n",
+					*time_ms / 1000);
+}
+
+static bool is_scaling_required(struct qpnp_qg *chip)
+{
+	if ((abs(chip->catch_up_soc - chip->msoc) < chip->dt.delta_soc) &&
+		chip->catch_up_soc != 0 && chip->catch_up_soc != 100)
+		return false;
+
+	if (chip->catch_up_soc == chip->msoc)
+		/* SOC has not changed */
+		return false;
+
+
+	if (chip->catch_up_soc > chip->msoc && !is_usb_present(chip))
+		/* USB is not present and SOC has increased */
+		return false;
+
+	return true;
+}
+
+static void update_msoc(struct qpnp_qg *chip)
+{
+	int rc;
+
+	if (chip->catch_up_soc > chip->msoc) {
+		/* SOC increased */
+		if (is_usb_present(chip)) /* Increment if USB is present */
+			chip->msoc += chip->dt.delta_soc;
+	} else {
+		/* SOC dropped */
+		chip->msoc -= chip->dt.delta_soc;
+	}
+	chip->msoc = CAP(0, 100, chip->msoc);
+
+	/* update the SOC register */
+	rc = qg_write_monotonic_soc(chip, chip->msoc);
+	if (rc < 0)
+		pr_err("Failed to update MSOC register rc=%d\n", rc);
+
+	/* update SDAM with the new MSOC */
+	rc = qg_sdam_write(SDAM_SOC, chip->msoc);
+	if (rc < 0)
+		pr_err("Failed to update SDAM with MSOC rc=%d\n", rc);
+
+	qg_dbg(chip, QG_DEBUG_SOC,
+		"SOC scale: Update msoc=%d catch_up_soc=%d delta_soc=%d\n",
+		chip->msoc, chip->catch_up_soc, chip->dt.delta_soc);
+}
+
+static void scale_soc_stop(struct qpnp_qg *chip)
+{
+	chip->next_wakeup_ms = 0;
+	alarm_cancel(&chip->alarm_timer);
+
+	qg_dbg(chip, QG_DEBUG_SOC,
+			"SOC scale stopped: msoc=%d catch_up_soc=%d\n",
+			chip->msoc, chip->catch_up_soc);
+}
+
+static void scale_soc_work(struct work_struct *work)
+{
+	struct qpnp_qg *chip = container_of(work,
+			struct qpnp_qg, scale_soc_work);
+
+	mutex_lock(&chip->soc_lock);
+
+	if (!is_scaling_required(chip)) {
+		scale_soc_stop(chip);
+		goto done;
+	}
+
+	update_msoc(chip);
+
+	if (is_scaling_required(chip)) {
+		alarm_start_relative(&chip->alarm_timer,
+				ms_to_ktime(chip->next_wakeup_ms));
+	} else {
+		scale_soc_stop(chip);
+		goto done_psy;
+	}
+
+	qg_dbg(chip, QG_DEBUG_SOC,
+		"SOC scale: Work msoc=%d catch_up_soc=%d delta_soc=%d next_wakeup=%d sec\n",
+			chip->msoc, chip->catch_up_soc, chip->dt.delta_soc,
+			chip->next_wakeup_ms / 1000);
+
+done_psy:
+	power_supply_changed(chip->qg_psy);
+done:
+	pm_relax(chip->dev);
+	mutex_unlock(&chip->soc_lock);
+}
+
+static enum alarmtimer_restart
+	qpnp_msoc_timer(struct alarm *alarm, ktime_t now)
+{
+	struct qpnp_qg *chip = container_of(alarm,
+				struct qpnp_qg, alarm_timer);
+
+	/* timer callback runs in atomic context, cannot use voter */
+	pm_stay_awake(chip->dev);
+	schedule_work(&chip->scale_soc_work);
+
+	return ALARMTIMER_NORESTART;
+}
+
+int qg_scale_soc(struct qpnp_qg *chip, bool force_soc)
+{
+	int soc_points = 0;
+	int rc = 0, time_ms = 0;
+
+	mutex_lock(&chip->soc_lock);
+
+	qg_dbg(chip, QG_DEBUG_SOC,
+			"SOC scale: Start msoc=%d catch_up_soc=%d delta_soc=%d\n",
+			chip->msoc, chip->catch_up_soc, chip->dt.delta_soc);
+
+	if (force_soc) {
+		chip->msoc = chip->catch_up_soc;
+		rc = qg_write_monotonic_soc(chip, chip->msoc);
+		if (rc < 0)
+			pr_err("Failed to update MSOC register rc=%d\n", rc);
+
+		qg_dbg(chip, QG_DEBUG_SOC,
+			"SOC scale: Forced msoc=%d\n", chip->msoc);
+		goto done_psy;
+	}
+
+	if (!is_scaling_required(chip)) {
+		scale_soc_stop(chip);
+		goto done;
+	}
+
+	update_msoc(chip);
+
+	if (is_scaling_required(chip)) {
+		get_next_update_time(chip, &time_ms);
+		soc_points = abs(chip->msoc - chip->catch_up_soc)
+					/ chip->dt.delta_soc;
+		chip->next_wakeup_ms = (time_ms / (soc_points + 1))
+					- SOC_SCALE_HYST_MS;
+		if (chip->next_wakeup_ms < 0)
+			chip->next_wakeup_ms = 1; /* wake up immediately */
+		alarm_start_relative(&chip->alarm_timer,
+					ms_to_ktime(chip->next_wakeup_ms));
+	} else {
+		scale_soc_stop(chip);
+		goto done_psy;
+	}
+
+	qg_dbg(chip, QG_DEBUG_SOC,
+		"SOC scale: msoc=%d catch_up_soc=%d delta_soc=%d soc_points=%d next_wakeup=%d sec\n",
+			chip->msoc, chip->catch_up_soc,	chip->dt.delta_soc,
+			soc_points, chip->next_wakeup_ms / 1000);
+
+done_psy:
+	power_supply_changed(chip->qg_psy);
+done:
+	mutex_unlock(&chip->soc_lock);
+	return rc;
+}
+
+int qg_soc_init(struct qpnp_qg *chip)
+{
+	if (alarmtimer_get_rtcdev()) {
+		alarm_init(&chip->alarm_timer, ALARM_BOOTTIME,
+			qpnp_msoc_timer);
+	} else {
+		pr_err("Failed to get soc alarm-timer\n");
+		return -EINVAL;
+	}
+	INIT_WORK(&chip->scale_soc_work, scale_soc_work);
+
+	return 0;
+}
+
+void qg_soc_exit(struct qpnp_qg *chip)
+{
+	alarm_cancel(&chip->alarm_timer);
+}
diff --git a/drivers/power/supply/qcom/qg-soc.h b/drivers/power/supply/qcom/qg-soc.h
new file mode 100644
index 0000000..3b4eb60
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-soc.h
@@ -0,0 +1,20 @@
+/* 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_SOC_H__
+#define __QG_SOC_H__
+
+int qg_scale_soc(struct qpnp_qg *chip, bool force_soc);
+int qg_soc_init(struct qpnp_qg *chip);
+void qg_soc_exit(struct qpnp_qg *chip);
+
+#endif /* __QG_SOC_H__ */
diff --git a/drivers/power/supply/qcom/qg-util.c b/drivers/power/supply/qcom/qg-util.c
new file mode 100644
index 0000000..44e5048
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-util.c
@@ -0,0 +1,291 @@
+/* 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.
+ */
+
+#include <linux/alarmtimer.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+#include <uapi/linux/qg.h>
+#include "qg-sdam.h"
+#include "qg-core.h"
+#include "qg-reg.h"
+#include "qg-defs.h"
+
+static inline bool is_sticky_register(u32 addr)
+{
+	if ((addr & 0xFF) == QG_STATUS2_REG)
+		return true;
+
+	return false;
+}
+
+int qg_read(struct qpnp_qg *chip, u32 addr, u8 *val, int len)
+{
+	int rc, i;
+	u32 dummy = 0;
+
+	if (is_sticky_register(addr)) {
+		rc = regmap_write(chip->regmap, addr, dummy);
+		if (rc < 0) {
+			pr_err("Failed regmap_write for %04x rc=%d\n",
+						addr, rc);
+			return rc;
+		}
+	}
+
+	rc = regmap_bulk_read(chip->regmap, addr, val, len);
+	if (rc < 0) {
+		pr_err("Failed regmap_read for address %04x rc=%d\n", addr, rc);
+		return rc;
+	}
+
+	if (*chip->debug_mask & QG_DEBUG_BUS_READ) {
+		pr_info("length %d addr=%04x\n", len, addr);
+		for (i = 0; i < len; i++)
+			pr_info("val[%d]: %02x\n", i, val[i]);
+	}
+
+	return 0;
+}
+
+int qg_write(struct qpnp_qg *chip, u32 addr, u8 *val, int len)
+{
+	int rc, i;
+
+	mutex_lock(&chip->bus_lock);
+
+	if (len > 1)
+		rc = regmap_bulk_write(chip->regmap, addr, val, len);
+	else
+		rc = regmap_write(chip->regmap, addr, *val);
+
+	if (rc < 0) {
+		pr_err("Failed regmap_write for address %04x rc=%d\n",
+				addr, rc);
+		goto out;
+	}
+
+	if (*chip->debug_mask & QG_DEBUG_BUS_WRITE) {
+		pr_info("length %d addr=%04x\n", len, addr);
+		for (i = 0; i < len; i++)
+			pr_info("val[%d]: %02x\n", i, val[i]);
+	}
+out:
+	mutex_unlock(&chip->bus_lock);
+	return rc;
+}
+
+int qg_masked_write(struct qpnp_qg *chip, int addr, u32 mask, u32 val)
+{
+	int rc;
+
+	mutex_lock(&chip->bus_lock);
+
+	rc = regmap_update_bits(chip->regmap, addr, mask, val);
+	if (rc < 0) {
+		pr_err("Failed regmap_update_bits for address %04x rc=%d\n",
+				addr, rc);
+		goto out;
+	}
+
+	if (*chip->debug_mask & QG_DEBUG_BUS_WRITE)
+		pr_info("addr=%04x mask: %02x val: %02x\n", addr, mask, val);
+
+out:
+	mutex_unlock(&chip->bus_lock);
+	return rc;
+}
+
+int get_fifo_length(struct qpnp_qg *chip, u32 *fifo_length, bool rt)
+{
+	int rc;
+	u8 reg = 0;
+	u32 addr;
+
+	addr = rt ? QG_STATUS3_REG : QG_S2_NORMAL_MEAS_CTL2_REG;
+	rc = qg_read(chip, chip->qg_base + addr, &reg, 1);
+	if (rc < 0) {
+		pr_err("Failed to read FIFO length rc=%d\n", rc);
+		return rc;
+	}
+
+	if (rt) {
+		*fifo_length &= COUNT_FIFO_RT_MASK;
+	} else {
+		*fifo_length = (reg & FIFO_LENGTH_MASK) >> FIFO_LENGTH_SHIFT;
+		*fifo_length += 1;
+	}
+
+	return rc;
+}
+
+int get_sample_count(struct qpnp_qg *chip, u32 *sample_count)
+{
+	int rc;
+	u8 reg = 0;
+
+	rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL2_REG,
+					&reg, 1);
+	if (rc < 0) {
+		pr_err("Failed to read FIFO sample count rc=%d\n", rc);
+		return rc;
+	}
+
+	*sample_count = 1 << ((reg & NUM_OF_ACCUM_MASK) + 1);
+
+	return rc;
+}
+
+int get_sample_interval(struct qpnp_qg *chip, u32 *sample_interval)
+{
+	int rc;
+	u8 reg = 0;
+
+	rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL3_REG,
+					&reg, 1);
+	if (rc < 0) {
+		pr_err("Failed to read FIFO sample interval rc=%d\n", rc);
+		return rc;
+	}
+
+	*sample_interval = reg * 10;
+
+	return rc;
+}
+
+int get_rtc_time(unsigned long *rtc_time)
+{
+	struct rtc_time tm;
+	struct rtc_device *rtc;
+	int rc;
+
+	rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
+	if (rtc == NULL) {
+		pr_err("Failed to open rtc device (%s)\n",
+				CONFIG_RTC_HCTOSYS_DEVICE);
+		return -EINVAL;
+	}
+
+	rc = rtc_read_time(rtc, &tm);
+	if (rc) {
+		pr_err("Failed to read rtc time (%s) : %d\n",
+				CONFIG_RTC_HCTOSYS_DEVICE, rc);
+		goto close_time;
+	}
+
+	rc = rtc_valid_tm(&tm);
+	if (rc) {
+		pr_err("Invalid RTC time (%s): %d\n",
+				CONFIG_RTC_HCTOSYS_DEVICE, rc);
+		goto close_time;
+	}
+	rtc_tm_to_time(&tm, rtc_time);
+
+close_time:
+	rtc_class_close(rtc);
+	return rc;
+}
+
+int get_fifo_done_time(struct qpnp_qg *chip, bool rt, int *time_ms)
+{
+	int rc, length = 0;
+	u32 sample_count = 0, sample_interval = 0, acc_count = 0;
+
+	rc = get_fifo_length(chip, &length, rt ? true : false);
+	if (rc < 0)
+		return rc;
+
+	rc = get_sample_count(chip, &sample_count);
+	if (rc < 0)
+		return rc;
+
+	rc = get_sample_interval(chip, &sample_interval);
+	if (rc < 0)
+		return rc;
+
+	*time_ms = length * sample_count * sample_interval;
+
+	if (rt) {
+		rc = qg_read(chip, chip->qg_base + QG_ACCUM_CNT_RT_REG,
+					(u8 *)&acc_count, 1);
+		if (rc < 0)
+			return rc;
+
+		*time_ms += ((sample_count - acc_count) * sample_interval);
+	}
+
+	return 0;
+}
+
+static bool is_usb_available(struct qpnp_qg *chip)
+{
+	if (chip->usb_psy)
+		return true;
+
+	chip->usb_psy = power_supply_get_by_name("usb");
+	if (!chip->usb_psy)
+		return false;
+
+	return true;
+}
+
+bool is_usb_present(struct qpnp_qg *chip)
+{
+	union power_supply_propval pval = {0, };
+
+	if (is_usb_available(chip))
+		power_supply_get_property(chip->usb_psy,
+			POWER_SUPPLY_PROP_PRESENT, &pval);
+
+	return pval.intval ? true : false;
+}
+
+static bool is_parallel_available(struct qpnp_qg *chip)
+{
+	if (chip->parallel_psy)
+		return true;
+
+	chip->parallel_psy = power_supply_get_by_name("parallel");
+	if (!chip->parallel_psy)
+		return false;
+
+	return true;
+}
+
+bool is_parallel_enabled(struct qpnp_qg *chip)
+{
+	union power_supply_propval pval = {0, };
+
+	if (is_parallel_available(chip)) {
+		power_supply_get_property(chip->parallel_psy,
+			POWER_SUPPLY_PROP_CHARGING_ENABLED, &pval);
+	}
+
+	return pval.intval ? true : false;
+}
+
+int qg_write_monotonic_soc(struct qpnp_qg *chip, int msoc)
+{
+	u8 reg = 0;
+	int rc;
+
+	reg = (msoc * 255) / 100;
+	rc = qg_write(chip, chip->qg_base + QG_SOC_MONOTONIC_REG,
+				&reg, 1);
+	if (rc < 0)
+		pr_err("Failed to update QG_SOC_MONOTINIC reg rc=%d\n", rc);
+
+	return rc;
+}
diff --git a/drivers/power/supply/qcom/qg-util.h b/drivers/power/supply/qcom/qg-util.h
new file mode 100644
index 0000000..a3664e1
--- /dev/null
+++ b/drivers/power/supply/qcom/qg-util.h
@@ -0,0 +1,27 @@
+/* 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_UTIL_H__
+#define __QG_UTIL_H__
+
+int qg_read(struct qpnp_qg *chip, u32 addr, u8 *val, int len);
+int qg_write(struct qpnp_qg *chip, u32 addr, u8 *val, int len);
+int qg_masked_write(struct qpnp_qg *chip, int addr, u32 mask, u32 val);
+int get_fifo_length(struct qpnp_qg *chip, u32 *fifo_length, bool rt);
+int get_sample_count(struct qpnp_qg *chip, u32 *sample_count);
+int get_sample_interval(struct qpnp_qg *chip, u32 *sample_interval);
+int get_fifo_done_time(struct qpnp_qg *chip, bool rt, int *time_ms);
+int get_rtc_time(unsigned long *rtc_time);
+bool is_usb_present(struct qpnp_qg *chip);
+bool is_parallel_enabled(struct qpnp_qg *chip);
+int qg_write_monotonic_soc(struct qpnp_qg *chip, int msoc);
+
+#endif
diff --git a/drivers/power/supply/qcom/qpnp-qg.c b/drivers/power/supply/qcom/qpnp-qg.c
new file mode 100644
index 0000000..55cb5ef
--- /dev/null
+++ b/drivers/power/supply/qcom/qpnp-qg.c
@@ -0,0 +1,2376 @@
+/* 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/alarmtimer.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/ktime.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_batterydata.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/uaccess.h>
+#include <linux/pmic-voter.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <uapi/linux/qg.h>
+#include "qg-sdam.h"
+#include "qg-core.h"
+#include "qg-reg.h"
+#include "qg-util.h"
+#include "qg-soc.h"
+#include "qg-battery-profile.h"
+#include "qg-defs.h"
+
+static int qg_debug_mask;
+module_param_named(
+	debug_mask, qg_debug_mask, int, 0600
+);
+
+static int qg_get_battery_temp(struct qpnp_qg *chip, int *batt_temp);
+
+static bool is_battery_present(struct qpnp_qg *chip)
+{
+	u8 reg = 0;
+	int rc;
+
+	rc = qg_read(chip, chip->qg_base + QG_STATUS1_REG, &reg, 1);
+	if (rc < 0)
+		pr_err("Failed to read battery presence, rc=%d\n", rc);
+
+	return !!(reg & BATTERY_PRESENT_BIT);
+}
+
+#define DEBUG_BATT_ID_LOW	6000
+#define DEBUG_BATT_ID_HIGH	8500
+static bool is_debug_batt_id(struct qpnp_qg *chip)
+{
+	if (is_between(DEBUG_BATT_ID_LOW, DEBUG_BATT_ID_HIGH,
+					chip->batt_id_ohm))
+		return true;
+
+	return false;
+}
+
+static int qg_read_ocv(struct qpnp_qg *chip, u32 *ocv_uv, u8 type)
+{
+	int rc, addr;
+	u64 temp = 0;
+
+	switch (type) {
+	case GOOD_OCV:
+		addr = QG_S3_GOOD_OCV_V_DATA0_REG;
+		break;
+	case PON_OCV:
+		addr = QG_S7_PON_OCV_V_DATA0_REG;
+		break;
+	default:
+		pr_err("Invalid OCV type %d\n", type);
+		return -EINVAL;
+	}
+
+	rc = qg_read(chip, chip->qg_base + addr, (u8 *)&temp, 2);
+	if (rc < 0) {
+		pr_err("Failed to read ocv, rc=%d\n", rc);
+		return rc;
+	}
+
+	*ocv_uv = V_RAW_TO_UV(temp);
+
+	pr_debug("%s: OCV=%duV\n",
+		type == GOOD_OCV ? "GOOD_OCV" : "PON_OCV", *ocv_uv);
+
+	return rc;
+}
+
+static int qg_update_fifo_length(struct qpnp_qg *chip, u8 length)
+{
+	int rc;
+
+	if (!length || length > 8) {
+		pr_err("Invalid FIFO length %d\n", length);
+		return -EINVAL;
+	}
+
+	rc = qg_masked_write(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL2_REG,
+			FIFO_LENGTH_MASK, (length - 1) << FIFO_LENGTH_SHIFT);
+	if (rc < 0)
+		pr_err("Failed to write S2 FIFO length, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int qg_master_hold(struct qpnp_qg *chip, bool hold)
+{
+	int rc;
+
+	/* clear the master */
+	rc = qg_masked_write(chip, chip->qg_base + QG_DATA_CTL1_REG,
+					MASTER_HOLD_OR_CLR_BIT, 0);
+	if (rc < 0)
+		return rc;
+
+	if (hold) {
+		/* 0 -> 1, hold the master */
+		rc = qg_masked_write(chip, chip->qg_base + QG_DATA_CTL1_REG,
+					MASTER_HOLD_OR_CLR_BIT,
+					MASTER_HOLD_OR_CLR_BIT);
+		if (rc < 0)
+			return rc;
+	}
+
+	qg_dbg(chip, QG_DEBUG_STATUS, "Master hold = %d\n", hold);
+
+	return rc;
+}
+
+static void qg_notify_charger(struct qpnp_qg *chip)
+{
+	union power_supply_propval prop = {0, };
+	int rc;
+
+	if (!chip->batt_psy)
+		return;
+
+	if (is_debug_batt_id(chip)) {
+		prop.intval = 1;
+		power_supply_set_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_DEBUG_BATTERY, &prop);
+		return;
+	}
+
+	if (!chip->profile_loaded)
+		return;
+
+	prop.intval = chip->bp.float_volt_uv;
+	rc = power_supply_set_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop);
+	if (rc < 0) {
+		pr_err("Failed to set voltage_max property on batt_psy, rc=%d\n",
+			rc);
+		return;
+	}
+
+	prop.intval = chip->bp.fastchg_curr_ma * 1000;
+	rc = power_supply_set_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &prop);
+	if (rc < 0) {
+		pr_err("Failed to set constant_charge_current_max property on batt_psy, rc=%d\n",
+			rc);
+		return;
+	}
+
+	pr_debug("Notified charger on float voltage and FCC\n");
+}
+
+static bool is_batt_available(struct qpnp_qg *chip)
+{
+	if (chip->batt_psy)
+		return true;
+
+	chip->batt_psy = power_supply_get_by_name("battery");
+	if (!chip->batt_psy)
+		return false;
+
+	/* batt_psy is initialized, set the fcc and fv */
+	qg_notify_charger(chip);
+
+	return true;
+}
+
+static int qg_update_sdam_params(struct qpnp_qg *chip)
+{
+	int rc, batt_temp = 0, i;
+	unsigned long rtc_sec = 0;
+
+	rc = get_rtc_time(&rtc_sec);
+	if (rc < 0)
+		pr_err("Failed to get RTC time, rc=%d\n", rc);
+	else
+		chip->sdam_data[SDAM_TIME_SEC] = rtc_sec;
+
+	rc = qg_get_battery_temp(chip, &batt_temp);
+	if (rc < 0)
+		pr_err("Failed to get battery-temp, rc = %d\n", rc);
+	else
+		chip->sdam_data[SDAM_TEMP] = (u32)batt_temp;
+
+	rc = qg_sdam_write_all(chip->sdam_data);
+	if (rc < 0)
+		pr_err("Failed to write to SDAM rc=%d\n", rc);
+
+	for (i = 0; i < SDAM_MAX; i++)
+		qg_dbg(chip, QG_DEBUG_STATUS, "SDAM write param %d value=%d\n",
+					i, chip->sdam_data[i]);
+
+	return rc;
+}
+
+static int qg_process_fifo(struct qpnp_qg *chip, u32 fifo_length)
+{
+	int rc = 0, i, j = 0, temp;
+	u8 v_fifo[MAX_FIFO_LENGTH * 2], i_fifo[MAX_FIFO_LENGTH * 2];
+	u32 sample_interval = 0, sample_count = 0, fifo_v = 0, fifo_i = 0;
+
+	if (!fifo_length) {
+		pr_debug("No FIFO data\n");
+		return 0;
+	}
+
+	qg_dbg(chip, QG_DEBUG_FIFO, "FIFO length=%d\n", fifo_length);
+
+	rc = get_sample_interval(chip, &sample_interval);
+	if (rc < 0) {
+		pr_err("Failed to get FIFO sample interval, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = get_sample_count(chip, &sample_count);
+	if (rc < 0) {
+		pr_err("Failed to get FIFO sample count, rc=%d\n", rc);
+		return rc;
+	}
+
+	for (i = 0; i < fifo_length * 2; i = i + 2, j++) {
+		rc = qg_read(chip, chip->qg_base + QG_V_FIFO0_DATA0_REG + i,
+					&v_fifo[i], 2);
+		if (rc < 0) {
+			pr_err("Failed to read QG_V_FIFO, rc=%d\n", rc);
+			return rc;
+		}
+		rc = qg_read(chip, chip->qg_base + QG_I_FIFO0_DATA0_REG + i,
+					&i_fifo[i], 2);
+		if (rc < 0) {
+			pr_err("Failed to read QG_I_FIFO, rc=%d\n", rc);
+			return rc;
+		}
+
+		fifo_v = v_fifo[i] | (v_fifo[i + 1] << 8);
+		fifo_i = i_fifo[i] | (i_fifo[i + 1] << 8);
+
+		temp = sign_extend32(fifo_i, 15);
+
+		chip->kdata.fifo[j].v = V_RAW_TO_UV(fifo_v);
+		chip->kdata.fifo[j].i = I_RAW_TO_UA(temp);
+		chip->kdata.fifo[j].interval = sample_interval;
+		chip->kdata.fifo[j].count = sample_count;
+
+		qg_dbg(chip, QG_DEBUG_FIFO, "FIFO %d raw_v=%d uV=%d raw_i=%d uA=%d interval=%d count=%d\n",
+					j, fifo_v,
+					chip->kdata.fifo[j].v,
+					fifo_i,
+					(int)chip->kdata.fifo[j].i,
+					chip->kdata.fifo[j].interval,
+					chip->kdata.fifo[j].count);
+	}
+
+	chip->kdata.fifo_length = fifo_length;
+
+	return rc;
+}
+
+static int qg_process_accumulator(struct qpnp_qg *chip)
+{
+	int rc, sample_interval = 0;
+	u8 count, index = chip->kdata.fifo_length;
+	u64 acc_v = 0, acc_i = 0;
+	s64 temp = 0;
+
+	rc = qg_read(chip, chip->qg_base + QG_ACCUM_CNT_RT_REG,
+			&count, 1);
+	if (rc < 0) {
+		pr_err("Failed to read ACC count, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (!count) {
+		pr_debug("No ACCUMULATOR data!\n");
+		return 0;
+	}
+
+	rc = get_sample_interval(chip, &sample_interval);
+	if (rc < 0) {
+		pr_err("Failed to get ACC sample interval, rc=%d\n", rc);
+		return 0;
+	}
+
+	rc = qg_read(chip, chip->qg_base + QG_V_ACCUM_DATA0_RT_REG,
+			(u8 *)&acc_v, 3);
+	if (rc < 0) {
+		pr_err("Failed to read ACC RT V data, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_read(chip, chip->qg_base + QG_I_ACCUM_DATA0_RT_REG,
+			(u8 *)&acc_i, 3);
+	if (rc < 0) {
+		pr_err("Failed to read ACC RT I data, rc=%d\n", rc);
+		return rc;
+	}
+
+	temp = sign_extend64(acc_i, 23);
+
+	chip->kdata.fifo[index].v = V_RAW_TO_UV(div_u64(acc_v, count));
+	chip->kdata.fifo[index].i = I_RAW_TO_UA(div_s64(temp, count));
+	chip->kdata.fifo[index].interval = sample_interval;
+	chip->kdata.fifo[index].count = count;
+	chip->kdata.fifo_length++;
+
+	qg_dbg(chip, QG_DEBUG_FIFO, "ACC v_avg=%duV i_avg=%duA interval=%d count=%d\n",
+			chip->kdata.fifo[index].v,
+			(int)chip->kdata.fifo[index].i,
+			chip->kdata.fifo[index].interval,
+			chip->kdata.fifo[index].count);
+
+	return rc;
+}
+
+static int qg_process_rt_fifo(struct qpnp_qg *chip)
+{
+	int rc;
+	u32 fifo_length = 0;
+
+	/* Get the real-time FIFO length */
+	rc = get_fifo_length(chip, &fifo_length, true);
+	if (rc < 0) {
+		pr_err("Failed to read RT FIFO length, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_process_fifo(chip, fifo_length);
+	if (rc < 0) {
+		pr_err("Failed to process FIFO data, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_process_accumulator(chip);
+	if (rc < 0) {
+		pr_err("Failed to process ACC data, rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+#define VBAT_LOW_HYST_UV		50000 /* 50mV */
+static int qg_vbat_low_wa(struct qpnp_qg *chip)
+{
+	int rc, i;
+	u32 vbat_low_uv = chip->dt.vbatt_low_mv * 1000 + VBAT_LOW_HYST_UV;
+
+	if (!(chip->wa_flags & QG_VBAT_LOW_WA) || !chip->vbat_low)
+		return 0;
+
+	/*
+	 * PMI632 1.0 does not generate a falling VBAT_LOW IRQ.
+	 * To exit from VBAT_LOW config, check if any of the FIFO
+	 * averages is > vbat_low threshold and reconfigure the
+	 * FIFO length to normal.
+	 */
+	for (i = 0; i < chip->kdata.fifo_length; i++) {
+		if (chip->kdata.fifo[i].v > vbat_low_uv) {
+			rc = qg_master_hold(chip, true);
+			if (rc < 0) {
+				pr_err("Failed to hold master, rc=%d\n", rc);
+				goto done;
+			}
+			rc = qg_update_fifo_length(chip,
+					chip->dt.s2_fifo_length);
+			if (rc < 0)
+				goto done;
+
+			rc = qg_master_hold(chip, false);
+			if (rc < 0) {
+				pr_err("Failed to release master, rc=%d\n", rc);
+				goto done;
+			}
+			/* FIFOs restarted */
+			chip->last_fifo_update_time = ktime_get();
+
+			chip->vbat_low = false;
+			pr_info("Exit VBAT_LOW vbat_avg=%duV vbat_low=%duV updated fifo_length=%d\n",
+					chip->kdata.fifo[i].v, vbat_low_uv,
+					chip->dt.s2_fifo_length);
+			break;
+		}
+	}
+
+	return 0;
+
+done:
+	qg_master_hold(chip, false);
+	return rc;
+}
+
+#define MIN_FIFO_FULL_TIME_MS			12000
+static int process_rt_fifo_data(struct qpnp_qg *chip,
+				bool vbat_low, bool update_smb)
+{
+	int rc = 0;
+	ktime_t now = ktime_get();
+	s64 time_delta;
+
+	/*
+	 * Reject the FIFO read event if there are back-to-back requests
+	 * This is done to gaurantee that there is always a minimum FIFO
+	 * data to be processed, ignore this if vbat_low is set.
+	 */
+	time_delta = ktime_ms_delta(now, chip->last_user_update_time);
+
+	qg_dbg(chip, QG_DEBUG_FIFO, "time_delta=%lld ms vbat_low=%d\n",
+				time_delta, vbat_low);
+
+	if (time_delta > MIN_FIFO_FULL_TIME_MS || vbat_low || update_smb) {
+		rc = qg_master_hold(chip, true);
+		if (rc < 0) {
+			pr_err("Failed to hold master, rc=%d\n", rc);
+			goto done;
+		}
+
+		rc = qg_process_rt_fifo(chip);
+		if (rc < 0) {
+			pr_err("Failed to process FIFO real-time, rc=%d\n", rc);
+			goto done;
+		}
+
+		if (vbat_low) {
+			/* change FIFO length */
+			rc = qg_update_fifo_length(chip,
+					chip->dt.s2_vbat_low_fifo_length);
+			if (rc < 0)
+				goto done;
+
+			qg_dbg(chip, QG_DEBUG_STATUS,
+				"FIFO length updated to %d vbat_low=%d\n",
+					chip->dt.s2_vbat_low_fifo_length,
+					vbat_low);
+		}
+
+		if (update_smb) {
+			rc = qg_masked_write(chip, chip->qg_base +
+				QG_MODE_CTL1_REG, PARALLEL_IBAT_SENSE_EN_BIT,
+				chip->parallel_enabled ?
+					PARALLEL_IBAT_SENSE_EN_BIT : 0);
+			if (rc < 0) {
+				pr_err("Failed to update SMB_EN, rc=%d\n", rc);
+				goto done;
+			}
+			qg_dbg(chip, QG_DEBUG_STATUS, "Parallel SENSE %d\n",
+						chip->parallel_enabled);
+		}
+
+		rc = qg_master_hold(chip, false);
+		if (rc < 0) {
+			pr_err("Failed to release master, rc=%d\n", rc);
+			goto done;
+		}
+		/* FIFOs restarted */
+		chip->last_fifo_update_time = ktime_get();
+
+		/* signal the read thread */
+		chip->data_ready = true;
+		wake_up_interruptible(&chip->qg_wait_q);
+		chip->last_user_update_time = now;
+
+		/* vote to stay awake until userspace reads data */
+		vote(chip->awake_votable, FIFO_RT_DONE_VOTER, true, 0);
+	} else {
+		qg_dbg(chip, QG_DEBUG_FIFO, "FIFO processing too early time_delta=%lld\n",
+							time_delta);
+	}
+done:
+	qg_master_hold(chip, false);
+	return rc;
+}
+
+static void process_udata_work(struct work_struct *work)
+{
+	struct qpnp_qg *chip = container_of(work,
+			struct qpnp_qg, udata_work);
+	int rc;
+
+	if (chip->udata.param[QG_SOC].valid) {
+		qg_dbg(chip, QG_DEBUG_SOC, "udata SOC=%d last SOC=%d\n",
+			chip->udata.param[QG_SOC].data, chip->catch_up_soc);
+
+		/* Only scale if SOC has changed */
+		if (chip->catch_up_soc != chip->udata.param[QG_SOC].data) {
+			chip->catch_up_soc = chip->udata.param[QG_SOC].data;
+			qg_scale_soc(chip, false);
+		}
+
+		/* update parameters to SDAM */
+		chip->sdam_data[SDAM_SOC] =
+				chip->udata.param[QG_SOC].data;
+		chip->sdam_data[SDAM_OCV_UV] =
+				chip->udata.param[QG_OCV_UV].data;
+		chip->sdam_data[SDAM_RBAT_MOHM] =
+				chip->udata.param[QG_RBAT_MOHM].data;
+		chip->sdam_data[SDAM_VALID] = 1;
+
+		rc = qg_update_sdam_params(chip);
+		if (rc < 0)
+			pr_err("Failed to update SDAM params, rc=%d\n", rc);
+	}
+
+	vote(chip->awake_votable, UDATA_READY_VOTER, false, 0);
+}
+
+static irqreturn_t qg_default_irq_handler(int irq, void *data)
+{
+	struct qpnp_qg *chip = data;
+
+	qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n");
+
+	return IRQ_HANDLED;
+}
+
+#define MAX_FIFO_DELTA_PERCENT		10
+static irqreturn_t qg_fifo_update_done_handler(int irq, void *data)
+{
+	ktime_t now = ktime_get();
+	int rc, hw_delta_ms = 0, margin_ms = 0;
+	u32 fifo_length = 0;
+	s64 time_delta_ms = 0;
+	struct qpnp_qg *chip = data;
+
+	time_delta_ms = ktime_ms_delta(now, chip->last_fifo_update_time);
+	chip->last_fifo_update_time = now;
+
+	qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n");
+	mutex_lock(&chip->data_lock);
+
+	rc = get_fifo_length(chip, &fifo_length, false);
+	if (rc < 0) {
+		pr_err("Failed to get FIFO length, rc=%d\n", rc);
+		goto done;
+	}
+
+	rc = qg_process_fifo(chip, fifo_length);
+	if (rc < 0) {
+		pr_err("Failed to process QG FIFO, rc=%d\n", rc);
+		goto done;
+	}
+
+	rc = qg_vbat_low_wa(chip);
+	if (rc < 0) {
+		pr_err("Failed to apply VBAT LOW WA, rc=%d\n", rc);
+		goto done;
+	}
+
+	rc = get_fifo_done_time(chip, false, &hw_delta_ms);
+	if (rc < 0)
+		hw_delta_ms = 0;
+	else
+		margin_ms = (hw_delta_ms * MAX_FIFO_DELTA_PERCENT) / 100;
+
+	if (abs(hw_delta_ms - time_delta_ms) < margin_ms) {
+		chip->kdata.param[QG_FIFO_TIME_DELTA].data = time_delta_ms;
+		chip->kdata.param[QG_FIFO_TIME_DELTA].valid = true;
+		qg_dbg(chip, QG_DEBUG_FIFO, "FIFO_done time_delta_ms=%lld\n",
+							time_delta_ms);
+	}
+
+	/* signal the read thread */
+	chip->data_ready = true;
+	wake_up_interruptible(&chip->qg_wait_q);
+
+	/* vote to stay awake until userspace reads data */
+	vote(chip->awake_votable, FIFO_DONE_VOTER, true, 0);
+
+done:
+	mutex_unlock(&chip->data_lock);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t qg_vbat_low_handler(int irq, void *data)
+{
+	int rc;
+	struct qpnp_qg *chip = data;
+	u8 status = 0;
+
+	qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n");
+	mutex_lock(&chip->data_lock);
+
+	rc = qg_read(chip, chip->qg_base + QG_INT_RT_STS_REG, &status, 1);
+	if (rc < 0) {
+		pr_err("Failed to read RT status, rc=%d\n", rc);
+		goto done;
+	}
+	chip->vbat_low = !!(status & VBAT_LOW_INT_RT_STS_BIT);
+
+	rc = process_rt_fifo_data(chip, chip->vbat_low, false);
+	if (rc < 0)
+		pr_err("Failed to process RT FIFO data, rc=%d\n", rc);
+
+	qg_dbg(chip, QG_DEBUG_IRQ, "VBAT_LOW = %d\n", chip->vbat_low);
+done:
+	mutex_unlock(&chip->data_lock);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t qg_vbat_empty_handler(int irq, void *data)
+{
+	struct qpnp_qg *chip = data;
+	u32 ocv_uv = 0;
+
+	qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n");
+	pr_warn("VBATT EMPTY SOC = 0\n");
+
+	chip->catch_up_soc = 0;
+	qg_scale_soc(chip, true);
+
+	qg_sdam_read(SDAM_OCV_UV, &ocv_uv);
+	chip->sdam_data[SDAM_SOC] = 0;
+	chip->sdam_data[SDAM_OCV_UV] = ocv_uv;
+	chip->sdam_data[SDAM_VALID] = 1;
+
+	qg_update_sdam_params(chip);
+
+	if (chip->qg_psy)
+		power_supply_changed(chip->qg_psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t qg_good_ocv_handler(int irq, void *data)
+{
+	int rc;
+	u32 ocv_uv;
+	struct qpnp_qg *chip = data;
+
+	qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n");
+
+	mutex_lock(&chip->data_lock);
+
+	rc = qg_read_ocv(chip, &ocv_uv, GOOD_OCV);
+	if (rc < 0) {
+		pr_err("Failed to read good_ocv, rc=%d\n", rc);
+		goto done;
+	}
+
+	chip->kdata.param[QG_GOOD_OCV_UV].data = ocv_uv;
+	chip->kdata.param[QG_GOOD_OCV_UV].valid = true;
+
+	vote(chip->awake_votable, GOOD_OCV_VOTER, true, 0);
+
+	/* signal the readd thread */
+	chip->data_ready = true;
+	wake_up_interruptible(&chip->qg_wait_q);
+done:
+	mutex_unlock(&chip->data_lock);
+	return IRQ_HANDLED;
+}
+
+static struct qg_irq_info qg_irqs[] = {
+	[QG_BATT_MISSING_IRQ] = {
+		.name		= "qg-batt-missing",
+		.handler	= qg_default_irq_handler,
+	},
+	[QG_VBATT_LOW_IRQ] = {
+		.name		= "qg-vbat-low",
+		.handler	= qg_vbat_low_handler,
+		.wake		= true,
+	},
+	[QG_VBATT_EMPTY_IRQ] = {
+		.name		= "qg-vbat-empty",
+		.handler	= qg_vbat_empty_handler,
+		.wake		= true,
+	},
+	[QG_FIFO_UPDATE_DONE_IRQ] = {
+		.name		= "qg-fifo-done",
+		.handler	= qg_fifo_update_done_handler,
+		.wake		= true,
+	},
+	[QG_GOOD_OCV_IRQ] = {
+		.name		= "qg-good-ocv",
+		.handler	= qg_good_ocv_handler,
+		.wake		= true,
+	},
+	[QG_FSM_STAT_CHG_IRQ] = {
+		.name		= "qg-fsm-state-chg",
+		.handler	= qg_default_irq_handler,
+	},
+	[QG_EVENT_IRQ] = {
+		.name		= "qg-event",
+		.handler	= qg_default_irq_handler,
+	},
+};
+
+static int qg_awake_cb(struct votable *votable, void *data, int awake,
+			const char *client)
+{
+	struct qpnp_qg *chip = data;
+
+	if (awake)
+		pm_stay_awake(chip->dev);
+	else
+		pm_relax(chip->dev);
+
+	pr_debug("client: %s awake: %d\n", client, awake);
+	return 0;
+}
+
+static int qg_fifo_irq_disable_cb(struct votable *votable, void *data,
+				int disable, const char *client)
+{
+	if (disable) {
+		if (qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].wake)
+			disable_irq_wake(
+				qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq);
+		if (qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq)
+			disable_irq_nosync(
+				qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq);
+	} else {
+		if (qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq)
+			enable_irq(qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq);
+		if (qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].wake)
+			enable_irq_wake(
+				qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq);
+	}
+
+	return 0;
+}
+
+static int qg_vbatt_irq_disable_cb(struct votable *votable, void *data,
+				int disable, const char *client)
+{
+	if (disable) {
+		if (qg_irqs[QG_VBATT_LOW_IRQ].wake)
+			disable_irq_wake(qg_irqs[QG_VBATT_LOW_IRQ].irq);
+		if (qg_irqs[QG_VBATT_EMPTY_IRQ].wake)
+			disable_irq_wake(qg_irqs[QG_VBATT_EMPTY_IRQ].irq);
+		if (qg_irqs[QG_VBATT_LOW_IRQ].irq)
+			disable_irq_nosync(qg_irqs[QG_VBATT_LOW_IRQ].irq);
+		if (qg_irqs[QG_VBATT_EMPTY_IRQ].irq)
+			disable_irq_nosync(qg_irqs[QG_VBATT_EMPTY_IRQ].irq);
+	} else {
+		if (qg_irqs[QG_VBATT_LOW_IRQ].irq)
+			enable_irq(qg_irqs[QG_VBATT_LOW_IRQ].irq);
+		if (qg_irqs[QG_VBATT_EMPTY_IRQ].irq)
+			enable_irq(qg_irqs[QG_VBATT_EMPTY_IRQ].irq);
+		if (qg_irqs[QG_VBATT_LOW_IRQ].wake)
+			enable_irq_wake(qg_irqs[QG_VBATT_LOW_IRQ].irq);
+		if (qg_irqs[QG_VBATT_EMPTY_IRQ].wake)
+			enable_irq_wake(qg_irqs[QG_VBATT_EMPTY_IRQ].irq);
+	}
+
+	return 0;
+}
+
+static int qg_good_ocv_irq_disable_cb(struct votable *votable, void *data,
+				int disable, const char *client)
+{
+	if (disable) {
+		if (qg_irqs[QG_GOOD_OCV_IRQ].wake)
+			disable_irq_wake(qg_irqs[QG_GOOD_OCV_IRQ].irq);
+		if (qg_irqs[QG_GOOD_OCV_IRQ].irq)
+			disable_irq_nosync(qg_irqs[QG_GOOD_OCV_IRQ].irq);
+	} else {
+		if (qg_irqs[QG_GOOD_OCV_IRQ].irq)
+			enable_irq(qg_irqs[QG_GOOD_OCV_IRQ].irq);
+		if (qg_irqs[QG_GOOD_OCV_IRQ].wake)
+			enable_irq_wake(qg_irqs[QG_GOOD_OCV_IRQ].irq);
+	}
+
+	return 0;
+}
+
+#define DEFAULT_BATT_TYPE	"Unknown Battery"
+#define MISSING_BATT_TYPE	"Missing Battery"
+#define DEBUG_BATT_TYPE		"Debug Board"
+static const char *qg_get_battery_type(struct qpnp_qg *chip)
+{
+	if (chip->battery_missing)
+		return MISSING_BATT_TYPE;
+
+	if (is_debug_batt_id(chip))
+		return DEBUG_BATT_TYPE;
+
+	if (chip->bp.batt_type_str) {
+		if (chip->profile_loaded)
+			return chip->bp.batt_type_str;
+	}
+
+	return DEFAULT_BATT_TYPE;
+}
+
+static int qg_get_battery_current(struct qpnp_qg *chip, int *ibat_ua)
+{
+	int rc = 0, last_ibat = 0;
+
+	if (chip->battery_missing) {
+		*ibat_ua = 0;
+		return 0;
+	}
+
+	rc = qg_read(chip, chip->qg_base + QG_LAST_ADC_I_DATA0_REG,
+				(u8 *)&last_ibat, 2);
+	if (rc < 0) {
+		pr_err("Failed to read LAST_ADV_I reg, rc=%d\n", rc);
+		return rc;
+	}
+
+	last_ibat = sign_extend32(last_ibat, 15);
+	*ibat_ua = I_RAW_TO_UA(last_ibat);
+
+	return rc;
+}
+
+static int qg_get_battery_voltage(struct qpnp_qg *chip, int *vbat_uv)
+{
+	int rc = 0;
+	u64 last_vbat = 0;
+
+	if (chip->battery_missing) {
+		*vbat_uv = 3700000;
+		return 0;
+	}
+
+	rc = qg_read(chip, chip->qg_base + QG_LAST_ADC_V_DATA0_REG,
+				(u8 *)&last_vbat, 2);
+	if (rc < 0) {
+		pr_err("Failed to read LAST_ADV_V reg, rc=%d\n", rc);
+		return rc;
+	}
+
+	*vbat_uv = V_RAW_TO_UV(last_vbat);
+
+	return rc;
+}
+
+#define DEBUG_BATT_SOC		67
+#define BATT_MISSING_SOC	50
+#define EMPTY_SOC		0
+static int qg_get_battery_capacity(struct qpnp_qg *chip, int *soc)
+{
+	if (is_debug_batt_id(chip)) {
+		*soc = DEBUG_BATT_SOC;
+		return 0;
+	}
+
+	if (chip->battery_missing || !chip->profile_loaded) {
+		*soc = BATT_MISSING_SOC;
+		return 0;
+	}
+
+	*soc = chip->msoc;
+
+	return 0;
+}
+
+static int qg_get_battery_temp(struct qpnp_qg *chip, int *temp)
+{
+	int rc = 0;
+	struct qpnp_vadc_result result;
+
+	if (chip->battery_missing) {
+		*temp = 250;
+		return 0;
+	}
+
+	rc = qpnp_vadc_read(chip->vadc_dev, VADC_BAT_THERM_PU2, &result);
+	if (rc) {
+		pr_err("Failed reading adc channel=%d, rc=%d\n",
+					VADC_BAT_THERM_PU2, rc);
+		return rc;
+	}
+	pr_debug("batt_temp = %lld meas = 0x%llx\n",
+			result.physical, result.measurement);
+
+	*temp = (int)result.physical;
+
+	return rc;
+}
+
+static int qg_psy_set_property(struct power_supply *psy,
+			       enum power_supply_property psp,
+			       const union power_supply_propval *pval)
+{
+	return 0;
+}
+
+static int qg_psy_get_property(struct power_supply *psy,
+			       enum power_supply_property psp,
+			       union power_supply_propval *pval)
+{
+	struct qpnp_qg *chip = power_supply_get_drvdata(psy);
+	int rc = 0;
+
+	pval->intval = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CAPACITY:
+		rc = qg_get_battery_capacity(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		rc = qg_get_battery_voltage(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		rc = qg_get_battery_current(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+		rc = qg_sdam_read(SDAM_OCV_UV, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_TEMP:
+		rc = qg_get_battery_temp(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_RESISTANCE_ID:
+		pval->intval = chip->batt_id_ohm;
+		break;
+	case POWER_SUPPLY_PROP_DEBUG_BATTERY:
+		pval->intval = is_debug_batt_id(chip);
+		break;
+	case POWER_SUPPLY_PROP_RESISTANCE:
+		rc = qg_sdam_read(SDAM_RBAT_MOHM, &pval->intval);
+		if (!rc)
+			pval->intval *= 1000;
+		break;
+	case POWER_SUPPLY_PROP_RESISTANCE_CAPACITIVE:
+		pval->intval = chip->dt.rbat_conn_mohm;
+		break;
+	case POWER_SUPPLY_PROP_BATTERY_TYPE:
+		pval->strval = qg_get_battery_type(chip);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+		pval->intval = chip->dt.vbatt_cutoff_mv * 1000;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		pval->intval = chip->bp.float_volt_uv;
+		break;
+	case POWER_SUPPLY_PROP_BATT_FULL_CURRENT:
+		pval->intval = chip->dt.iterm_ma * 1000;
+		break;
+	case POWER_SUPPLY_PROP_BATT_PROFILE_VERSION:
+		pval->intval = chip->bp.qg_profile_version;
+		break;
+	default:
+		pr_debug("Unsupported property %d\n", psp);
+		break;
+	}
+
+	return rc;
+}
+
+static int qg_property_is_writeable(struct power_supply *psy,
+				enum power_supply_property psp)
+{
+	return 0;
+}
+
+static enum power_supply_property qg_psy_props[] = {
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_VOLTAGE_OCV,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_RESISTANCE,
+	POWER_SUPPLY_PROP_RESISTANCE_ID,
+	POWER_SUPPLY_PROP_RESISTANCE_CAPACITIVE,
+	POWER_SUPPLY_PROP_DEBUG_BATTERY,
+	POWER_SUPPLY_PROP_BATTERY_TYPE,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_BATT_FULL_CURRENT,
+	POWER_SUPPLY_PROP_BATT_PROFILE_VERSION,
+};
+
+static const struct power_supply_desc qg_psy_desc = {
+	.name = "bms",
+	.type = POWER_SUPPLY_TYPE_BMS,
+	.properties = qg_psy_props,
+	.num_properties = ARRAY_SIZE(qg_psy_props),
+	.get_property = qg_psy_get_property,
+	.set_property = qg_psy_set_property,
+	.property_is_writeable = qg_property_is_writeable,
+};
+
+static int qg_charge_full_update(struct qpnp_qg *chip)
+{
+
+	vote(chip->good_ocv_irq_disable_votable,
+		QG_INIT_STATE_IRQ_DISABLE, !chip->charge_done, 0);
+
+	/* TODO: add hold-soc-at-full logic */
+	return 0;
+}
+
+static int qg_parallel_status_update(struct qpnp_qg *chip)
+{
+	int rc;
+	bool parallel_enabled = is_parallel_enabled(chip);
+
+	if (parallel_enabled == chip->parallel_enabled)
+		return 0;
+
+	chip->parallel_enabled = parallel_enabled;
+	qg_dbg(chip, QG_DEBUG_STATUS,
+		"Parallel status changed Enabled=%d\n", parallel_enabled);
+
+	mutex_lock(&chip->data_lock);
+
+	rc = process_rt_fifo_data(chip, false, true);
+	if (rc < 0)
+		pr_err("Failed to process RT FIFO data, rc=%d\n", rc);
+
+	mutex_unlock(&chip->data_lock);
+
+	return 0;
+}
+
+static int qg_usb_status_update(struct qpnp_qg *chip)
+{
+	bool usb_present = is_usb_present(chip);
+
+	if (chip->usb_present != usb_present) {
+		qg_dbg(chip, QG_DEBUG_STATUS,
+			"USB status changed Present=%d\n",
+							usb_present);
+		qg_scale_soc(chip, false);
+	}
+
+	chip->usb_present = usb_present;
+
+	return 0;
+}
+
+static void qg_status_change_work(struct work_struct *work)
+{
+	struct qpnp_qg *chip = container_of(work,
+			struct qpnp_qg, qg_status_change_work);
+	union power_supply_propval prop = {0, };
+	int rc = 0;
+
+	if (!is_batt_available(chip)) {
+		pr_debug("batt-psy not available\n");
+		goto out;
+	}
+
+	rc = power_supply_get_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_STATUS, &prop);
+	if (rc < 0)
+		pr_err("Failed to get charger status, rc=%d\n", rc);
+	else
+		chip->charge_status = prop.intval;
+
+	rc = power_supply_get_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_CHARGE_DONE, &prop);
+	if (rc < 0)
+		pr_err("Failed to get charge done status, rc=%d\n", rc);
+	else
+		chip->charge_done = prop.intval;
+
+	rc = qg_parallel_status_update(chip);
+	if (rc < 0)
+		pr_err("Failed to update parallel-status, rc=%d\n", rc);
+
+	rc = qg_usb_status_update(chip);
+	if (rc < 0)
+		pr_err("Failed to update usb status, rc=%d\n", rc);
+
+	rc = qg_charge_full_update(chip);
+	if (rc < 0)
+		pr_err("Failed in charge_full_update, rc=%d\n", rc);
+out:
+	pm_relax(chip->dev);
+}
+
+static int qg_notifier_cb(struct notifier_block *nb,
+			unsigned long event, void *data)
+{
+	struct power_supply *psy = data;
+	struct qpnp_qg *chip = container_of(nb, struct qpnp_qg, nb);
+
+	if (event != PSY_EVENT_PROP_CHANGED)
+		return NOTIFY_OK;
+
+	if (work_pending(&chip->qg_status_change_work))
+		return NOTIFY_OK;
+
+	if ((strcmp(psy->desc->name, "battery") == 0)
+		|| (strcmp(psy->desc->name, "parallel") == 0)
+		|| (strcmp(psy->desc->name, "usb") == 0)) {
+		/*
+		 * 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);
+		schedule_work(&chip->qg_status_change_work);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int qg_init_psy(struct qpnp_qg *chip)
+{
+	struct power_supply_config qg_psy_cfg;
+	int rc;
+
+	qg_psy_cfg.drv_data = chip;
+	qg_psy_cfg.of_node = NULL;
+	qg_psy_cfg.supplied_to = NULL;
+	qg_psy_cfg.num_supplicants = 0;
+	chip->qg_psy = devm_power_supply_register(chip->dev,
+				&qg_psy_desc, &qg_psy_cfg);
+	if (IS_ERR_OR_NULL(chip->qg_psy)) {
+		pr_err("Failed to register qg_psy rc = %ld\n",
+				PTR_ERR(chip->qg_psy));
+		return -ENODEV;
+	}
+
+	chip->nb.notifier_call = qg_notifier_cb;
+	rc = power_supply_reg_notifier(&chip->nb);
+	if (rc < 0)
+		pr_err("Failed register psy notifier rc = %d\n", rc);
+
+	return rc;
+}
+
+static ssize_t qg_device_read(struct file *file, char __user *buf, size_t count,
+			  loff_t *ppos)
+{
+	int rc;
+	struct qpnp_qg *chip = file->private_data;
+	unsigned long data_size = sizeof(chip->kdata);
+
+	/* non-blocking access, return */
+	if (!chip->data_ready && (file->f_flags & O_NONBLOCK))
+		return -EAGAIN;
+
+	/* blocking access wait on data_ready */
+	if (!(file->f_flags & O_NONBLOCK)) {
+		rc = wait_event_interruptible(chip->qg_wait_q,
+					chip->data_ready);
+		if (rc < 0) {
+			pr_debug("Failed wait! rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	mutex_lock(&chip->data_lock);
+
+	if (!chip->data_ready) {
+		pr_debug("No Data, false wakeup\n");
+		rc = -EFAULT;
+		goto fail_read;
+	}
+
+	if (copy_to_user(buf, &chip->kdata, data_size)) {
+		pr_err("Failed in copy_to_user\n");
+		rc = -EFAULT;
+		goto fail_read;
+	}
+	chip->data_ready = false;
+
+	/* clear data */
+	memset(&chip->kdata, 0, sizeof(chip->kdata));
+
+	/* release all wake sources */
+	vote(chip->awake_votable, GOOD_OCV_VOTER, false, 0);
+	vote(chip->awake_votable, FIFO_DONE_VOTER, false, 0);
+	vote(chip->awake_votable, FIFO_RT_DONE_VOTER, false, 0);
+	vote(chip->awake_votable, SUSPEND_DATA_VOTER, false, 0);
+
+	qg_dbg(chip, QG_DEBUG_DEVICE,
+			"QG device read complete Size=%ld\n", data_size);
+
+	mutex_unlock(&chip->data_lock);
+
+	return data_size;
+
+fail_read:
+	mutex_unlock(&chip->data_lock);
+	return rc;
+}
+
+static ssize_t qg_device_write(struct file *file, const char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	int rc = -EINVAL;
+	struct qpnp_qg *chip = file->private_data;
+	unsigned long data_size = sizeof(chip->udata);
+
+	mutex_lock(&chip->data_lock);
+	if (count == 0) {
+		pr_err("No data!\n");
+		goto fail;
+	}
+
+	if (count != 0 && count < data_size) {
+		pr_err("Invalid datasize %zu expected %zu\n", count, data_size);
+		goto fail;
+	}
+
+	if (copy_from_user(&chip->udata, buf, data_size)) {
+		pr_err("Failed in copy_from_user\n");
+		rc = -EFAULT;
+		goto fail;
+	}
+
+	rc = data_size;
+	vote(chip->awake_votable, UDATA_READY_VOTER, true, 0);
+	schedule_work(&chip->udata_work);
+	qg_dbg(chip, QG_DEBUG_DEVICE, "QG write complete size=%d\n", rc);
+fail:
+	mutex_unlock(&chip->data_lock);
+	return rc;
+}
+
+static unsigned int qg_device_poll(struct file *file, poll_table *wait)
+{
+	struct qpnp_qg *chip = file->private_data;
+	unsigned int mask;
+
+	poll_wait(file, &chip->qg_wait_q, wait);
+
+	if (chip->data_ready)
+		mask = POLLIN | POLLRDNORM;
+	else
+		mask = POLLERR;
+
+	return mask;
+}
+
+static int qg_device_open(struct inode *inode, struct file *file)
+{
+	struct qpnp_qg *chip = container_of(inode->i_cdev,
+				struct qpnp_qg, qg_cdev);
+
+	file->private_data = chip;
+	qg_dbg(chip, QG_DEBUG_DEVICE, "QG device opened!\n");
+
+	return 0;
+}
+
+static const struct file_operations qg_fops = {
+	.owner		= THIS_MODULE,
+	.open		= qg_device_open,
+	.read		= qg_device_read,
+	.write		= qg_device_write,
+	.poll		= qg_device_poll,
+};
+
+static int qg_register_device(struct qpnp_qg *chip)
+{
+	int rc;
+
+	rc = alloc_chrdev_region(&chip->dev_no, 0, 1, "qg");
+	if (rc < 0) {
+		pr_err("Failed to allocate chardev rc=%d\n", rc);
+		return rc;
+	}
+
+	cdev_init(&chip->qg_cdev, &qg_fops);
+	rc = cdev_add(&chip->qg_cdev, chip->dev_no, 1);
+	if (rc < 0) {
+		pr_err("Failed to cdev_add rc=%d\n", rc);
+		goto unregister_chrdev;
+	}
+
+	chip->qg_class = class_create(THIS_MODULE, "qg");
+	if (IS_ERR_OR_NULL(chip->qg_class)) {
+		pr_err("Failed to create qg class\n");
+		rc = -EINVAL;
+		goto delete_cdev;
+	}
+	chip->qg_device = device_create(chip->qg_class, NULL, chip->dev_no,
+					NULL, "qg");
+	if (IS_ERR(chip->qg_device)) {
+		pr_err("Failed to create qg_device\n");
+		rc = -EINVAL;
+		goto destroy_class;
+	}
+
+	qg_dbg(chip, QG_DEBUG_DEVICE, "'/dev/qg' successfully created\n");
+
+	return 0;
+
+destroy_class:
+	class_destroy(chip->qg_class);
+delete_cdev:
+	cdev_del(&chip->qg_cdev);
+unregister_chrdev:
+	unregister_chrdev_region(chip->dev_no, 1);
+	return rc;
+}
+
+#define BID_RPULL_OHM		100000
+#define BID_VREF_MV		1875
+static int get_batt_id_ohm(struct qpnp_qg *chip, u32 *batt_id_ohm)
+{
+	int rc, batt_id_mv;
+	int64_t denom;
+	struct qpnp_vadc_result result;
+
+	/* Read battery-id */
+	rc = qpnp_vadc_read(chip->vadc_dev, VADC_BAT_ID_PU2, &result);
+	if (rc) {
+		pr_err("Failed to read BATT_ID over vadc, rc=%d\n", rc);
+		return rc;
+	}
+
+	batt_id_mv = result.physical / 1000;
+	if (batt_id_mv == 0) {
+		pr_debug("batt_id_mv = 0 from ADC\n");
+		return 0;
+	}
+
+	denom = div64_s64(BID_VREF_MV * 1000, batt_id_mv) - 1000;
+	if (denom <= 0) {
+		/* batt id connector might be open, return 0 kohms */
+		return 0;
+	}
+
+	*batt_id_ohm = div64_u64(BID_RPULL_OHM * 1000 + denom / 2, denom);
+
+	qg_dbg(chip, QG_DEBUG_PROFILE, "batt_id_mv=%d, batt_id_ohm=%d\n",
+					batt_id_mv, *batt_id_ohm);
+
+	return 0;
+}
+
+static int qg_load_battery_profile(struct qpnp_qg *chip)
+{
+	struct device_node *node = chip->dev->of_node;
+	struct device_node *batt_node, *profile_node;
+	int rc;
+
+	batt_node = of_find_node_by_name(node, "qcom,battery-data");
+	if (!batt_node) {
+		pr_err("Batterydata not available\n");
+		return -ENXIO;
+	}
+
+	profile_node = of_batterydata_get_best_profile(batt_node,
+				chip->batt_id_ohm / 1000, NULL);
+	if (IS_ERR(profile_node)) {
+		rc = PTR_ERR(profile_node);
+		pr_err("Failed to detect valid QG battery profile %d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_string(profile_node, "qcom,battery-type",
+				&chip->bp.batt_type_str);
+	if (rc < 0) {
+		pr_err("Failed to detect battery type rc:%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_batterydata_init(profile_node);
+	if (rc < 0) {
+		pr_err("Failed to initialize battery-profile rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv",
+				&chip->bp.float_volt_uv);
+	if (rc < 0) {
+		pr_err("Failed to read battery float-voltage rc:%d\n", rc);
+		chip->bp.float_volt_uv = -EINVAL;
+	}
+
+	rc = of_property_read_u32(profile_node, "qcom,fastchg-current-ma",
+				&chip->bp.fastchg_curr_ma);
+	if (rc < 0) {
+		pr_err("Failed to read battery fastcharge current rc:%d\n", rc);
+		chip->bp.fastchg_curr_ma = -EINVAL;
+	}
+
+	rc = of_property_read_u32(profile_node, "qcom,qg-batt-profile-ver",
+				&chip->bp.qg_profile_version);
+	if (rc < 0) {
+		pr_err("Failed to read QG profile version rc:%d\n", rc);
+		chip->bp.qg_profile_version = -EINVAL;
+	}
+
+	qg_dbg(chip, QG_DEBUG_PROFILE, "profile=%s FV=%duV FCC=%dma\n",
+			chip->bp.batt_type_str, chip->bp.float_volt_uv,
+			chip->bp.fastchg_curr_ma);
+
+	return 0;
+}
+
+static int qg_setup_battery(struct qpnp_qg *chip)
+{
+	int rc;
+
+	if (!is_battery_present(chip)) {
+		qg_dbg(chip, QG_DEBUG_PROFILE, "Battery Missing!\n");
+		chip->battery_missing = true;
+		chip->profile_loaded = false;
+	} else {
+		/* battery present */
+		rc = get_batt_id_ohm(chip, &chip->batt_id_ohm);
+		if (rc < 0) {
+			pr_err("Failed to detect batt_id rc=%d\n", rc);
+			chip->profile_loaded = false;
+		} else {
+			rc = qg_load_battery_profile(chip);
+			if (rc < 0)
+				pr_err("Failed to load battery-profile rc=%d\n",
+								rc);
+			else
+				chip->profile_loaded = true;
+		}
+	}
+
+	qg_dbg(chip, QG_DEBUG_PROFILE, "battery_missing=%d batt_id_ohm=%d Ohm profile_loaded=%d profile=%s\n",
+			chip->battery_missing, chip->batt_id_ohm,
+			chip->profile_loaded, chip->bp.batt_type_str);
+
+	return 0;
+}
+
+static int qg_determine_pon_soc(struct qpnp_qg *chip)
+{
+	u8 status;
+	int rc, batt_temp = 0;
+	bool use_pon_ocv = false;
+	unsigned long rtc_sec = 0;
+	u32 ocv_uv = 0, soc = 0, shutdown[SDAM_MAX] = {0};
+
+	if (!chip->profile_loaded) {
+		qg_dbg(chip, QG_DEBUG_PON, "No Profile, skipping PON soc\n");
+		return 0;
+	}
+
+	rc = qg_get_battery_temp(chip, &batt_temp);
+	if (rc) {
+		pr_err("Failed to read BATT_TEMP at PON rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_read(chip, chip->qg_base + QG_STATUS2_REG,
+					&status, 1);
+	if (rc < 0) {
+		pr_err("Failed to read status2 register rc=%d\n", rc);
+		return rc;
+	}
+
+	if (status & GOOD_OCV_BIT) {
+		qg_dbg(chip, QG_DEBUG_PON, "Using GOOD_OCV @ PON\n");
+		rc = qg_read_ocv(chip, &ocv_uv, GOOD_OCV);
+		if (rc < 0) {
+			pr_err("Failed to read good_ocv rc=%d\n", rc);
+			use_pon_ocv = true;
+		} else {
+			rc = lookup_soc_ocv(&soc, ocv_uv, batt_temp, false);
+			if (rc < 0) {
+				pr_err("Failed to lookup SOC (GOOD_OCV) @ PON rc=%d\n",
+					rc);
+				use_pon_ocv = true;
+			}
+		}
+	} else {
+		rc = get_rtc_time(&rtc_sec);
+		if (rc < 0) {
+			pr_err("Failed to read RTC time rc=%d\n", rc);
+			use_pon_ocv = true;
+			goto done;
+		}
+
+		rc = qg_sdam_read_all(shutdown);
+		if (rc < 0) {
+			pr_err("Failed to read shutdown params rc=%d\n", rc);
+			use_pon_ocv = true;
+			goto done;
+		}
+		qg_dbg(chip, QG_DEBUG_PON, "Shutdown: SOC=%d OCV=%duV time=%dsecs, time_now=%ldsecs\n",
+				shutdown[SDAM_SOC],
+				shutdown[SDAM_OCV_UV],
+				shutdown[SDAM_TIME_SEC],
+				rtc_sec);
+		/*
+		 * Use the shutdown SOC if
+		 * 1. The device was powered off for < 180 seconds
+		 * 2. SDAM read is a success & SDAM data is valid
+		 */
+		use_pon_ocv = true;
+		if (!rc && shutdown[SDAM_VALID] &&
+			((rtc_sec - shutdown[SDAM_TIME_SEC]) < 180)) {
+			use_pon_ocv = false;
+			ocv_uv = shutdown[SDAM_OCV_UV];
+			soc = shutdown[SDAM_SOC];
+			qg_dbg(chip, QG_DEBUG_PON, "Using SHUTDOWN_SOC @ PON\n");
+		}
+	}
+done:
+	/*
+	 * Use PON OCV if
+	 * OCV_UV is not set or shutdown SOC is invalid.
+	 */
+	if (use_pon_ocv || !ocv_uv || !rtc_sec) {
+		qg_dbg(chip, QG_DEBUG_PON, "Using PON_OCV @ PON\n");
+		rc = qg_read_ocv(chip, &ocv_uv, PON_OCV);
+		if (rc < 0) {
+			pr_err("Failed to read HW PON ocv rc=%d\n", rc);
+			return rc;
+		}
+		rc = lookup_soc_ocv(&soc, ocv_uv, batt_temp, false);
+		if (rc < 0) {
+			pr_err("Failed to lookup SOC @ PON rc=%d\n", rc);
+			soc = 50;
+		}
+	}
+
+	chip->pon_soc = chip->catch_up_soc = chip->msoc = soc;
+	chip->kdata.param[QG_PON_OCV_UV].data = ocv_uv;
+	chip->kdata.param[QG_PON_OCV_UV].valid = true;
+
+	/* write back to SDAM */
+	chip->sdam_data[SDAM_SOC] = soc;
+	chip->sdam_data[SDAM_OCV_UV] = ocv_uv;
+	chip->sdam_data[SDAM_VALID] = 1;
+
+	rc = qg_write_monotonic_soc(chip, chip->msoc);
+	if (rc < 0)
+		pr_err("Failed to update MSOC register rc=%d\n", rc);
+
+	rc = qg_update_sdam_params(chip);
+	if (rc < 0)
+		pr_err("Failed to update sdam params rc=%d\n", rc);
+
+	pr_info("use_pon_ocv=%d good_ocv=%d ocv_uv=%duV temp=%d soc=%d\n",
+			use_pon_ocv, !!(status & GOOD_OCV_BIT),
+			ocv_uv, batt_temp, chip->msoc);
+
+	return 0;
+}
+
+static int qg_set_wa_flags(struct qpnp_qg *chip)
+{
+	switch (chip->pmic_rev_id->pmic_subtype) {
+	case PMI632_SUBTYPE:
+		if (chip->pmic_rev_id->rev4 == PMI632_V1P0_REV4)
+			chip->wa_flags |= QG_VBAT_LOW_WA;
+		break;
+	default:
+		pr_err("Unsupported PMIC subtype %d\n",
+			chip->pmic_rev_id->pmic_subtype);
+		return -EINVAL;
+	}
+
+	qg_dbg(chip, QG_DEBUG_PON, "wa_flags = %x\n", chip->wa_flags);
+
+	return 0;
+}
+
+static int qg_hw_init(struct qpnp_qg *chip)
+{
+	int rc, temp;
+	u8 reg;
+
+	rc = qg_set_wa_flags(chip);
+	if (rc < 0) {
+		pr_err("Failed to update PMIC type flags, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_master_hold(chip, true);
+	if (rc < 0) {
+		pr_err("Failed to hold master, rc=%d\n", rc);
+		goto done_fifo;
+	}
+
+	rc = qg_process_rt_fifo(chip);
+	if (rc < 0) {
+		pr_err("Failed to process FIFO real-time, rc=%d\n", rc);
+		goto done_fifo;
+	}
+
+	/* update the changed S2 fifo DT parameters */
+	if (chip->dt.s2_fifo_length > 0) {
+		rc = qg_update_fifo_length(chip, chip->dt.s2_fifo_length);
+		if (rc < 0)
+			goto done_fifo;
+	}
+
+	if (chip->dt.s2_acc_length > 0) {
+		reg = ilog2(chip->dt.s2_acc_length) - 1;
+		rc = qg_masked_write(chip, chip->qg_base +
+				QG_S2_NORMAL_MEAS_CTL2_REG,
+				NUM_OF_ACCUM_MASK, reg);
+		if (rc < 0) {
+			pr_err("Failed to write S2 ACC length, rc=%d\n", rc);
+			goto done_fifo;
+		}
+	}
+
+	if (chip->dt.s2_acc_intvl_ms > 0) {
+		reg = chip->dt.s2_acc_intvl_ms / 10;
+		rc = qg_write(chip, chip->qg_base +
+				QG_S2_NORMAL_MEAS_CTL3_REG,
+				&reg, 1);
+		if (rc < 0) {
+			pr_err("Failed to write S2 ACC intrvl, rc=%d\n", rc);
+			goto done_fifo;
+		}
+	}
+
+	/* signal the read thread */
+	chip->data_ready = true;
+	wake_up_interruptible(&chip->qg_wait_q);
+
+done_fifo:
+	rc = qg_master_hold(chip, false);
+	if (rc < 0) {
+		pr_err("Failed to release master, rc=%d\n", rc);
+		return rc;
+	}
+	chip->last_fifo_update_time = ktime_get();
+
+	if (chip->dt.ocv_timer_expiry_min != -EINVAL) {
+		if (chip->dt.ocv_timer_expiry_min < 2)
+			chip->dt.ocv_timer_expiry_min = 2;
+		else if (chip->dt.ocv_timer_expiry_min > 30)
+			chip->dt.ocv_timer_expiry_min = 30;
+
+		reg = (chip->dt.ocv_timer_expiry_min - 2) / 4;
+		rc = qg_masked_write(chip,
+			chip->qg_base + QG_S3_SLEEP_OCV_MEAS_CTL4_REG,
+			SLEEP_IBAT_QUALIFIED_LENGTH_MASK, reg);
+		if (rc < 0) {
+			pr_err("Failed to write OCV timer, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (chip->dt.ocv_tol_threshold_uv != -EINVAL) {
+		if (chip->dt.ocv_tol_threshold_uv < 0)
+			chip->dt.ocv_tol_threshold_uv = 0;
+		else if (chip->dt.ocv_tol_threshold_uv > 12262)
+			chip->dt.ocv_tol_threshold_uv = 12262;
+
+		reg = chip->dt.ocv_tol_threshold_uv / 195;
+		rc = qg_masked_write(chip,
+			chip->qg_base + QG_S3_SLEEP_OCV_TREND_CTL2_REG,
+			TREND_TOL_MASK, reg);
+		if (rc < 0) {
+			pr_err("Failed to write OCV tol-thresh, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (chip->dt.s3_entry_fifo_length != -EINVAL) {
+		if (chip->dt.s3_entry_fifo_length < 1)
+			chip->dt.s3_entry_fifo_length = 1;
+		else if (chip->dt.s3_entry_fifo_length > 8)
+			chip->dt.s3_entry_fifo_length = 8;
+
+		reg = chip->dt.s3_entry_fifo_length - 1;
+		rc = qg_masked_write(chip,
+			chip->qg_base + QG_S3_SLEEP_OCV_IBAT_CTL1_REG,
+			SLEEP_IBAT_QUALIFIED_LENGTH_MASK, reg);
+		if (rc < 0) {
+			pr_err("Failed to write S3-entry fifo-length, rc=%d\n",
+							rc);
+			return rc;
+		}
+	}
+
+	if (chip->dt.s3_entry_ibat_ua != -EINVAL) {
+		if (chip->dt.s3_entry_ibat_ua < 0)
+			chip->dt.s3_entry_ibat_ua = 0;
+		else if (chip->dt.s3_entry_ibat_ua > 155550)
+			chip->dt.s3_entry_ibat_ua = 155550;
+
+		reg = chip->dt.s3_entry_ibat_ua / 610;
+		rc = qg_write(chip, chip->qg_base +
+				QG_S3_ENTRY_IBAT_THRESHOLD_REG,
+				&reg, 1);
+		if (rc < 0) {
+			pr_err("Failed to write S3-entry ibat-uA, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (chip->dt.s3_exit_ibat_ua != -EINVAL) {
+		if (chip->dt.s3_exit_ibat_ua < 0)
+			chip->dt.s3_exit_ibat_ua = 0;
+		else if (chip->dt.s3_exit_ibat_ua > 155550)
+			chip->dt.s3_exit_ibat_ua = 155550;
+
+		rc = qg_read(chip, chip->qg_base +
+				QG_S3_ENTRY_IBAT_THRESHOLD_REG,
+				&reg, 1);
+		if (rc < 0) {
+			pr_err("Failed to read S3-entry ibat-uA, rc=%d", rc);
+			return rc;
+		}
+		temp = reg * 610;
+		if (chip->dt.s3_exit_ibat_ua < temp)
+			chip->dt.s3_exit_ibat_ua = temp;
+		else
+			chip->dt.s3_exit_ibat_ua -= temp;
+
+		reg = chip->dt.s3_exit_ibat_ua / 610;
+		rc = qg_write(chip,
+			chip->qg_base + QG_S3_EXIT_IBAT_THRESHOLD_REG,
+			&reg, 1);
+		if (rc < 0) {
+			pr_err("Failed to write S3-entry ibat-uA, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	/* vbat low */
+	if (chip->dt.vbatt_low_mv < 0)
+		chip->dt.vbatt_low_mv = 0;
+	else if (chip->dt.vbatt_low_mv > 12750)
+		chip->dt.vbatt_low_mv = 12750;
+
+	reg = chip->dt.vbatt_low_mv / 50;
+	rc = qg_write(chip, chip->qg_base + QG_VBAT_LOW_THRESHOLD_REG,
+					&reg, 1);
+	if (rc < 0) {
+		pr_err("Failed to write vbat-low, rc=%d\n", rc);
+		return rc;
+	}
+
+	/* vbat empty */
+	if (chip->dt.vbatt_empty_mv < 0)
+		chip->dt.vbatt_empty_mv = 0;
+	else if (chip->dt.vbatt_empty_mv > 12750)
+		chip->dt.vbatt_empty_mv = 12750;
+
+	reg = chip->dt.vbatt_empty_mv / 50;
+	rc = qg_write(chip, chip->qg_base + QG_VBAT_EMPTY_THRESHOLD_REG,
+					&reg, 1);
+	if (rc < 0) {
+		pr_err("Failed to write vbat-empty, rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int qg_post_init(struct qpnp_qg *chip)
+{
+	/* disable all IRQs if profile is not loaded */
+	if (!chip->profile_loaded) {
+		vote(chip->vbatt_irq_disable_votable,
+				PROFILE_IRQ_DISABLE, true, 0);
+		vote(chip->fifo_irq_disable_votable,
+				PROFILE_IRQ_DISABLE, true, 0);
+		vote(chip->good_ocv_irq_disable_votable,
+				PROFILE_IRQ_DISABLE, true, 0);
+	} else {
+		/* disable GOOD_OCV IRQ at init */
+		vote(chip->good_ocv_irq_disable_votable,
+				QG_INIT_STATE_IRQ_DISABLE, true, 0);
+	}
+
+	return 0;
+}
+
+static int qg_get_irq_index_byname(const char *irq_name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(qg_irqs); i++) {
+		if (strcmp(qg_irqs[i].name, irq_name) == 0)
+			return i;
+	}
+
+	return -ENOENT;
+}
+
+static int qg_request_interrupt(struct qpnp_qg *chip,
+		struct device_node *node, const char *irq_name)
+{
+	int rc, irq, irq_index;
+
+	irq = of_irq_get_byname(node, irq_name);
+	if (irq < 0) {
+		pr_err("Failed to get irq %s byname\n", irq_name);
+		return irq;
+	}
+
+	irq_index = qg_get_irq_index_byname(irq_name);
+	if (irq_index < 0) {
+		pr_err("%s is not a defined irq\n", irq_name);
+		return irq_index;
+	}
+
+	if (!qg_irqs[irq_index].handler)
+		return 0;
+
+	rc = devm_request_threaded_irq(chip->dev, irq, NULL,
+				qg_irqs[irq_index].handler,
+				IRQF_ONESHOT, irq_name, chip);
+	if (rc < 0) {
+		pr_err("Failed to request irq %d\n", irq);
+		return rc;
+	}
+
+	qg_irqs[irq_index].irq = irq;
+	if (qg_irqs[irq_index].wake)
+		enable_irq_wake(irq);
+
+	qg_dbg(chip, QG_DEBUG_PON, "IRQ %s registered wakeable=%d\n",
+			qg_irqs[irq_index].name, qg_irqs[irq_index].wake);
+
+	return 0;
+}
+
+static int qg_request_irqs(struct qpnp_qg *chip)
+{
+	struct device_node *node = chip->dev->of_node;
+	struct device_node *child;
+	const char *name;
+	struct property *prop;
+	int rc = 0;
+
+	for_each_available_child_of_node(node, child) {
+		of_property_for_each_string(child, "interrupt-names",
+					    prop, name) {
+			rc = qg_request_interrupt(chip, child, name);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
+
+	return 0;
+}
+
+#define DEFAULT_VBATT_EMPTY_MV		3200
+#define DEFAULT_VBATT_CUTOFF_MV		3400
+#define DEFAULT_VBATT_LOW_MV		3500
+#define DEFAULT_ITERM_MA		100
+#define DEFAULT_S2_FIFO_LENGTH		5
+#define DEFAULT_S2_VBAT_LOW_LENGTH	2
+#define DEFAULT_S2_ACC_LENGTH		128
+#define DEFAULT_S2_ACC_INTVL_MS		100
+#define DEFAULT_DELTA_SOC		1
+static int qg_parse_dt(struct qpnp_qg *chip)
+{
+	int rc = 0;
+	struct device_node *revid_node, *child, *node = chip->dev->of_node;
+	u32 base, temp;
+	u8 type;
+
+	if (!node)  {
+		pr_err("Failed to find device-tree node\n");
+		return -ENXIO;
+	}
+
+	revid_node = of_parse_phandle(node, "qcom,pmic-revid", 0);
+	if (!revid_node) {
+		pr_err("Missing qcom,pmic-revid property - driver failed\n");
+		return -EINVAL;
+	}
+
+	chip->pmic_rev_id = get_revid_data(revid_node);
+	of_node_put(revid_node);
+	if (IS_ERR_OR_NULL(chip->pmic_rev_id)) {
+		pr_err("Failed to get pmic_revid, rc=%ld\n",
+			PTR_ERR(chip->pmic_rev_id));
+		/*
+		 * the revid peripheral must be registered, any failure
+		 * here only indicates that the rev-id module has not
+		 * probed yet.
+		 */
+		return -EPROBE_DEFER;
+	}
+
+	qg_dbg(chip, QG_DEBUG_PON, "PMIC subtype %d Digital major %d\n",
+		chip->pmic_rev_id->pmic_subtype, chip->pmic_rev_id->rev4);
+
+	for_each_available_child_of_node(node, child) {
+		rc = of_property_read_u32(child, "reg", &base);
+		if (rc < 0) {
+			pr_err("Failed to read base address, rc=%d\n", rc);
+			return rc;
+		}
+
+		rc = qg_read(chip, base + PERPH_TYPE_REG, &type, 1);
+		if (rc < 0) {
+			pr_err("Failed to read type, rc=%d\n", rc);
+			return rc;
+		}
+
+		switch (type) {
+		case QG_TYPE:
+			chip->qg_base = base;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!chip->qg_base) {
+		pr_err("QG device node missing\n");
+		return -EINVAL;
+	}
+
+	/* S2 state params */
+	rc = of_property_read_u32(node, "qcom,s2-fifo-length", &temp);
+	if (rc < 0)
+		chip->dt.s2_fifo_length = DEFAULT_S2_FIFO_LENGTH;
+	else
+		chip->dt.s2_fifo_length = temp;
+
+	rc = of_property_read_u32(node, "qcom,s2-vbat-low-fifo-length", &temp);
+	if (rc < 0)
+		chip->dt.s2_vbat_low_fifo_length = DEFAULT_S2_VBAT_LOW_LENGTH;
+	else
+		chip->dt.s2_vbat_low_fifo_length = temp;
+
+	rc = of_property_read_u32(node, "qcom,s2-acc-length", &temp);
+	if (rc < 0)
+		chip->dt.s2_acc_length = DEFAULT_S2_ACC_LENGTH;
+	else
+		chip->dt.s2_acc_length = temp;
+
+	rc = of_property_read_u32(node, "qcom,s2-acc-interval-ms", &temp);
+	if (rc < 0)
+		chip->dt.s2_acc_intvl_ms = DEFAULT_S2_ACC_INTVL_MS;
+	else
+		chip->dt.s2_acc_intvl_ms = temp;
+
+	qg_dbg(chip, QG_DEBUG_PON, "DT: S2 FIFO length=%d low_vbat_length=%d acc_length=%d acc_interval=%d\n",
+		chip->dt.s2_fifo_length, chip->dt.s2_vbat_low_fifo_length,
+		chip->dt.s2_acc_length, chip->dt.s2_acc_intvl_ms);
+
+	/* OCV params */
+	rc = of_property_read_u32(node, "qcom,ocv-timer-expiry-min", &temp);
+	if (rc < 0)
+		chip->dt.ocv_timer_expiry_min = -EINVAL;
+	else
+		chip->dt.ocv_timer_expiry_min = temp;
+
+	rc = of_property_read_u32(node, "qcom,ocv-tol-threshold-uv", &temp);
+	if (rc < 0)
+		chip->dt.ocv_tol_threshold_uv = -EINVAL;
+	else
+		chip->dt.ocv_tol_threshold_uv = temp;
+
+	qg_dbg(chip, QG_DEBUG_PON, "DT: OCV timer_expiry =%dmin ocv_tol_threshold=%duV\n",
+		chip->dt.ocv_timer_expiry_min, chip->dt.ocv_tol_threshold_uv);
+
+	/* S3 sleep configuration */
+	rc = of_property_read_u32(node, "qcom,s3-entry-fifo-length", &temp);
+	if (rc < 0)
+		chip->dt.s3_entry_fifo_length = -EINVAL;
+	else
+		chip->dt.s3_entry_fifo_length = temp;
+
+	rc = of_property_read_u32(node, "qcom,s3-entry-ibat-ua", &temp);
+	if (rc < 0)
+		chip->dt.s3_entry_ibat_ua = -EINVAL;
+	else
+		chip->dt.s3_entry_ibat_ua = temp;
+
+	rc = of_property_read_u32(node, "qcom,s3-entry-ibat-ua", &temp);
+	if (rc < 0)
+		chip->dt.s3_exit_ibat_ua = -EINVAL;
+	else
+		chip->dt.s3_exit_ibat_ua = temp;
+
+	/* VBAT thresholds */
+	rc = of_property_read_u32(node, "qcom,vbatt-empty-mv", &temp);
+	if (rc < 0)
+		chip->dt.vbatt_empty_mv = DEFAULT_VBATT_EMPTY_MV;
+	else
+		chip->dt.vbatt_empty_mv = temp;
+
+	rc = of_property_read_u32(node, "qcom,vbatt-low-mv", &temp);
+	if (rc < 0)
+		chip->dt.vbatt_low_mv = DEFAULT_VBATT_LOW_MV;
+	else
+		chip->dt.vbatt_low_mv = temp;
+
+	rc = of_property_read_u32(node, "qcom,vbatt-cutoff-mv", &temp);
+	if (rc < 0)
+		chip->dt.vbatt_cutoff_mv = DEFAULT_VBATT_CUTOFF_MV;
+	else
+		chip->dt.vbatt_cutoff_mv = temp;
+
+	/* IBAT thresholds */
+	rc = of_property_read_u32(node, "qcom,qg-iterm-ma", &temp);
+	if (rc < 0)
+		chip->dt.iterm_ma = DEFAULT_ITERM_MA;
+	else
+		chip->dt.iterm_ma = temp;
+
+	rc = of_property_read_u32(node, "qcom,delta-soc", &temp);
+	if (rc < 0)
+		chip->dt.delta_soc = DEFAULT_DELTA_SOC;
+	else
+		chip->dt.delta_soc = temp;
+
+	rc = of_property_read_u32(node, "qcom,rbat-conn-mohm", &temp);
+	if (rc < 0)
+		chip->dt.rbat_conn_mohm = 0;
+	else
+		chip->dt.rbat_conn_mohm = temp;
+
+	qg_dbg(chip, QG_DEBUG_PON, "DT: vbatt_empty_mv=%dmV vbatt_low_mv=%dmV delta_soc=%d\n",
+			chip->dt.vbatt_empty_mv, chip->dt.vbatt_low_mv,
+			chip->dt.delta_soc);
+
+	return 0;
+}
+
+static int process_suspend(struct qpnp_qg *chip)
+{
+	int rc;
+	u32 fifo_rt_length = 0, sleep_fifo_length = 0;
+
+	/* ignore any suspend processing if we are charging */
+	if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) {
+		qg_dbg(chip, QG_DEBUG_PM, "Charging @ suspend - ignore processing\n");
+		return 0;
+	}
+
+	rc = get_fifo_length(chip, &fifo_rt_length, true);
+	if (rc < 0) {
+		pr_err("Failed to read FIFO RT count, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_read(chip, chip->qg_base + QG_S3_SLEEP_OCV_IBAT_CTL1_REG,
+			(u8 *)&sleep_fifo_length, 1);
+	if (rc < 0) {
+		pr_err("Failed to read sleep FIFO count, rc=%d\n", rc);
+		return rc;
+	}
+	sleep_fifo_length &= SLEEP_IBAT_QUALIFIED_LENGTH_MASK;
+	/*
+	 * If the real-time FIFO count is greater than
+	 * the the #fifo to enter sleep, save the FIFO data
+	 * and reset the fifo count.
+	 */
+	if (fifo_rt_length >= (chip->dt.s2_fifo_length - sleep_fifo_length)) {
+		rc = qg_master_hold(chip, true);
+		if (rc < 0) {
+			pr_err("Failed to hold master, rc=%d\n", rc);
+			return rc;
+		}
+
+		rc = qg_process_rt_fifo(chip);
+		if (rc < 0) {
+			pr_err("Failed to process FIFO real-time, rc=%d\n", rc);
+			qg_master_hold(chip, false);
+			return rc;
+		}
+
+		rc = qg_master_hold(chip, false);
+		if (rc < 0) {
+			pr_err("Failed to release master, rc=%d\n", rc);
+			return rc;
+		}
+		/* FIFOs restarted */
+		chip->last_fifo_update_time = ktime_get();
+
+		chip->suspend_data = true;
+	}
+
+	qg_dbg(chip, QG_DEBUG_PM, "FIFO rt_length=%d sleep_fifo_length=%d default_s2_count=%d suspend_data=%d\n",
+			fifo_rt_length, sleep_fifo_length,
+			chip->dt.s2_fifo_length, chip->suspend_data);
+
+	return rc;
+}
+
+static int process_resume(struct qpnp_qg *chip)
+{
+	int rc, batt_temp = 0;
+	u8 status2 = 0, rt_status = 0;
+	u32 ocv_uv = 0, soc = 0;
+
+	rc = qg_read(chip, chip->qg_base + QG_STATUS2_REG, &status2, 1);
+	if (rc < 0) {
+		pr_err("Failed to read status2 register, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (status2 & GOOD_OCV_BIT) {
+		rc = qg_read_ocv(chip, &ocv_uv, GOOD_OCV);
+		if (rc < 0) {
+			pr_err("Failed to read good_ocv, rc=%d\n", rc);
+			return rc;
+		}
+		rc = qg_get_battery_temp(chip, &batt_temp);
+		if (rc < 0) {
+			pr_err("Failed to read BATT_TEMP, rc=%d\n", rc);
+			return rc;
+		}
+
+		chip->kdata.param[QG_GOOD_OCV_UV].data = ocv_uv;
+		chip->kdata.param[QG_GOOD_OCV_UV].valid = true;
+		chip->suspend_data = false;
+		rc = lookup_soc_ocv(&soc, ocv_uv, batt_temp, false);
+		if (rc < 0) {
+			pr_err("Failed to lookup OCV, rc=%d\n", rc);
+			return rc;
+		}
+		chip->catch_up_soc = soc;
+		/* update the SOC immediately */
+		qg_scale_soc(chip, true);
+
+		qg_dbg(chip, QG_DEBUG_PM, "GOOD OCV @ resume good_ocv=%d uV soc=%d\n",
+				ocv_uv, soc);
+	}
+	/*
+	 * If the wakeup was not because of FIFO_DONE
+	 * send the pending data collected during suspend.
+	 */
+	rc = qg_read(chip, chip->qg_base + QG_INT_LATCHED_STS_REG,
+						&rt_status, 1);
+	if (rc < 0) {
+		pr_err("Failed to read latched status register, rc=%d\n", rc);
+		return rc;
+	}
+	rt_status &= FIFO_UPDATE_DONE_INT_LAT_STS_BIT;
+
+	if (!rt_status && chip->suspend_data) {
+		vote(chip->awake_votable, SUSPEND_DATA_VOTER, true, 0);
+		/* signal the read thread */
+		chip->data_ready = true;
+		wake_up_interruptible(&chip->qg_wait_q);
+	}
+
+	qg_dbg(chip, QG_DEBUG_PM, "fifo_done rt_status=%d suspend_data=%d data_ready=%d\n",
+			!!rt_status, chip->suspend_data, chip->data_ready);
+
+	chip->suspend_data = false;
+
+	return rc;
+}
+
+static int qpnp_qg_suspend_noirq(struct device *dev)
+{
+	int rc;
+	struct qpnp_qg *chip = dev_get_drvdata(dev);
+
+	mutex_lock(&chip->data_lock);
+
+	rc = process_suspend(chip);
+	if (rc < 0)
+		pr_err("Failed to process QG suspend, rc=%d\n", rc);
+
+	mutex_unlock(&chip->data_lock);
+
+	return 0;
+}
+
+static int qpnp_qg_resume_noirq(struct device *dev)
+{
+	int rc;
+	struct qpnp_qg *chip = dev_get_drvdata(dev);
+
+	mutex_lock(&chip->data_lock);
+
+	rc = process_resume(chip);
+	if (rc < 0)
+		pr_err("Failed to process QG resume, rc=%d\n", rc);
+
+	mutex_unlock(&chip->data_lock);
+
+	return 0;
+}
+
+static const struct dev_pm_ops qpnp_qg_pm_ops = {
+	.suspend_noirq	= qpnp_qg_suspend_noirq,
+	.resume_noirq	= qpnp_qg_resume_noirq,
+};
+
+static int qpnp_qg_probe(struct platform_device *pdev)
+{
+	int rc = 0, soc = 0;
+	struct qpnp_qg *chip;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!chip->regmap) {
+		pr_err("Parent regmap is unavailable\n");
+		return -ENXIO;
+	}
+
+	/* VADC for BID */
+	chip->vadc_dev = qpnp_get_vadc(&pdev->dev, "qg");
+	if (IS_ERR(chip->vadc_dev)) {
+		rc = PTR_ERR(chip->vadc_dev);
+		if (rc != -EPROBE_DEFER)
+			pr_err("Failed to find VADC node, rc=%d\n", rc);
+
+		return rc;
+	}
+
+	chip->dev = &pdev->dev;
+	chip->debug_mask = &qg_debug_mask;
+	platform_set_drvdata(pdev, chip);
+	INIT_WORK(&chip->udata_work, process_udata_work);
+	INIT_WORK(&chip->qg_status_change_work, qg_status_change_work);
+	mutex_init(&chip->bus_lock);
+	mutex_init(&chip->soc_lock);
+	mutex_init(&chip->data_lock);
+	init_waitqueue_head(&chip->qg_wait_q);
+
+	rc = qg_parse_dt(chip);
+	if (rc < 0) {
+		pr_err("Failed to parse DT, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_hw_init(chip);
+	if (rc < 0) {
+		pr_err("Failed to hw_init, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_setup_battery(chip);
+	if (rc < 0) {
+		pr_err("Failed to setup battery, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_register_device(chip);
+	if (rc < 0) {
+		pr_err("Failed to register QG char device, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_sdam_init(chip->dev);
+	if (rc < 0) {
+		pr_err("Failed to initialize QG SDAM, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_soc_init(chip);
+	if (rc < 0) {
+		pr_err("Failed to initialize SOC scaling init rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qg_determine_pon_soc(chip);
+	if (rc < 0) {
+		pr_err("Failed to determine initial state, rc=%d\n", rc);
+		goto fail_device;
+	}
+
+	chip->awake_votable = create_votable("QG_WS", VOTE_SET_ANY,
+					 qg_awake_cb, chip);
+	if (IS_ERR(chip->awake_votable)) {
+		rc = PTR_ERR(chip->awake_votable);
+		chip->awake_votable = NULL;
+		goto fail_device;
+	}
+
+	chip->vbatt_irq_disable_votable = create_votable("QG_VBATT_IRQ_DISABLE",
+				VOTE_SET_ANY, qg_vbatt_irq_disable_cb, chip);
+	if (IS_ERR(chip->vbatt_irq_disable_votable)) {
+		rc = PTR_ERR(chip->vbatt_irq_disable_votable);
+		chip->vbatt_irq_disable_votable = NULL;
+		goto fail_device;
+	}
+
+	chip->fifo_irq_disable_votable = create_votable("QG_FIFO_IRQ_DISABLE",
+				VOTE_SET_ANY, qg_fifo_irq_disable_cb, chip);
+	if (IS_ERR(chip->fifo_irq_disable_votable)) {
+		rc = PTR_ERR(chip->fifo_irq_disable_votable);
+		chip->fifo_irq_disable_votable = NULL;
+		goto fail_device;
+	}
+
+	chip->good_ocv_irq_disable_votable =
+			create_votable("QG_GOOD_IRQ_DISABLE",
+			VOTE_SET_ANY, qg_good_ocv_irq_disable_cb, chip);
+	if (IS_ERR(chip->good_ocv_irq_disable_votable)) {
+		rc = PTR_ERR(chip->good_ocv_irq_disable_votable);
+		chip->good_ocv_irq_disable_votable = NULL;
+		goto fail_device;
+	}
+
+	rc = qg_init_psy(chip);
+	if (rc < 0) {
+		pr_err("Failed to initialize QG psy, rc=%d\n", rc);
+		goto fail_votable;
+	}
+
+	rc = qg_request_irqs(chip);
+	if (rc < 0) {
+		pr_err("Failed to register QG interrupts, rc=%d\n", rc);
+		goto fail_votable;
+	}
+
+	rc = qg_post_init(chip);
+	if (rc < 0) {
+		pr_err("Failed in qg_post_init rc=%d\n", rc);
+		goto fail_votable;
+	}
+
+	qg_get_battery_capacity(chip, &soc);
+	pr_info("QG initialized! battery_profile=%s SOC=%d\n",
+				qg_get_battery_type(chip), soc);
+
+	return rc;
+
+fail_votable:
+	destroy_votable(chip->awake_votable);
+fail_device:
+	device_destroy(chip->qg_class, chip->dev_no);
+	cdev_del(&chip->qg_cdev);
+	unregister_chrdev_region(chip->dev_no, 1);
+	return rc;
+}
+
+static int qpnp_qg_remove(struct platform_device *pdev)
+{
+	struct qpnp_qg *chip = platform_get_drvdata(pdev);
+
+	qg_batterydata_exit();
+	qg_soc_exit(chip);
+
+	cancel_work_sync(&chip->udata_work);
+	cancel_work_sync(&chip->qg_status_change_work);
+	device_destroy(chip->qg_class, chip->dev_no);
+	cdev_del(&chip->qg_cdev);
+	unregister_chrdev_region(chip->dev_no, 1);
+	mutex_destroy(&chip->bus_lock);
+	mutex_destroy(&chip->data_lock);
+	mutex_destroy(&chip->soc_lock);
+	if (chip->awake_votable)
+		destroy_votable(chip->awake_votable);
+
+	return 0;
+}
+
+static const struct of_device_id match_table[] = {
+	{ .compatible = "qcom,qpnp-qg", },
+	{ },
+};
+
+static struct platform_driver qpnp_qg_driver = {
+	.driver		= {
+		.name		= "qcom,qpnp-qg",
+		.owner		= THIS_MODULE,
+		.of_match_table	= match_table,
+		.pm		= &qpnp_qg_pm_ops,
+	},
+	.probe		= qpnp_qg_probe,
+	.remove		= qpnp_qg_remove,
+};
+module_platform_driver(qpnp_qg_driver);
+
+MODULE_DESCRIPTION("QPNP QG Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index ea727f2..f8e92fbc 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -367,6 +367,8 @@
 header-y += qbt1000.h
 header-y += qcedev.h
 header-y += qcota.h
+header-y += qg.h
+header-y += qg-profile.h
 header-y += qnx4_fs.h
 header-y += qnxtypes.h
 header-y += qrng.h
diff --git a/include/uapi/linux/qg-profile.h b/include/uapi/linux/qg-profile.h
new file mode 100644
index 0000000..bffddbb
--- /dev/null
+++ b/include/uapi/linux/qg-profile.h
@@ -0,0 +1,66 @@
+#ifndef __QG_PROFILE_H__
+#define __QG_PROFILE_H__
+
+#include <linux/ioctl.h>
+
+/**
+ * enum profile_table - Table index for battery profile data
+ */
+enum profile_table {
+	TABLE_SOC_OCV1,
+	TABLE_SOC_OCV2,
+	TABLE_FCC1,
+	TABLE_FCC2,
+	TABLE_Z1,
+	TABLE_Z2,
+	TABLE_Z3,
+	TABLE_Z4,
+	TABLE_Z5,
+	TABLE_Z6,
+	TABLE_Y1,
+	TABLE_Y2,
+	TABLE_Y3,
+	TABLE_Y4,
+	TABLE_Y5,
+	TABLE_Y6,
+	TABLE_MAX,
+};
+
+/**
+ * struct battery_params - Battery profile data to be exchanged
+ * @soc:	SOC (state of charge) of the battery
+ * @ocv_uv:	OCV (open circuit voltage) of the battery
+ * @batt_temp:	Battery temperature in deci-degree
+ * @var:	'X' axis param for interpolation
+ * @table_index:Table index to be used for interpolation
+ */
+struct battery_params {
+	int soc;
+	int ocv_uv;
+	int fcc_mah;
+	int slope;
+	int var;
+	int batt_temp;
+	int table_index;
+};
+
+/* Profile MIN / MAX values */
+#define QG_MIN_SOC				0
+#define QG_MAX_SOC				10000
+#define QG_MIN_OCV_UV				3000000
+#define QG_MAX_OCV_UV				5000000
+#define QG_MIN_VAR				0
+#define QG_MAX_VAR				65535
+#define QG_MIN_FCC_MAH				100
+#define QG_MAX_FCC_MAH				16000
+#define QG_MIN_SLOPE				1
+#define QG_MAX_SLOPE				50000
+
+/*  IOCTLs to query battery profile data */
+#define BPIOCXSOC	_IOWR('B', 0x01, struct battery_params) /* SOC */
+#define BPIOCXOCV	_IOWR('B', 0x02, struct battery_params) /* OCV */
+#define BPIOCXFCC	_IOWR('B', 0x03, struct battery_params) /* FCC */
+#define BPIOCXSLOPE	_IOWR('B', 0x04, struct battery_params) /* Slope */
+#define BPIOCXVAR	_IOWR('B', 0x05, struct battery_params) /* All-other */
+
+#endif /* __QG_PROFILE_H__ */
diff --git a/include/uapi/linux/qg.h b/include/uapi/linux/qg.h
new file mode 100644
index 0000000..c0b2b6e
--- /dev/null
+++ b/include/uapi/linux/qg.h
@@ -0,0 +1,50 @@
+#ifndef __QG_H__
+#define __QG_H__
+
+#define MAX_FIFO_LENGTH		16
+
+enum qg {
+	QG_SOC,
+	QG_OCV_UV,
+	QG_RBAT_MOHM,
+	QG_PON_OCV_UV,
+	QG_GOOD_OCV_UV,
+	QG_ESR,
+	QG_CHARGE_COUNTER,
+	QG_FIFO_TIME_DELTA,
+	QG_RESERVED_1,
+	QG_RESERVED_2,
+	QG_RESERVED_3,
+	QG_RESERVED_4,
+	QG_RESERVED_5,
+	QG_RESERVED_6,
+	QG_RESERVED_7,
+	QG_RESERVED_8,
+	QG_RESERVED_9,
+	QG_RESERVED_10,
+	QG_MAX,
+};
+
+struct fifo_data {
+	unsigned int			v;
+	unsigned int			i;
+	unsigned int			count;
+	unsigned int			interval;
+};
+
+struct qg_param {
+	unsigned int			data;
+	bool				valid;
+};
+
+struct qg_kernel_data {
+	unsigned int			fifo_length;
+	struct fifo_data		fifo[MAX_FIFO_LENGTH];
+	struct qg_param			param[QG_MAX];
+};
+
+struct qg_user_data {
+	struct qg_param			param[QG_MAX];
+};
+
+#endif