drivers: add a snapshot of various QPNP PMIC peripheral drivers

Add a snapshot of several Qualcomm Technologies, Inc. QPNP PMIC
peripheral drivers.  These drivers manage various modules found
within PMIC chips.

This snapshot is taken as of msm-4.4
commit d24550bbf50f ("Merge "ARM: dts: msm: Add slimbus slave
device for wcn3990 on sdm630"").

Change-Id: I842f81737eec1ca11bf31534e9299bd7a6511f6c
Signed-off-by: David Collins <collinsd@codeaurora.org>
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index ed37e59..9338ff7 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -362,6 +362,25 @@
 	help
 	  Say yes here to support the PXA GPIO device
 
+config GPIO_QPNP_PIN
+	tristate "Qualcomm Technologies, Inc. QPNP GPIO support"
+	depends on SPMI
+	help
+	  Say 'y' here to include support for the Qualcomm Technologies, Inc.
+	  QPNP GPIO driver.  This driver supports Device Tree and allows a
+	  device_node to be registered as a gpio-controller.  It does not handle
+	  GPIO interrupts directly; they are handled via the SPMI arbiter
+	  interrupt driver.
+
+config GPIO_QPNP_PIN_DEBUG
+	bool "Qualcomm Technologies, Inc. QPNP GPIO debug support"
+	depends on GPIO_QPNP_PIN && DEBUG_FS
+	help
+	  Say 'y' here to include debug support for the Qualcomm Technologies,
+	  Inc. QPNP GPIO driver.  This provides a userspace debug interface to
+	  get and set all of the supported features of PMIC GPIO and MPP pins
+	  including those which are managed by the gpio framework.
+
 config GPIO_RCAR
 	tristate "Renesas R-Car GPIO"
 	depends on ARCH_RENESAS || COMPILE_TEST
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 1b08983..68418a6 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -91,6 +91,7 @@
 obj-$(CONFIG_GPIO_PISOSR)	+= gpio-pisosr.o
 obj-$(CONFIG_GPIO_PL061)	+= gpio-pl061.o
 obj-$(CONFIG_GPIO_PXA)		+= gpio-pxa.o
+obj-$(CONFIG_GPIO_QPNP_PIN)	+= qpnp-pin.o
 obj-$(CONFIG_GPIO_RC5T583)	+= gpio-rc5t583.o
 obj-$(CONFIG_GPIO_RDC321X)	+= gpio-rdc321x.o
 obj-$(CONFIG_GPIO_RCAR)		+= gpio-rcar.o
diff --git a/drivers/gpio/qpnp-pin.c b/drivers/gpio/qpnp-pin.c
new file mode 100644
index 0000000..d89aca9
--- /dev/null
+++ b/drivers/gpio/qpnp-pin.c
@@ -0,0 +1,1714 @@
+/* Copyright (c) 2012-2017, 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) "%s: " fmt, __func__
+
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/qpnp/pin.h>
+
+#define Q_REG_ADDR(q_spec, reg_index)	\
+		((q_spec)->offset + reg_index)
+
+#define Q_REG_STATUS1			0x8
+#define Q_REG_STATUS1_VAL_MASK		0x1
+#define Q_REG_STATUS1_GPIO_EN_REV0_MASK	0x2
+#define Q_REG_STATUS1_GPIO_EN_MASK	0x80
+#define Q_REG_STATUS1_MPP_EN_MASK	0x80
+
+#define Q_NUM_CTL_REGS			0xD
+
+/* revision registers base address offsets */
+#define Q_REG_DIG_MINOR_REV		0x0
+#define Q_REG_DIG_MAJOR_REV		0x1
+#define Q_REG_ANA_MINOR_REV		0x2
+
+/* type registers base address offsets */
+#define Q_REG_TYPE			0x4
+#define Q_REG_SUBTYPE			0x5
+
+/* gpio peripheral type and subtype values */
+#define Q_GPIO_TYPE			0x10
+#define Q_GPIO_SUBTYPE_GPIO_4CH		0x1
+#define Q_GPIO_SUBTYPE_GPIOC_4CH	0x5
+#define Q_GPIO_SUBTYPE_GPIO_8CH		0x9
+#define Q_GPIO_SUBTYPE_GPIOC_8CH	0xD
+#define Q_GPIO_SUBTYPE_GPIO_LV		0x10
+#define Q_GPIO_SUBTYPE_GPIO_MV		0x11
+
+/* mpp peripheral type and subtype values */
+#define Q_MPP_TYPE				0x11
+#define Q_MPP_SUBTYPE_4CH_NO_ANA_OUT		0x3
+#define Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT	0x4
+#define Q_MPP_SUBTYPE_4CH_NO_SINK		0x5
+#define Q_MPP_SUBTYPE_ULT_4CH_NO_SINK		0x6
+#define Q_MPP_SUBTYPE_4CH_FULL_FUNC		0x7
+#define Q_MPP_SUBTYPE_8CH_FULL_FUNC		0xF
+
+/* control register base address offsets */
+#define Q_REG_MODE_CTL			0x40
+#define Q_REG_DIG_VIN_CTL		0x41
+#define Q_REG_DIG_PULL_CTL		0x42
+#define Q_REG_DIG_IN_CTL		0x43
+#define Q_REG_DIG_OUT_SRC_CTL		0x44
+#define Q_REG_DIG_OUT_CTL		0x45
+#define Q_REG_EN_CTL			0x46
+#define Q_REG_AOUT_CTL			0x48
+#define Q_REG_AIN_CTL			0x4A
+#define Q_REG_APASS_SEL_CTL		0x4A
+#define Q_REG_SINK_CTL			0x4C
+
+/* control register regs array indices */
+#define Q_REG_I_MODE_CTL		0
+#define Q_REG_I_DIG_VIN_CTL		1
+#define Q_REG_I_DIG_PULL_CTL		2
+#define Q_REG_I_DIG_IN_CTL		3
+#define Q_REG_I_DIG_OUT_SRC_CTL		4
+#define Q_REG_I_DIG_OUT_CTL		5
+#define Q_REG_I_EN_CTL			6
+#define Q_REG_I_AOUT_CTL		8
+#define Q_REG_I_APASS_SEL_CTL		10
+#define Q_REG_I_AIN_CTL			10
+#define Q_REG_I_SINK_CTL		12
+
+/* control reg: mode */
+#define Q_REG_OUT_INVERT_SHIFT		0
+#define Q_REG_OUT_INVERT_MASK		0x1
+#define Q_REG_SRC_SEL_SHIFT		1
+#define Q_REG_SRC_SEL_MASK		0xE
+#define Q_REG_MODE_SEL_SHIFT		4
+#define Q_REG_MODE_SEL_MASK		0x70
+#define Q_REG_LV_MV_MODE_SEL_SHIFT	0
+#define Q_REG_LV_MV_MODE_SEL_MASK	0x3
+
+/* control reg: dig_out_src (GPIO LV/MV only) */
+#define Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT 0
+#define Q_REG_DIG_OUT_SRC_SRC_SEL_MASK	0xF
+#define Q_REG_DIG_OUT_SRC_INVERT_SHIFT	7
+#define Q_REG_DIG_OUT_SRC_INVERT_MASK	0x80
+
+/* control reg: dig_vin */
+#define Q_REG_VIN_SHIFT			0
+#define Q_REG_VIN_MASK			0x7
+
+/* control reg: dig_pull */
+#define Q_REG_PULL_SHIFT		0
+#define Q_REG_PULL_MASK			0x7
+
+/* control reg: dig_out */
+#define Q_REG_OUT_STRENGTH_SHIFT	0
+#define Q_REG_OUT_STRENGTH_MASK		0x3
+#define Q_REG_OUT_TYPE_SHIFT		4
+#define Q_REG_OUT_TYPE_MASK		0x30
+
+/* control reg: dig_in_ctl */
+#define Q_REG_DTEST_SEL_SHIFT			0
+#define Q_REG_DTEST_SEL_MASK			0xF
+#define Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT		0
+#define Q_REG_LV_MV_DTEST_SEL_CFG_MASK		0x7
+#define Q_REG_LV_MV_DTEST_SEL_EN_SHIFT		7
+#define Q_REG_LV_MV_DTEST_SEL_EN_MASK		0x80
+
+/* control reg: en */
+#define Q_REG_MASTER_EN_SHIFT		7
+#define Q_REG_MASTER_EN_MASK		0x80
+
+/* control reg: ana_out */
+#define Q_REG_AOUT_REF_SHIFT		0
+#define Q_REG_AOUT_REF_MASK		0x7
+
+/* control reg: ana_in */
+#define Q_REG_AIN_ROUTE_SHIFT		0
+#define Q_REG_AIN_ROUTE_MASK		0x7
+
+/* control reg: sink */
+#define Q_REG_CS_OUT_SHIFT		0
+#define Q_REG_CS_OUT_MASK		0x7
+
+/* control ref: apass_sel */
+#define Q_REG_APASS_SEL_SHIFT		0
+#define Q_REG_APASS_SEL_MASK		0x3
+
+enum qpnp_pin_param_type {
+	Q_PIN_CFG_MODE,
+	Q_PIN_CFG_OUTPUT_TYPE,
+	Q_PIN_CFG_INVERT,
+	Q_PIN_CFG_PULL,
+	Q_PIN_CFG_VIN_SEL,
+	Q_PIN_CFG_OUT_STRENGTH,
+	Q_PIN_CFG_SRC_SEL,
+	Q_PIN_CFG_MASTER_EN,
+	Q_PIN_CFG_AOUT_REF,
+	Q_PIN_CFG_AIN_ROUTE,
+	Q_PIN_CFG_CS_OUT,
+	Q_PIN_CFG_APASS_SEL,
+	Q_PIN_CFG_DTEST_SEL,
+	Q_PIN_CFG_INVALID,
+};
+
+#define Q_NUM_PARAMS			Q_PIN_CFG_INVALID
+
+/* param error checking */
+#define QPNP_PIN_GPIO_MODE_INVALID		3
+#define QPNP_PIN_GPIO_LV_MV_MODE_INVALID	4
+#define QPNP_PIN_MPP_MODE_INVALID		7
+#define QPNP_PIN_INVERT_INVALID			2
+#define QPNP_PIN_OUT_BUF_INVALID		3
+#define QPNP_PIN_GPIO_LV_MV_OUT_BUF_INVALID	4
+#define QPNP_PIN_VIN_4CH_INVALID		5
+#define QPNP_PIN_VIN_8CH_INVALID		8
+#define QPNP_PIN_GPIO_LV_VIN_INVALID		1
+#define QPNP_PIN_GPIO_MV_VIN_INVALID		2
+#define QPNP_PIN_GPIO_PULL_INVALID		6
+#define QPNP_PIN_MPP_PULL_INVALID		4
+#define QPNP_PIN_OUT_STRENGTH_INVALID		4
+#define QPNP_PIN_SRC_INVALID			8
+#define QPNP_PIN_GPIO_LV_MV_SRC_INVALID		16
+#define QPNP_PIN_MASTER_INVALID			2
+#define QPNP_PIN_AOUT_REF_INVALID		8
+#define QPNP_PIN_AIN_ROUTE_INVALID		8
+#define QPNP_PIN_CS_OUT_INVALID			8
+#define QPNP_PIN_APASS_SEL_INVALID		4
+#define QPNP_PIN_DTEST_SEL_INVALID		4
+
+struct qpnp_pin_spec {
+	uint8_t slave;			/* 0-15 */
+	uint16_t offset;		/* 0-255 */
+	uint32_t gpio_chip_idx;		/* offset from gpio_chip base */
+	uint32_t pmic_pin;		/* PMIC pin number */
+	int irq;			/* logical IRQ number */
+	u8 regs[Q_NUM_CTL_REGS];	/* Control regs */
+	u8 num_ctl_regs;		/* usable number on this pin */
+	u8 type;			/* peripheral type */
+	u8 subtype;			/* peripheral subtype */
+	u8 dig_major_rev;
+	struct device_node *node;
+	enum qpnp_pin_param_type params[Q_NUM_PARAMS];
+	struct qpnp_pin_chip *q_chip;
+};
+
+struct qpnp_pin_chip {
+	struct gpio_chip	gpio_chip;
+	struct platform_device	*pdev;
+	struct regmap		*regmap;
+	struct qpnp_pin_spec	**pmic_pins;
+	struct qpnp_pin_spec	**chip_gpios;
+	uint32_t		pmic_pin_lowest;
+	uint32_t		pmic_pin_highest;
+	struct device_node	*int_ctrl;
+	struct list_head	chip_list;
+	struct dentry		*dfs_dir;
+	bool			chip_registered;
+};
+
+static LIST_HEAD(qpnp_pin_chips);
+static DEFINE_MUTEX(qpnp_pin_chips_lock);
+
+static inline void qpnp_pmic_pin_set_spec(struct qpnp_pin_chip *q_chip,
+					      uint32_t pmic_pin,
+					      struct qpnp_pin_spec *spec)
+{
+	q_chip->pmic_pins[pmic_pin - q_chip->pmic_pin_lowest] = spec;
+}
+
+static inline struct qpnp_pin_spec *qpnp_pmic_pin_get_spec(
+						struct qpnp_pin_chip *q_chip,
+						uint32_t pmic_pin)
+{
+	if (pmic_pin < q_chip->pmic_pin_lowest ||
+	    pmic_pin > q_chip->pmic_pin_highest)
+		return NULL;
+
+	return q_chip->pmic_pins[pmic_pin - q_chip->pmic_pin_lowest];
+}
+
+static inline struct qpnp_pin_spec *qpnp_chip_gpio_get_spec(
+						struct qpnp_pin_chip *q_chip,
+						uint32_t chip_gpio)
+{
+	if (chip_gpio >= q_chip->gpio_chip.ngpio)
+		return NULL;
+
+	return q_chip->chip_gpios[chip_gpio];
+}
+
+static inline void qpnp_chip_gpio_set_spec(struct qpnp_pin_chip *q_chip,
+					      uint32_t chip_gpio,
+					      struct qpnp_pin_spec *spec)
+{
+	q_chip->chip_gpios[chip_gpio] = spec;
+}
+
+static bool is_gpio_lv_mv(struct qpnp_pin_spec *q_spec)
+{
+	if ((q_spec->type == Q_GPIO_TYPE) &&
+		(q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_LV ||
+		q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_MV))
+		return true;
+
+	return false;
+}
+
+/*
+ * Determines whether a specified param's configuration is correct.
+ * This check is two tier. First a check is done whether the hardware
+ * supports this param and value requested. The second check validates
+ * that the configuration is correct, given the fact that the hardware
+ * supports it.
+ *
+ * Returns
+ *	-ENXIO is the hardware does not support this param.
+ *	-EINVAL if the the hardware does support this param, but the
+ *	requested value is outside the supported range.
+ */
+static int qpnp_pin_check_config(enum qpnp_pin_param_type idx,
+				 struct qpnp_pin_spec *q_spec, uint32_t val)
+{
+	u8 subtype = q_spec->subtype;
+
+	switch (idx) {
+	case Q_PIN_CFG_MODE:
+		if (q_spec->type == Q_GPIO_TYPE) {
+			if (is_gpio_lv_mv(q_spec)) {
+				if (val >= QPNP_PIN_GPIO_LV_MV_MODE_INVALID)
+					return -EINVAL;
+			} else if (val >= QPNP_PIN_GPIO_MODE_INVALID) {
+				return -EINVAL;
+			}
+		} else if (q_spec->type == Q_MPP_TYPE) {
+			if (val >= QPNP_PIN_MPP_MODE_INVALID)
+				return -EINVAL;
+			if ((subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT ||
+			     subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK) &&
+			     (val == QPNP_PIN_MODE_BIDIR))
+				return -ENXIO;
+		}
+		break;
+	case Q_PIN_CFG_OUTPUT_TYPE:
+		if (q_spec->type != Q_GPIO_TYPE)
+			return -ENXIO;
+		if ((val == QPNP_PIN_OUT_BUF_OPEN_DRAIN_NMOS ||
+		    val == QPNP_PIN_OUT_BUF_OPEN_DRAIN_PMOS) &&
+		    (subtype == Q_GPIO_SUBTYPE_GPIOC_4CH ||
+		    (subtype == Q_GPIO_SUBTYPE_GPIOC_8CH)))
+			return -EINVAL;
+		else if (is_gpio_lv_mv(q_spec) &&
+			val >= QPNP_PIN_GPIO_LV_MV_OUT_BUF_INVALID)
+			return -EINVAL;
+		else if (val >= QPNP_PIN_OUT_BUF_INVALID)
+			return -EINVAL;
+		break;
+	case Q_PIN_CFG_INVERT:
+		if (val >= QPNP_PIN_INVERT_INVALID)
+			return -EINVAL;
+		break;
+	case Q_PIN_CFG_PULL:
+		if (q_spec->type == Q_GPIO_TYPE &&
+		    val >= QPNP_PIN_GPIO_PULL_INVALID)
+			return -EINVAL;
+		if (q_spec->type == Q_MPP_TYPE) {
+			if (val >= QPNP_PIN_MPP_PULL_INVALID)
+				return -EINVAL;
+			if (subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT ||
+			    subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK)
+				return -ENXIO;
+		}
+		break;
+	case Q_PIN_CFG_VIN_SEL:
+		if (is_gpio_lv_mv(q_spec)) {
+			if (subtype == Q_GPIO_SUBTYPE_GPIO_LV) {
+				if (val >= QPNP_PIN_GPIO_LV_VIN_INVALID)
+					return -EINVAL;
+			} else {
+				if (val >= QPNP_PIN_GPIO_MV_VIN_INVALID)
+					return -EINVAL;
+			}
+		} else if (val >= QPNP_PIN_VIN_8CH_INVALID) {
+			return -EINVAL;
+		} else if (val >= QPNP_PIN_VIN_4CH_INVALID) {
+			if (q_spec->type == Q_GPIO_TYPE &&
+			   (subtype == Q_GPIO_SUBTYPE_GPIO_4CH ||
+			    subtype == Q_GPIO_SUBTYPE_GPIOC_4CH))
+				return -EINVAL;
+			if (q_spec->type == Q_MPP_TYPE &&
+			   (subtype == Q_MPP_SUBTYPE_4CH_NO_ANA_OUT ||
+			    subtype == Q_MPP_SUBTYPE_4CH_NO_SINK ||
+			    subtype == Q_MPP_SUBTYPE_4CH_FULL_FUNC ||
+			    subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT ||
+			    subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK))
+				return -EINVAL;
+		}
+		break;
+	case Q_PIN_CFG_OUT_STRENGTH:
+		if (q_spec->type != Q_GPIO_TYPE)
+			return -ENXIO;
+		if (val >= QPNP_PIN_OUT_STRENGTH_INVALID ||
+		    val == 0)
+			return -EINVAL;
+		break;
+	case Q_PIN_CFG_SRC_SEL:
+		if (q_spec->type == Q_MPP_TYPE &&
+		    (val == QPNP_PIN_SEL_FUNC_1 ||
+		     val == QPNP_PIN_SEL_FUNC_2))
+			return -EINVAL;
+		if (is_gpio_lv_mv(q_spec)) {
+			if (val >= QPNP_PIN_GPIO_LV_MV_SRC_INVALID)
+				return -EINVAL;
+		} else if (val >= QPNP_PIN_SRC_INVALID) {
+			return -EINVAL;
+		}
+		break;
+	case Q_PIN_CFG_MASTER_EN:
+		if (val >= QPNP_PIN_MASTER_INVALID)
+			return -EINVAL;
+		break;
+	case Q_PIN_CFG_AOUT_REF:
+		if (q_spec->type != Q_MPP_TYPE)
+			return -ENXIO;
+		if (subtype == Q_MPP_SUBTYPE_4CH_NO_ANA_OUT ||
+		    subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT)
+			return -ENXIO;
+		if (val >= QPNP_PIN_AOUT_REF_INVALID)
+			return -EINVAL;
+		break;
+	case Q_PIN_CFG_AIN_ROUTE:
+		if (q_spec->type != Q_MPP_TYPE)
+			return -ENXIO;
+		if (val >= QPNP_PIN_AIN_ROUTE_INVALID)
+			return -EINVAL;
+		break;
+	case Q_PIN_CFG_CS_OUT:
+		if (q_spec->type != Q_MPP_TYPE)
+			return -ENXIO;
+		if (subtype == Q_MPP_SUBTYPE_4CH_NO_SINK ||
+		    subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK)
+			return -ENXIO;
+		if (val >= QPNP_PIN_CS_OUT_INVALID)
+			return -EINVAL;
+		break;
+	case Q_PIN_CFG_APASS_SEL:
+		if (!is_gpio_lv_mv(q_spec))
+			return -ENXIO;
+		if (val >= QPNP_PIN_APASS_SEL_INVALID)
+			return -EINVAL;
+		break;
+	case Q_PIN_CFG_DTEST_SEL:
+		if (val > QPNP_PIN_DTEST_SEL_INVALID)
+			return -EINVAL;
+		break;
+	default:
+		pr_err("invalid param type %u specified\n", idx);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+#define Q_CHK_INVALID(idx, q_spec, val) \
+	(qpnp_pin_check_config(idx, q_spec, val) == -EINVAL)
+
+static int qpnp_pin_check_constraints(struct qpnp_pin_spec *q_spec,
+				      struct qpnp_pin_cfg *param)
+{
+	int pin = q_spec->pmic_pin;
+	const char *name;
+
+	name = (q_spec->type == Q_GPIO_TYPE) ? "gpio" : "mpp";
+
+	if (Q_CHK_INVALID(Q_PIN_CFG_MODE, q_spec, param->mode))
+		pr_err("invalid direction value %d for %s %d\n",
+						param->mode, name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_INVERT, q_spec, param->invert))
+		pr_err("invalid invert polarity value %d for %s %d\n",
+						param->invert,  name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_SRC_SEL, q_spec, param->src_sel))
+		pr_err("invalid source select value %d for %s %d\n",
+						param->src_sel, name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_OUT_STRENGTH,
+						q_spec, param->out_strength))
+		pr_err("invalid out strength value %d for %s %d\n",
+					param->out_strength,  name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_OUTPUT_TYPE,
+						 q_spec, param->output_type))
+		pr_err("invalid out type value %d for %s %d\n",
+					param->output_type,  name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_VIN_SEL, q_spec, param->vin_sel))
+		pr_err("invalid vin select %d value for %s %d\n",
+						param->vin_sel, name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_PULL, q_spec, param->pull))
+		pr_err("invalid pull value %d for pin %s %d\n",
+						param->pull,  name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_MASTER_EN, q_spec, param->master_en))
+		pr_err("invalid master_en value %d for %s %d\n",
+						param->master_en, name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_AOUT_REF, q_spec, param->aout_ref))
+		pr_err("invalid aout_reg value %d for %s %d\n",
+						param->aout_ref, name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_AIN_ROUTE, q_spec, param->ain_route))
+		pr_err("invalid ain_route value %d for %s %d\n",
+						param->ain_route, name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_CS_OUT, q_spec, param->cs_out))
+		pr_err("invalid cs_out value %d for %s %d\n",
+						param->cs_out, name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_APASS_SEL, q_spec, param->apass_sel))
+		pr_err("invalid apass_sel value %d for %s %d\n",
+						param->apass_sel, name, pin);
+	else if (Q_CHK_INVALID(Q_PIN_CFG_DTEST_SEL, q_spec, param->dtest_sel))
+		pr_err("invalid dtest_sel value %d for %s %d\n",
+					param->dtest_sel, name, pin);
+	else
+		return 0;
+
+	return -EINVAL;
+}
+
+static inline u8 q_reg_get(u8 *reg, int shift, int mask)
+{
+	return (*reg & mask) >> shift;
+}
+
+static inline void q_reg_set(u8 *reg, int shift, int mask, int value)
+{
+	*reg |= (value << shift) & mask;
+}
+
+static inline void q_reg_clr_set(u8 *reg, int shift, int mask, int value)
+{
+	*reg &= ~mask;
+	*reg |= (value << shift) & mask;
+}
+
+/*
+ * Calculate the minimum number of registers that must be read / written
+ * in order to satisfy the full feature set of the given pin.
+ */
+static int qpnp_pin_ctl_regs_init(struct qpnp_pin_spec *q_spec)
+{
+	if (q_spec->type == Q_GPIO_TYPE) {
+		if (is_gpio_lv_mv(q_spec))
+			q_spec->num_ctl_regs = 11;
+		else
+			q_spec->num_ctl_regs = 7;
+	} else if (q_spec->type == Q_MPP_TYPE) {
+		switch (q_spec->subtype) {
+		case Q_MPP_SUBTYPE_4CH_NO_SINK:
+		case Q_MPP_SUBTYPE_ULT_4CH_NO_SINK:
+			q_spec->num_ctl_regs = 12;
+			break;
+		case Q_MPP_SUBTYPE_4CH_NO_ANA_OUT:
+		case Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT:
+		case Q_MPP_SUBTYPE_4CH_FULL_FUNC:
+		case Q_MPP_SUBTYPE_8CH_FULL_FUNC:
+			q_spec->num_ctl_regs = 13;
+			break;
+		default:
+			pr_err("Invalid MPP subtype 0x%x\n", q_spec->subtype);
+			return -EINVAL;
+		}
+	} else {
+		pr_err("Invalid type 0x%x\n", q_spec->type);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int qpnp_pin_read_regs(struct qpnp_pin_chip *q_chip,
+			      struct qpnp_pin_spec *q_spec)
+{
+	int bytes_left = q_spec->num_ctl_regs;
+	int rc;
+	char *buf_p = &q_spec->regs[0];
+	u16 reg_addr = Q_REG_ADDR(q_spec, Q_REG_MODE_CTL);
+
+	while (bytes_left > 0) {
+		rc = regmap_bulk_read(q_chip->regmap, reg_addr, buf_p,
+				      bytes_left < 8 ? bytes_left : 8);
+		if (rc)
+			return rc;
+		bytes_left -= 8;
+		buf_p += 8;
+		reg_addr += 8;
+	}
+	return 0;
+}
+
+static int qpnp_pin_write_regs(struct qpnp_pin_chip *q_chip,
+			       struct qpnp_pin_spec *q_spec)
+{
+	int bytes_left = q_spec->num_ctl_regs;
+	int rc;
+	char *buf_p = &q_spec->regs[0];
+	u16 reg_addr = Q_REG_ADDR(q_spec, Q_REG_MODE_CTL);
+
+	while (bytes_left > 0) {
+		rc = regmap_bulk_write(q_chip->regmap, reg_addr, buf_p,
+				       bytes_left < 8 ? bytes_left : 8);
+		if (rc)
+			return rc;
+		bytes_left -= 8;
+		buf_p += 8;
+		reg_addr += 8;
+	}
+	return 0;
+}
+
+static int qpnp_pin_cache_regs(struct qpnp_pin_chip *q_chip,
+			       struct qpnp_pin_spec *q_spec)
+{
+	int rc;
+	struct device *dev = &q_chip->pdev->dev;
+
+	rc = qpnp_pin_read_regs(q_chip, q_spec);
+	if (rc)
+		dev_err(dev, "%s: unable to read control regs\n", __func__);
+
+	return rc;
+}
+
+#define Q_HAVE_HW_SP(idx, q_spec, val) \
+	(qpnp_pin_check_config(idx, q_spec, val) == 0)
+
+static int _qpnp_pin_config(struct qpnp_pin_chip *q_chip,
+			    struct qpnp_pin_spec *q_spec,
+			    struct qpnp_pin_cfg *param)
+{
+	struct device *dev = &q_chip->pdev->dev;
+	int rc;
+	u8 shift, mask, *reg;
+
+	rc = qpnp_pin_check_constraints(q_spec, param);
+	if (rc)
+		goto gpio_cfg;
+
+	/* set mode */
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_MODE, q_spec, param->mode)) {
+		if (is_gpio_lv_mv(q_spec)) {
+			shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+			mask = Q_REG_LV_MV_MODE_SEL_MASK;
+		} else {
+			shift = Q_REG_MODE_SEL_SHIFT;
+			mask = Q_REG_MODE_SEL_MASK;
+		}
+		q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL],
+			shift, mask, param->mode);
+	}
+
+	/* output specific configuration */
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_INVERT, q_spec, param->invert)) {
+		if (is_gpio_lv_mv(q_spec)) {
+			shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+			mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+			reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+		} else {
+			shift = Q_REG_OUT_INVERT_SHIFT;
+			mask = Q_REG_OUT_INVERT_MASK;
+			reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+		}
+		q_reg_clr_set(reg, shift, mask, param->invert);
+	}
+
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_SRC_SEL, q_spec, param->src_sel)) {
+		if (is_gpio_lv_mv(q_spec)) {
+			shift = Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT;
+			mask = Q_REG_DIG_OUT_SRC_SRC_SEL_MASK;
+			reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+		} else {
+			shift = Q_REG_SRC_SEL_SHIFT;
+			mask = Q_REG_SRC_SEL_MASK;
+			reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+		}
+		q_reg_clr_set(reg, shift, mask, param->src_sel);
+	}
+
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_OUT_STRENGTH, q_spec, param->out_strength))
+		q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+			  Q_REG_OUT_STRENGTH_SHIFT, Q_REG_OUT_STRENGTH_MASK,
+			  param->out_strength);
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_OUTPUT_TYPE, q_spec, param->output_type))
+		q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+			  Q_REG_OUT_TYPE_SHIFT, Q_REG_OUT_TYPE_MASK,
+			  param->output_type);
+
+	/* input config */
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_DTEST_SEL, q_spec, param->dtest_sel)
+			&& param->dtest_sel) {
+		if (is_gpio_lv_mv(q_spec)) {
+			q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+					Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT,
+					Q_REG_LV_MV_DTEST_SEL_CFG_MASK,
+					param->dtest_sel - 1);
+			q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+					Q_REG_LV_MV_DTEST_SEL_EN_SHIFT,
+					Q_REG_LV_MV_DTEST_SEL_EN_MASK, 0x1);
+		} else {
+			q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+					Q_REG_DTEST_SEL_SHIFT,
+					Q_REG_DTEST_SEL_MASK,
+					BIT(param->dtest_sel - 1));
+		}
+	}
+
+	/* config applicable for both input / output */
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_VIN_SEL, q_spec, param->vin_sel))
+		q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_VIN_CTL],
+			  Q_REG_VIN_SHIFT, Q_REG_VIN_MASK,
+			  param->vin_sel);
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_PULL, q_spec, param->pull))
+		q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_PULL_CTL],
+			  Q_REG_PULL_SHIFT, Q_REG_PULL_MASK,
+			  param->pull);
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_MASTER_EN, q_spec, param->master_en))
+		q_reg_clr_set(&q_spec->regs[Q_REG_I_EN_CTL],
+			  Q_REG_MASTER_EN_SHIFT, Q_REG_MASTER_EN_MASK,
+			  param->master_en);
+
+	/* mpp specific config */
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_AOUT_REF, q_spec, param->aout_ref))
+		q_reg_clr_set(&q_spec->regs[Q_REG_I_AOUT_CTL],
+			  Q_REG_AOUT_REF_SHIFT, Q_REG_AOUT_REF_MASK,
+			  param->aout_ref);
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_AIN_ROUTE, q_spec, param->ain_route))
+		q_reg_clr_set(&q_spec->regs[Q_REG_I_AIN_CTL],
+			  Q_REG_AIN_ROUTE_SHIFT, Q_REG_AIN_ROUTE_MASK,
+			  param->ain_route);
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_CS_OUT, q_spec, param->cs_out))
+		q_reg_clr_set(&q_spec->regs[Q_REG_I_SINK_CTL],
+			  Q_REG_CS_OUT_SHIFT, Q_REG_CS_OUT_MASK,
+			  param->cs_out);
+	if (Q_HAVE_HW_SP(Q_PIN_CFG_APASS_SEL, q_spec, param->apass_sel))
+		q_reg_clr_set(&q_spec->regs[Q_REG_I_APASS_SEL_CTL],
+			  Q_REG_APASS_SEL_SHIFT, Q_REG_APASS_SEL_MASK,
+			  param->apass_sel);
+
+	rc = qpnp_pin_write_regs(q_chip, q_spec);
+	if (rc) {
+		dev_err(&q_chip->pdev->dev,
+			"%s: unable to write master enable\n",
+								__func__);
+		goto gpio_cfg;
+	}
+
+	return 0;
+
+gpio_cfg:
+	dev_err(dev, "%s: unable to set default config for pmic pin %d\n",
+						__func__, q_spec->pmic_pin);
+
+	return rc;
+}
+
+int qpnp_pin_config(int gpio, struct qpnp_pin_cfg *param)
+{
+	int rc, chip_offset;
+	struct qpnp_pin_chip *q_chip;
+	struct qpnp_pin_spec *q_spec = NULL;
+	struct gpio_chip *gpio_chip;
+
+	if (param == NULL)
+		return -EINVAL;
+
+	mutex_lock(&qpnp_pin_chips_lock);
+	list_for_each_entry(q_chip, &qpnp_pin_chips, chip_list) {
+		gpio_chip = &q_chip->gpio_chip;
+		if (gpio >= gpio_chip->base
+				&& gpio < gpio_chip->base + gpio_chip->ngpio) {
+			chip_offset = gpio - gpio_chip->base;
+			q_spec = qpnp_chip_gpio_get_spec(q_chip, chip_offset);
+			if (WARN_ON(!q_spec)) {
+				mutex_unlock(&qpnp_pin_chips_lock);
+				return -ENODEV;
+			}
+			break;
+		}
+	}
+	mutex_unlock(&qpnp_pin_chips_lock);
+
+	if (!q_spec)
+		return -ENODEV;
+
+	rc = _qpnp_pin_config(q_chip, q_spec, param);
+
+	return rc;
+}
+EXPORT_SYMBOL(qpnp_pin_config);
+
+int qpnp_pin_map(const char *name, uint32_t pmic_pin)
+{
+	struct qpnp_pin_chip *q_chip;
+	struct qpnp_pin_spec *q_spec = NULL;
+
+	if (!name)
+		return -EINVAL;
+
+	mutex_lock(&qpnp_pin_chips_lock);
+	list_for_each_entry(q_chip, &qpnp_pin_chips, chip_list) {
+		if (strcmp(q_chip->gpio_chip.label, name) != 0)
+			continue;
+		if (q_chip->pmic_pin_lowest <= pmic_pin &&
+		    q_chip->pmic_pin_highest >= pmic_pin) {
+			q_spec = qpnp_pmic_pin_get_spec(q_chip, pmic_pin);
+			mutex_unlock(&qpnp_pin_chips_lock);
+			if (WARN_ON(!q_spec))
+				return -ENODEV;
+			return q_chip->gpio_chip.base + q_spec->gpio_chip_idx;
+		}
+	}
+	mutex_unlock(&qpnp_pin_chips_lock);
+	return -EINVAL;
+}
+EXPORT_SYMBOL(qpnp_pin_map);
+
+static int qpnp_pin_to_irq(struct gpio_chip *gpio_chip, unsigned int offset)
+{
+	struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+	struct qpnp_pin_spec *q_spec;
+	struct of_phandle_args oirq;
+
+	q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+	if (!q_spec)
+		return -EINVAL;
+
+	/* if we have mapped this pin previously return the virq */
+	if (q_spec->irq)
+		return q_spec->irq;
+
+	/* call into irq_domain to get irq mapping */
+	oirq.np = q_chip->int_ctrl;
+	oirq.args[0] = to_spmi_device(q_chip->pdev->dev.parent)->usid;
+	oirq.args[1] = (q_spec->offset >> 8) & 0xFF;
+	oirq.args[2] = 0;
+	oirq.args[3] = IRQ_TYPE_NONE;
+	oirq.args_count = 4;
+
+	q_spec->irq = irq_create_of_mapping(&oirq);
+	if (!q_spec->irq) {
+		dev_err(&q_chip->pdev->dev, "%s: invalid irq for gpio %u\n",
+						__func__, q_spec->pmic_pin);
+		WARN_ON(1);
+		return -EINVAL;
+	}
+
+	return q_spec->irq;
+}
+
+static int qpnp_pin_get(struct gpio_chip *gpio_chip, unsigned int offset)
+{
+	struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+	struct qpnp_pin_spec *q_spec = NULL;
+	u8 buf, en_mask, shift, mask, reg;
+	unsigned int val;
+	int rc;
+
+	if (WARN_ON(!q_chip))
+		return -ENODEV;
+
+	q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+	if (WARN_ON(!q_spec))
+		return -ENODEV;
+
+	if (is_gpio_lv_mv(q_spec)) {
+		mask = Q_REG_LV_MV_MODE_SEL_MASK;
+		shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+	} else {
+		mask = Q_REG_MODE_SEL_MASK;
+		shift = Q_REG_MODE_SEL_SHIFT;
+	}
+
+	/* gpio val is from RT status iff input is enabled */
+	if (q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], shift, mask)
+					== QPNP_PIN_MODE_DIG_IN) {
+		rc = regmap_read(q_chip->regmap,
+				 Q_REG_ADDR(q_spec, Q_REG_STATUS1), &val);
+		if (rc)
+			return rc;
+		buf = val;
+
+		if (q_spec->type == Q_GPIO_TYPE && q_spec->dig_major_rev == 0)
+			en_mask = Q_REG_STATUS1_GPIO_EN_REV0_MASK;
+		else if (q_spec->type == Q_GPIO_TYPE &&
+			 q_spec->dig_major_rev > 0)
+			en_mask = Q_REG_STATUS1_GPIO_EN_MASK;
+		else /* MPP */
+			en_mask = Q_REG_STATUS1_MPP_EN_MASK;
+
+		if (!(buf & en_mask))
+			return -EPERM;
+
+		return buf & Q_REG_STATUS1_VAL_MASK;
+	}
+
+	if (is_gpio_lv_mv(q_spec)) {
+		shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+		mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+		reg = q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+	} else {
+		shift = Q_REG_OUT_INVERT_SHIFT;
+		mask = Q_REG_OUT_INVERT_MASK;
+		reg = q_spec->regs[Q_REG_I_MODE_CTL];
+	}
+
+	return (reg & mask) >> shift;
+}
+
+static int __qpnp_pin_set(struct qpnp_pin_chip *q_chip,
+			   struct qpnp_pin_spec *q_spec, int value)
+{
+	int rc;
+	u8 shift, mask, *reg;
+	u16 address;
+
+	if (!q_chip || !q_spec)
+		return -EINVAL;
+
+	if (is_gpio_lv_mv(q_spec)) {
+		shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+		mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+		reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+		address = Q_REG_ADDR(q_spec, Q_REG_DIG_OUT_SRC_CTL);
+	} else {
+		shift = Q_REG_OUT_INVERT_SHIFT;
+		mask = Q_REG_OUT_INVERT_MASK;
+		reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+		address = Q_REG_ADDR(q_spec, Q_REG_MODE_CTL);
+	}
+
+	q_reg_clr_set(reg, shift, mask, !!value);
+
+	rc = regmap_write(q_chip->regmap, address, *reg);
+	if (rc)
+		dev_err(&q_chip->pdev->dev, "%s: spmi write failed\n",
+								__func__);
+	return rc;
+}
+
+
+static void qpnp_pin_set(struct gpio_chip *gpio_chip,
+		unsigned int offset, int value)
+{
+	struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+	struct qpnp_pin_spec *q_spec;
+
+	if (WARN_ON(!q_chip))
+		return;
+
+	q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+	if (WARN_ON(!q_spec))
+		return;
+
+	__qpnp_pin_set(q_chip, q_spec, value);
+}
+
+static int qpnp_pin_set_mode(struct qpnp_pin_chip *q_chip,
+				   struct qpnp_pin_spec *q_spec, int mode)
+{
+	int rc;
+	u8 shift, mask;
+
+	if (!q_chip || !q_spec)
+		return -EINVAL;
+
+	if (qpnp_pin_check_config(Q_PIN_CFG_MODE, q_spec, mode)) {
+		pr_err("invalid mode specification %d\n", mode);
+		return -EINVAL;
+	}
+
+	if (is_gpio_lv_mv(q_spec)) {
+		shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+		mask = Q_REG_LV_MV_MODE_SEL_MASK;
+	} else {
+		shift = Q_REG_MODE_SEL_SHIFT;
+		mask = Q_REG_MODE_SEL_MASK;
+	}
+
+	q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], shift, mask, mode);
+
+	rc = regmap_write(q_chip->regmap, Q_REG_ADDR(q_spec, Q_REG_MODE_CTL),
+			  *&q_spec->regs[Q_REG_I_MODE_CTL]);
+	return rc;
+}
+
+static int qpnp_pin_direction_input(struct gpio_chip *gpio_chip,
+		unsigned int offset)
+{
+	struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+	struct qpnp_pin_spec *q_spec;
+
+	if (WARN_ON(!q_chip))
+		return -ENODEV;
+
+	q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+	if (WARN_ON(!q_spec))
+		return -ENODEV;
+
+	return qpnp_pin_set_mode(q_chip, q_spec, QPNP_PIN_MODE_DIG_IN);
+}
+
+static int qpnp_pin_direction_output(struct gpio_chip *gpio_chip,
+		unsigned int offset, int val)
+{
+	int rc;
+	struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+	struct qpnp_pin_spec *q_spec;
+
+	if (WARN_ON(!q_chip))
+		return -ENODEV;
+
+	q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+	if (WARN_ON(!q_spec))
+		return -ENODEV;
+
+	rc = __qpnp_pin_set(q_chip, q_spec, val);
+	if (rc)
+		return rc;
+
+	rc = qpnp_pin_set_mode(q_chip, q_spec, QPNP_PIN_MODE_DIG_OUT);
+
+	return rc;
+}
+
+static int qpnp_pin_of_gpio_xlate(struct gpio_chip *gpio_chip,
+				   const struct of_phandle_args *gpio_spec,
+				   u32 *flags)
+{
+	struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+	struct qpnp_pin_spec *q_spec;
+
+	if (WARN_ON(gpio_chip->of_gpio_n_cells < 2)) {
+		pr_err("of_gpio_n_cells < 2\n");
+		return -EINVAL;
+	}
+
+	q_spec = qpnp_pmic_pin_get_spec(q_chip, gpio_spec->args[0]);
+	if (!q_spec) {
+		pr_err("no such PMIC gpio %u in device topology\n",
+							gpio_spec->args[0]);
+		return -EINVAL;
+	}
+
+	if (flags)
+		*flags = gpio_spec->args[1];
+
+	return q_spec->gpio_chip_idx;
+}
+
+static int qpnp_pin_apply_config(struct qpnp_pin_chip *q_chip,
+				  struct qpnp_pin_spec *q_spec)
+{
+	struct qpnp_pin_cfg param;
+	struct device_node *node = q_spec->node;
+	int rc;
+	u8 shift, mask, *reg;
+
+	if (is_gpio_lv_mv(q_spec)) {
+		shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+		mask = Q_REG_LV_MV_MODE_SEL_MASK;
+	} else {
+		shift = Q_REG_MODE_SEL_SHIFT;
+		mask = Q_REG_MODE_SEL_MASK;
+	}
+	param.mode	   = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL],
+							shift, mask);
+
+	param.output_type  = q_reg_get(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+				       Q_REG_OUT_TYPE_SHIFT,
+				       Q_REG_OUT_TYPE_MASK);
+
+	if (is_gpio_lv_mv(q_spec)) {
+		shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+		mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+		reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+	} else {
+		shift = Q_REG_OUT_INVERT_SHIFT;
+		mask = Q_REG_OUT_INVERT_MASK;
+		reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+	}
+	param.invert	   = q_reg_get(reg, shift, mask);
+
+	param.pull	   = q_reg_get(&q_spec->regs[Q_REG_I_DIG_PULL_CTL],
+				       Q_REG_PULL_SHIFT, Q_REG_PULL_MASK);
+	param.vin_sel	   = q_reg_get(&q_spec->regs[Q_REG_I_DIG_VIN_CTL],
+				       Q_REG_VIN_SHIFT, Q_REG_VIN_MASK);
+	param.out_strength = q_reg_get(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+				       Q_REG_OUT_STRENGTH_SHIFT,
+				       Q_REG_OUT_STRENGTH_MASK);
+
+	if (is_gpio_lv_mv(q_spec)) {
+		shift = Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT;
+		mask = Q_REG_DIG_OUT_SRC_SRC_SEL_MASK;
+		reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+	} else {
+		shift = Q_REG_SRC_SEL_SHIFT;
+		mask = Q_REG_SRC_SEL_MASK;
+		reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+	}
+	param.src_sel   = q_reg_get(reg, shift, mask);
+
+	param.master_en    = q_reg_get(&q_spec->regs[Q_REG_I_EN_CTL],
+				       Q_REG_MASTER_EN_SHIFT,
+				       Q_REG_MASTER_EN_MASK);
+	param.aout_ref    = q_reg_get(&q_spec->regs[Q_REG_I_AOUT_CTL],
+				       Q_REG_AOUT_REF_SHIFT,
+				       Q_REG_AOUT_REF_MASK);
+	param.ain_route    = q_reg_get(&q_spec->regs[Q_REG_I_AIN_CTL],
+				       Q_REG_AIN_ROUTE_SHIFT,
+				       Q_REG_AIN_ROUTE_MASK);
+	param.cs_out    = q_reg_get(&q_spec->regs[Q_REG_I_SINK_CTL],
+				       Q_REG_CS_OUT_SHIFT,
+				       Q_REG_CS_OUT_MASK);
+	param.apass_sel    = q_reg_get(&q_spec->regs[Q_REG_I_APASS_SEL_CTL],
+				       Q_REG_APASS_SEL_SHIFT,
+				       Q_REG_APASS_SEL_MASK);
+	if (is_gpio_lv_mv(q_spec)) {
+		param.dtest_sel = q_reg_get(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+				Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT,
+				Q_REG_LV_MV_DTEST_SEL_CFG_MASK);
+	} else {
+		 param.dtest_sel = q_reg_get(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+				Q_REG_DTEST_SEL_SHIFT,
+				Q_REG_DTEST_SEL_MASK);
+	}
+
+	of_property_read_u32(node, "qcom,mode",
+		&param.mode);
+	of_property_read_u32(node, "qcom,output-type",
+		&param.output_type);
+	of_property_read_u32(node, "qcom,invert",
+		&param.invert);
+	of_property_read_u32(node, "qcom,pull",
+		&param.pull);
+	of_property_read_u32(node, "qcom,vin-sel",
+		&param.vin_sel);
+	of_property_read_u32(node, "qcom,out-strength",
+		&param.out_strength);
+	of_property_read_u32(node, "qcom,src-sel",
+		&param.src_sel);
+	of_property_read_u32(node, "qcom,master-en",
+		&param.master_en);
+	of_property_read_u32(node, "qcom,aout-ref",
+		&param.aout_ref);
+	of_property_read_u32(node, "qcom,ain-route",
+		&param.ain_route);
+	of_property_read_u32(node, "qcom,cs-out",
+		&param.cs_out);
+	of_property_read_u32(node, "qcom,apass-sel",
+		&param.apass_sel);
+	of_property_read_u32(node, "qcom,dtest-sel",
+		&param.dtest_sel);
+
+	rc = _qpnp_pin_config(q_chip, q_spec, &param);
+
+	return rc;
+}
+
+static int qpnp_pin_free_chip(struct qpnp_pin_chip *q_chip)
+{
+	int i, rc = 0;
+
+	if (q_chip->chip_gpios)
+		for (i = 0; i < q_chip->gpio_chip.ngpio; i++)
+			kfree(q_chip->chip_gpios[i]);
+
+	mutex_lock(&qpnp_pin_chips_lock);
+	list_del(&q_chip->chip_list);
+	mutex_unlock(&qpnp_pin_chips_lock);
+	if (q_chip->chip_registered)
+		gpiochip_remove(&q_chip->gpio_chip);
+
+	kfree(q_chip->chip_gpios);
+	kfree(q_chip->pmic_pins);
+	kfree(q_chip);
+	return rc;
+}
+
+#ifdef CONFIG_GPIO_QPNP_PIN_DEBUG
+struct qpnp_pin_reg {
+	uint32_t addr;
+	uint32_t idx;
+	uint32_t shift;
+	uint32_t mask;
+};
+
+static struct dentry *driver_dfs_dir;
+
+static int qpnp_pin_reg_attr(enum qpnp_pin_param_type type,
+			     struct qpnp_pin_reg *cfg,
+			struct qpnp_pin_spec *q_spec)
+{
+	switch (type) {
+	case Q_PIN_CFG_MODE:
+		if (is_gpio_lv_mv(q_spec)) {
+			cfg->shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+			cfg->mask = Q_REG_LV_MV_MODE_SEL_MASK;
+		} else {
+			cfg->shift = Q_REG_MODE_SEL_SHIFT;
+			cfg->mask = Q_REG_MODE_SEL_MASK;
+		}
+		cfg->addr = Q_REG_MODE_CTL;
+		cfg->idx = Q_REG_I_MODE_CTL;
+		break;
+	case Q_PIN_CFG_OUTPUT_TYPE:
+		cfg->addr = Q_REG_DIG_OUT_CTL;
+		cfg->idx = Q_REG_I_DIG_OUT_CTL;
+		cfg->shift = Q_REG_OUT_TYPE_SHIFT;
+		cfg->mask = Q_REG_OUT_TYPE_MASK;
+		break;
+	case Q_PIN_CFG_INVERT:
+		if (is_gpio_lv_mv(q_spec)) {
+			cfg->addr = Q_REG_DIG_OUT_SRC_CTL;
+			cfg->idx = Q_REG_I_DIG_OUT_SRC_CTL;
+			cfg->shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+			cfg->mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+		} else {
+			cfg->addr = Q_REG_MODE_CTL;
+			cfg->idx = Q_REG_I_MODE_CTL;
+			cfg->shift = Q_REG_OUT_INVERT_SHIFT;
+			cfg->mask = Q_REG_OUT_INVERT_MASK;
+		}
+		break;
+	case Q_PIN_CFG_PULL:
+		cfg->addr = Q_REG_DIG_PULL_CTL;
+		cfg->idx = Q_REG_I_DIG_PULL_CTL;
+		cfg->shift = Q_REG_PULL_SHIFT;
+		cfg->mask = Q_REG_PULL_MASK;
+		break;
+	case Q_PIN_CFG_VIN_SEL:
+		cfg->addr = Q_REG_DIG_VIN_CTL;
+		cfg->idx = Q_REG_I_DIG_VIN_CTL;
+		cfg->shift = Q_REG_VIN_SHIFT;
+		cfg->mask = Q_REG_VIN_MASK;
+		break;
+	case Q_PIN_CFG_OUT_STRENGTH:
+		cfg->addr = Q_REG_DIG_OUT_CTL;
+		cfg->idx = Q_REG_I_DIG_OUT_CTL;
+		cfg->shift = Q_REG_OUT_STRENGTH_SHIFT;
+		cfg->mask = Q_REG_OUT_STRENGTH_MASK;
+		break;
+	case Q_PIN_CFG_SRC_SEL:
+		if (is_gpio_lv_mv(q_spec)) {
+			cfg->addr = Q_REG_DIG_OUT_SRC_CTL;
+			cfg->idx = Q_REG_I_DIG_OUT_SRC_CTL;
+			cfg->shift = Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT;
+			cfg->mask = Q_REG_DIG_OUT_SRC_SRC_SEL_MASK;
+		} else {
+			cfg->addr = Q_REG_MODE_CTL;
+			cfg->idx = Q_REG_I_MODE_CTL;
+			cfg->shift = Q_REG_SRC_SEL_SHIFT;
+			cfg->mask = Q_REG_SRC_SEL_MASK;
+		}
+		break;
+	case Q_PIN_CFG_MASTER_EN:
+		cfg->addr = Q_REG_EN_CTL;
+		cfg->idx = Q_REG_I_EN_CTL;
+		cfg->shift = Q_REG_MASTER_EN_SHIFT;
+		cfg->mask = Q_REG_MASTER_EN_MASK;
+		break;
+	case Q_PIN_CFG_AOUT_REF:
+		cfg->addr = Q_REG_AOUT_CTL;
+		cfg->idx = Q_REG_I_AOUT_CTL;
+		cfg->shift = Q_REG_AOUT_REF_SHIFT;
+		cfg->mask = Q_REG_AOUT_REF_MASK;
+		break;
+	case Q_PIN_CFG_AIN_ROUTE:
+		cfg->addr = Q_REG_AIN_CTL;
+		cfg->idx = Q_REG_I_AIN_CTL;
+		cfg->shift = Q_REG_AIN_ROUTE_SHIFT;
+		cfg->mask = Q_REG_AIN_ROUTE_MASK;
+		break;
+	case Q_PIN_CFG_CS_OUT:
+		cfg->addr = Q_REG_SINK_CTL;
+		cfg->idx = Q_REG_I_SINK_CTL;
+		cfg->shift = Q_REG_CS_OUT_SHIFT;
+		cfg->mask = Q_REG_CS_OUT_MASK;
+		break;
+	case Q_PIN_CFG_APASS_SEL:
+		cfg->addr = Q_REG_APASS_SEL_CTL;
+		cfg->idx = Q_REG_I_APASS_SEL_CTL;
+		cfg->shift = Q_REG_APASS_SEL_SHIFT;
+		cfg->mask = Q_REG_APASS_SEL_MASK;
+		break;
+	case Q_PIN_CFG_DTEST_SEL:
+		if (is_gpio_lv_mv(q_spec)) {
+			cfg->shift = Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT;
+			cfg->mask = Q_REG_LV_MV_DTEST_SEL_CFG_MASK;
+		} else {
+			cfg->shift = Q_REG_DTEST_SEL_SHIFT;
+			cfg->mask = Q_REG_DTEST_SEL_MASK;
+		}
+		cfg->addr = Q_REG_DIG_IN_CTL;
+		cfg->idx = Q_REG_I_DIG_IN_CTL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int qpnp_pin_debugfs_get(void *data, u64 *val)
+{
+	enum qpnp_pin_param_type *idx = data;
+	struct qpnp_pin_spec *q_spec;
+	struct qpnp_pin_reg cfg = {};
+	int rc;
+
+	q_spec = container_of(idx, struct qpnp_pin_spec, params[*idx]);
+
+	rc = qpnp_pin_reg_attr(*idx, &cfg, q_spec);
+	if (rc)
+		return rc;
+
+	*val = q_reg_get(&q_spec->regs[cfg.idx], cfg.shift, cfg.mask);
+	return 0;
+}
+
+static int qpnp_pin_debugfs_set(void *data, u64 val)
+{
+	enum qpnp_pin_param_type *idx = data;
+	struct qpnp_pin_spec *q_spec;
+	struct qpnp_pin_chip *q_chip;
+	struct qpnp_pin_reg cfg = {};
+	int rc;
+
+	q_spec = container_of(idx, struct qpnp_pin_spec, params[*idx]);
+	q_chip = q_spec->q_chip;
+
+	/*
+	 * special handling for GPIO_LV/MV 'dtest-sel'
+	 * if (dtest_sel == 0) then disable dtest-sel
+	 * else enable and set dtest.
+	 */
+	if ((q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_LV ||
+		q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_MV) &&
+				*idx == Q_PIN_CFG_DTEST_SEL) {
+		/* enable/disable DTEST */
+		cfg.shift = Q_REG_LV_MV_DTEST_SEL_EN_SHIFT;
+		cfg.mask = Q_REG_LV_MV_DTEST_SEL_EN_MASK;
+		cfg.addr = Q_REG_DIG_IN_CTL;
+		cfg.idx = Q_REG_I_DIG_IN_CTL;
+		q_reg_clr_set(&q_spec->regs[cfg.idx],
+				cfg.shift, cfg.mask, !!val);
+	}
+
+	rc = qpnp_pin_check_config(*idx, q_spec, val);
+	if (rc)
+		return rc;
+
+	rc = qpnp_pin_reg_attr(*idx, &cfg, q_spec);
+	if (rc)
+		return rc;
+
+	if (*idx == Q_PIN_CFG_DTEST_SEL && val)  {
+		if (is_gpio_lv_mv(q_spec))
+			val -= 1;
+		else
+			val = BIT(val - 1);
+	}
+
+	q_reg_clr_set(&q_spec->regs[cfg.idx], cfg.shift, cfg.mask, val);
+	rc = regmap_write(q_chip->regmap, Q_REG_ADDR(q_spec, cfg.addr),
+			  *&q_spec->regs[cfg.idx]);
+
+	return rc;
+}
+DEFINE_SIMPLE_ATTRIBUTE(qpnp_pin_fops, qpnp_pin_debugfs_get,
+			qpnp_pin_debugfs_set, "%llu\n");
+
+#define DEBUGFS_BUF_SIZE 11 /* supports 2^32 in decimal */
+
+struct qpnp_pin_debugfs_args {
+	enum qpnp_pin_param_type type;
+	const char *filename;
+};
+
+static struct qpnp_pin_debugfs_args dfs_args[Q_NUM_PARAMS] = {
+	{ Q_PIN_CFG_MODE, "mode" },
+	{ Q_PIN_CFG_OUTPUT_TYPE, "output_type" },
+	{ Q_PIN_CFG_INVERT, "invert" },
+	{ Q_PIN_CFG_PULL, "pull" },
+	{ Q_PIN_CFG_VIN_SEL, "vin_sel" },
+	{ Q_PIN_CFG_OUT_STRENGTH, "out_strength" },
+	{ Q_PIN_CFG_SRC_SEL, "src_sel" },
+	{ Q_PIN_CFG_MASTER_EN, "master_en" },
+	{ Q_PIN_CFG_AOUT_REF, "aout_ref" },
+	{ Q_PIN_CFG_AIN_ROUTE, "ain_route" },
+	{ Q_PIN_CFG_CS_OUT, "cs_out" },
+	{ Q_PIN_CFG_APASS_SEL, "apass_sel" },
+	{ Q_PIN_CFG_DTEST_SEL, "dtest-sel" },
+};
+
+static int qpnp_pin_debugfs_create(struct qpnp_pin_chip *q_chip)
+{
+	struct platform_device *pdev = q_chip->pdev;
+	struct device *dev = &pdev->dev;
+	struct qpnp_pin_spec *q_spec;
+	enum qpnp_pin_param_type *params;
+	enum qpnp_pin_param_type type;
+	char pmic_pin[DEBUGFS_BUF_SIZE];
+	const char *filename;
+	struct dentry *dfs, *dfs_io_dir;
+	int i, j, rc;
+
+	q_chip->dfs_dir = debugfs_create_dir(q_chip->gpio_chip.label,
+							driver_dfs_dir);
+	if (q_chip->dfs_dir == NULL) {
+		dev_err(dev, "%s: cannot register chip debugfs directory %s\n",
+						__func__, dev->of_node->name);
+		return -ENODEV;
+	}
+
+	for (i = 0; i < q_chip->gpio_chip.ngpio; i++) {
+		q_spec = qpnp_chip_gpio_get_spec(q_chip, i);
+		params = q_spec->params;
+		snprintf(pmic_pin, DEBUGFS_BUF_SIZE, "%u", q_spec->pmic_pin);
+		dfs_io_dir = debugfs_create_dir(pmic_pin, q_chip->dfs_dir);
+		if (dfs_io_dir == NULL)
+			goto dfs_err;
+
+		for (j = 0; j < Q_NUM_PARAMS; j++) {
+			type = dfs_args[j].type;
+			filename = dfs_args[j].filename;
+
+			/*
+			 * Use a value of '0' to see if the pin has even basic
+			 * support for a function. Do not create a file if
+			 * it doesn't.
+			 */
+			rc = qpnp_pin_check_config(type, q_spec, 0);
+			if (rc == -ENXIO)
+				continue;
+
+			params[type] = type;
+			dfs = debugfs_create_file(filename, 0644, dfs_io_dir,
+					&q_spec->params[type], &qpnp_pin_fops);
+			if (dfs == NULL)
+				goto dfs_err;
+		}
+	}
+	return 0;
+dfs_err:
+	dev_err(dev, "%s: cannot register debugfs for pmic gpio %u on chip %s\n",
+			__func__, q_spec->pmic_pin, dev->of_node->name);
+	debugfs_remove_recursive(q_chip->dfs_dir);
+	return -ENFILE;
+}
+#else
+static int qpnp_pin_debugfs_create(struct qpnp_pin_chip *q_chip)
+{
+	return 0;
+}
+#endif
+
+static int qpnp_pin_is_valid_pin(struct qpnp_pin_spec *q_spec)
+{
+	if (q_spec->type == Q_GPIO_TYPE)
+		switch (q_spec->subtype) {
+		case Q_GPIO_SUBTYPE_GPIO_4CH:
+		case Q_GPIO_SUBTYPE_GPIOC_4CH:
+		case Q_GPIO_SUBTYPE_GPIO_8CH:
+		case Q_GPIO_SUBTYPE_GPIOC_8CH:
+		case Q_GPIO_SUBTYPE_GPIO_LV:
+		case Q_GPIO_SUBTYPE_GPIO_MV:
+			return 1;
+		}
+	else if (q_spec->type == Q_MPP_TYPE)
+		switch (q_spec->subtype) {
+		case Q_MPP_SUBTYPE_4CH_NO_ANA_OUT:
+		case Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT:
+		case Q_MPP_SUBTYPE_4CH_NO_SINK:
+		case Q_MPP_SUBTYPE_ULT_4CH_NO_SINK:
+		case Q_MPP_SUBTYPE_4CH_FULL_FUNC:
+		case Q_MPP_SUBTYPE_8CH_FULL_FUNC:
+			return 1;
+		}
+
+	return 0;
+}
+
+static int qpnp_pin_probe(struct platform_device *pdev)
+{
+	struct qpnp_pin_chip *q_chip;
+	struct qpnp_pin_spec *q_spec;
+	unsigned int base;
+	struct device_node *child;
+	int i, rc;
+	u32 lowest_gpio = UINT_MAX, highest_gpio = 0;
+	u32 gpio;
+	char version[Q_REG_SUBTYPE - Q_REG_DIG_MAJOR_REV + 1];
+	const char *pin_dev_name;
+
+	pin_dev_name = dev_name(&pdev->dev);
+	if (!pin_dev_name) {
+		dev_err(&pdev->dev,
+			"%s: label binding undefined for node %s\n",
+					__func__,
+			pdev->dev.of_node->full_name);
+		return -EINVAL;
+	}
+
+	q_chip = kzalloc(sizeof(*q_chip), GFP_KERNEL);
+	if (!q_chip)
+		return -ENOMEM;
+
+	q_chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!q_chip->regmap) {
+		dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+		return -EINVAL;
+	}
+	q_chip->pdev = pdev;
+	dev_set_drvdata(&pdev->dev, q_chip);
+
+	mutex_lock(&qpnp_pin_chips_lock);
+	list_add(&q_chip->chip_list, &qpnp_pin_chips);
+	mutex_unlock(&qpnp_pin_chips_lock);
+
+	/* first scan through nodes to find the range required for allocation */
+	i = 0;
+	for_each_available_child_of_node(pdev->dev.of_node, child) {
+		rc = of_property_read_u32(child, "qcom,pin-num", &gpio);
+		if (rc) {
+			dev_err(&pdev->dev,
+				"%s: unable to get qcom,pin-num property\n",
+								__func__);
+			goto err_probe;
+		}
+
+		if (gpio < lowest_gpio)
+			lowest_gpio = gpio;
+		if (gpio > highest_gpio)
+			highest_gpio = gpio;
+		i++;
+	}
+	q_chip->gpio_chip.ngpio = i;
+
+	if (highest_gpio < lowest_gpio) {
+		dev_err(&pdev->dev,
+			"%s: no device nodes specified in topology\n",
+								__func__);
+		rc = -EINVAL;
+		goto err_probe;
+	} else if (lowest_gpio == 0) {
+		dev_err(&pdev->dev, "%s: 0 is not a valid PMIC GPIO\n",
+								__func__);
+		rc = -EINVAL;
+		goto err_probe;
+	}
+
+	q_chip->pmic_pin_lowest = lowest_gpio;
+	q_chip->pmic_pin_highest = highest_gpio;
+
+	/* allocate gpio lookup tables */
+	q_chip->pmic_pins = kzalloc(sizeof(struct qpnp_pin_spec *) *
+					(highest_gpio - lowest_gpio + 1),
+					GFP_KERNEL);
+	q_chip->chip_gpios = kzalloc(sizeof(struct qpnp_pin_spec *) *
+					q_chip->gpio_chip.ngpio,
+					GFP_KERNEL);
+	if (!q_chip->pmic_pins || !q_chip->chip_gpios) {
+		dev_err(&pdev->dev, "%s: unable to allocate memory\n",
+								__func__);
+		rc = -ENOMEM;
+		goto err_probe;
+	}
+
+	/* get interrupt controller device_node */
+	q_chip->int_ctrl = of_irq_find_parent(pdev->dev.of_node);
+	if (!q_chip->int_ctrl) {
+		dev_err(&pdev->dev, "%s: Can't find interrupt parent\n",
+								__func__);
+		rc = -EINVAL;
+		goto err_probe;
+	}
+	i = 0;
+	/* now scan through again and populate the lookup table */
+	for_each_available_child_of_node(pdev->dev.of_node, child) {
+		rc = of_property_read_u32(child, "reg", &base);
+		if (rc < 0) {
+			dev_err(&pdev->dev,
+				"Couldn't find reg in node = %s rc = %d\n",
+				child->full_name, rc);
+			goto err_probe;
+		}
+
+		rc = of_property_read_u32(child, "qcom,pin-num", &gpio);
+		if (rc) {
+			dev_err(&pdev->dev,
+				"%s: unable to get qcom,pin-num property\n",
+								__func__);
+			goto err_probe;
+		}
+
+		q_spec = kzalloc(sizeof(struct qpnp_pin_spec), GFP_KERNEL);
+		if (!q_spec) {
+			rc = -ENOMEM;
+			goto err_probe;
+		}
+
+		q_spec->slave = to_spmi_device(pdev->dev.parent)->usid;
+		q_spec->offset = base;
+		q_spec->gpio_chip_idx = i;
+		q_spec->pmic_pin = gpio;
+		q_spec->node = child;
+		q_spec->q_chip = q_chip;
+
+		rc = regmap_bulk_read(q_chip->regmap,
+				Q_REG_ADDR(q_spec, Q_REG_DIG_MAJOR_REV),
+				&version[0],
+				ARRAY_SIZE(version));
+		if (rc) {
+			dev_err(&pdev->dev, "%s: unable to read type regs\n",
+						__func__);
+			goto err_probe;
+		}
+		q_spec->dig_major_rev = version[Q_REG_DIG_MAJOR_REV -
+						Q_REG_DIG_MAJOR_REV];
+		q_spec->type	= version[Q_REG_TYPE - Q_REG_DIG_MAJOR_REV];
+		q_spec->subtype = version[Q_REG_SUBTYPE - Q_REG_DIG_MAJOR_REV];
+
+		if (!qpnp_pin_is_valid_pin(q_spec)) {
+			dev_err(&pdev->dev,
+				"%s: invalid pin type (type=0x%x subtype=0x%x)\n",
+				       __func__, q_spec->type, q_spec->subtype);
+			goto err_probe;
+		}
+
+		rc = qpnp_pin_ctl_regs_init(q_spec);
+		if (rc)
+			goto err_probe;
+
+		/* initialize lookup table params */
+		qpnp_pmic_pin_set_spec(q_chip, gpio, q_spec);
+		qpnp_chip_gpio_set_spec(q_chip, i, q_spec);
+		i++;
+	}
+
+	q_chip->gpio_chip.base = -1;
+	q_chip->gpio_chip.label = pin_dev_name;
+	q_chip->gpio_chip.direction_input = qpnp_pin_direction_input;
+	q_chip->gpio_chip.direction_output = qpnp_pin_direction_output;
+	q_chip->gpio_chip.to_irq = qpnp_pin_to_irq;
+	q_chip->gpio_chip.get = qpnp_pin_get;
+	q_chip->gpio_chip.set = qpnp_pin_set;
+	q_chip->gpio_chip.parent = &pdev->dev;
+	q_chip->gpio_chip.of_xlate = qpnp_pin_of_gpio_xlate;
+	q_chip->gpio_chip.of_gpio_n_cells = 2;
+	q_chip->gpio_chip.can_sleep = 0;
+
+	rc = gpiochip_add_data(&q_chip->gpio_chip, q_chip);
+	if (rc) {
+		dev_err(&pdev->dev, "%s: Can't add gpio chip, rc = %d\n",
+								__func__, rc);
+		goto err_probe;
+	}
+
+	q_chip->chip_registered = true;
+	/* now configure gpio config defaults if they exist */
+	for (i = 0; i < q_chip->gpio_chip.ngpio; i++) {
+		q_spec = qpnp_chip_gpio_get_spec(q_chip, i);
+		if (WARN_ON(!q_spec)) {
+			rc = -ENODEV;
+			goto err_probe;
+		}
+
+		rc = qpnp_pin_cache_regs(q_chip, q_spec);
+		if (rc)
+			goto err_probe;
+
+		rc = qpnp_pin_apply_config(q_chip, q_spec);
+		if (rc)
+			goto err_probe;
+	}
+
+	dev_dbg(&pdev->dev, "%s: gpio_chip registered between %d-%u\n",
+			__func__, q_chip->gpio_chip.base,
+			(q_chip->gpio_chip.base + q_chip->gpio_chip.ngpio) - 1);
+
+	rc = qpnp_pin_debugfs_create(q_chip);
+	if (rc) {
+		dev_err(&pdev->dev, "%s: debugfs creation failed\n",
+			__func__);
+		goto err_probe;
+	}
+
+	return 0;
+
+err_probe:
+	qpnp_pin_free_chip(q_chip);
+	return rc;
+}
+
+static int qpnp_pin_remove(struct platform_device *pdev)
+{
+	struct qpnp_pin_chip *q_chip = dev_get_drvdata(&pdev->dev);
+
+	debugfs_remove_recursive(q_chip->dfs_dir);
+
+	return qpnp_pin_free_chip(q_chip);
+}
+
+static const struct of_device_id spmi_match_table[] = {
+	{	.compatible = "qcom,qpnp-pin",
+	},
+	{}
+};
+
+static const struct platform_device_id qpnp_pin_id[] = {
+	{ "qcom,qpnp-pin", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(spmi, qpnp_pin_id);
+
+static struct platform_driver qpnp_pin_driver = {
+	.driver		= {
+		.name		= "qcom,qpnp-pin",
+		.of_match_table = spmi_match_table,
+	},
+	.probe		= qpnp_pin_probe,
+	.remove		= qpnp_pin_remove,
+	.id_table	= qpnp_pin_id,
+};
+
+static int __init qpnp_pin_init(void)
+{
+#ifdef CONFIG_GPIO_QPNP_PIN_DEBUG
+	driver_dfs_dir = debugfs_create_dir("qpnp_pin", NULL);
+	if (driver_dfs_dir == NULL)
+		pr_err("Cannot register top level debugfs directory\n");
+#endif
+
+	return platform_driver_register(&qpnp_pin_driver);
+}
+
+static void __exit qpnp_pin_exit(void)
+{
+#ifdef CONFIG_GPIO_QPNP_PIN_DEBUG
+	debugfs_remove_recursive(driver_dfs_dir);
+#endif
+	platform_driver_unregister(&qpnp_pin_driver);
+}
+
+MODULE_DESCRIPTION("QPNP PMIC gpio driver");
+MODULE_LICENSE("GPL v2");
+
+subsys_initcall(qpnp_pin_init);
+module_exit(qpnp_pin_exit);
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 99c0514..2efd8d0 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -408,6 +408,15 @@
 	  To compile this driver as a module, choose M here: the module will
 	  be called qcom-spmi-vadc.
 
+config QCOM_TADC
+	tristate "Qualcomm Technologies, Inc. TADC driver"
+	depends on MFD_I2C_PMIC
+	help
+	  Say yes here to support the Qualcomm Technologies, Inc. telemetry ADC.
+	  The TADC provides battery temperature, skin temperature,
+	  die temperature, battery voltage, battery current, input voltage,
+	  input current, and OTG current.
+
 config ROCKCHIP_SARADC
 	tristate "Rockchip SARADC driver"
 	depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST)
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 7a40c04..1da80bd 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -39,6 +39,7 @@
 obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
 obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
 obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
+obj-$(CONFIG_QCOM_TADC) += qcom-tadc.o
 obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
 obj-$(CONFIG_STX104) += stx104.o
 obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
diff --git a/drivers/iio/adc/qcom-tadc.c b/drivers/iio/adc/qcom-tadc.c
new file mode 100644
index 0000000..9241288
--- /dev/null
+++ b/drivers/iio/adc/qcom-tadc.c
@@ -0,0 +1,1089 @@
+/* Copyright (c) 2016-2017, 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) "TADC: %s: " fmt, __func__
+
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define TADC_REVISION1_REG			0x00
+#define TADC_REVISION2_REG			0x01
+#define TADC_REVISION3_REG			0x02
+#define TADC_REVISION4_REG			0x03
+#define TADC_PERPH_TYPE_REG			0x04
+#define TADC_PERPH_SUBTYPE_REG			0x05
+
+/* TADC register definitions */
+#define TADC_SW_CH_CONV_REG(chip)		(chip->tadc_base + 0x06)
+#define TADC_MBG_ERR_REG(chip)			(chip->tadc_base + 0x07)
+#define TADC_EN_CTL_REG(chip)			(chip->tadc_base + 0x46)
+#define TADC_CONV_REQ_REG(chip)			(chip->tadc_base + 0x51)
+#define TADC_HWTRIG_CONV_CH_EN_REG(chip)	(chip->tadc_base + 0x52)
+#define TADC_HW_SETTLE_DELAY_REG(chip)		(chip->tadc_base + 0x53)
+#define TADC_LONG_HW_SETTLE_DLY_EN_REG(chip)	(chip->tadc_base + 0x54)
+#define TADC_LONG_HW_SETTLE_DLY_REG(chip)	(chip->tadc_base + 0x55)
+#define TADC_ADC_BUF_CH_REG(chip)		(chip->tadc_base + 0x56)
+#define TADC_ADC_AAF_CH_REG(chip)		(chip->tadc_base + 0x57)
+#define TADC_ADC_DATA_RDBK_REG(chip)		(chip->tadc_base + 0x58)
+#define TADC_CH1_ADC_LO_REG(chip)		(chip->tadc_base + 0x60)
+#define TADC_CH1_ADC_HI_REG(chip)		(chip->tadc_base + 0x61)
+#define TADC_CH2_ADC_LO_REG(chip)		(chip->tadc_base + 0x62)
+#define TADC_CH2_ADC_HI_REG(chip)		(chip->tadc_base + 0x63)
+#define TADC_CH3_ADC_LO_REG(chip)		(chip->tadc_base + 0x64)
+#define TADC_CH3_ADC_HI_REG(chip)		(chip->tadc_base + 0x65)
+#define TADC_CH4_ADC_LO_REG(chip)		(chip->tadc_base + 0x66)
+#define TADC_CH4_ADC_HI_REG(chip)		(chip->tadc_base + 0x67)
+#define TADC_CH5_ADC_LO_REG(chip)		(chip->tadc_base + 0x68)
+#define TADC_CH5_ADC_HI_REG(chip)		(chip->tadc_base + 0x69)
+#define TADC_CH6_ADC_LO_REG(chip)		(chip->tadc_base + 0x70)
+#define TADC_CH6_ADC_HI_REG(chip)		(chip->tadc_base + 0x71)
+#define TADC_CH7_ADC_LO_REG(chip)		(chip->tadc_base + 0x72)
+#define TADC_CH7_ADC_HI_REG(chip)		(chip->tadc_base + 0x73)
+#define TADC_CH8_ADC_LO_REG(chip)		(chip->tadc_base + 0x74)
+#define TADC_CH8_ADC_HI_REG(chip)		(chip->tadc_base + 0x75)
+
+/* TADC_CMP register definitions */
+#define TADC_CMP_THR1_CMP_REG(chip)		(chip->tadc_cmp_base + 0x51)
+#define TADC_CMP_THR1_CH1_CMP_LO_REG(chip)	(chip->tadc_cmp_base + 0x52)
+#define TADC_CMP_THR1_CH1_CMP_HI_REG(chip)	(chip->tadc_cmp_base + 0x53)
+#define TADC_CMP_THR1_CH2_CMP_LO_REG(chip)	(chip->tadc_cmp_base + 0x54)
+#define TADC_CMP_THR1_CH2_CMP_HI_REG(chip)	(chip->tadc_cmp_base + 0x55)
+#define TADC_CMP_THR1_CH3_CMP_LO_REG(chip)	(chip->tadc_cmp_base + 0x56)
+#define TADC_CMP_THR1_CH3_CMP_HI_REG(chip)	(chip->tadc_cmp_base + 0x57)
+#define TADC_CMP_THR2_CMP_REG(chip)		(chip->tadc_cmp_base + 0x67)
+#define TADC_CMP_THR2_CH1_CMP_LO_REG(chip)	(chip->tadc_cmp_base + 0x68)
+#define TADC_CMP_THR2_CH1_CMP_HI_REG(chip)	(chip->tadc_cmp_base + 0x69)
+#define TADC_CMP_THR2_CH2_CMP_LO_REG(chip)	(chip->tadc_cmp_base + 0x6A)
+#define TADC_CMP_THR2_CH2_CMP_HI_REG(chip)	(chip->tadc_cmp_base + 0x6B)
+#define TADC_CMP_THR2_CH3_CMP_LO_REG(chip)	(chip->tadc_cmp_base + 0x6C)
+#define TADC_CMP_THR2_CH3_CMP_HI_REG(chip)	(chip->tadc_cmp_base + 0x6D)
+#define TADC_CMP_THR3_CMP_REG(chip)		(chip->tadc_cmp_base + 0x7D)
+#define TADC_CMP_THR3_CH1_CMP_LO_REG(chip)	(chip->tadc_cmp_base + 0x7E)
+#define TADC_CMP_THR3_CH1_CMP_HI_REG(chip)	(chip->tadc_cmp_base + 0x7F)
+#define TADC_CMP_THR3_CH2_CMP_LO_REG(chip)	(chip->tadc_cmp_base + 0x80)
+#define TADC_CMP_THR3_CH2_CMP_HI_REG(chip)	(chip->tadc_cmp_base + 0x81)
+#define TADC_CMP_THR3_CH3_CMP_LO_REG(chip)	(chip->tadc_cmp_base + 0x82)
+#define TADC_CMP_THR3_CH3_CMP_HI_REG(chip)	(chip->tadc_cmp_base + 0x83)
+#define TADC_CMP_THR4_CMP_REG(chip)		(chip->tadc_cmp_base + 0x93)
+#define TADC_CMP_THR4_CH1_CMP_LO_REG(chip)	(chip->tadc_cmp_base + 0x94)
+#define TADC_CMP_THR4_CH1_CMP_HI_REG(chip)	(chip->tadc_cmp_base + 0x95)
+#define TADC_CMP_THR1_CH1_HYST_REG(chip)	(chip->tadc_cmp_base + 0xB0)
+#define TADC_CMP_THR2_CH1_HYST_REG(chip)	(chip->tadc_cmp_base + 0xB1)
+#define TADC_CMP_THR3_CH1_HYST_REG(chip)	(chip->tadc_cmp_base + 0xB2)
+#define TADC_CMP_THR4_CH1_HYST_REG(chip)	(chip->tadc_cmp_base + 0xB3)
+
+/* 10 bits of resolution */
+#define TADC_RESOLUTION			1024
+/* number of hardware channels */
+#define TADC_NUM_CH			8
+
+enum tadc_chan_id {
+	TADC_THERM1 = 0,
+	TADC_THERM2,
+	TADC_DIE_TEMP,
+	TADC_BATT_I,
+	TADC_BATT_V,
+	TADC_INPUT_I,
+	TADC_INPUT_V,
+	TADC_OTG_I,
+	/* virtual channels */
+	TADC_BATT_P,
+	TADC_INPUT_P,
+	TADC_THERM1_THR1,
+	TADC_THERM1_THR2,
+	TADC_THERM1_THR3,
+	TADC_THERM1_THR4,
+	TADC_THERM2_THR1,
+	TADC_THERM2_THR2,
+	TADC_THERM2_THR3,
+	TADC_DIE_TEMP_THR1,
+	TADC_DIE_TEMP_THR2,
+	TADC_DIE_TEMP_THR3,
+	TADC_CHAN_ID_MAX,
+};
+
+#define TADC_CHAN(_name, _type, _channel, _info_mask)	\
+{							\
+	.type			= _type,		\
+	.channel		= _channel,		\
+	.info_mask_separate	= _info_mask,		\
+	.extend_name		= _name,		\
+}
+
+#define TADC_THERM_CHAN(_name, _channel)		\
+TADC_CHAN(_name, IIO_TEMP, _channel,			\
+	BIT(IIO_CHAN_INFO_RAW) |			\
+	BIT(IIO_CHAN_INFO_PROCESSED))
+
+#define TADC_TEMP_CHAN(_name, _channel)			\
+TADC_CHAN(_name, IIO_TEMP, _channel,			\
+	BIT(IIO_CHAN_INFO_RAW) |			\
+	BIT(IIO_CHAN_INFO_PROCESSED) |			\
+	BIT(IIO_CHAN_INFO_SCALE) |			\
+	BIT(IIO_CHAN_INFO_OFFSET))
+
+#define TADC_CURRENT_CHAN(_name, _channel)		\
+TADC_CHAN(_name, IIO_CURRENT, _channel,			\
+	BIT(IIO_CHAN_INFO_RAW) |			\
+	BIT(IIO_CHAN_INFO_PROCESSED) |			\
+	BIT(IIO_CHAN_INFO_SCALE))
+
+
+#define TADC_VOLTAGE_CHAN(_name, _channel)		\
+TADC_CHAN(_name, IIO_VOLTAGE, _channel,			\
+	BIT(IIO_CHAN_INFO_RAW) |			\
+	BIT(IIO_CHAN_INFO_PROCESSED) |			\
+	BIT(IIO_CHAN_INFO_SCALE))
+
+#define TADC_POWER_CHAN(_name, _channel)		\
+TADC_CHAN(_name, IIO_POWER, _channel,			\
+	BIT(IIO_CHAN_INFO_PROCESSED))
+
+static const struct iio_chan_spec tadc_iio_chans[] = {
+	[TADC_THERM1]		= TADC_THERM_CHAN(
+					"batt", TADC_THERM1),
+	[TADC_THERM2]		= TADC_THERM_CHAN(
+					"skin", TADC_THERM2),
+	[TADC_DIE_TEMP]		= TADC_TEMP_CHAN(
+					"die", TADC_DIE_TEMP),
+	[TADC_BATT_I]		= TADC_CURRENT_CHAN(
+					"batt", TADC_BATT_I),
+	[TADC_BATT_V]		= TADC_VOLTAGE_CHAN(
+					"batt", TADC_BATT_V),
+	[TADC_INPUT_I]		= TADC_CURRENT_CHAN(
+					"input", TADC_INPUT_I),
+	[TADC_INPUT_V]		= TADC_VOLTAGE_CHAN(
+					"input", TADC_INPUT_V),
+	[TADC_OTG_I]		= TADC_CURRENT_CHAN(
+					"otg", TADC_OTG_I),
+	[TADC_BATT_P]		= TADC_POWER_CHAN(
+					"batt", TADC_BATT_P),
+	[TADC_INPUT_P]		= TADC_POWER_CHAN(
+					"input", TADC_INPUT_P),
+	[TADC_THERM1_THR1]	= TADC_THERM_CHAN(
+					"batt_warm", TADC_THERM1_THR1),
+	[TADC_THERM1_THR2]	= TADC_THERM_CHAN(
+					"batt_cool", TADC_THERM1_THR2),
+	[TADC_THERM1_THR3]	= TADC_THERM_CHAN(
+					"batt_cold", TADC_THERM1_THR3),
+	[TADC_THERM1_THR4]	= TADC_THERM_CHAN(
+					"batt_hot", TADC_THERM1_THR4),
+	[TADC_THERM2_THR1]	= TADC_THERM_CHAN(
+					"skin_lb", TADC_THERM2_THR1),
+	[TADC_THERM2_THR2]	= TADC_THERM_CHAN(
+					"skin_ub", TADC_THERM2_THR2),
+	[TADC_THERM2_THR3]	= TADC_THERM_CHAN(
+					"skin_rst", TADC_THERM2_THR3),
+	[TADC_DIE_TEMP_THR1]	= TADC_THERM_CHAN(
+					"die_lb", TADC_DIE_TEMP_THR1),
+	[TADC_DIE_TEMP_THR2]	= TADC_THERM_CHAN(
+					"die_ub", TADC_DIE_TEMP_THR2),
+	[TADC_DIE_TEMP_THR3]	= TADC_THERM_CHAN(
+					"die_rst", TADC_DIE_TEMP_THR3),
+};
+
+struct tadc_therm_thr {
+	int	addr_lo;
+	int	addr_hi;
+};
+
+struct tadc_chan_data {
+	s32			scale;
+	s32			offset;
+	u32			rbias;
+	const struct tadc_pt	*table;
+	size_t			tablesize;
+	struct tadc_therm_thr	thr[4];
+};
+
+struct tadc_chip {
+	struct device		*dev;
+	struct regmap		*regmap;
+	u32			tadc_base;
+	u32			tadc_cmp_base;
+	struct tadc_chan_data	chans[TADC_NUM_CH];
+	struct completion	eoc_complete;
+	struct mutex		write_lock;
+};
+
+struct tadc_pt {
+	s32 x;
+	s32 y;
+};
+
+/*
+ * Thermistor tables are generated by the B-parameter equation which is a
+ * simplifed version of the Steinhart-Hart equation.
+ *
+ * (1 / T) = (1 / T0) + (1 / B) * ln(R / R0)
+ *
+ * Where R0 is the resistance at temperature T0, and T0 is typically room
+ * temperature (25C).
+ */
+static const struct tadc_pt tadc_therm_3450b_68k[] = {
+	{ 4151,		120000 },
+	{ 4648,		115000 },
+	{ 5220,		110000 },
+	{ 5880,		105000 },
+	{ 6644,		100000 },
+	{ 7533,		95000 },
+	{ 8571,		90000 },
+	{ 9786,		85000 },
+	{ 11216,	80000 },
+	{ 12906,	75000 },
+	{ 14910,	70000 },
+	{ 17300,	65000 },
+	{ 20163,	60000 },
+	{ 23609,	55000 },
+	{ 27780,	50000 },
+	{ 32855,	45000 },
+	{ 39065,	40000 },
+	{ 46712,	35000 },
+	{ 56185,	30000 },
+	{ 68000,	25000 },
+	{ 82837,	20000 },
+	{ 101604,	15000 },
+	{ 125525,	10000 },
+	{ 156261,	5000 },
+	{ 196090,	0 },
+	{ 248163,	-5000 },
+	{ 316887,	-10000 },
+	{ 408493,	-15000 },
+	{ 531889,	-20000 },
+	{ 699966,	-25000 },
+	{ 931618,	-30000 },
+	{ 1254910,	-35000 },
+	{ 1712127,	-40000 },
+};
+
+static bool tadc_is_reg_locked(struct tadc_chip *chip, u16 reg)
+{
+	if ((reg & 0xFF00) == chip->tadc_cmp_base)
+		return true;
+
+	if (reg == TADC_HWTRIG_CONV_CH_EN_REG(chip))
+		return true;
+
+	return false;
+}
+
+static int tadc_read(struct tadc_chip *chip, u16 reg, u8 *val, size_t count)
+{
+	int rc = 0;
+
+	rc = regmap_bulk_read(chip->regmap, reg, val, count);
+	if (rc < 0)
+		pr_err("Couldn't read 0x%04x rc=%d\n", reg, rc);
+
+	return rc;
+}
+
+static int tadc_write(struct tadc_chip *chip, u16 reg, u8 data)
+{
+	int rc = 0;
+
+	mutex_lock(&chip->write_lock);
+	if (tadc_is_reg_locked(chip, reg)) {
+		rc = regmap_write(chip->regmap, (reg & 0xFF00) | 0xD0, 0xA5);
+		if (rc < 0) {
+			pr_err("Couldn't unlock secure register rc=%d\n", rc);
+			goto unlock;
+		}
+	}
+
+	rc = regmap_write(chip->regmap, reg, data);
+	if (rc < 0) {
+		pr_err("Couldn't write 0x%02x to 0x%04x rc=%d\n",
+								data, reg, rc);
+		goto unlock;
+	}
+
+unlock:
+	mutex_unlock(&chip->write_lock);
+	return rc;
+}
+static int tadc_bulk_write(struct tadc_chip *chip, u16 reg, u8 *data,
+								size_t count)
+{
+	int rc = 0, i;
+
+	mutex_lock(&chip->write_lock);
+	for (i = 0; i < count; ++i, ++reg) {
+		if (tadc_is_reg_locked(chip, reg)) {
+			rc = regmap_write(chip->regmap,
+						(reg & 0xFF00) | 0xD0, 0xA5);
+			if (rc < 0) {
+				pr_err("Couldn't unlock secure register rc=%d\n",
+								rc);
+				goto unlock;
+			}
+		}
+
+		rc = regmap_write(chip->regmap, reg, data[i]);
+		if (rc < 0) {
+			pr_err("Couldn't write 0x%02x to 0x%04x rc=%d\n",
+							data[i], reg, rc);
+			goto unlock;
+		}
+	}
+
+unlock:
+	mutex_unlock(&chip->write_lock);
+	return rc;
+}
+
+static int tadc_lerp(const struct tadc_pt *pts, size_t size, bool inv,
+							s32 input, s32 *output)
+{
+	int i;
+	s64 temp;
+	bool ascending;
+
+	if (pts == NULL) {
+		pr_err("Table is NULL\n");
+		return -EINVAL;
+	}
+
+	if (size < 1) {
+		pr_err("Table has no entries\n");
+		return -ENOENT;
+	}
+
+	if (size == 1) {
+		*output = inv ? pts[0].x : pts[0].y;
+		return 0;
+	}
+
+	ascending = inv ? (pts[0].y < pts[1].y) : (pts[0].x < pts[1].x);
+	if (ascending ? (input <= (inv ? pts[0].y : pts[0].x)) :
+			(input >= (inv ? pts[0].y : pts[0].x))) {
+		*output = inv ? pts[0].x : pts[0].y;
+		return 0;
+	}
+
+	if (ascending ? (input >= (inv ? pts[size - 1].y : pts[size - 1].x)) :
+			(input <= (inv ? pts[size - 1].y : pts[size - 1].x))) {
+		*output = inv ? pts[size - 1].x : pts[size - 1].y;
+		return 0;
+	}
+
+	for (i = 1; i < size; i++)
+		if (ascending ? (input <= (inv ? pts[i].y : pts[i].x)) :
+				(input >= (inv ? pts[i].y : pts[i].x)))
+			break;
+
+	if (inv) {
+		temp = (s64)(pts[i].x - pts[i - 1].x) *
+						(s64)(input - pts[i - 1].y);
+		temp = div_s64(temp, pts[i].y - pts[i - 1].y);
+		*output = temp + pts[i - 1].x;
+	} else {
+		temp = (s64)(pts[i].y - pts[i - 1].y) *
+						(s64)(input - pts[i - 1].x);
+		temp = div_s64(temp, pts[i].x - pts[i - 1].x);
+		*output = temp + pts[i - 1].y;
+	}
+
+	return 0;
+}
+
+/*
+ * Process the result of a thermistor reading.
+ *
+ * The voltage input to the ADC is a result of a voltage divider circuit.
+ * Vout = (Rtherm / (Rbias + Rtherm)) * Vbias
+ *
+ * The ADC value is based on the output voltage of the voltage divider, and the
+ * bias voltage.
+ * ADC = (Vin * 1024) / Vbias
+ *
+ * Combine these equations and solve for Rtherm
+ * Rtherm = (ADC * Rbias) / (1024 - ADC)
+ */
+static int tadc_get_processed_therm(const struct tadc_chan_data *chan_data,
+							s16 adc, s32 *result)
+{
+	s32 rtherm;
+
+	rtherm = div_s64((s64)adc * chan_data->rbias, TADC_RESOLUTION - adc);
+	return tadc_lerp(chan_data->table, chan_data->tablesize, false, rtherm,
+									result);
+}
+
+static int tadc_get_raw_therm(const struct tadc_chan_data *chan_data,
+							int mdegc, int *result)
+{
+	int rc;
+	s32 rtherm;
+
+	rc = tadc_lerp(chan_data->table, chan_data->tablesize, true, mdegc,
+								&rtherm);
+	if (rc < 0) {
+		pr_err("Couldn't interpolate %d\n rc=%d", mdegc, rc);
+		return rc;
+	}
+
+	*result = div64_s64((s64)rtherm * TADC_RESOLUTION,
+						(s64)chan_data->rbias + rtherm);
+	return 0;
+}
+
+static int tadc_read_channel(struct tadc_chip *chip, u16 address, int *adc)
+{
+	u8 val[2];
+	int rc;
+
+	rc = tadc_read(chip, address, val, ARRAY_SIZE(val));
+	if (rc < 0) {
+		pr_err("Couldn't read channel rc=%d\n", rc);
+		return rc;
+	}
+
+	/* the 10th bit is the sign bit for all channels */
+	*adc = sign_extend32(val[0] | val[1] << BITS_PER_BYTE, 10);
+	return rc;
+}
+
+static int tadc_write_channel(struct tadc_chip *chip, u16 address, int adc)
+{
+	u8 val[2];
+	int rc;
+
+	/* the 10th bit is the sign bit for all channels */
+	adc = sign_extend32(adc, 10);
+	val[0] = (u8)adc;
+	val[1] = (u8)(adc >> BITS_PER_BYTE);
+	rc = tadc_bulk_write(chip, address, val, 2);
+	if (rc < 0) {
+		pr_err("Couldn't write to channel rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+#define CONVERSION_TIMEOUT_MS 100
+static int tadc_do_conversion(struct tadc_chip *chip, u8 channels, s16 *adc)
+{
+	unsigned long timeout, timeleft;
+	u8 val[TADC_NUM_CH * 2];
+	int rc, i;
+
+	rc = tadc_read(chip, TADC_MBG_ERR_REG(chip), val, 1);
+	if (rc < 0) {
+		pr_err("Couldn't read mbg error status rc=%d\n", rc);
+		return rc;
+	}
+
+	if (val[0] != 0) {
+		tadc_write(chip, TADC_EN_CTL_REG(chip), 0);
+		tadc_write(chip, TADC_EN_CTL_REG(chip), 0x80);
+	}
+
+	rc = tadc_write(chip, TADC_CONV_REQ_REG(chip), channels);
+	if (rc < 0) {
+		pr_err("Couldn't write conversion request rc=%d\n", rc);
+		return rc;
+	}
+
+	timeout = msecs_to_jiffies(CONVERSION_TIMEOUT_MS);
+	timeleft = wait_for_completion_timeout(&chip->eoc_complete, timeout);
+
+	if (timeleft == 0) {
+		rc = tadc_read(chip, TADC_SW_CH_CONV_REG(chip), val, 1);
+		if (rc < 0) {
+			pr_err("Couldn't read conversion status rc=%d\n", rc);
+			return rc;
+		}
+
+		if (val[0] != channels) {
+			pr_err("Conversion timed out\n");
+			return -ETIMEDOUT;
+		}
+	}
+
+	rc = tadc_read(chip, TADC_CH1_ADC_LO_REG(chip), val, ARRAY_SIZE(val));
+	if (rc < 0) {
+		pr_err("Couldn't read adc channels rc=%d\n", rc);
+		return rc;
+	}
+
+	for (i = 0; i < TADC_NUM_CH; i++)
+		adc[i] = (s16)(val[i * 2] | (u16)val[i * 2 + 1] << 8);
+
+	return jiffies_to_msecs(timeout - timeleft);
+}
+
+static int tadc_read_raw(struct iio_dev *indio_dev,
+		struct iio_chan_spec const *chan, int *val, int *val2,
+		long mask)
+{
+	struct tadc_chip *chip = iio_priv(indio_dev);
+	struct tadc_chan_data *chan_data = NULL;
+	int rc, offset = 0, scale, scale2, scale_type;
+	s16 adc[TADC_NUM_CH];
+
+	switch (chan->channel) {
+	case TADC_THERM1_THR1:
+	case TADC_THERM1_THR2:
+	case TADC_THERM1_THR3:
+	case TADC_THERM1_THR4:
+		chan_data = &chip->chans[TADC_THERM1];
+		break;
+	case TADC_THERM2_THR1:
+	case TADC_THERM2_THR2:
+	case TADC_THERM2_THR3:
+		chan_data = &chip->chans[TADC_THERM2];
+		break;
+	case TADC_DIE_TEMP_THR1:
+	case TADC_DIE_TEMP_THR2:
+	case TADC_DIE_TEMP_THR3:
+		chan_data = &chip->chans[TADC_DIE_TEMP];
+		break;
+	default:
+		if (chan->channel >= ARRAY_SIZE(chip->chans)) {
+			pr_err("Channel %d is out of bounds\n", chan->channel);
+			return -EINVAL;
+		}
+
+		chan_data = &chip->chans[chan->channel];
+		break;
+	}
+
+	if (!chan_data)
+		return -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->channel) {
+		case TADC_THERM1_THR1:
+		case TADC_THERM2_THR1:
+		case TADC_DIE_TEMP_THR1:
+			rc = tadc_read_channel(chip,
+					chan_data->thr[0].addr_lo, val);
+			break;
+		case TADC_THERM1_THR2:
+		case TADC_THERM2_THR2:
+		case TADC_DIE_TEMP_THR2:
+			rc = tadc_read_channel(chip,
+					chan_data->thr[1].addr_lo, val);
+			break;
+		case TADC_THERM1_THR3:
+		case TADC_THERM2_THR3:
+		case TADC_DIE_TEMP_THR3:
+			rc = tadc_read_channel(chip,
+					chan_data->thr[2].addr_lo, val);
+			break;
+		case TADC_THERM1_THR4:
+			rc = tadc_read_channel(chip,
+					chan_data->thr[3].addr_lo, val);
+			break;
+		default:
+			rc = tadc_do_conversion(chip, BIT(chan->channel), adc);
+			if (rc >= 0)
+				*val = adc[chan->channel];
+			break;
+		}
+
+		if (rc < 0) {
+			pr_err("Couldn't read channel %d\n", chan->channel);
+			return rc;
+		}
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_PROCESSED:
+		switch (chan->channel) {
+		case TADC_THERM1:
+		case TADC_THERM2:
+		case TADC_THERM1_THR1:
+		case TADC_THERM1_THR2:
+		case TADC_THERM1_THR3:
+		case TADC_THERM1_THR4:
+		case TADC_THERM2_THR1:
+		case TADC_THERM2_THR2:
+		case TADC_THERM2_THR3:
+			rc = tadc_read_raw(indio_dev, chan, val, NULL,
+							IIO_CHAN_INFO_RAW);
+			if (rc < 0)
+				return rc;
+
+			rc = tadc_get_processed_therm(chan_data, *val, val);
+			if (rc < 0) {
+				pr_err("Couldn't process 0x%04x from channel %d rc=%d\n",
+						*val, chan->channel, rc);
+				return rc;
+			}
+			break;
+		case TADC_BATT_P:
+			rc = tadc_do_conversion(chip,
+				BIT(TADC_BATT_I) | BIT(TADC_BATT_V), adc);
+			if (rc < 0) {
+				pr_err("Couldn't read battery current and voltage channels rc=%d\n",
+									rc);
+				return rc;
+			}
+
+			*val = adc[TADC_BATT_I] * adc[TADC_BATT_V];
+			break;
+		case TADC_INPUT_P:
+			rc = tadc_do_conversion(chip,
+				BIT(TADC_INPUT_I) | BIT(TADC_INPUT_V), adc);
+			if (rc < 0) {
+				pr_err("Couldn't read input current and voltage channels rc=%d\n",
+									rc);
+				return rc;
+			}
+
+			*val = adc[TADC_INPUT_I] * adc[TADC_INPUT_V];
+			break;
+		default:
+			rc = tadc_read_raw(indio_dev, chan, val, NULL,
+							IIO_CHAN_INFO_RAW);
+			if (rc < 0)
+				return rc;
+
+			/* offset is optional */
+			rc = tadc_read_raw(indio_dev, chan, &offset, NULL,
+							IIO_CHAN_INFO_OFFSET);
+			if (rc < 0)
+				return rc;
+
+			scale_type = tadc_read_raw(indio_dev, chan,
+					&scale, &scale2, IIO_CHAN_INFO_SCALE);
+			switch (scale_type) {
+			case IIO_VAL_INT:
+				*val = *val * scale + offset;
+				break;
+			case IIO_VAL_FRACTIONAL:
+				*val = div_s64((s64)*val * scale + offset,
+									scale2);
+				break;
+			default:
+				return -EINVAL;
+			}
+			break;
+		}
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->channel) {
+		case TADC_DIE_TEMP:
+		case TADC_DIE_TEMP_THR1:
+		case TADC_DIE_TEMP_THR2:
+			*val = chan_data->scale;
+			return IIO_VAL_INT;
+		case TADC_BATT_I:
+		case TADC_BATT_V:
+		case TADC_INPUT_I:
+		case TADC_INPUT_V:
+		case TADC_OTG_I:
+			*val = chan_data->scale;
+			*val2 = TADC_RESOLUTION;
+			return IIO_VAL_FRACTIONAL;
+		}
+
+		return -EINVAL;
+	case IIO_CHAN_INFO_OFFSET:
+		*val = chan_data->offset;
+		return IIO_VAL_INT;
+	}
+
+	return -EINVAL;
+}
+
+static int tadc_write_raw(struct iio_dev *indio_dev,
+		struct iio_chan_spec const *chan, int val, int val2,
+		long mask)
+{
+	struct tadc_chip *chip = iio_priv(indio_dev);
+	const struct tadc_chan_data *chan_data;
+	int rc, raw;
+	s32 rem;
+
+	switch (chan->channel) {
+	case TADC_THERM1_THR1:
+	case TADC_THERM1_THR2:
+	case TADC_THERM1_THR3:
+	case TADC_THERM1_THR4:
+		chan_data = &chip->chans[TADC_THERM1];
+		break;
+	case TADC_THERM2_THR1:
+	case TADC_THERM2_THR2:
+	case TADC_THERM2_THR3:
+		chan_data = &chip->chans[TADC_THERM2];
+		break;
+	case TADC_DIE_TEMP_THR1:
+	case TADC_DIE_TEMP_THR2:
+	case TADC_DIE_TEMP_THR3:
+		chan_data = &chip->chans[TADC_DIE_TEMP];
+		break;
+	default:
+		if (chan->channel >= ARRAY_SIZE(chip->chans)) {
+			pr_err("Channel %d is out of bounds\n", chan->channel);
+			return -EINVAL;
+		}
+
+		chan_data = &chip->chans[chan->channel];
+		break;
+	}
+
+	if (!chan_data)
+		return -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+		switch (chan->channel) {
+		case TADC_THERM1_THR1:
+		case TADC_THERM1_THR2:
+		case TADC_THERM1_THR3:
+		case TADC_THERM1_THR4:
+		case TADC_THERM2_THR1:
+		case TADC_THERM2_THR2:
+		case TADC_THERM2_THR3:
+			rc = tadc_get_raw_therm(chan_data, val, &raw);
+			if (rc < 0) {
+				pr_err("Couldn't get raw value rc=%d\n", rc);
+				return rc;
+			}
+			break;
+		case TADC_DIE_TEMP_THR1:
+		case TADC_DIE_TEMP_THR2:
+		case TADC_DIE_TEMP_THR3:
+			/* DIV_ROUND_CLOSEST does not like negative numbers */
+			raw = div_s64_rem(val - chan_data->offset,
+							chan_data->scale, &rem);
+			if (abs(rem) >= abs(chan_data->scale / 2))
+				raw++;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		rc = tadc_write_raw(indio_dev, chan, raw, 0,
+							IIO_CHAN_INFO_RAW);
+		if (rc < 0) {
+			pr_err("Couldn't write raw rc=%d\n", rc);
+			return rc;
+		}
+
+		break;
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->channel) {
+		case TADC_THERM1_THR1:
+		case TADC_THERM2_THR1:
+		case TADC_DIE_TEMP_THR1:
+			rc = tadc_write_channel(chip,
+					chan_data->thr[0].addr_lo, val);
+			break;
+		case TADC_THERM1_THR2:
+		case TADC_THERM2_THR2:
+		case TADC_DIE_TEMP_THR2:
+			rc = tadc_write_channel(chip,
+					chan_data->thr[1].addr_lo, val);
+			break;
+		case TADC_THERM1_THR3:
+		case TADC_THERM2_THR3:
+		case TADC_DIE_TEMP_THR3:
+			rc = tadc_write_channel(chip,
+					chan_data->thr[2].addr_lo, val);
+			break;
+		case TADC_THERM1_THR4:
+			rc = tadc_write_channel(chip,
+					chan_data->thr[3].addr_lo, val);
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		if (rc < 0) {
+			pr_err("Couldn't write channel %d\n", chan->channel);
+			return rc;
+		}
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+static irqreturn_t handle_eoc(int irq, void *dev_id)
+{
+	struct tadc_chip *chip = dev_id;
+
+	complete(&chip->eoc_complete);
+	return IRQ_HANDLED;
+}
+
+static int tadc_set_therm_table(struct tadc_chan_data *chan_data, u32 beta,
+				u32 rtherm)
+{
+	if (beta == 3450 && rtherm == 68000) {
+		chan_data->table = tadc_therm_3450b_68k;
+		chan_data->tablesize = ARRAY_SIZE(tadc_therm_3450b_68k);
+		return 0;
+	}
+
+	return -ENOENT;
+}
+
+static int tadc_parse_dt(struct tadc_chip *chip)
+{
+	struct device_node *child, *node;
+	struct tadc_chan_data *chan_data;
+	u32 chan_id, rtherm, beta;
+	int rc = 0;
+
+	node = chip->dev->of_node;
+	for_each_available_child_of_node(node, child) {
+		rc = of_property_read_u32(child, "reg", &chan_id);
+		if (rc < 0) {
+			pr_err("Couldn't find channel for %s rc=%d",
+							child->name, rc);
+			return rc;
+		}
+
+		if (chan_id > TADC_NUM_CH - 1) {
+			pr_err("Channel %d is out of range [0, %d]\n",
+						chan_id, TADC_NUM_CH - 1);
+			return -EINVAL;
+		}
+
+		chan_data = &chip->chans[chan_id];
+		if (chan_id == TADC_THERM1 || chan_id == TADC_THERM2) {
+			rc = of_property_read_u32(child,
+					"qcom,rbias", &chan_data->rbias);
+			if (rc < 0) {
+				pr_err("Couldn't read qcom,rbias rc=%d\n", rc);
+				return rc;
+			}
+
+			rc = of_property_read_u32(child,
+					"qcom,beta-coefficient", &beta);
+			if (rc < 0) {
+				pr_err("Couldn't read qcom,beta-coefficient rc=%d\n",
+									rc);
+				return rc;
+			}
+
+			rc = of_property_read_u32(child,
+					"qcom,rtherm-at-25degc", &rtherm);
+			if (rc < 0) {
+				pr_err("Couldn't read qcom,rtherm-at-25degc rc=%d\n",
+					rc);
+				return rc;
+			}
+
+			rc = tadc_set_therm_table(chan_data, beta, rtherm);
+			if (rc < 0) {
+				pr_err("Couldn't set therm table rc=%d\n", rc);
+				return rc;
+			}
+		} else {
+			rc = of_property_read_s32(child, "qcom,scale",
+							&chan_data->scale);
+			if (rc < 0) {
+				pr_err("Couldn't read scale rc=%d\n", rc);
+				return rc;
+			}
+
+			of_property_read_s32(child, "qcom,offset",
+							&chan_data->offset);
+		}
+	}
+
+	return rc;
+}
+
+static int tadc_init_hw(struct tadc_chip *chip)
+{
+	int rc;
+
+	chip->chans[TADC_THERM1].thr[0].addr_lo =
+					TADC_CMP_THR1_CH1_CMP_LO_REG(chip);
+	chip->chans[TADC_THERM1].thr[0].addr_hi =
+					TADC_CMP_THR1_CH1_CMP_HI_REG(chip);
+	chip->chans[TADC_THERM1].thr[1].addr_lo =
+					TADC_CMP_THR2_CH1_CMP_LO_REG(chip);
+	chip->chans[TADC_THERM1].thr[1].addr_hi =
+					TADC_CMP_THR2_CH1_CMP_HI_REG(chip);
+	chip->chans[TADC_THERM1].thr[2].addr_lo =
+					TADC_CMP_THR3_CH1_CMP_LO_REG(chip);
+	chip->chans[TADC_THERM1].thr[2].addr_hi =
+					TADC_CMP_THR3_CH1_CMP_HI_REG(chip);
+	chip->chans[TADC_THERM1].thr[3].addr_lo =
+					TADC_CMP_THR4_CH1_CMP_LO_REG(chip);
+	chip->chans[TADC_THERM1].thr[3].addr_hi =
+					TADC_CMP_THR4_CH1_CMP_HI_REG(chip);
+
+	chip->chans[TADC_THERM2].thr[0].addr_lo =
+					TADC_CMP_THR1_CH2_CMP_LO_REG(chip);
+	chip->chans[TADC_THERM2].thr[0].addr_hi =
+					TADC_CMP_THR1_CH2_CMP_HI_REG(chip);
+	chip->chans[TADC_THERM2].thr[1].addr_lo =
+					TADC_CMP_THR2_CH2_CMP_LO_REG(chip);
+	chip->chans[TADC_THERM2].thr[1].addr_hi =
+					TADC_CMP_THR2_CH2_CMP_HI_REG(chip);
+	chip->chans[TADC_THERM2].thr[2].addr_lo =
+					TADC_CMP_THR3_CH2_CMP_LO_REG(chip);
+	chip->chans[TADC_THERM2].thr[2].addr_hi =
+					TADC_CMP_THR3_CH2_CMP_HI_REG(chip);
+
+	chip->chans[TADC_DIE_TEMP].thr[0].addr_lo =
+					TADC_CMP_THR1_CH3_CMP_LO_REG(chip);
+	chip->chans[TADC_DIE_TEMP].thr[0].addr_hi =
+					TADC_CMP_THR1_CH3_CMP_HI_REG(chip);
+	chip->chans[TADC_DIE_TEMP].thr[1].addr_lo =
+					TADC_CMP_THR2_CH3_CMP_LO_REG(chip);
+	chip->chans[TADC_DIE_TEMP].thr[1].addr_hi =
+					TADC_CMP_THR2_CH3_CMP_HI_REG(chip);
+	chip->chans[TADC_DIE_TEMP].thr[2].addr_lo =
+					TADC_CMP_THR3_CH3_CMP_LO_REG(chip);
+	chip->chans[TADC_DIE_TEMP].thr[2].addr_hi =
+					TADC_CMP_THR3_CH3_CMP_HI_REG(chip);
+
+	rc = tadc_write(chip, TADC_CMP_THR1_CMP_REG(chip), 0);
+	if (rc < 0) {
+		pr_err("Couldn't enable hardware triggers rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = tadc_write(chip, TADC_CMP_THR2_CMP_REG(chip), 0);
+	if (rc < 0) {
+		pr_err("Couldn't enable hardware triggers rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = tadc_write(chip, TADC_CMP_THR3_CMP_REG(chip), 0);
+	if (rc < 0) {
+		pr_err("Couldn't enable hardware triggers rc=%d\n", rc);
+		return rc;
+	}
+
+	/* enable all temperature hardware triggers */
+	rc = tadc_write(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip),
+							BIT(TADC_THERM1) |
+							BIT(TADC_THERM2) |
+							BIT(TADC_DIE_TEMP));
+	if (rc < 0) {
+		pr_err("Couldn't enable hardware triggers rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static const struct iio_info tadc_info = {
+	.read_raw		= &tadc_read_raw,
+	.write_raw		= &tadc_write_raw,
+	.driver_module		= THIS_MODULE,
+};
+
+static int tadc_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct iio_dev *indio_dev;
+	struct tadc_chip *chip;
+	int rc, irq;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*chip));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	chip = iio_priv(indio_dev);
+	chip->dev = &pdev->dev;
+	init_completion(&chip->eoc_complete);
+
+	rc = of_property_read_u32(node, "reg", &chip->tadc_base);
+	if (rc < 0) {
+		pr_err("Couldn't read base address rc=%d\n", rc);
+		return rc;
+	}
+	chip->tadc_cmp_base = chip->tadc_base + 0x100;
+
+	mutex_init(&chip->write_lock);
+	chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
+	if (!chip->regmap) {
+		pr_err("Couldn't get regmap\n");
+		return -ENODEV;
+	}
+
+	rc = tadc_parse_dt(chip);
+	if (rc < 0) {
+		pr_err("Couldn't parse device tree rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = tadc_init_hw(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize hardware rc=%d\n", rc);
+		return rc;
+	}
+
+	irq = of_irq_get_byname(node, "eoc");
+	if (irq < 0) {
+		pr_err("Couldn't get eoc irq rc=%d\n", irq);
+		return irq;
+	}
+
+	rc = devm_request_threaded_irq(chip->dev, irq, NULL, handle_eoc,
+						IRQF_ONESHOT, "eoc", chip);
+	if (rc < 0) {
+		pr_err("Couldn't request irq %d rc=%d\n", irq, rc);
+		return rc;
+	}
+
+	indio_dev->dev.parent = chip->dev;
+	indio_dev->name = pdev->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &tadc_info;
+	indio_dev->channels = tadc_iio_chans;
+	indio_dev->num_channels = ARRAY_SIZE(tadc_iio_chans);
+
+	rc = devm_iio_device_register(chip->dev, indio_dev);
+	if (rc < 0) {
+		pr_err("Couldn't register IIO device rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int tadc_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static const struct of_device_id tadc_match_table[] = {
+	{ .compatible = "qcom,tadc" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tadc_match_table);
+
+static struct platform_driver tadc_driver = {
+	.driver	= {
+		.name		= "qcom-tadc",
+		.of_match_table	= tadc_match_table,
+	},
+	.probe	= tadc_probe,
+	.remove	= tadc_remove,
+};
+module_platform_driver(tadc_driver);
+
+MODULE_DESCRIPTION("Qualcomm Technologies Inc. TADC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 7a628c6..5f144a6 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -659,6 +659,43 @@
 	  This option enabled support for the LEDs on the Mellanox
 	  boards. Say Y to enabled these.
 
+config LEDS_QPNP
+	tristate "Support for QPNP LEDs"
+	depends on LEDS_CLASS && SPMI
+	help
+	  This driver supports the LED functionality of Qualcomm Technologies,
+	  Inc. QPNP PMICs.  It primarily supports controlling tri-color RGB
+	  LEDs in both PWM and light pattern generator (LPG) modes.  For older
+	  PMICs, it also supports WLEDs and flash LEDs.
+
+config LEDS_QPNP_FLASH
+	tristate "Support for QPNP Flash LEDs"
+	depends on LEDS_CLASS && SPMI
+	help
+	  This driver supports the flash LED functionality of Qualcomm
+	  Technologies, Inc. QPNP PMICs.  This driver supports PMICs up through
+	  PM8994.  It can configure the flash LED target current for several
+	  independent channels.
+
+config LEDS_QPNP_FLASH_V2
+	tristate "Support for QPNP V2 Flash LEDs"
+	depends on LEDS_CLASS && MFD_SPMI_PMIC && !LEDS_QPNP_FLASH
+	help
+	  This driver supports the flash V2 LED functionality of Qualcomm
+	  Technologies, Inc. QPNP PMICs.  This driver supports PMICs starting
+	  from PMI8998.  It can configure the flash LED target current for
+	  several independent channels.  It also supports various over current
+	  and over temperature mitigation features.
+
+config LEDS_QPNP_WLED
+	tristate "Support for QPNP WLED"
+	depends on LEDS_CLASS && SPMI
+	help
+	  This driver supports the WLED (White LED) functionality of Qualcomm
+	  Technologies, Inc. QPNP PMICs.  WLED is used for LCD backlight with
+	  variable brightness.  It also supports outputting the Avdd supply for
+	  AMOLED displays.
+
 comment "LED Triggers"
 source "drivers/leds/trigger/Kconfig"
 
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 3965070..e0ca2e8 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -71,6 +71,10 @@
 obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
 obj-$(CONFIG_LEDS_PM8058)		+= leds-pm8058.o
 obj-$(CONFIG_LEDS_MLXCPLD)		+= leds-mlxcpld.o
+obj-$(CONFIG_LEDS_QPNP)			+= leds-qpnp.o
+obj-$(CONFIG_LEDS_QPNP_FLASH)		+= leds-qpnp-flash.o
+obj-$(CONFIG_LEDS_QPNP_FLASH_V2)	+= leds-qpnp-flash-v2.o
+obj-$(CONFIG_LEDS_QPNP_WLED)		+= leds-qpnp-wled.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
diff --git a/drivers/leds/leds-qpnp-flash-v2.c b/drivers/leds/leds-qpnp-flash-v2.c
new file mode 100644
index 0000000..08809a9
--- /dev/null
+++ b/drivers/leds/leds-qpnp-flash-v2.c
@@ -0,0 +1,2265 @@
+/* Copyright (c) 2016-2017, 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)	"flashv2: %s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/gpio.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/regulator/consumer.h>
+#include <linux/leds-qpnp-flash.h>
+#include <linux/leds-qpnp-flash-v2.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/log2.h>
+#include "leds.h"
+
+#define	FLASH_LED_REG_LED_STATUS1(base)		(base + 0x08)
+#define	FLASH_LED_REG_LED_STATUS2(base)		(base + 0x09)
+#define	FLASH_LED_REG_INT_RT_STS(base)		(base + 0x10)
+#define	FLASH_LED_REG_SAFETY_TMR(base)		(base + 0x40)
+#define	FLASH_LED_REG_TGR_CURRENT(base)		(base + 0x43)
+#define	FLASH_LED_REG_MOD_CTRL(base)		(base + 0x46)
+#define	FLASH_LED_REG_IRES(base)		(base + 0x47)
+#define	FLASH_LED_REG_STROBE_CFG(base)		(base + 0x48)
+#define	FLASH_LED_REG_STROBE_CTRL(base)		(base + 0x49)
+#define	FLASH_LED_EN_LED_CTRL(base)		(base + 0x4C)
+#define	FLASH_LED_REG_HDRM_PRGM(base)		(base + 0x4D)
+#define	FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(base)	(base + 0x50)
+#define	FLASH_LED_REG_WARMUP_DELAY(base)	(base + 0x51)
+#define	FLASH_LED_REG_ISC_DELAY(base)		(base + 0x52)
+#define	FLASH_LED_REG_THERMAL_RMP_DN_RATE(base)	(base + 0x55)
+#define	FLASH_LED_REG_THERMAL_THRSH1(base)	(base + 0x56)
+#define	FLASH_LED_REG_THERMAL_THRSH2(base)	(base + 0x57)
+#define	FLASH_LED_REG_THERMAL_THRSH3(base)	(base + 0x58)
+#define	FLASH_LED_REG_THERMAL_HYSTERESIS(base)	(base + 0x59)
+#define	FLASH_LED_REG_THERMAL_DEBOUNCE(base)	(base + 0x5A)
+#define	FLASH_LED_REG_VPH_DROOP_THRESHOLD(base)	(base + 0x61)
+#define	FLASH_LED_REG_VPH_DROOP_DEBOUNCE(base)	(base + 0x62)
+#define	FLASH_LED_REG_ILED_GRT_THRSH(base)	(base + 0x67)
+#define	FLASH_LED_REG_LED1N2_ICLAMP_LOW(base)	(base + 0x68)
+#define	FLASH_LED_REG_LED1N2_ICLAMP_MID(base)	(base + 0x69)
+#define	FLASH_LED_REG_LED3_ICLAMP_LOW(base)	(base + 0x6A)
+#define	FLASH_LED_REG_LED3_ICLAMP_MID(base)	(base + 0x6B)
+#define	FLASH_LED_REG_MITIGATION_SEL(base)	(base + 0x6E)
+#define	FLASH_LED_REG_MITIGATION_SW(base)	(base + 0x6F)
+#define	FLASH_LED_REG_LMH_LEVEL(base)		(base + 0x70)
+#define	FLASH_LED_REG_CURRENT_DERATE_EN(base)	(base + 0x76)
+
+#define	FLASH_LED_HDRM_VOL_MASK			GENMASK(7, 4)
+#define	FLASH_LED_CURRENT_MASK			GENMASK(6, 0)
+#define	FLASH_LED_ENABLE_MASK			GENMASK(2, 0)
+#define	FLASH_HW_STROBE_MASK			GENMASK(2, 0)
+#define	FLASH_LED_ISC_WARMUP_DELAY_MASK		GENMASK(1, 0)
+#define	FLASH_LED_CURRENT_DERATE_EN_MASK	GENMASK(2, 0)
+#define	FLASH_LED_VPH_DROOP_DEBOUNCE_MASK	GENMASK(1, 0)
+#define	FLASH_LED_CHGR_MITIGATION_SEL_MASK	GENMASK(5, 4)
+#define	FLASH_LED_LMH_MITIGATION_SEL_MASK	GENMASK(1, 0)
+#define	FLASH_LED_ILED_GRT_THRSH_MASK		GENMASK(5, 0)
+#define	FLASH_LED_LMH_LEVEL_MASK		GENMASK(1, 0)
+#define	FLASH_LED_VPH_DROOP_HYSTERESIS_MASK	GENMASK(5, 4)
+#define	FLASH_LED_VPH_DROOP_THRESHOLD_MASK	GENMASK(2, 0)
+#define	FLASH_LED_THERMAL_HYSTERESIS_MASK	GENMASK(1, 0)
+#define	FLASH_LED_THERMAL_DEBOUNCE_MASK		GENMASK(1, 0)
+#define	FLASH_LED_THERMAL_THRSH_MASK		GENMASK(2, 0)
+#define	FLASH_LED_MOD_CTRL_MASK			BIT(7)
+#define	FLASH_LED_HW_SW_STROBE_SEL_BIT		BIT(2)
+#define	FLASH_LED_VPH_DROOP_FAULT_MASK		BIT(4)
+#define	FLASH_LED_LMH_MITIGATION_EN_MASK	BIT(0)
+#define	FLASH_LED_CHGR_MITIGATION_EN_MASK	BIT(4)
+#define	THERMAL_OTST1_RAMP_CTRL_MASK		BIT(7)
+#define	THERMAL_OTST1_RAMP_CTRL_SHIFT		7
+#define	THERMAL_DERATE_SLOW_SHIFT		4
+#define	THERMAL_DERATE_SLOW_MASK		GENMASK(6, 4)
+#define	THERMAL_DERATE_FAST_MASK		GENMASK(2, 0)
+
+#define	VPH_DROOP_DEBOUNCE_US_TO_VAL(val_us)	(val_us / 8)
+#define	VPH_DROOP_HYST_MV_TO_VAL(val_mv)	(val_mv / 25)
+#define	VPH_DROOP_THRESH_MV_TO_VAL(val_mv)	((val_mv / 100) - 25)
+#define	VPH_DROOP_THRESH_VAL_TO_UV(val)		((val + 25) * 100000)
+#define	MITIGATION_THRSH_MA_TO_VAL(val_ma)	(val_ma / 100)
+#define	CURRENT_MA_TO_REG_VAL(curr_ma, ires_ua)	((curr_ma * 1000) / ires_ua - 1)
+#define	SAFETY_TMR_TO_REG_VAL(duration_ms)	((duration_ms / 10) - 1)
+#define	THERMAL_HYST_TEMP_TO_VAL(val, divisor)	(val / divisor)
+
+#define	FLASH_LED_ISC_WARMUP_DELAY_SHIFT	6
+#define	FLASH_LED_WARMUP_DELAY_DEFAULT		2
+#define	FLASH_LED_ISC_DELAY_DEFAULT		3
+#define	FLASH_LED_VPH_DROOP_DEBOUNCE_DEFAULT	2
+#define	FLASH_LED_VPH_DROOP_HYST_SHIFT		4
+#define	FLASH_LED_VPH_DROOP_HYST_DEFAULT	2
+#define	FLASH_LED_VPH_DROOP_THRESH_DEFAULT	5
+#define	FLASH_LED_DEBOUNCE_MAX			3
+#define	FLASH_LED_HYSTERESIS_MAX		3
+#define	FLASH_LED_VPH_DROOP_THRESH_MAX		7
+#define	THERMAL_DERATE_SLOW_MAX			314592
+#define	THERMAL_DERATE_FAST_MAX			512
+#define	THERMAL_DEBOUNCE_TIME_MAX		64
+#define	THERMAL_DERATE_HYSTERESIS_MAX		3
+#define	FLASH_LED_THERMAL_THRSH_MIN		3
+#define	FLASH_LED_THERMAL_THRSH_MAX		7
+#define	FLASH_LED_THERMAL_OTST_LEVELS		3
+#define	FLASH_LED_VLED_MAX_DEFAULT_UV		3500000
+#define	FLASH_LED_IBATT_OCP_THRESH_DEFAULT_UA	4500000
+#define	FLASH_LED_RPARA_DEFAULT_UOHM		0
+#define	FLASH_LED_SAFETY_TMR_ENABLE		BIT(7)
+#define	FLASH_LED_LMH_LEVEL_DEFAULT		0
+#define	FLASH_LED_LMH_MITIGATION_ENABLE		1
+#define	FLASH_LED_LMH_MITIGATION_DISABLE	0
+#define	FLASH_LED_CHGR_MITIGATION_ENABLE	BIT(4)
+#define	FLASH_LED_CHGR_MITIGATION_DISABLE	0
+#define	FLASH_LED_MITIGATION_SEL_DEFAULT	2
+#define	FLASH_LED_MITIGATION_SEL_MAX		2
+#define	FLASH_LED_CHGR_MITIGATION_SEL_SHIFT	4
+#define	FLASH_LED_MITIGATION_THRSH_DEFAULT	0xA
+#define	FLASH_LED_MITIGATION_THRSH_MAX		0x1F
+#define	FLASH_LED_LMH_OCV_THRESH_DEFAULT_UV	3700000
+#define	FLASH_LED_LMH_RBATT_THRESH_DEFAULT_UOHM	400000
+#define	FLASH_LED_IRES_BASE			3
+#define	FLASH_LED_IRES_DIVISOR			2500
+#define	FLASH_LED_IRES_MIN_UA			5000
+#define	FLASH_LED_IRES_DEFAULT_UA		12500
+#define	FLASH_LED_IRES_DEFAULT_VAL		0x00
+#define	FLASH_LED_HDRM_VOL_SHIFT		4
+#define	FLASH_LED_HDRM_VOL_DEFAULT_MV		0x80
+#define	FLASH_LED_HDRM_VOL_HI_LO_WIN_DEFAULT_MV	0x04
+#define	FLASH_LED_HDRM_VOL_BASE_MV		125
+#define	FLASH_LED_HDRM_VOL_STEP_MV		25
+#define	FLASH_LED_STROBE_CFG_DEFAULT		0x00
+#define	FLASH_LED_HW_STROBE_OPTION_1		0x00
+#define	FLASH_LED_HW_STROBE_OPTION_2		0x01
+#define	FLASH_LED_HW_STROBE_OPTION_3		0x02
+#define	FLASH_LED_ENABLE			BIT(0)
+#define	FLASH_LED_MOD_ENABLE			BIT(7)
+#define	FLASH_LED_DISABLE			0x00
+#define	FLASH_LED_SAFETY_TMR_DISABLED		0x13
+#define	FLASH_LED_MIN_CURRENT_MA		25
+#define	FLASH_LED_MAX_TOTAL_CURRENT_MA		3750
+
+/* notifier call chain for flash-led irqs */
+static ATOMIC_NOTIFIER_HEAD(irq_notifier_list);
+
+enum flash_led_type {
+	FLASH_LED_TYPE_FLASH,
+	FLASH_LED_TYPE_TORCH,
+};
+
+enum {
+	LED1 = 0,
+	LED2,
+	LED3,
+};
+
+/*
+ * Configurations for each individual LED
+ */
+struct flash_node_data {
+	struct platform_device		*pdev;
+	struct led_classdev		cdev;
+	struct pinctrl			*pinctrl;
+	struct pinctrl_state		*gpio_state_active;
+	struct pinctrl_state		*gpio_state_suspend;
+	struct pinctrl_state		*hw_strobe_state_active;
+	struct pinctrl_state		*hw_strobe_state_suspend;
+	int				hw_strobe_gpio;
+	int				ires_ua;
+	int				max_current;
+	int				current_ma;
+	u8				duration;
+	u8				id;
+	u8				type;
+	u8				ires;
+	u8				hdrm_val;
+	u8				current_reg_val;
+	u8				trigger;
+	bool				led_on;
+};
+
+
+struct flash_switch_data {
+	struct platform_device		*pdev;
+	struct regulator		*vreg;
+	struct led_classdev		cdev;
+	int				led_mask;
+	bool				regulator_on;
+	bool				enabled;
+};
+
+/*
+ * Flash LED configuration read from device tree
+ */
+struct flash_led_platform_data {
+	struct pmic_revid_data	*pmic_rev_id;
+	int			*thermal_derate_current;
+	int			all_ramp_up_done_irq;
+	int			all_ramp_down_done_irq;
+	int			led_fault_irq;
+	int			ibatt_ocp_threshold_ua;
+	int			vled_max_uv;
+	int			rpara_uohm;
+	int			lmh_rbatt_threshold_uohm;
+	int			lmh_ocv_threshold_uv;
+	int			thermal_derate_slow;
+	int			thermal_derate_fast;
+	int			thermal_hysteresis;
+	int			thermal_debounce;
+	int			thermal_thrsh1;
+	int			thermal_thrsh2;
+	int			thermal_thrsh3;
+	u32			led1n2_iclamp_low_ma;
+	u32			led1n2_iclamp_mid_ma;
+	u32			led3_iclamp_low_ma;
+	u32			led3_iclamp_mid_ma;
+	u8			isc_delay;
+	u8			warmup_delay;
+	u8			current_derate_en_cfg;
+	u8			vph_droop_threshold;
+	u8			vph_droop_hysteresis;
+	u8			vph_droop_debounce;
+	u8			lmh_mitigation_sel;
+	u8			chgr_mitigation_sel;
+	u8			lmh_level;
+	u8			iled_thrsh_val;
+	u8			hw_strobe_option;
+	bool			hdrm_auto_mode_en;
+	bool			thermal_derate_en;
+	bool			otst_ramp_bkup_en;
+};
+
+/*
+ * Flash LED data structure containing flash LED attributes
+ */
+struct qpnp_flash_led {
+	struct flash_led_platform_data	*pdata;
+	struct platform_device		*pdev;
+	struct regmap			*regmap;
+	struct flash_node_data		*fnode;
+	struct flash_switch_data	*snode;
+	struct power_supply		*bms_psy;
+	struct notifier_block		nb;
+	spinlock_t			lock;
+	int				num_fnodes;
+	int				num_snodes;
+	int				enable;
+	u16				base;
+	bool				trigger_lmh;
+	bool				trigger_chgr;
+};
+
+static int thermal_derate_slow_table[] = {
+	128, 256, 512, 1024, 2048, 4096, 8192, 314592,
+};
+
+static int thermal_derate_fast_table[] = {
+	32, 64, 96, 128, 256, 384, 512,
+};
+
+static int otst1_threshold_table[] = {
+	85, 79, 73, 67, 109, 103, 97, 91,
+};
+
+static int otst2_threshold_table[] = {
+	110, 104, 98, 92, 134, 128, 122, 116,
+};
+
+static int otst3_threshold_table[] = {
+	125, 119, 113, 107, 149, 143, 137, 131,
+};
+
+static int qpnp_flash_led_read(struct qpnp_flash_led *led, u16 addr, u8 *data)
+{
+	int rc;
+	uint val;
+
+	rc = regmap_read(led->regmap, addr, &val);
+	if (rc < 0) {
+		pr_err("Unable to read from 0x%04X rc = %d\n", addr, rc);
+		return rc;
+	}
+
+	pr_debug("Read 0x%02X from addr 0x%04X\n", val, addr);
+	*data = (u8)val;
+	return 0;
+}
+
+static int qpnp_flash_led_write(struct qpnp_flash_led *led, u16 addr, u8 data)
+{
+	int rc;
+
+	rc = regmap_write(led->regmap, addr, data);
+	if (rc < 0) {
+		pr_err("Unable to write to 0x%04X rc = %d\n", addr, rc);
+		return rc;
+	}
+
+	pr_debug("Wrote 0x%02X to addr 0x%04X\n", data, addr);
+	return 0;
+}
+
+static int
+qpnp_flash_led_masked_read(struct qpnp_flash_led *led, u16 addr, u8 mask,
+								u8 *val)
+{
+	int rc;
+
+	rc = qpnp_flash_led_read(led, addr, val);
+	if (rc < 0)
+		return rc;
+
+	*val &= mask;
+	return rc;
+}
+
+static int
+qpnp_flash_led_masked_write(struct qpnp_flash_led *led, u16 addr, u8 mask,
+								u8 val)
+{
+	int rc;
+
+	rc = regmap_update_bits(led->regmap, addr, mask, val);
+	if (rc < 0)
+		pr_err("Unable to update bits from 0x%04X, rc = %d\n", addr,
+			rc);
+	else
+		pr_debug("Wrote 0x%02X to addr 0x%04X\n", val, addr);
+
+	return rc;
+}
+
+static enum
+led_brightness qpnp_flash_led_brightness_get(struct led_classdev *led_cdev)
+{
+	return led_cdev->brightness;
+}
+
+static int qpnp_flash_led_init_settings(struct qpnp_flash_led *led)
+{
+	int rc, i, addr_offset;
+	u8 val = 0, mask;
+
+	for (i = 0; i < led->num_fnodes; i++) {
+		addr_offset = led->fnode[i].id;
+		rc = qpnp_flash_led_write(led,
+			FLASH_LED_REG_HDRM_PRGM(led->base + addr_offset),
+			led->fnode[i].hdrm_val);
+		if (rc < 0)
+			return rc;
+
+		val |= 0x1 << led->fnode[i].id;
+	}
+
+	rc = qpnp_flash_led_write(led,
+				FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(led->base),
+				val);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_ISC_DELAY(led->base),
+			FLASH_LED_ISC_WARMUP_DELAY_MASK,
+			led->pdata->isc_delay);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_WARMUP_DELAY(led->base),
+			FLASH_LED_ISC_WARMUP_DELAY_MASK,
+			led->pdata->warmup_delay);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_CURRENT_DERATE_EN(led->base),
+			FLASH_LED_CURRENT_DERATE_EN_MASK,
+			led->pdata->current_derate_en_cfg);
+	if (rc < 0)
+		return rc;
+
+	val = (led->pdata->otst_ramp_bkup_en << THERMAL_OTST1_RAMP_CTRL_SHIFT);
+	mask = THERMAL_OTST1_RAMP_CTRL_MASK;
+	if (led->pdata->thermal_derate_slow >= 0) {
+		val |= (led->pdata->thermal_derate_slow <<
+				THERMAL_DERATE_SLOW_SHIFT);
+		mask |= THERMAL_DERATE_SLOW_MASK;
+	}
+
+	if (led->pdata->thermal_derate_fast >= 0) {
+		val |= led->pdata->thermal_derate_fast;
+		mask |= THERMAL_DERATE_FAST_MASK;
+	}
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_THERMAL_RMP_DN_RATE(led->base),
+			mask, val);
+	if (rc < 0)
+		return rc;
+
+	if (led->pdata->thermal_debounce >= 0) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_THERMAL_DEBOUNCE(led->base),
+				FLASH_LED_THERMAL_DEBOUNCE_MASK,
+				led->pdata->thermal_debounce);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (led->pdata->thermal_hysteresis >= 0) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_THERMAL_HYSTERESIS(led->base),
+				FLASH_LED_THERMAL_HYSTERESIS_MASK,
+				led->pdata->thermal_hysteresis);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (led->pdata->thermal_thrsh1 >= 0) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_THERMAL_THRSH1(led->base),
+				FLASH_LED_THERMAL_THRSH_MASK,
+				led->pdata->thermal_thrsh1);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (led->pdata->thermal_thrsh2 >= 0) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_THERMAL_THRSH2(led->base),
+				FLASH_LED_THERMAL_THRSH_MASK,
+				led->pdata->thermal_thrsh2);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (led->pdata->thermal_thrsh3 >= 0) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_THERMAL_THRSH3(led->base),
+				FLASH_LED_THERMAL_THRSH_MASK,
+				led->pdata->thermal_thrsh3);
+		if (rc < 0)
+			return rc;
+	}
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_VPH_DROOP_DEBOUNCE(led->base),
+			FLASH_LED_VPH_DROOP_DEBOUNCE_MASK,
+			led->pdata->vph_droop_debounce);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_VPH_DROOP_THRESHOLD(led->base),
+			FLASH_LED_VPH_DROOP_THRESHOLD_MASK,
+			led->pdata->vph_droop_threshold);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_VPH_DROOP_THRESHOLD(led->base),
+			FLASH_LED_VPH_DROOP_HYSTERESIS_MASK,
+			led->pdata->vph_droop_hysteresis);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_MITIGATION_SEL(led->base),
+			FLASH_LED_LMH_MITIGATION_SEL_MASK,
+			led->pdata->lmh_mitigation_sel);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_MITIGATION_SEL(led->base),
+			FLASH_LED_CHGR_MITIGATION_SEL_MASK,
+			led->pdata->chgr_mitigation_sel);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_LMH_LEVEL(led->base),
+			FLASH_LED_LMH_LEVEL_MASK,
+			led->pdata->lmh_level);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_ILED_GRT_THRSH(led->base),
+			FLASH_LED_ILED_GRT_THRSH_MASK,
+			led->pdata->iled_thrsh_val);
+	if (rc < 0)
+		return rc;
+
+	if (led->pdata->led1n2_iclamp_low_ma) {
+		val = CURRENT_MA_TO_REG_VAL(led->pdata->led1n2_iclamp_low_ma,
+						led->fnode[0].ires_ua);
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_LED1N2_ICLAMP_LOW(led->base),
+				FLASH_LED_CURRENT_MASK, val);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (led->pdata->led1n2_iclamp_mid_ma) {
+		val = CURRENT_MA_TO_REG_VAL(led->pdata->led1n2_iclamp_mid_ma,
+						led->fnode[0].ires_ua);
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_LED1N2_ICLAMP_MID(led->base),
+				FLASH_LED_CURRENT_MASK, val);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (led->pdata->led3_iclamp_low_ma) {
+		val = CURRENT_MA_TO_REG_VAL(led->pdata->led3_iclamp_low_ma,
+						led->fnode[3].ires_ua);
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_LED3_ICLAMP_LOW(led->base),
+				FLASH_LED_CURRENT_MASK, val);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (led->pdata->led3_iclamp_mid_ma) {
+		val = CURRENT_MA_TO_REG_VAL(led->pdata->led3_iclamp_mid_ma,
+						led->fnode[3].ires_ua);
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_LED3_ICLAMP_MID(led->base),
+				FLASH_LED_CURRENT_MASK, val);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static int qpnp_flash_led_hw_strobe_enable(struct flash_node_data *fnode,
+						int hw_strobe_option, bool on)
+{
+	int rc = 0;
+
+	/*
+	 * If the LED controlled by this fnode is not GPIO controlled
+	 * for the given strobe_option, return.
+	 */
+	if (hw_strobe_option == FLASH_LED_HW_STROBE_OPTION_1)
+		return 0;
+	else if (hw_strobe_option == FLASH_LED_HW_STROBE_OPTION_2
+						&& fnode->id != LED3)
+		return 0;
+	else if (hw_strobe_option == FLASH_LED_HW_STROBE_OPTION_3
+						&& fnode->id == LED1)
+		return 0;
+
+	if (gpio_is_valid(fnode->hw_strobe_gpio)) {
+		gpio_set_value(fnode->hw_strobe_gpio, on ? 1 : 0);
+	} else if (fnode->hw_strobe_state_active &&
+					fnode->hw_strobe_state_suspend) {
+		rc = pinctrl_select_state(fnode->pinctrl,
+			on ? fnode->hw_strobe_state_active :
+			fnode->hw_strobe_state_suspend);
+		if (rc < 0) {
+			pr_err("failed to change hw strobe pin state\n");
+			return rc;
+		}
+	}
+
+	return rc;
+}
+
+static int qpnp_flash_led_regulator_enable(struct qpnp_flash_led *led,
+				struct flash_switch_data *snode, bool on)
+{
+	int rc = 0;
+
+	if (!snode || !snode->vreg)
+		return 0;
+
+	if (snode->regulator_on == on)
+		return 0;
+
+	if (on)
+		rc = regulator_enable(snode->vreg);
+	else
+		rc = regulator_disable(snode->vreg);
+
+	if (rc < 0) {
+		pr_err("regulator_%s failed, rc=%d\n",
+			on ? "enable" : "disable", rc);
+		return rc;
+	}
+
+	snode->regulator_on = on ? true : false;
+	return 0;
+}
+
+static int get_property_from_fg(struct qpnp_flash_led *led,
+		enum power_supply_property prop, int *val)
+{
+	int rc;
+	union power_supply_propval pval = {0, };
+
+	if (!led->bms_psy) {
+		pr_err("no bms psy found\n");
+		return -EINVAL;
+	}
+
+	rc = power_supply_get_property(led->bms_psy, prop, &pval);
+	if (rc) {
+		pr_err("bms psy doesn't support reading prop %d rc = %d\n",
+			prop, rc);
+		return rc;
+	}
+
+	*val = pval.intval;
+	return rc;
+}
+
+#define VOLTAGE_HDRM_DEFAULT_MV	350
+static int qpnp_flash_led_get_voltage_headroom(struct qpnp_flash_led *led)
+{
+	int i, voltage_hdrm_mv = 0, voltage_hdrm_max = 0;
+
+	for (i = 0; i < led->num_fnodes; i++) {
+		if (led->fnode[i].led_on) {
+			if (led->fnode[i].id < 2) {
+				if (led->fnode[i].current_ma < 750)
+					voltage_hdrm_mv = 125;
+				else if (led->fnode[i].current_ma < 1000)
+					voltage_hdrm_mv = 175;
+				else if (led->fnode[i].current_ma < 1250)
+					voltage_hdrm_mv = 250;
+				else
+					voltage_hdrm_mv = 350;
+			} else {
+				if (led->fnode[i].current_ma < 375)
+					voltage_hdrm_mv = 125;
+				else if (led->fnode[i].current_ma < 500)
+					voltage_hdrm_mv = 175;
+				else if (led->fnode[i].current_ma < 625)
+					voltage_hdrm_mv = 250;
+				else
+					voltage_hdrm_mv = 350;
+			}
+
+			voltage_hdrm_max = max(voltage_hdrm_max,
+						voltage_hdrm_mv);
+		}
+	}
+
+	if (!voltage_hdrm_max)
+		return VOLTAGE_HDRM_DEFAULT_MV;
+
+	return voltage_hdrm_max;
+}
+
+#define UCONV			1000000LL
+#define MCONV			1000LL
+#define FLASH_VDIP_MARGIN	50000
+#define BOB_EFFICIENCY		900LL
+#define VIN_FLASH_MIN_UV	3300000LL
+static int qpnp_flash_led_calc_max_current(struct qpnp_flash_led *led)
+{
+	int ocv_uv, rbatt_uohm, ibat_now, voltage_hdrm_mv, rc;
+	int64_t ibat_flash_ua, avail_flash_ua, avail_flash_power_fw;
+	int64_t ibat_safe_ua, vin_flash_uv, vph_flash_uv, vph_flash_vdip;
+
+	/* RESISTANCE = esr_uohm + rslow_uohm */
+	rc = get_property_from_fg(led, POWER_SUPPLY_PROP_RESISTANCE,
+			&rbatt_uohm);
+	if (rc < 0) {
+		pr_err("bms psy does not support resistance, rc=%d\n", rc);
+		return rc;
+	}
+
+	/* If no battery is connected, return max possible flash current */
+	if (!rbatt_uohm)
+		return FLASH_LED_MAX_TOTAL_CURRENT_MA;
+
+	rc = get_property_from_fg(led, POWER_SUPPLY_PROP_VOLTAGE_OCV, &ocv_uv);
+	if (rc < 0) {
+		pr_err("bms psy does not support OCV, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = get_property_from_fg(led, POWER_SUPPLY_PROP_CURRENT_NOW,
+			&ibat_now);
+	if (rc < 0) {
+		pr_err("bms psy does not support current, rc=%d\n", rc);
+		return rc;
+	}
+
+	rbatt_uohm += led->pdata->rpara_uohm;
+	voltage_hdrm_mv = qpnp_flash_led_get_voltage_headroom(led);
+	vph_flash_vdip =
+		VPH_DROOP_THRESH_VAL_TO_UV(led->pdata->vph_droop_threshold)
+							+ FLASH_VDIP_MARGIN;
+
+	/* Check if LMH_MITIGATION needs to be triggered */
+	if (!led->trigger_lmh && (ocv_uv < led->pdata->lmh_ocv_threshold_uv ||
+			rbatt_uohm > led->pdata->lmh_rbatt_threshold_uohm)) {
+		led->trigger_lmh = true;
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_MITIGATION_SW(led->base),
+				FLASH_LED_LMH_MITIGATION_EN_MASK,
+				FLASH_LED_LMH_MITIGATION_ENABLE);
+		if (rc < 0) {
+			pr_err("trigger lmh mitigation failed, rc=%d\n", rc);
+			return rc;
+		}
+
+		/* Wait for LMH mitigation to take effect */
+		udelay(100);
+
+		return qpnp_flash_led_calc_max_current(led);
+	}
+
+	/*
+	 * Calculate the maximum current that can pulled out of the battery
+	 * before the battery voltage dips below a safe threshold.
+	 */
+	ibat_safe_ua = div_s64((ocv_uv - vph_flash_vdip) * UCONV,
+				rbatt_uohm);
+
+	if (ibat_safe_ua <= led->pdata->ibatt_ocp_threshold_ua) {
+		/*
+		 * If the calculated current is below the OCP threshold, then
+		 * use it as the possible flash current.
+		 */
+		ibat_flash_ua = ibat_safe_ua - ibat_now;
+		vph_flash_uv = vph_flash_vdip;
+	} else {
+		/*
+		 * If the calculated current is above the OCP threshold, then
+		 * use the ocp threshold instead.
+		 *
+		 * Any higher current will be tripping the battery OCP.
+		 */
+		ibat_flash_ua = led->pdata->ibatt_ocp_threshold_ua - ibat_now;
+		vph_flash_uv = ocv_uv - div64_s64((int64_t)rbatt_uohm
+				* led->pdata->ibatt_ocp_threshold_ua, UCONV);
+	}
+	/* Calculate the input voltage of the flash module. */
+	vin_flash_uv = max((led->pdata->vled_max_uv +
+				(voltage_hdrm_mv * MCONV)), VIN_FLASH_MIN_UV);
+	/* Calculate the available power for the flash module. */
+	avail_flash_power_fw = BOB_EFFICIENCY * vph_flash_uv * ibat_flash_ua;
+	/*
+	 * Calculate the available amount of current the flash module can draw
+	 * before collapsing the battery. (available power/ flash input voltage)
+	 */
+	avail_flash_ua = div64_s64(avail_flash_power_fw, vin_flash_uv * MCONV);
+	pr_debug("avail_iflash=%lld, ocv=%d, ibat=%d, rbatt=%d, trigger_lmh=%d\n",
+		avail_flash_ua, ocv_uv, ibat_now, rbatt_uohm, led->trigger_lmh);
+	return min(FLASH_LED_MAX_TOTAL_CURRENT_MA,
+			(int)(div64_s64(avail_flash_ua, MCONV)));
+}
+
+static int qpnp_flash_led_calc_thermal_current_lim(struct qpnp_flash_led *led)
+{
+	int thermal_current_lim = 0;
+	int rc;
+	u8 thermal_thrsh1, thermal_thrsh2, thermal_thrsh3, otst_status;
+
+	/* Store THERMAL_THRSHx register values */
+	rc = qpnp_flash_led_masked_read(led,
+			FLASH_LED_REG_THERMAL_THRSH1(led->base),
+			FLASH_LED_THERMAL_THRSH_MASK,
+			&thermal_thrsh1);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_read(led,
+			FLASH_LED_REG_THERMAL_THRSH2(led->base),
+			FLASH_LED_THERMAL_THRSH_MASK,
+			&thermal_thrsh2);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_read(led,
+			FLASH_LED_REG_THERMAL_THRSH3(led->base),
+			FLASH_LED_THERMAL_THRSH_MASK,
+			&thermal_thrsh3);
+	if (rc < 0)
+		return rc;
+
+	/* Lower THERMAL_THRSHx thresholds to minimum */
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_THERMAL_THRSH1(led->base),
+			FLASH_LED_THERMAL_THRSH_MASK,
+			FLASH_LED_THERMAL_THRSH_MIN);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_THERMAL_THRSH2(led->base),
+			FLASH_LED_THERMAL_THRSH_MASK,
+			FLASH_LED_THERMAL_THRSH_MIN);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_THERMAL_THRSH3(led->base),
+			FLASH_LED_THERMAL_THRSH_MASK,
+			FLASH_LED_THERMAL_THRSH_MIN);
+	if (rc < 0)
+		return rc;
+
+	/* Check THERMAL_OTST status */
+	rc = qpnp_flash_led_read(led,
+			FLASH_LED_REG_LED_STATUS2(led->base),
+			&otst_status);
+	if (rc < 0)
+		return rc;
+
+	/* Look up current limit based on THERMAL_OTST status */
+	if (otst_status)
+		thermal_current_lim =
+			led->pdata->thermal_derate_current[otst_status >> 1];
+
+	/* Restore THERMAL_THRESHx registers to original values */
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_THERMAL_THRSH1(led->base),
+			FLASH_LED_THERMAL_THRSH_MASK,
+			thermal_thrsh1);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_THERMAL_THRSH2(led->base),
+			FLASH_LED_THERMAL_THRSH_MASK,
+			thermal_thrsh2);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_THERMAL_THRSH3(led->base),
+			FLASH_LED_THERMAL_THRSH_MASK,
+			thermal_thrsh3);
+	if (rc < 0)
+		return rc;
+
+	return thermal_current_lim;
+}
+
+static int qpnp_flash_led_get_max_avail_current(struct qpnp_flash_led *led)
+{
+	int max_avail_current, thermal_current_lim = 0;
+
+	led->trigger_lmh = false;
+	max_avail_current = qpnp_flash_led_calc_max_current(led);
+	if (led->pdata->thermal_derate_en)
+		thermal_current_lim =
+			qpnp_flash_led_calc_thermal_current_lim(led);
+
+	if (thermal_current_lim)
+		max_avail_current = min(max_avail_current, thermal_current_lim);
+
+	return max_avail_current;
+}
+
+static void qpnp_flash_led_node_set(struct flash_node_data *fnode, int value)
+{
+	int prgm_current_ma = value;
+
+	if (value <= 0)
+		prgm_current_ma = 0;
+	else if (value < FLASH_LED_MIN_CURRENT_MA)
+		prgm_current_ma = FLASH_LED_MIN_CURRENT_MA;
+
+	prgm_current_ma = min(prgm_current_ma, fnode->max_current);
+	fnode->current_ma = prgm_current_ma;
+	fnode->cdev.brightness = prgm_current_ma;
+	fnode->current_reg_val = CURRENT_MA_TO_REG_VAL(prgm_current_ma,
+					fnode->ires_ua);
+	fnode->led_on = prgm_current_ma != 0;
+}
+
+static int qpnp_flash_led_switch_disable(struct flash_switch_data *snode)
+{
+	struct qpnp_flash_led *led = dev_get_drvdata(&snode->pdev->dev);
+	int i, rc, addr_offset;
+
+	rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_EN_LED_CTRL(led->base),
+				snode->led_mask, FLASH_LED_DISABLE);
+	if (rc < 0)
+		return rc;
+
+	if (led->trigger_lmh) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_MITIGATION_SW(led->base),
+				FLASH_LED_LMH_MITIGATION_EN_MASK,
+				FLASH_LED_LMH_MITIGATION_DISABLE);
+		if (rc < 0) {
+			pr_err("disable lmh mitigation failed, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (!led->trigger_chgr) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_MITIGATION_SW(led->base),
+				FLASH_LED_CHGR_MITIGATION_EN_MASK,
+				FLASH_LED_CHGR_MITIGATION_DISABLE);
+		if (rc < 0) {
+			pr_err("disable chgr mitigation failed, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	led->enable--;
+	if (led->enable == 0) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_MOD_CTRL(led->base),
+				FLASH_LED_MOD_CTRL_MASK, FLASH_LED_DISABLE);
+		if (rc < 0)
+			return rc;
+	}
+
+	for (i = 0; i < led->num_fnodes; i++) {
+		if (!led->fnode[i].led_on ||
+				!(snode->led_mask & BIT(led->fnode[i].id)))
+			continue;
+
+		addr_offset = led->fnode[i].id;
+		rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_TGR_CURRENT(led->base + addr_offset),
+			FLASH_LED_CURRENT_MASK, 0);
+		if (rc < 0)
+			return rc;
+
+		led->fnode[i].led_on = false;
+
+		if (led->fnode[i].pinctrl) {
+			rc = pinctrl_select_state(led->fnode[i].pinctrl,
+					led->fnode[i].gpio_state_suspend);
+			if (rc < 0) {
+				pr_err("failed to disable GPIO, rc=%d\n", rc);
+				return rc;
+			}
+		}
+
+		if (led->fnode[i].trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT) {
+			rc = qpnp_flash_led_hw_strobe_enable(&led->fnode[i],
+					led->pdata->hw_strobe_option, false);
+			if (rc < 0) {
+				pr_err("Unable to disable hw strobe, rc=%d\n",
+					rc);
+				return rc;
+			}
+		}
+	}
+
+	snode->enabled = false;
+	return 0;
+}
+
+static int qpnp_flash_led_switch_set(struct flash_switch_data *snode, bool on)
+{
+	struct qpnp_flash_led *led = dev_get_drvdata(&snode->pdev->dev);
+	int rc, i, addr_offset;
+	u8 val, mask;
+
+	if (snode->enabled == on) {
+		pr_debug("Switch node is already %s!\n",
+			on ? "enabled" : "disabled");
+		return 0;
+	}
+
+	if (!on) {
+		rc = qpnp_flash_led_switch_disable(snode);
+		return rc;
+	}
+
+	/* Iterate over all leds for this switch node */
+	val = 0;
+	for (i = 0; i < led->num_fnodes; i++)
+		if (snode->led_mask & BIT(led->fnode[i].id))
+			val |= led->fnode[i].ires << (led->fnode[i].id * 2);
+
+	rc = qpnp_flash_led_masked_write(led, FLASH_LED_REG_IRES(led->base),
+						FLASH_LED_CURRENT_MASK, val);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_masked_write(led,
+					FLASH_LED_REG_STROBE_CFG(led->base),
+					FLASH_LED_ENABLE_MASK,
+					led->pdata->hw_strobe_option);
+	if (rc < 0)
+		return rc;
+
+	val = 0;
+	for (i = 0; i < led->num_fnodes; i++) {
+		if (!led->fnode[i].led_on ||
+				!(snode->led_mask & BIT(led->fnode[i].id)))
+			continue;
+
+		addr_offset = led->fnode[i].id;
+		if (led->fnode[i].trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT)
+			mask = FLASH_HW_STROBE_MASK;
+		else
+			mask = FLASH_LED_HW_SW_STROBE_SEL_BIT;
+		rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_STROBE_CTRL(led->base + addr_offset),
+			mask, led->fnode[i].trigger);
+		if (rc < 0)
+			return rc;
+
+		rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_TGR_CURRENT(led->base + addr_offset),
+			FLASH_LED_CURRENT_MASK, led->fnode[i].current_reg_val);
+		if (rc < 0)
+			return rc;
+
+		rc = qpnp_flash_led_write(led,
+			FLASH_LED_REG_SAFETY_TMR(led->base + addr_offset),
+			led->fnode[i].duration);
+		if (rc < 0)
+			return rc;
+
+		val |= FLASH_LED_ENABLE << led->fnode[i].id;
+
+		if (led->fnode[i].pinctrl) {
+			rc = pinctrl_select_state(led->fnode[i].pinctrl,
+					led->fnode[i].gpio_state_active);
+			if (rc < 0) {
+				pr_err("failed to enable GPIO rc=%d\n", rc);
+				return rc;
+			}
+		}
+
+		if (led->fnode[i].trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT) {
+			rc = qpnp_flash_led_hw_strobe_enable(&led->fnode[i],
+					led->pdata->hw_strobe_option, true);
+			if (rc < 0) {
+				pr_err("Unable to enable hw strobe rc=%d\n",
+					rc);
+				return rc;
+			}
+		}
+	}
+
+	if (led->enable == 0) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_MOD_CTRL(led->base),
+				FLASH_LED_MOD_CTRL_MASK, FLASH_LED_MOD_ENABLE);
+		if (rc < 0)
+			return rc;
+	}
+	led->enable++;
+
+	if (led->trigger_lmh) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_MITIGATION_SW(led->base),
+				FLASH_LED_LMH_MITIGATION_EN_MASK,
+				FLASH_LED_LMH_MITIGATION_ENABLE);
+		if (rc < 0) {
+			pr_err("trigger lmh mitigation failed, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (led->trigger_chgr) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_MITIGATION_SW(led->base),
+				FLASH_LED_CHGR_MITIGATION_EN_MASK,
+				FLASH_LED_CHGR_MITIGATION_ENABLE);
+		if (rc < 0) {
+			pr_err("trigger chgr mitigation failed, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	rc = qpnp_flash_led_masked_write(led,
+					FLASH_LED_EN_LED_CTRL(led->base),
+					snode->led_mask, val);
+	if (rc < 0)
+		return rc;
+
+	snode->enabled = true;
+	return 0;
+}
+
+int qpnp_flash_led_prepare(struct led_trigger *trig, int options,
+					int *max_current)
+{
+	struct led_classdev *led_cdev;
+	struct flash_switch_data *snode;
+	struct qpnp_flash_led *led;
+	int rc;
+
+	if (!trig) {
+		pr_err("Invalid led_trigger provided\n");
+		return -EINVAL;
+	}
+
+	led_cdev = trigger_to_lcdev(trig);
+	if (!led_cdev) {
+		pr_err("Invalid led_cdev in trigger %s\n", trig->name);
+		return -EINVAL;
+	}
+
+	snode = container_of(led_cdev, struct flash_switch_data, cdev);
+	led = dev_get_drvdata(&snode->pdev->dev);
+
+	if (!(options & FLASH_LED_PREPARE_OPTIONS_MASK)) {
+		pr_err("Invalid options %d\n", options);
+		return -EINVAL;
+	}
+
+	if (options & ENABLE_REGULATOR) {
+		rc = qpnp_flash_led_regulator_enable(led, snode, true);
+		if (rc < 0) {
+			pr_err("enable regulator failed, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (options & DISABLE_REGULATOR) {
+		rc = qpnp_flash_led_regulator_enable(led, snode, false);
+		if (rc < 0) {
+			pr_err("disable regulator failed, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (options & QUERY_MAX_CURRENT) {
+		rc = qpnp_flash_led_get_max_avail_current(led);
+		if (rc < 0) {
+			pr_err("query max current failed, rc=%d\n", rc);
+			return rc;
+		}
+		*max_current = rc;
+	}
+
+	led->trigger_chgr = false;
+	if (options & PRE_FLASH)
+		led->trigger_chgr = true;
+
+	return 0;
+}
+
+static void qpnp_flash_led_brightness_set(struct led_classdev *led_cdev,
+						enum led_brightness value)
+{
+	struct flash_node_data *fnode = NULL;
+	struct flash_switch_data *snode = NULL;
+	struct qpnp_flash_led *led = NULL;
+	int rc;
+
+	/*
+	 * strncmp() must be used here since a prefix comparison is required
+	 * in order to support names like led:switch_0 and led:flash_1.
+	 */
+	if (!strncmp(led_cdev->name, "led:switch", strlen("led:switch"))) {
+		snode = container_of(led_cdev, struct flash_switch_data, cdev);
+		led = dev_get_drvdata(&snode->pdev->dev);
+	} else if (!strncmp(led_cdev->name, "led:flash", strlen("led:flash")) ||
+			!strncmp(led_cdev->name, "led:torch",
+						strlen("led:torch"))) {
+		fnode = container_of(led_cdev, struct flash_node_data, cdev);
+		led = dev_get_drvdata(&fnode->pdev->dev);
+	}
+
+	if (!led) {
+		pr_err("Failed to get flash driver data\n");
+		return;
+	}
+
+	spin_lock(&led->lock);
+	if (snode) {
+		rc = qpnp_flash_led_switch_set(snode, value > 0);
+		if (rc < 0)
+			pr_err("Failed to set flash LED switch rc=%d\n", rc);
+	} else if (fnode) {
+		qpnp_flash_led_node_set(fnode, value);
+	}
+
+	spin_unlock(&led->lock);
+}
+
+/* sysfs show function for flash_max_current */
+static ssize_t qpnp_flash_led_max_current_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int rc;
+	struct flash_switch_data *snode;
+	struct qpnp_flash_led *led;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+	snode = container_of(led_cdev, struct flash_switch_data, cdev);
+	led = dev_get_drvdata(&snode->pdev->dev);
+
+	rc = qpnp_flash_led_get_max_avail_current(led);
+	if (rc < 0)
+		pr_err("query max current failed, rc=%d\n", rc);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", rc);
+}
+
+/* sysfs attributes exported by flash_led */
+static struct device_attribute qpnp_flash_led_attrs[] = {
+	__ATTR(max_current, 0664, qpnp_flash_led_max_current_show, NULL),
+};
+
+static int flash_led_psy_notifier_call(struct notifier_block *nb,
+		unsigned long ev, void *v)
+{
+	struct power_supply *psy = v;
+	struct qpnp_flash_led *led =
+			container_of(nb, struct qpnp_flash_led, nb);
+
+	if (ev != PSY_EVENT_PROP_CHANGED)
+		return NOTIFY_OK;
+
+	if (!strcmp(psy->desc->name, "bms")) {
+		led->bms_psy = power_supply_get_by_name("bms");
+		if (!led->bms_psy)
+			pr_err("Failed to get bms power_supply\n");
+		else
+			power_supply_unreg_notifier(&led->nb);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int flash_led_psy_register_notifier(struct qpnp_flash_led *led)
+{
+	int rc;
+
+	led->nb.notifier_call = flash_led_psy_notifier_call;
+	rc = power_supply_reg_notifier(&led->nb);
+	if (rc < 0) {
+		pr_err("Couldn't register psy notifier, rc = %d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+/* irq handler */
+static irqreturn_t qpnp_flash_led_irq_handler(int irq, void *_led)
+{
+	struct qpnp_flash_led *led = _led;
+	enum flash_led_irq_type irq_type = INVALID_IRQ;
+	int rc;
+	u8 irq_status, led_status1, led_status2;
+
+	pr_debug("irq received, irq=%d\n", irq);
+
+	rc = qpnp_flash_led_read(led,
+			FLASH_LED_REG_INT_RT_STS(led->base), &irq_status);
+	if (rc < 0) {
+		pr_err("Failed to read interrupt status reg, rc=%d\n", rc);
+		goto exit;
+	}
+
+	if (irq == led->pdata->all_ramp_up_done_irq)
+		irq_type = ALL_RAMP_UP_DONE_IRQ;
+	else if (irq == led->pdata->all_ramp_down_done_irq)
+		irq_type = ALL_RAMP_DOWN_DONE_IRQ;
+	else if (irq == led->pdata->led_fault_irq)
+		irq_type = LED_FAULT_IRQ;
+
+	if (irq_type == ALL_RAMP_UP_DONE_IRQ)
+		atomic_notifier_call_chain(&irq_notifier_list,
+						irq_type, NULL);
+
+	if (irq_type == LED_FAULT_IRQ) {
+		rc = qpnp_flash_led_read(led,
+			FLASH_LED_REG_LED_STATUS1(led->base), &led_status1);
+		if (rc < 0) {
+			pr_err("Failed to read led_status1 reg, rc=%d\n", rc);
+			goto exit;
+		}
+
+		rc = qpnp_flash_led_read(led,
+			FLASH_LED_REG_LED_STATUS2(led->base), &led_status2);
+		if (rc < 0) {
+			pr_err("Failed to read led_status2 reg, rc=%d\n", rc);
+			goto exit;
+		}
+
+		if (led_status1)
+			pr_emerg("led short/open fault detected! led_status1=%x\n",
+				led_status1);
+
+		if (led_status2 & FLASH_LED_VPH_DROOP_FAULT_MASK)
+			pr_emerg("led vph_droop fault detected!\n");
+	}
+
+	pr_debug("irq handled, irq_type=%x, irq_status=%x\n", irq_type,
+		irq_status);
+
+exit:
+	return IRQ_HANDLED;
+}
+
+int qpnp_flash_led_register_irq_notifier(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_register(&irq_notifier_list, nb);
+}
+
+int qpnp_flash_led_unregister_irq_notifier(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_unregister(&irq_notifier_list, nb);
+}
+
+static int qpnp_flash_led_parse_each_led_dt(struct qpnp_flash_led *led,
+			struct flash_node_data *fnode, struct device_node *node)
+{
+	const char *temp_string;
+	int rc;
+	u32 val;
+	bool strobe_sel = 0, edge_trigger = 0, active_high = 0;
+
+	fnode->pdev = led->pdev;
+	fnode->cdev.brightness_set = qpnp_flash_led_brightness_set;
+	fnode->cdev.brightness_get = qpnp_flash_led_brightness_get;
+
+	rc = of_property_read_string(node, "qcom,led-name", &fnode->cdev.name);
+	if (rc < 0) {
+		pr_err("Unable to read flash LED names\n");
+		return rc;
+	}
+
+	rc = of_property_read_string(node, "label", &temp_string);
+	if (!rc) {
+		if (!strcmp(temp_string, "flash")) {
+			fnode->type = FLASH_LED_TYPE_FLASH;
+		} else if (!strcmp(temp_string, "torch")) {
+			fnode->type = FLASH_LED_TYPE_TORCH;
+		} else {
+			pr_err("Wrong flash LED type\n");
+			return rc;
+		}
+	} else {
+		pr_err("Unable to read flash LED label\n");
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,id", &val);
+	if (!rc) {
+		fnode->id = (u8)val;
+	} else {
+		pr_err("Unable to read flash LED ID\n");
+		return rc;
+	}
+
+	rc = of_property_read_string(node, "qcom,default-led-trigger",
+						&fnode->cdev.default_trigger);
+	if (rc < 0) {
+		pr_err("Unable to read trigger name\n");
+		return rc;
+	}
+
+	fnode->ires_ua = FLASH_LED_IRES_DEFAULT_UA;
+	fnode->ires = FLASH_LED_IRES_DEFAULT_VAL;
+	rc = of_property_read_u32(node, "qcom,ires-ua", &val);
+	if (!rc) {
+		fnode->ires_ua = val;
+		fnode->ires = FLASH_LED_IRES_BASE -
+			(val - FLASH_LED_IRES_MIN_UA) / FLASH_LED_IRES_DIVISOR;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read current resolution rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,max-current", &val);
+	if (!rc) {
+		if (val < FLASH_LED_MIN_CURRENT_MA)
+			val = FLASH_LED_MIN_CURRENT_MA;
+		fnode->max_current = val;
+		fnode->cdev.max_brightness = val;
+	} else {
+		pr_err("Unable to read max current, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,current-ma", &val);
+	if (!rc) {
+		if (val < FLASH_LED_MIN_CURRENT_MA ||
+				val > fnode->max_current)
+			pr_warn("Invalid operational current specified, capping it\n");
+		if (val < FLASH_LED_MIN_CURRENT_MA)
+			val = FLASH_LED_MIN_CURRENT_MA;
+		if (val > fnode->max_current)
+			val = fnode->max_current;
+		fnode->current_ma = val;
+		fnode->cdev.brightness = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read operational current, rc=%d\n", rc);
+		return rc;
+	}
+
+	fnode->duration = FLASH_LED_SAFETY_TMR_DISABLED;
+	rc = of_property_read_u32(node, "qcom,duration-ms", &val);
+	if (!rc) {
+		fnode->duration = (u8)(SAFETY_TMR_TO_REG_VAL(val) |
+					FLASH_LED_SAFETY_TMR_ENABLE);
+	} else if (rc == -EINVAL) {
+		if (fnode->type == FLASH_LED_TYPE_FLASH) {
+			pr_err("Timer duration is required for flash LED\n");
+			return rc;
+		}
+	} else {
+		pr_err("Unable to read timer duration\n");
+		return rc;
+	}
+
+	fnode->hdrm_val = FLASH_LED_HDRM_VOL_DEFAULT_MV;
+	rc = of_property_read_u32(node, "qcom,hdrm-voltage-mv", &val);
+	if (!rc) {
+		val = (val - FLASH_LED_HDRM_VOL_BASE_MV) /
+						FLASH_LED_HDRM_VOL_STEP_MV;
+		fnode->hdrm_val = (val << FLASH_LED_HDRM_VOL_SHIFT) &
+							FLASH_LED_HDRM_VOL_MASK;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read headroom voltage\n");
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,hdrm-vol-hi-lo-win-mv", &val);
+	if (!rc) {
+		fnode->hdrm_val |= (val / FLASH_LED_HDRM_VOL_STEP_MV) &
+						~FLASH_LED_HDRM_VOL_MASK;
+	} else if (rc == -EINVAL) {
+		fnode->hdrm_val |= FLASH_LED_HDRM_VOL_HI_LO_WIN_DEFAULT_MV;
+	} else {
+		pr_err("Unable to read hdrm hi-lo window voltage\n");
+		return rc;
+	}
+
+	strobe_sel = of_property_read_bool(node, "qcom,hw-strobe-sel");
+	if (strobe_sel) {
+		edge_trigger = of_property_read_bool(node,
+						"qcom,hw-strobe-edge-trigger");
+		active_high = !of_property_read_bool(node,
+						"qcom,hw-strobe-active-low");
+	}
+	fnode->trigger = (strobe_sel << 2) | (edge_trigger << 1) | active_high;
+
+	if (fnode->trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT) {
+		if (of_find_property(node, "qcom,hw-strobe-gpio", NULL)) {
+			fnode->hw_strobe_gpio = of_get_named_gpio(node,
+						"qcom,hw-strobe-gpio", 0);
+			if (fnode->hw_strobe_gpio < 0) {
+				pr_err("Invalid gpio specified\n");
+				return fnode->hw_strobe_gpio;
+			}
+			gpio_direction_output(fnode->hw_strobe_gpio, 0);
+		} else {
+			fnode->hw_strobe_gpio = -1;
+			fnode->hw_strobe_state_active =
+				pinctrl_lookup_state(fnode->pinctrl,
+				"strobe_enable");
+			if (IS_ERR_OR_NULL(fnode->hw_strobe_state_active)) {
+				pr_err("No active pin for hardware strobe, rc=%ld\n",
+					PTR_ERR(fnode->hw_strobe_state_active));
+				fnode->hw_strobe_state_active = NULL;
+			}
+
+			fnode->hw_strobe_state_suspend =
+				pinctrl_lookup_state(fnode->pinctrl,
+				"strobe_disable");
+			if (IS_ERR_OR_NULL(fnode->hw_strobe_state_suspend)) {
+				pr_err("No suspend pin for hardware strobe, rc=%ld\n",
+					PTR_ERR(fnode->hw_strobe_state_suspend)
+					);
+				fnode->hw_strobe_state_suspend = NULL;
+			}
+		}
+	}
+
+	rc = led_classdev_register(&led->pdev->dev, &fnode->cdev);
+	if (rc < 0) {
+		pr_err("Unable to register led node %d\n", fnode->id);
+		return rc;
+	}
+
+	fnode->cdev.dev->of_node = node;
+
+	fnode->pinctrl = devm_pinctrl_get(fnode->cdev.dev);
+	if (IS_ERR_OR_NULL(fnode->pinctrl)) {
+		pr_debug("No pinctrl defined\n");
+		fnode->pinctrl = NULL;
+	} else {
+		fnode->gpio_state_active =
+			pinctrl_lookup_state(fnode->pinctrl, "led_enable");
+		if (IS_ERR_OR_NULL(fnode->gpio_state_active)) {
+			pr_err("Cannot lookup LED active state\n");
+			devm_pinctrl_put(fnode->pinctrl);
+			fnode->pinctrl = NULL;
+			return PTR_ERR(fnode->gpio_state_active);
+		}
+
+		fnode->gpio_state_suspend =
+			pinctrl_lookup_state(fnode->pinctrl, "led_disable");
+		if (IS_ERR_OR_NULL(fnode->gpio_state_suspend)) {
+			pr_err("Cannot lookup LED disable state\n");
+			devm_pinctrl_put(fnode->pinctrl);
+			fnode->pinctrl = NULL;
+			return PTR_ERR(fnode->gpio_state_suspend);
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_flash_led_parse_and_register_switch(struct qpnp_flash_led *led,
+						struct flash_switch_data *snode,
+						struct device_node *node)
+{
+	int rc = 0, num;
+	char reg_name[16], reg_sup_name[16];
+
+	rc = of_property_read_string(node, "qcom,led-name", &snode->cdev.name);
+	if (rc < 0) {
+		pr_err("Failed to read switch node name, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = sscanf(snode->cdev.name, "led:switch_%d", &num);
+	if (!rc) {
+		pr_err("No number for switch device?\n");
+		return -EINVAL;
+	}
+
+	rc = of_property_read_string(node, "qcom,default-led-trigger",
+					&snode->cdev.default_trigger);
+	if (rc < 0) {
+		pr_err("Unable to read trigger name, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,led-mask", &snode->led_mask);
+	if (rc < 0) {
+		pr_err("Unable to read led mask rc=%d\n", rc);
+		return rc;
+	}
+
+	if (snode->led_mask < 1 || snode->led_mask > 7) {
+		pr_err("Invalid value for led-mask\n");
+		return -EINVAL;
+	}
+
+	scnprintf(reg_name, sizeof(reg_name), "switch%d-supply", num);
+	if (of_find_property(led->pdev->dev.of_node, reg_name, NULL)) {
+		scnprintf(reg_sup_name, sizeof(reg_sup_name), "switch%d", num);
+		snode->vreg = devm_regulator_get(&led->pdev->dev, reg_sup_name);
+		if (IS_ERR_OR_NULL(snode->vreg)) {
+			rc = PTR_ERR(snode->vreg);
+			if (rc != -EPROBE_DEFER)
+				pr_err("Failed to get regulator, rc=%d\n", rc);
+			snode->vreg = NULL;
+			return rc;
+		}
+	}
+
+	snode->pdev = led->pdev;
+	snode->cdev.brightness_set = qpnp_flash_led_brightness_set;
+	snode->cdev.brightness_get = qpnp_flash_led_brightness_get;
+	snode->cdev.flags |= LED_KEEP_TRIGGER;
+	rc = led_classdev_register(&led->pdev->dev, &snode->cdev);
+	if (rc < 0) {
+		pr_err("Unable to register led switch node\n");
+		return rc;
+	}
+
+	snode->cdev.dev->of_node = node;
+	return 0;
+}
+
+static int get_code_from_table(int *table, int len, int value)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		if (value == table[i])
+			break;
+	}
+
+	if (i == len) {
+		pr_err("Couldn't find %d from table\n", value);
+		return -ENODATA;
+	}
+
+	return i;
+}
+
+static int qpnp_flash_led_parse_common_dt(struct qpnp_flash_led *led,
+						struct device_node *node)
+{
+	struct device_node *revid_node;
+	int rc;
+	u32 val;
+	bool short_circuit_det, open_circuit_det, vph_droop_det;
+
+	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;
+	}
+
+	led->pdata->pmic_rev_id = get_revid_data(revid_node);
+	if (IS_ERR_OR_NULL(led->pdata->pmic_rev_id)) {
+		pr_err("Unable to get pmic_revid rc=%ld\n",
+			PTR_ERR(led->pdata->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;
+	}
+
+	pr_debug("PMIC subtype %d Digital major %d\n",
+		led->pdata->pmic_rev_id->pmic_subtype,
+		led->pdata->pmic_rev_id->rev4);
+
+	led->pdata->hdrm_auto_mode_en = of_property_read_bool(node,
+							"qcom,hdrm-auto-mode");
+
+	led->pdata->isc_delay = FLASH_LED_ISC_DELAY_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,isc-delay-us", &val);
+	if (!rc) {
+		led->pdata->isc_delay =
+				val >> FLASH_LED_ISC_WARMUP_DELAY_SHIFT;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read ISC delay, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->warmup_delay = FLASH_LED_WARMUP_DELAY_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,warmup-delay-us", &val);
+	if (!rc) {
+		led->pdata->warmup_delay =
+				val >> FLASH_LED_ISC_WARMUP_DELAY_SHIFT;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read WARMUP delay, rc=%d\n", rc);
+		return rc;
+	}
+
+	short_circuit_det =
+		of_property_read_bool(node, "qcom,short-circuit-det");
+	open_circuit_det = of_property_read_bool(node, "qcom,open-circuit-det");
+	vph_droop_det = of_property_read_bool(node, "qcom,vph-droop-det");
+	led->pdata->current_derate_en_cfg = (vph_droop_det << 2) |
+				(open_circuit_det << 1) | short_circuit_det;
+
+	led->pdata->thermal_derate_en =
+		of_property_read_bool(node, "qcom,thermal-derate-en");
+
+	if (led->pdata->thermal_derate_en) {
+		led->pdata->thermal_derate_current =
+			devm_kcalloc(&led->pdev->dev,
+					FLASH_LED_THERMAL_OTST_LEVELS,
+					sizeof(int), GFP_KERNEL);
+		if (!led->pdata->thermal_derate_current)
+			return -ENOMEM;
+
+		rc = of_property_read_u32_array(node,
+					"qcom,thermal-derate-current",
+					led->pdata->thermal_derate_current,
+					FLASH_LED_THERMAL_OTST_LEVELS);
+		if (rc < 0) {
+			pr_err("Unable to read thermal current limits, rc=%d\n",
+				rc);
+			return rc;
+		}
+	}
+
+	led->pdata->otst_ramp_bkup_en =
+		!of_property_read_bool(node, "qcom,otst-ramp-back-up-dis");
+
+	led->pdata->thermal_derate_slow = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,thermal-derate-slow", &val);
+	if (!rc) {
+		if (val < 0 || val > THERMAL_DERATE_SLOW_MAX) {
+			pr_err("Invalid thermal_derate_slow %d\n", val);
+			return -EINVAL;
+		}
+
+		led->pdata->thermal_derate_slow =
+			get_code_from_table(thermal_derate_slow_table,
+				ARRAY_SIZE(thermal_derate_slow_table), val);
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read thermal derate slow, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->thermal_derate_fast = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,thermal-derate-fast", &val);
+	if (!rc) {
+		if (val < 0 || val > THERMAL_DERATE_FAST_MAX) {
+			pr_err("Invalid thermal_derate_fast %d\n", val);
+			return -EINVAL;
+		}
+
+		led->pdata->thermal_derate_fast =
+			get_code_from_table(thermal_derate_fast_table,
+				ARRAY_SIZE(thermal_derate_fast_table), val);
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read thermal derate fast, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->thermal_debounce = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,thermal-debounce", &val);
+	if (!rc) {
+		if (val < 0 || val > THERMAL_DEBOUNCE_TIME_MAX) {
+			pr_err("Invalid thermal_debounce %d\n", val);
+			return -EINVAL;
+		}
+
+		if (val >= 0 && val < 16)
+			led->pdata->thermal_debounce = 0;
+		else
+			led->pdata->thermal_debounce = ilog2(val) - 3;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read thermal debounce, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->thermal_hysteresis = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,thermal-hysteresis", &val);
+	if (!rc) {
+		if (led->pdata->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+			val = THERMAL_HYST_TEMP_TO_VAL(val, 20);
+		else
+			val = THERMAL_HYST_TEMP_TO_VAL(val, 15);
+
+		if (val < 0 || val > THERMAL_DERATE_HYSTERESIS_MAX) {
+			pr_err("Invalid thermal_derate_hysteresis %d\n", val);
+			return -EINVAL;
+		}
+
+		led->pdata->thermal_hysteresis = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read thermal hysteresis, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->thermal_thrsh1 = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,thermal-thrsh1", &val);
+	if (!rc) {
+		led->pdata->thermal_thrsh1 =
+			get_code_from_table(otst1_threshold_table,
+				ARRAY_SIZE(otst1_threshold_table), val);
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read thermal thrsh1, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->thermal_thrsh2 = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,thermal-thrsh2", &val);
+	if (!rc) {
+		led->pdata->thermal_thrsh2 =
+			get_code_from_table(otst2_threshold_table,
+				ARRAY_SIZE(otst2_threshold_table), val);
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read thermal thrsh2, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->thermal_thrsh3 = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,thermal-thrsh3", &val);
+	if (!rc) {
+		led->pdata->thermal_thrsh3 =
+			get_code_from_table(otst3_threshold_table,
+				ARRAY_SIZE(otst3_threshold_table), val);
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read thermal thrsh3, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->vph_droop_debounce = FLASH_LED_VPH_DROOP_DEBOUNCE_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,vph-droop-debounce-us", &val);
+	if (!rc) {
+		led->pdata->vph_droop_debounce =
+			VPH_DROOP_DEBOUNCE_US_TO_VAL(val);
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read VPH droop debounce, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (led->pdata->vph_droop_debounce > FLASH_LED_DEBOUNCE_MAX) {
+		pr_err("Invalid VPH droop debounce specified\n");
+		return -EINVAL;
+	}
+
+	led->pdata->vph_droop_threshold = FLASH_LED_VPH_DROOP_THRESH_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,vph-droop-threshold-mv", &val);
+	if (!rc) {
+		led->pdata->vph_droop_threshold =
+			VPH_DROOP_THRESH_MV_TO_VAL(val);
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read VPH droop threshold, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (led->pdata->vph_droop_threshold > FLASH_LED_VPH_DROOP_THRESH_MAX) {
+		pr_err("Invalid VPH droop threshold specified\n");
+		return -EINVAL;
+	}
+
+	led->pdata->vph_droop_hysteresis =
+			FLASH_LED_VPH_DROOP_HYST_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,vph-droop-hysteresis-mv", &val);
+	if (!rc) {
+		led->pdata->vph_droop_hysteresis =
+			VPH_DROOP_HYST_MV_TO_VAL(val);
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read VPH droop hysteresis, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (led->pdata->vph_droop_hysteresis > FLASH_LED_HYSTERESIS_MAX) {
+		pr_err("Invalid VPH droop hysteresis specified\n");
+		return -EINVAL;
+	}
+
+	led->pdata->vph_droop_hysteresis <<= FLASH_LED_VPH_DROOP_HYST_SHIFT;
+
+	rc = of_property_read_u32(node, "qcom,hw-strobe-option", &val);
+	if (!rc) {
+		led->pdata->hw_strobe_option = (u8)val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse hw strobe option, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,led1n2-iclamp-low-ma", &val);
+	if (!rc) {
+		led->pdata->led1n2_iclamp_low_ma = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read led1n2_iclamp_low current, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,led1n2-iclamp-mid-ma", &val);
+	if (!rc) {
+		led->pdata->led1n2_iclamp_mid_ma = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read led1n2_iclamp_mid current, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,led3-iclamp-low-ma", &val);
+	if (!rc) {
+		led->pdata->led3_iclamp_low_ma = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read led3_iclamp_low current, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,led3-iclamp-mid-ma", &val);
+	if (!rc) {
+		led->pdata->led3_iclamp_mid_ma = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to read led3_iclamp_mid current, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->vled_max_uv = FLASH_LED_VLED_MAX_DEFAULT_UV;
+	rc = of_property_read_u32(node, "qcom,vled-max-uv", &val);
+	if (!rc) {
+		led->pdata->vled_max_uv = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse vled_max voltage, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->ibatt_ocp_threshold_ua =
+		FLASH_LED_IBATT_OCP_THRESH_DEFAULT_UA;
+	rc = of_property_read_u32(node, "qcom,ibatt-ocp-threshold-ua", &val);
+	if (!rc) {
+		led->pdata->ibatt_ocp_threshold_ua = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse ibatt_ocp threshold, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->rpara_uohm = FLASH_LED_RPARA_DEFAULT_UOHM;
+	rc = of_property_read_u32(node, "qcom,rparasitic-uohm", &val);
+	if (!rc) {
+		led->pdata->rpara_uohm = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse rparasitic, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->lmh_ocv_threshold_uv =
+		FLASH_LED_LMH_OCV_THRESH_DEFAULT_UV;
+	rc = of_property_read_u32(node, "qcom,lmh-ocv-threshold-uv", &val);
+	if (!rc) {
+		led->pdata->lmh_ocv_threshold_uv = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse lmh ocv threshold, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->lmh_rbatt_threshold_uohm =
+		FLASH_LED_LMH_RBATT_THRESH_DEFAULT_UOHM;
+	rc = of_property_read_u32(node, "qcom,lmh-rbatt-threshold-uohm", &val);
+	if (!rc) {
+		led->pdata->lmh_rbatt_threshold_uohm = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse lmh rbatt threshold, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->lmh_level = FLASH_LED_LMH_LEVEL_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,lmh-level", &val);
+	if (!rc) {
+		led->pdata->lmh_level = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse lmh_level, rc=%d\n", rc);
+		return rc;
+	}
+
+	led->pdata->lmh_mitigation_sel = FLASH_LED_MITIGATION_SEL_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,lmh-mitigation-sel", &val);
+	if (!rc) {
+		led->pdata->lmh_mitigation_sel = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse lmh_mitigation_sel, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (led->pdata->lmh_mitigation_sel > FLASH_LED_MITIGATION_SEL_MAX) {
+		pr_err("Invalid lmh_mitigation_sel specified\n");
+		return -EINVAL;
+	}
+
+	led->pdata->chgr_mitigation_sel = FLASH_LED_MITIGATION_SEL_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,chgr-mitigation-sel", &val);
+	if (!rc) {
+		led->pdata->chgr_mitigation_sel = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse chgr_mitigation_sel, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (led->pdata->chgr_mitigation_sel > FLASH_LED_MITIGATION_SEL_MAX) {
+		pr_err("Invalid chgr_mitigation_sel specified\n");
+		return -EINVAL;
+	}
+
+	led->pdata->chgr_mitigation_sel <<= FLASH_LED_CHGR_MITIGATION_SEL_SHIFT;
+
+	led->pdata->iled_thrsh_val = FLASH_LED_MITIGATION_THRSH_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,iled-thrsh-ma", &val);
+	if (!rc) {
+		led->pdata->iled_thrsh_val = MITIGATION_THRSH_MA_TO_VAL(val);
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse iled_thrsh_val, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (led->pdata->iled_thrsh_val > FLASH_LED_MITIGATION_THRSH_MAX) {
+		pr_err("Invalid iled_thrsh_val specified\n");
+		return -EINVAL;
+	}
+
+	led->pdata->all_ramp_up_done_irq =
+		of_irq_get_byname(node, "all-ramp-up-done-irq");
+	if (led->pdata->all_ramp_up_done_irq < 0)
+		pr_debug("all-ramp-up-done-irq not used\n");
+
+	led->pdata->all_ramp_down_done_irq =
+		of_irq_get_byname(node, "all-ramp-down-done-irq");
+	if (led->pdata->all_ramp_down_done_irq < 0)
+		pr_debug("all-ramp-down-done-irq not used\n");
+
+	led->pdata->led_fault_irq =
+		of_irq_get_byname(node, "led-fault-irq");
+	if (led->pdata->led_fault_irq < 0)
+		pr_debug("led-fault-irq not used\n");
+
+	return 0;
+}
+
+static int qpnp_flash_led_probe(struct platform_device *pdev)
+{
+	struct qpnp_flash_led *led;
+	struct device_node *node, *temp;
+	const char *temp_string;
+	unsigned int base;
+	int rc, i = 0, j = 0;
+
+	node = pdev->dev.of_node;
+	if (!node) {
+		pr_err("No flash LED nodes defined\n");
+		return -ENODEV;
+	}
+
+	rc = of_property_read_u32(node, "reg", &base);
+	if (rc < 0) {
+		pr_err("Couldn't find reg in node %s, rc = %d\n",
+			node->full_name, rc);
+		return rc;
+	}
+
+	led = devm_kzalloc(&pdev->dev, sizeof(struct qpnp_flash_led),
+								GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!led->regmap) {
+		pr_err("Couldn't get parent's regmap\n");
+		return -EINVAL;
+	}
+
+	led->base = base;
+	led->pdev = pdev;
+	led->pdata = devm_kzalloc(&pdev->dev,
+			sizeof(struct flash_led_platform_data), GFP_KERNEL);
+	if (!led->pdata)
+		return -ENOMEM;
+
+	rc = qpnp_flash_led_parse_common_dt(led, node);
+	if (rc < 0) {
+		pr_err("Failed to parse common flash LED device tree\n");
+		return rc;
+	}
+
+	for_each_available_child_of_node(node, temp) {
+		rc = of_property_read_string(temp, "label", &temp_string);
+		if (rc < 0) {
+			pr_err("Failed to parse label, rc=%d\n", rc);
+			return rc;
+		}
+
+		if (!strcmp("switch", temp_string)) {
+			led->num_snodes++;
+		} else if (!strcmp("flash", temp_string) ||
+				!strcmp("torch", temp_string)) {
+			led->num_fnodes++;
+		} else {
+			pr_err("Invalid label for led node\n");
+			return -EINVAL;
+		}
+	}
+
+	if (!led->num_fnodes) {
+		pr_err("No LED nodes defined\n");
+		return -ECHILD;
+	}
+
+	led->fnode = devm_kcalloc(&pdev->dev, led->num_fnodes,
+				sizeof(*led->fnode),
+				GFP_KERNEL);
+	if (!led->fnode)
+		return -ENOMEM;
+
+	led->snode = devm_kcalloc(&pdev->dev, led->num_snodes,
+				sizeof(*led->snode),
+				GFP_KERNEL);
+	if (!led->snode)
+		return -ENOMEM;
+
+	temp = NULL;
+	i = 0;
+	j = 0;
+	for_each_available_child_of_node(node, temp) {
+		rc = of_property_read_string(temp, "label", &temp_string);
+		if (rc < 0) {
+			pr_err("Failed to parse label, rc=%d\n", rc);
+			return rc;
+		}
+
+		if (!strcmp("flash", temp_string) ||
+				!strcmp("torch", temp_string)) {
+			rc = qpnp_flash_led_parse_each_led_dt(led,
+					&led->fnode[i++], temp);
+			if (rc < 0) {
+				pr_err("Unable to parse flash node %d rc=%d\n",
+					i, rc);
+				goto error_led_register;
+			}
+		}
+
+		if (!strcmp("switch", temp_string)) {
+			rc = qpnp_flash_led_parse_and_register_switch(led,
+					&led->snode[j++], temp);
+			if (rc < 0) {
+				pr_err("Unable to parse and register switch node, rc=%d\n",
+					rc);
+				goto error_switch_register;
+			}
+		}
+	}
+
+	/* setup irqs */
+	if (led->pdata->all_ramp_up_done_irq >= 0) {
+		rc = devm_request_threaded_irq(&led->pdev->dev,
+			led->pdata->all_ramp_up_done_irq,
+			NULL, qpnp_flash_led_irq_handler,
+			IRQF_ONESHOT,
+			"qpnp_flash_led_all_ramp_up_done_irq", led);
+		if (rc < 0) {
+			pr_err("Unable to request all_ramp_up_done(%d) IRQ(err:%d)\n",
+				led->pdata->all_ramp_up_done_irq, rc);
+			goto error_switch_register;
+		}
+	}
+
+	if (led->pdata->all_ramp_down_done_irq >= 0) {
+		rc = devm_request_threaded_irq(&led->pdev->dev,
+			led->pdata->all_ramp_down_done_irq,
+			NULL, qpnp_flash_led_irq_handler,
+			IRQF_ONESHOT,
+			"qpnp_flash_led_all_ramp_down_done_irq", led);
+		if (rc < 0) {
+			pr_err("Unable to request all_ramp_down_done(%d) IRQ(err:%d)\n",
+				led->pdata->all_ramp_down_done_irq, rc);
+			goto error_switch_register;
+		}
+	}
+
+	if (led->pdata->led_fault_irq >= 0) {
+		rc = devm_request_threaded_irq(&led->pdev->dev,
+			led->pdata->led_fault_irq,
+			NULL, qpnp_flash_led_irq_handler,
+			IRQF_ONESHOT,
+			"qpnp_flash_led_fault_irq", led);
+		if (rc < 0) {
+			pr_err("Unable to request led_fault(%d) IRQ(err:%d)\n",
+				led->pdata->led_fault_irq, rc);
+			goto error_switch_register;
+		}
+	}
+
+	led->bms_psy = power_supply_get_by_name("bms");
+	if (!led->bms_psy) {
+		rc = flash_led_psy_register_notifier(led);
+		if (rc < 0) {
+			pr_err("Couldn't register psy notifier, rc = %d\n", rc);
+			goto error_switch_register;
+		}
+	}
+
+	rc = qpnp_flash_led_init_settings(led);
+	if (rc < 0) {
+		pr_err("Failed to initialize flash LED, rc=%d\n", rc);
+		goto unreg_notifier;
+	}
+
+	for (i = 0; i < led->num_snodes; i++) {
+		for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++) {
+			rc = sysfs_create_file(&led->snode[i].cdev.dev->kobj,
+					&qpnp_flash_led_attrs[j].attr);
+			if (rc < 0) {
+				pr_err("sysfs creation failed, rc=%d\n", rc);
+				goto sysfs_fail;
+			}
+		}
+	}
+
+	spin_lock_init(&led->lock);
+
+	dev_set_drvdata(&pdev->dev, led);
+
+	return 0;
+
+sysfs_fail:
+	for (--j; j >= 0; j--)
+		sysfs_remove_file(&led->snode[i].cdev.dev->kobj,
+				&qpnp_flash_led_attrs[j].attr);
+
+	for (--i; i >= 0; i--) {
+		for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++)
+			sysfs_remove_file(&led->snode[i].cdev.dev->kobj,
+					&qpnp_flash_led_attrs[j].attr);
+	}
+
+	i = led->num_snodes;
+unreg_notifier:
+	power_supply_unreg_notifier(&led->nb);
+error_switch_register:
+	while (i > 0)
+		led_classdev_unregister(&led->snode[--i].cdev);
+	i = led->num_fnodes;
+error_led_register:
+	while (i > 0)
+		led_classdev_unregister(&led->fnode[--i].cdev);
+
+	return rc;
+}
+
+static int qpnp_flash_led_remove(struct platform_device *pdev)
+{
+	struct qpnp_flash_led *led = dev_get_drvdata(&pdev->dev);
+	int i, j;
+
+	for (i = 0; i < led->num_snodes; i++) {
+		for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++)
+			sysfs_remove_file(&led->snode[i].cdev.dev->kobj,
+					&qpnp_flash_led_attrs[j].attr);
+
+		if (led->snode[i].regulator_on)
+			qpnp_flash_led_regulator_enable(led,
+					&led->snode[i], false);
+	}
+
+	while (i > 0)
+		led_classdev_unregister(&led->snode[--i].cdev);
+
+	i = led->num_fnodes;
+	while (i > 0)
+		led_classdev_unregister(&led->fnode[--i].cdev);
+
+	power_supply_unreg_notifier(&led->nb);
+	return 0;
+}
+
+const struct of_device_id qpnp_flash_led_match_table[] = {
+	{ .compatible = "qcom,qpnp-flash-led-v2",},
+	{ },
+};
+
+static struct platform_driver qpnp_flash_led_driver = {
+	.driver		= {
+		.name = "qcom,qpnp-flash-led-v2",
+		.of_match_table = qpnp_flash_led_match_table,
+	},
+	.probe		= qpnp_flash_led_probe,
+	.remove		= qpnp_flash_led_remove,
+};
+
+static int __init qpnp_flash_led_init(void)
+{
+	return platform_driver_register(&qpnp_flash_led_driver);
+}
+late_initcall(qpnp_flash_led_init);
+
+static void __exit qpnp_flash_led_exit(void)
+{
+	platform_driver_unregister(&qpnp_flash_led_driver);
+}
+module_exit(qpnp_flash_led_exit);
+
+MODULE_DESCRIPTION("QPNP Flash LED driver v2");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("leds:leds-qpnp-flash-v2");
diff --git a/drivers/leds/leds-qpnp-flash.c b/drivers/leds/leds-qpnp-flash.c
new file mode 100644
index 0000000..c27c059
--- /dev/null
+++ b/drivers/leds/leds-qpnp-flash.c
@@ -0,0 +1,2649 @@
+/* Copyright (c) 2014-2017, 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/init.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/errno.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/power_supply.h>
+#include <linux/leds-qpnp-flash.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include "leds.h"
+
+#define FLASH_LED_PERIPHERAL_SUBTYPE(base)			(base + 0x05)
+#define FLASH_SAFETY_TIMER(base)				(base + 0x40)
+#define FLASH_MAX_CURRENT(base)					(base + 0x41)
+#define FLASH_LED0_CURRENT(base)				(base + 0x42)
+#define FLASH_LED1_CURRENT(base)				(base + 0x43)
+#define	FLASH_CLAMP_CURRENT(base)				(base + 0x44)
+#define FLASH_MODULE_ENABLE_CTRL(base)				(base + 0x46)
+#define	FLASH_LED_STROBE_CTRL(base)				(base + 0x47)
+#define FLASH_LED_TMR_CTRL(base)				(base + 0x48)
+#define FLASH_HEADROOM(base)					(base + 0x4A)
+#define	FLASH_STARTUP_DELAY(base)				(base + 0x4B)
+#define FLASH_MASK_ENABLE(base)					(base + 0x4C)
+#define FLASH_VREG_OK_FORCE(base)				(base + 0x4F)
+#define FLASH_FAULT_DETECT(base)				(base + 0x51)
+#define	FLASH_THERMAL_DRATE(base)				(base + 0x52)
+#define	FLASH_CURRENT_RAMP(base)				(base + 0x54)
+#define	FLASH_VPH_PWR_DROOP(base)				(base + 0x5A)
+#define	FLASH_HDRM_SNS_ENABLE_CTRL0(base)			(base + 0x5C)
+#define	FLASH_HDRM_SNS_ENABLE_CTRL1(base)			(base + 0x5D)
+#define	FLASH_LED_UNLOCK_SECURE(base)				(base + 0xD0)
+#define FLASH_PERPH_RESET_CTRL(base)				(base + 0xDA)
+#define	FLASH_TORCH(base)					(base + 0xE4)
+
+#define FLASH_STATUS_REG_MASK					0xFF
+#define FLASH_LED_FAULT_STATUS(base)				(base + 0x08)
+#define INT_LATCHED_STS(base)					(base + 0x18)
+#define IN_POLARITY_HIGH(base)					(base + 0x12)
+#define INT_SET_TYPE(base)					(base + 0x11)
+#define INT_EN_SET(base)					(base + 0x15)
+#define INT_LATCHED_CLR(base)					(base + 0x14)
+
+#define	FLASH_HEADROOM_MASK					0x03
+#define FLASH_STARTUP_DLY_MASK					0x03
+#define	FLASH_VREG_OK_FORCE_MASK				0xC0
+#define	FLASH_FAULT_DETECT_MASK					0x80
+#define	FLASH_THERMAL_DERATE_MASK				0xBF
+#define FLASH_SECURE_MASK					0xFF
+#define FLASH_TORCH_MASK					0x03
+#define FLASH_CURRENT_MASK					0x7F
+#define FLASH_TMR_MASK						0x03
+#define FLASH_TMR_SAFETY					0x00
+#define FLASH_SAFETY_TIMER_MASK					0x7F
+#define FLASH_MODULE_ENABLE_MASK				0xE0
+#define FLASH_STROBE_MASK					0xC0
+#define FLASH_CURRENT_RAMP_MASK					0xBF
+#define FLASH_VPH_PWR_DROOP_MASK				0xF3
+#define FLASH_LED_HDRM_SNS_ENABLE_MASK				0x81
+#define	FLASH_MASK_MODULE_CONTRL_MASK				0xE0
+#define FLASH_FOLLOW_OTST2_RB_MASK				0x08
+
+#define FLASH_LED_TRIGGER_DEFAULT				"none"
+#define FLASH_LED_HEADROOM_DEFAULT_MV				500
+#define FLASH_LED_STARTUP_DELAY_DEFAULT_US			128
+#define FLASH_LED_CLAMP_CURRENT_DEFAULT_MA			200
+#define	FLASH_LED_THERMAL_DERATE_THRESHOLD_DEFAULT_C		80
+#define	FLASH_LED_RAMP_UP_STEP_DEFAULT_US			3
+#define	FLASH_LED_RAMP_DN_STEP_DEFAULT_US			3
+#define	FLASH_LED_VPH_PWR_DROOP_THRESHOLD_DEFAULT_MV		3200
+#define	FLASH_LED_VPH_PWR_DROOP_DEBOUNCE_TIME_DEFAULT_US	10
+#define FLASH_LED_THERMAL_DERATE_RATE_DEFAULT_PERCENT		2
+#define FLASH_RAMP_UP_DELAY_US_MIN				1000
+#define	FLASH_RAMP_UP_DELAY_US_MAX				1001
+#define FLASH_RAMP_DN_DELAY_US_MIN				2160
+#define	FLASH_RAMP_DN_DELAY_US_MAX				2161
+#define FLASH_BOOST_REGULATOR_PROBE_DELAY_MS			2000
+#define	FLASH_TORCH_MAX_LEVEL					0x0F
+#define	FLASH_MAX_LEVEL						0x4F
+#define	FLASH_LED_FLASH_HW_VREG_OK				0x40
+#define	FLASH_LED_FLASH_SW_VREG_OK				0x80
+#define FLASH_LED_STROBE_TYPE_HW				0x04
+#define	FLASH_DURATION_DIVIDER					10
+#define	FLASH_LED_HEADROOM_DIVIDER				100
+#define	FLASH_LED_HEADROOM_OFFSET				2
+#define	FLASH_LED_MAX_CURRENT_MA				1000
+#define	FLASH_LED_THERMAL_THRESHOLD_MIN				95
+#define	FLASH_LED_THERMAL_DEVIDER				10
+#define	FLASH_LED_VPH_DROOP_THRESHOLD_MIN_MV			2500
+#define	FLASH_LED_VPH_DROOP_THRESHOLD_DIVIDER			100
+#define	FLASH_LED_HDRM_SNS_ENABLE				0x81
+#define	FLASH_LED_HDRM_SNS_DISABLE				0x01
+#define	FLASH_LED_UA_PER_MA					1000
+#define	FLASH_LED_MASK_MODULE_MASK2_ENABLE			0x20
+#define	FLASH_LED_MASK3_ENABLE_SHIFT				7
+#define	FLASH_LED_MODULE_CTRL_DEFAULT				0x60
+#define	FLASH_LED_CURRENT_READING_DELAY_MIN			5000
+#define	FLASH_LED_CURRENT_READING_DELAY_MAX			5001
+#define	FLASH_LED_OPEN_FAULT_DETECTED				0xC
+
+#define FLASH_UNLOCK_SECURE					0xA5
+#define FLASH_LED_TORCH_ENABLE					0x00
+#define FLASH_LED_TORCH_DISABLE					0x03
+#define FLASH_MODULE_ENABLE					0x80
+#define FLASH_LED0_TRIGGER					0x80
+#define FLASH_LED1_TRIGGER					0x40
+#define FLASH_LED0_ENABLEMENT					0x40
+#define FLASH_LED1_ENABLEMENT					0x20
+#define FLASH_LED_DISABLE					0x00
+#define	FLASH_LED_MIN_CURRENT_MA				13
+#define FLASH_SUBTYPE_DUAL					0x01
+#define FLASH_SUBTYPE_SINGLE					0x02
+
+/*
+ * ID represents physical LEDs for individual control purpose.
+ */
+enum flash_led_id {
+	FLASH_LED_0 = 0,
+	FLASH_LED_1,
+	FLASH_LED_SWITCH,
+};
+
+enum flash_led_type {
+	FLASH = 0,
+	TORCH,
+	SWITCH,
+};
+
+enum thermal_derate_rate {
+	RATE_1_PERCENT = 0,
+	RATE_1P25_PERCENT,
+	RATE_2_PERCENT,
+	RATE_2P5_PERCENT,
+	RATE_5_PERCENT,
+};
+
+enum current_ramp_steps {
+	RAMP_STEP_0P2_US = 0,
+	RAMP_STEP_0P4_US,
+	RAMP_STEP_0P8_US,
+	RAMP_STEP_1P6_US,
+	RAMP_STEP_3P3_US,
+	RAMP_STEP_6P7_US,
+	RAMP_STEP_13P5_US,
+	RAMP_STEP_27US,
+};
+
+struct flash_regulator_data {
+	struct regulator	*regs;
+	const char		*reg_name;
+	u32			max_volt_uv;
+};
+
+/*
+ * Configurations for each individual LED
+ */
+struct flash_node_data {
+	struct platform_device		*pdev;
+	struct regmap			*regmap;
+	struct led_classdev		cdev;
+	struct work_struct		work;
+	struct flash_regulator_data	*reg_data;
+	u16				max_current;
+	u16				prgm_current;
+	u16				prgm_current2;
+	u16				duration;
+	u8				id;
+	u8				type;
+	u8				trigger;
+	u8				enable;
+	u8				num_regulators;
+	bool				flash_on;
+};
+
+/*
+ * Flash LED configuration read from device tree
+ */
+struct flash_led_platform_data {
+	unsigned int			temp_threshold_num;
+	unsigned int			temp_derate_curr_num;
+	unsigned int			*die_temp_derate_curr_ma;
+	unsigned int			*die_temp_threshold_degc;
+	u16				ramp_up_step;
+	u16				ramp_dn_step;
+	u16				vph_pwr_droop_threshold;
+	u16				headroom;
+	u16				clamp_current;
+	u8				thermal_derate_threshold;
+	u8				vph_pwr_droop_debounce_time;
+	u8				startup_dly;
+	u8				thermal_derate_rate;
+	bool				pmic_charger_support;
+	bool				self_check_en;
+	bool				thermal_derate_en;
+	bool				current_ramp_en;
+	bool				vph_pwr_droop_en;
+	bool				hdrm_sns_ch0_en;
+	bool				hdrm_sns_ch1_en;
+	bool				power_detect_en;
+	bool				mask3_en;
+	bool				follow_rb_disable;
+	bool				die_current_derate_en;
+};
+
+struct qpnp_flash_led_buffer {
+	size_t rpos;
+	size_t wpos;
+	size_t len;
+	char data[0];
+};
+
+/*
+ * Flash LED data structure containing flash LED attributes
+ */
+struct qpnp_flash_led {
+	struct pmic_revid_data		*revid_data;
+	struct platform_device		*pdev;
+	struct regmap			*regmap;
+	struct flash_led_platform_data	*pdata;
+	struct pinctrl			*pinctrl;
+	struct pinctrl_state		*gpio_state_active;
+	struct pinctrl_state		*gpio_state_suspend;
+	struct flash_node_data		*flash_node;
+	struct power_supply		*battery_psy;
+	struct workqueue_struct		*ordered_workq;
+	struct qpnp_vadc_chip		*vadc_dev;
+	struct mutex			flash_led_lock;
+	struct qpnp_flash_led_buffer	*log;
+	struct dentry			*dbgfs_root;
+	int				num_leds;
+	u32				buffer_cnt;
+	u16				base;
+	u16				current_addr;
+	u16				current2_addr;
+	u8				peripheral_type;
+	u8				fault_reg;
+	bool				gpio_enabled;
+	bool				charging_enabled;
+	bool				strobe_debug;
+	bool				dbg_feature_en;
+	bool				open_fault;
+};
+
+static u8 qpnp_flash_led_ctrl_dbg_regs[] = {
+	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+	0x4A, 0x4B, 0x4C, 0x4F, 0x51, 0x52, 0x54, 0x55, 0x5A, 0x5C, 0x5D,
+};
+
+static int flash_led_dbgfs_file_open(struct qpnp_flash_led *led,
+					struct file *file)
+{
+	struct qpnp_flash_led_buffer *log;
+	size_t logbufsize = SZ_4K;
+
+	log = kzalloc(logbufsize, GFP_KERNEL);
+	if (!log)
+		return -ENOMEM;
+
+	log->rpos = 0;
+	log->wpos = 0;
+	log->len = logbufsize - sizeof(*log);
+	led->log = log;
+
+	led->buffer_cnt = 1;
+	file->private_data = led;
+
+	return 0;
+}
+
+static int flash_led_dfs_open(struct inode *inode, struct file *file)
+{
+	struct qpnp_flash_led *led = inode->i_private;
+
+	return flash_led_dbgfs_file_open(led, file);
+}
+
+static int flash_led_dfs_close(struct inode *inode, struct file *file)
+{
+	struct qpnp_flash_led *led = file->private_data;
+
+	if (led && led->log) {
+		file->private_data = NULL;
+		kfree(led->log);
+	}
+
+	return 0;
+}
+
+static int print_to_log(struct qpnp_flash_led_buffer *log,
+					const char *fmt, ...)
+{
+	va_list args;
+	int cnt;
+	char *log_buf = &log->data[log->wpos];
+	size_t size = log->len - log->wpos;
+
+	va_start(args, fmt);
+	cnt = vscnprintf(log_buf, size, fmt, args);
+	va_end(args);
+
+	log->wpos += cnt;
+	return cnt;
+}
+
+static ssize_t flash_led_dfs_latched_reg_read(struct file *fp, char __user *buf,
+					size_t count, loff_t *ppos) {
+	struct qpnp_flash_led *led = fp->private_data;
+	struct qpnp_flash_led_buffer *log = led->log;
+	uint val;
+	int rc;
+	size_t len;
+	size_t ret;
+
+	if (log->rpos >= log->wpos && led->buffer_cnt == 0)
+		return 0;
+
+	rc = regmap_read(led->regmap, INT_LATCHED_STS(led->base), &val);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+				"Unable to read from address %x, rc(%d)\n",
+				INT_LATCHED_STS(led->base), rc);
+		return -EINVAL;
+	}
+	led->buffer_cnt--;
+
+	rc = print_to_log(log, "0x%05X ", INT_LATCHED_STS(led->base));
+	if (rc == 0)
+		return rc;
+
+	rc = print_to_log(log, "0x%02X ", val);
+	if (rc == 0)
+		return rc;
+
+	if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
+		log->data[log->wpos - 1] = '\n';
+
+	len = min(count, log->wpos - log->rpos);
+
+	ret = copy_to_user(buf, &log->data[log->rpos], len);
+	if (ret) {
+		pr_err("error copy register value to user\n");
+		return -EFAULT;
+	}
+
+	len -= ret;
+	*ppos += len;
+	log->rpos += len;
+
+	return len;
+}
+
+static ssize_t flash_led_dfs_fault_reg_read(struct file *fp, char __user *buf,
+					size_t count, loff_t *ppos) {
+	struct qpnp_flash_led *led = fp->private_data;
+	struct qpnp_flash_led_buffer *log = led->log;
+	int rc;
+	size_t len;
+	size_t ret;
+
+	if (log->rpos >= log->wpos && led->buffer_cnt == 0)
+		return 0;
+
+	led->buffer_cnt--;
+
+	rc = print_to_log(log, "0x%05X ", FLASH_LED_FAULT_STATUS(led->base));
+	if (rc == 0)
+		return rc;
+
+	rc = print_to_log(log, "0x%02X ", led->fault_reg);
+	if (rc == 0)
+		return rc;
+
+	if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
+		log->data[log->wpos - 1] = '\n';
+
+	len = min(count, log->wpos - log->rpos);
+
+	ret = copy_to_user(buf, &log->data[log->rpos], len);
+	if (ret) {
+		pr_err("error copy register value to user\n");
+		return -EFAULT;
+	}
+
+	len -= ret;
+	*ppos += len;
+	log->rpos += len;
+
+	return len;
+}
+
+static ssize_t flash_led_dfs_fault_reg_enable(struct file *file,
+			const char __user *buf, size_t count, loff_t *ppos) {
+
+	u8 *val;
+	int pos = 0;
+	int cnt = 0;
+	int data;
+	size_t ret = 0;
+
+	struct qpnp_flash_led *led = file->private_data;
+	char *kbuf = kmalloc(count + 1, GFP_KERNEL);
+
+	if (!kbuf)
+		return -ENOMEM;
+
+	ret = copy_from_user(kbuf, buf, count);
+	if (!ret) {
+		pr_err("failed to copy data from user\n");
+		ret = -EFAULT;
+		goto free_buf;
+	}
+
+	count -= ret;
+	*ppos += count;
+	kbuf[count] = '\0';
+	val = kbuf;
+	while (sscanf(kbuf + pos, "%i", &data) == 1) {
+		pos++;
+		val[cnt++] = data & 0xff;
+	}
+
+	if (!cnt)
+		goto free_buf;
+
+	ret = count;
+	if (*val == 1)
+		led->strobe_debug = true;
+	else
+		led->strobe_debug = false;
+
+free_buf:
+	kfree(kbuf);
+	return ret;
+}
+
+static ssize_t flash_led_dfs_dbg_enable(struct file *file,
+			const char __user *buf, size_t count, loff_t *ppos) {
+
+	u8 *val;
+	int pos = 0;
+	int cnt = 0;
+	int data;
+	size_t ret = 0;
+	struct qpnp_flash_led *led = file->private_data;
+	char *kbuf = kmalloc(count + 1, GFP_KERNEL);
+
+	if (!kbuf)
+		return -ENOMEM;
+
+	ret = copy_from_user(kbuf, buf, count);
+	if (ret == count) {
+		pr_err("failed to copy data from user\n");
+		ret = -EFAULT;
+		goto free_buf;
+	}
+	count -= ret;
+	*ppos += count;
+	kbuf[count] = '\0';
+	val = kbuf;
+	while (sscanf(kbuf + pos, "%i", &data) == 1) {
+		pos++;
+		val[cnt++] = data & 0xff;
+	}
+
+	if (!cnt)
+		goto free_buf;
+
+	ret = count;
+	if (*val == 1)
+		led->dbg_feature_en = true;
+	else
+		led->dbg_feature_en = false;
+
+free_buf:
+	kfree(kbuf);
+	return ret;
+}
+
+static const struct file_operations flash_led_dfs_latched_reg_fops = {
+	.open		= flash_led_dfs_open,
+	.release	= flash_led_dfs_close,
+	.read		= flash_led_dfs_latched_reg_read,
+};
+
+static const struct file_operations flash_led_dfs_strobe_reg_fops = {
+	.open		= flash_led_dfs_open,
+	.release	= flash_led_dfs_close,
+	.read		= flash_led_dfs_fault_reg_read,
+	.write		= flash_led_dfs_fault_reg_enable,
+};
+
+static const struct file_operations flash_led_dfs_dbg_feature_fops = {
+	.open		= flash_led_dfs_open,
+	.release	= flash_led_dfs_close,
+	.write		= flash_led_dfs_dbg_enable,
+};
+
+static int
+qpnp_led_masked_write(struct qpnp_flash_led *led, u16 addr, u8 mask, u8 val)
+{
+	int rc;
+
+	rc = regmap_update_bits(led->regmap, addr, mask, val);
+	if (rc)
+		dev_err(&led->pdev->dev,
+			"Unable to update_bits to addr=%x, rc(%d)\n", addr, rc);
+
+	dev_dbg(&led->pdev->dev, "Write 0x%02X to addr 0x%02X\n", val, addr);
+
+	return rc;
+}
+
+static int qpnp_flash_led_get_allowed_die_temp_curr(struct qpnp_flash_led *led,
+							int64_t die_temp_degc)
+{
+	int die_temp_curr_ma;
+
+	if (die_temp_degc >= led->pdata->die_temp_threshold_degc[0])
+		die_temp_curr_ma =  0;
+	else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[1])
+		die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[0];
+	else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[2])
+		die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[1];
+	else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[3])
+		die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[2];
+	else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[4])
+		die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[3];
+	else
+		die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[4];
+
+	return die_temp_curr_ma;
+}
+
+static int64_t qpnp_flash_led_get_die_temp(struct qpnp_flash_led *led)
+{
+	struct qpnp_vadc_result die_temp_result;
+	int rc;
+
+	rc = qpnp_vadc_read(led->vadc_dev, SPARE2, &die_temp_result);
+	if (rc) {
+		pr_err("failed to read the die temp\n");
+		return -EINVAL;
+	}
+
+	return die_temp_result.physical;
+}
+
+static int qpnp_get_pmic_revid(struct qpnp_flash_led *led)
+{
+	struct device_node *revid_dev_node;
+
+	revid_dev_node = of_parse_phandle(led->pdev->dev.of_node,
+				"qcom,pmic-revid", 0);
+	if (!revid_dev_node) {
+		dev_err(&led->pdev->dev,
+			"qcom,pmic-revid property missing\n");
+		return -EINVAL;
+	}
+
+	led->revid_data = get_revid_data(revid_dev_node);
+	if (IS_ERR(led->revid_data)) {
+		pr_err("Couldn't get revid data rc = %ld\n",
+				PTR_ERR(led->revid_data));
+		return PTR_ERR(led->revid_data);
+	}
+
+	return 0;
+}
+
+static int
+qpnp_flash_led_get_max_avail_current(struct flash_node_data *flash_node,
+					struct qpnp_flash_led *led)
+{
+	union power_supply_propval prop;
+	int64_t chg_temp_milidegc, die_temp_degc;
+	int max_curr_avail_ma = 2000;
+	int allowed_die_temp_curr_ma = 2000;
+	int rc;
+
+	if (led->pdata->power_detect_en) {
+		if (!led->battery_psy) {
+			dev_err(&led->pdev->dev,
+				"Failed to query power supply\n");
+			return -EINVAL;
+		}
+
+		/*
+		 * When charging is enabled, enforce this new enablement
+		 * sequence to reduce fuel gauge reading resolution.
+		 */
+		if (led->charging_enabled) {
+			rc = qpnp_led_masked_write(led,
+				FLASH_MODULE_ENABLE_CTRL(led->base),
+				FLASH_MODULE_ENABLE, FLASH_MODULE_ENABLE);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+				"Module enable reg write failed\n");
+				return -EINVAL;
+			}
+
+			usleep_range(FLASH_LED_CURRENT_READING_DELAY_MIN,
+				FLASH_LED_CURRENT_READING_DELAY_MAX);
+		}
+
+		power_supply_get_property(led->battery_psy,
+				POWER_SUPPLY_PROP_FLASH_CURRENT_MAX, &prop);
+		if (!prop.intval) {
+			dev_err(&led->pdev->dev,
+				"battery too low for flash\n");
+			return -EINVAL;
+		}
+
+		max_curr_avail_ma = (prop.intval / FLASH_LED_UA_PER_MA);
+	}
+
+	/*
+	 * When thermal mitigation is available, this logic will execute to
+	 * derate current based upon the PMIC die temperature.
+	 */
+	if (led->pdata->die_current_derate_en) {
+		chg_temp_milidegc = qpnp_flash_led_get_die_temp(led);
+		if (chg_temp_milidegc < 0)
+			return -EINVAL;
+
+		die_temp_degc = div_s64(chg_temp_milidegc, 1000);
+		allowed_die_temp_curr_ma =
+			qpnp_flash_led_get_allowed_die_temp_curr(led,
+								die_temp_degc);
+		if (allowed_die_temp_curr_ma < 0)
+			return -EINVAL;
+	}
+
+	max_curr_avail_ma = (max_curr_avail_ma >= allowed_die_temp_curr_ma)
+				? allowed_die_temp_curr_ma : max_curr_avail_ma;
+
+	return max_curr_avail_ma;
+}
+
+static ssize_t qpnp_flash_led_die_temp_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct qpnp_flash_led *led;
+	struct flash_node_data *flash_node;
+	unsigned long val;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+	led = dev_get_drvdata(&flash_node->pdev->dev);
+
+	/*'0' for disable die_temp feature; non-zero to enable feature*/
+	if (val == 0)
+		led->pdata->die_current_derate_en = false;
+	else
+		led->pdata->die_current_derate_en = true;
+
+	return count;
+}
+
+static ssize_t qpnp_led_strobe_type_store(struct device *dev,
+			struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct flash_node_data *flash_node;
+	unsigned long state;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret = -EINVAL;
+
+	ret = kstrtoul(buf, 10, &state);
+	if (ret)
+		return ret;
+
+	flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+
+	/* '0' for sw strobe; '1' for hw strobe */
+	if (state == 1)
+		flash_node->trigger |= FLASH_LED_STROBE_TYPE_HW;
+	else
+		flash_node->trigger &= ~FLASH_LED_STROBE_TYPE_HW;
+
+	return count;
+}
+
+static ssize_t qpnp_flash_led_dump_regs_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct qpnp_flash_led *led;
+	struct flash_node_data *flash_node;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	int rc, i, count = 0;
+	u16 addr;
+	uint val;
+
+	flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+	led = dev_get_drvdata(&flash_node->pdev->dev);
+	for (i = 0; i < ARRAY_SIZE(qpnp_flash_led_ctrl_dbg_regs); i++) {
+		addr = led->base + qpnp_flash_led_ctrl_dbg_regs[i];
+		rc = regmap_read(led->regmap, addr, &val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Unable to read from addr=%x, rc(%d)\n",
+				addr, rc);
+			return -EINVAL;
+		}
+
+		count += snprintf(buf + count, PAGE_SIZE - count,
+				"REG_0x%x = 0x%02x\n", addr, val);
+
+		if (count >= PAGE_SIZE)
+			return PAGE_SIZE - 1;
+	}
+
+	return count;
+}
+
+static ssize_t qpnp_flash_led_current_derate_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct qpnp_flash_led *led;
+	struct flash_node_data *flash_node;
+	unsigned long val;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+	led = dev_get_drvdata(&flash_node->pdev->dev);
+
+	/*'0' for disable derate feature; non-zero to enable derate feature */
+	if (val == 0)
+		led->pdata->power_detect_en = false;
+	else
+		led->pdata->power_detect_en = true;
+
+	return count;
+}
+
+static ssize_t qpnp_flash_led_max_current_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct qpnp_flash_led *led;
+	struct flash_node_data *flash_node;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	int max_curr_avail_ma = 0;
+
+	flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+	led = dev_get_drvdata(&flash_node->pdev->dev);
+
+	if (led->flash_node[0].flash_on)
+		max_curr_avail_ma += led->flash_node[0].max_current;
+	if (led->flash_node[1].flash_on)
+		max_curr_avail_ma += led->flash_node[1].max_current;
+
+	if (led->pdata->power_detect_en ||
+			led->pdata->die_current_derate_en) {
+		max_curr_avail_ma =
+			qpnp_flash_led_get_max_avail_current(flash_node, led);
+
+		if (max_curr_avail_ma < 0)
+			return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", max_curr_avail_ma);
+}
+
+static struct device_attribute qpnp_flash_led_attrs[] = {
+	__ATTR(strobe, 0664, NULL, qpnp_led_strobe_type_store),
+	__ATTR(reg_dump, 0664, qpnp_flash_led_dump_regs_show, NULL),
+	__ATTR(enable_current_derate, 0664, NULL,
+		qpnp_flash_led_current_derate_store),
+	__ATTR(max_allowed_current, 0664, qpnp_flash_led_max_current_show,
+		NULL),
+	__ATTR(enable_die_temp_current_derate, 0664, NULL,
+		qpnp_flash_led_die_temp_store),
+};
+
+static int qpnp_flash_led_get_thermal_derate_rate(const char *rate)
+{
+	/*
+	 * return 5% derate as default value if user specifies
+	 * a value un-supported
+	 */
+	if (strcmp(rate, "1_PERCENT") == 0)
+		return RATE_1_PERCENT;
+	else if (strcmp(rate, "1P25_PERCENT") == 0)
+		return RATE_1P25_PERCENT;
+	else if (strcmp(rate, "2_PERCENT") == 0)
+		return RATE_2_PERCENT;
+	else if (strcmp(rate, "2P5_PERCENT") == 0)
+		return RATE_2P5_PERCENT;
+	else if (strcmp(rate, "5_PERCENT") == 0)
+		return RATE_5_PERCENT;
+	else
+		return RATE_5_PERCENT;
+}
+
+static int qpnp_flash_led_get_ramp_step(const char *step)
+{
+	/*
+	 * return 27 us as default value if user specifies
+	 * a value un-supported
+	 */
+	if (strcmp(step, "0P2_US") == 0)
+		return RAMP_STEP_0P2_US;
+	else if (strcmp(step, "0P4_US") == 0)
+		return RAMP_STEP_0P4_US;
+	else if (strcmp(step, "0P8_US") == 0)
+		return RAMP_STEP_0P8_US;
+	else if (strcmp(step, "1P6_US") == 0)
+		return RAMP_STEP_1P6_US;
+	else if (strcmp(step, "3P3_US") == 0)
+		return RAMP_STEP_3P3_US;
+	else if (strcmp(step, "6P7_US") == 0)
+		return RAMP_STEP_6P7_US;
+	else if (strcmp(step, "13P5_US") == 0)
+		return RAMP_STEP_13P5_US;
+	else
+		return RAMP_STEP_27US;
+}
+
+static u8 qpnp_flash_led_get_droop_debounce_time(u8 val)
+{
+	/*
+	 * return 10 us as default value if user specifies
+	 * a value un-supported
+	 */
+	switch (val) {
+	case 0:
+		return 0;
+	case 10:
+		return 1;
+	case 32:
+		return 2;
+	case 64:
+		return 3;
+	default:
+		return 1;
+	}
+}
+
+static u8 qpnp_flash_led_get_startup_dly(u8 val)
+{
+	/*
+	 * return 128 us as default value if user specifies
+	 * a value un-supported
+	 */
+	switch (val) {
+	case 10:
+		return 0;
+	case 32:
+		return 1;
+	case 64:
+		return 2;
+	case 128:
+		return 3;
+	default:
+		return 3;
+	}
+}
+
+static int
+qpnp_flash_led_get_peripheral_type(struct qpnp_flash_led *led)
+{
+	int rc;
+	uint val;
+
+	rc = regmap_read(led->regmap,
+			 FLASH_LED_PERIPHERAL_SUBTYPE(led->base), &val);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+				"Unable to read peripheral subtype\n");
+		return -EINVAL;
+	}
+
+	return val;
+}
+
+static int qpnp_flash_led_module_disable(struct qpnp_flash_led *led,
+				struct flash_node_data *flash_node)
+{
+	union power_supply_propval psy_prop;
+	int rc;
+	uint val, tmp;
+
+	rc = regmap_read(led->regmap, FLASH_LED_STROBE_CTRL(led->base), &val);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Unable to read strobe reg\n");
+		return -EINVAL;
+	}
+
+	tmp = (~flash_node->trigger) & val;
+	if (!tmp) {
+		if (flash_node->type == TORCH) {
+			rc = qpnp_led_masked_write(led,
+				FLASH_LED_UNLOCK_SECURE(led->base),
+				FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Secure reg write failed\n");
+				return -EINVAL;
+			}
+
+			rc = qpnp_led_masked_write(led,
+				FLASH_TORCH(led->base),
+				FLASH_TORCH_MASK, FLASH_LED_TORCH_DISABLE);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Torch reg write failed\n");
+				return -EINVAL;
+			}
+		}
+
+		if (led->battery_psy &&
+			led->revid_data->pmic_subtype == PMI8996_SUBTYPE &&
+						!led->revid_data->rev3) {
+			psy_prop.intval = false;
+			rc = power_supply_set_property(led->battery_psy,
+					POWER_SUPPLY_PROP_FLASH_TRIGGER,
+							&psy_prop);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+				"Failed to enble charger i/p current limit\n");
+				return -EINVAL;
+			}
+		}
+
+		rc = qpnp_led_masked_write(led,
+				FLASH_MODULE_ENABLE_CTRL(led->base),
+				FLASH_MODULE_ENABLE_MASK,
+				FLASH_LED_MODULE_CTRL_DEFAULT);
+		if (rc) {
+			dev_err(&led->pdev->dev, "Module disable failed\n");
+			return -EINVAL;
+		}
+
+		if (led->pinctrl) {
+			rc = pinctrl_select_state(led->pinctrl,
+					led->gpio_state_suspend);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"failed to disable GPIO\n");
+				return -EINVAL;
+			}
+			led->gpio_enabled = false;
+		}
+
+		if (led->battery_psy) {
+			psy_prop.intval = false;
+			rc = power_supply_set_property(led->battery_psy,
+						POWER_SUPPLY_PROP_FLASH_ACTIVE,
+							&psy_prop);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+				"Failed to setup OTG pulse skip enable\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	if (flash_node->trigger & FLASH_LED0_TRIGGER) {
+		rc = qpnp_led_masked_write(led,
+				led->current_addr,
+				FLASH_CURRENT_MASK, 0x00);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"current register write failed\n");
+			return -EINVAL;
+		}
+	}
+
+	if (flash_node->trigger & FLASH_LED1_TRIGGER) {
+		rc = qpnp_led_masked_write(led,
+				led->current2_addr,
+				FLASH_CURRENT_MASK, 0x00);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"current register write failed\n");
+			return -EINVAL;
+		}
+	}
+
+	if (flash_node->id == FLASH_LED_SWITCH)
+		flash_node->trigger &= FLASH_LED_STROBE_TYPE_HW;
+
+	return 0;
+}
+
+static enum
+led_brightness qpnp_flash_led_brightness_get(struct led_classdev *led_cdev)
+{
+	return led_cdev->brightness;
+}
+
+static int flash_regulator_parse_dt(struct qpnp_flash_led *led,
+					struct flash_node_data *flash_node) {
+
+	int i = 0, rc;
+	struct device_node *node = flash_node->cdev.dev->of_node;
+	struct device_node *temp = NULL;
+	const char *temp_string;
+	u32 val;
+
+	flash_node->reg_data = devm_kzalloc(&led->pdev->dev,
+					sizeof(struct flash_regulator_data *) *
+						flash_node->num_regulators,
+						GFP_KERNEL);
+	if (!flash_node->reg_data) {
+		dev_err(&led->pdev->dev,
+				"Unable to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	for_each_child_of_node(node, temp) {
+		rc = of_property_read_string(temp, "regulator-name",
+							&temp_string);
+		if (!rc)
+			flash_node->reg_data[i].reg_name = temp_string;
+		else {
+			dev_err(&led->pdev->dev,
+					"Unable to read regulator name\n");
+			return rc;
+		}
+
+		rc = of_property_read_u32(temp, "max-voltage", &val);
+		if (!rc) {
+			flash_node->reg_data[i].max_volt_uv = val;
+		} else if (rc != -EINVAL) {
+			dev_err(&led->pdev->dev,
+					"Unable to read max voltage\n");
+			return rc;
+		}
+
+		i++;
+	}
+
+	return 0;
+}
+
+static int flash_regulator_setup(struct qpnp_flash_led *led,
+				struct flash_node_data *flash_node, bool on)
+{
+	int i, rc = 0;
+
+	if (on == false) {
+		i = flash_node->num_regulators;
+		goto error_regulator_setup;
+	}
+
+	for (i = 0; i < flash_node->num_regulators; i++) {
+		flash_node->reg_data[i].regs =
+			regulator_get(flash_node->cdev.dev,
+					flash_node->reg_data[i].reg_name);
+		if (IS_ERR(flash_node->reg_data[i].regs)) {
+			rc = PTR_ERR(flash_node->reg_data[i].regs);
+			dev_err(&led->pdev->dev,
+					"Failed to get regulator\n");
+			goto error_regulator_setup;
+		}
+
+		if (regulator_count_voltages(flash_node->reg_data[i].regs)
+									> 0) {
+			rc = regulator_set_voltage(flash_node->reg_data[i].regs,
+					flash_node->reg_data[i].max_volt_uv,
+					flash_node->reg_data[i].max_volt_uv);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"regulator set voltage failed\n");
+				regulator_put(flash_node->reg_data[i].regs);
+				goto error_regulator_setup;
+			}
+		}
+	}
+
+	return rc;
+
+error_regulator_setup:
+	while (i--) {
+		if (regulator_count_voltages(flash_node->reg_data[i].regs)
+									> 0) {
+			regulator_set_voltage(flash_node->reg_data[i].regs,
+				0, flash_node->reg_data[i].max_volt_uv);
+		}
+
+		regulator_put(flash_node->reg_data[i].regs);
+	}
+
+	return rc;
+}
+
+static int flash_regulator_enable(struct qpnp_flash_led *led,
+				struct flash_node_data *flash_node, bool on)
+{
+	int i, rc = 0;
+
+	if (on == false) {
+		i = flash_node->num_regulators;
+		goto error_regulator_enable;
+	}
+
+	for (i = 0; i < flash_node->num_regulators; i++) {
+		rc = regulator_enable(flash_node->reg_data[i].regs);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"regulator enable failed\n");
+			goto error_regulator_enable;
+		}
+	}
+
+	return rc;
+
+error_regulator_enable:
+	while (i--)
+		regulator_disable(flash_node->reg_data[i].regs);
+
+	return rc;
+}
+
+int qpnp_flash_led_prepare(struct led_trigger *trig, int options,
+					int *max_current)
+{
+	struct led_classdev *led_cdev = trigger_to_lcdev(trig);
+	struct flash_node_data *flash_node;
+	struct qpnp_flash_led *led;
+	int rc;
+
+	if (!led_cdev) {
+		pr_err("Invalid led_trigger provided\n");
+		return -EINVAL;
+	}
+
+	flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+	led = dev_get_drvdata(&flash_node->pdev->dev);
+
+	if (!(options & FLASH_LED_PREPARE_OPTIONS_MASK)) {
+		dev_err(&led->pdev->dev, "Invalid options %d\n", options);
+		return -EINVAL;
+	}
+
+	if (options & ENABLE_REGULATOR) {
+		rc = flash_regulator_enable(led, flash_node, true);
+		if (rc < 0) {
+			dev_err(&led->pdev->dev,
+				"enable regulator failed, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (options & DISABLE_REGULATOR) {
+		rc = flash_regulator_enable(led, flash_node, false);
+		if (rc < 0) {
+			dev_err(&led->pdev->dev,
+				"disable regulator failed, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (options & QUERY_MAX_CURRENT) {
+		rc = qpnp_flash_led_get_max_avail_current(flash_node, led);
+		if (rc < 0) {
+			dev_err(&led->pdev->dev,
+				"query max current failed, rc=%d\n", rc);
+			return rc;
+		}
+		*max_current = rc;
+	}
+
+	return 0;
+}
+
+static void qpnp_flash_led_work(struct work_struct *work)
+{
+	struct flash_node_data *flash_node = container_of(work,
+				struct flash_node_data, work);
+	struct qpnp_flash_led *led = dev_get_drvdata(&flash_node->pdev->dev);
+	union power_supply_propval psy_prop;
+	int rc, brightness = flash_node->cdev.brightness;
+	int max_curr_avail_ma = 0;
+	int total_curr_ma = 0;
+	int i;
+	u8 val;
+	uint temp;
+
+	mutex_lock(&led->flash_led_lock);
+
+	if (!brightness)
+		goto turn_off;
+
+	if (led->open_fault) {
+		dev_err(&led->pdev->dev, "Open fault detected\n");
+		mutex_unlock(&led->flash_led_lock);
+		return;
+	}
+
+	if (!flash_node->flash_on && flash_node->num_regulators > 0) {
+		rc = flash_regulator_enable(led, flash_node, true);
+		if (rc) {
+			mutex_unlock(&led->flash_led_lock);
+			return;
+		}
+	}
+
+	if (!led->gpio_enabled && led->pinctrl) {
+		rc = pinctrl_select_state(led->pinctrl,
+						led->gpio_state_active);
+		if (rc) {
+			dev_err(&led->pdev->dev, "failed to enable GPIO\n");
+			goto error_enable_gpio;
+		}
+		led->gpio_enabled = true;
+	}
+
+	if (led->dbg_feature_en) {
+		rc = qpnp_led_masked_write(led,
+						INT_SET_TYPE(led->base),
+						FLASH_STATUS_REG_MASK, 0x1F);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"INT_SET_TYPE write failed\n");
+			goto exit_flash_led_work;
+		}
+
+		rc = qpnp_led_masked_write(led,
+					IN_POLARITY_HIGH(led->base),
+					FLASH_STATUS_REG_MASK, 0x1F);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"IN_POLARITY_HIGH write failed\n");
+			goto exit_flash_led_work;
+		}
+
+		rc = qpnp_led_masked_write(led,
+					INT_EN_SET(led->base),
+					FLASH_STATUS_REG_MASK, 0x1F);
+		if (rc) {
+			dev_err(&led->pdev->dev, "INT_EN_SET write failed\n");
+			goto exit_flash_led_work;
+		}
+
+		rc = qpnp_led_masked_write(led,
+					INT_LATCHED_CLR(led->base),
+					FLASH_STATUS_REG_MASK, 0x1F);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"INT_LATCHED_CLR write failed\n");
+			goto exit_flash_led_work;
+		}
+	}
+
+	if (led->flash_node[led->num_leds - 1].id == FLASH_LED_SWITCH &&
+					flash_node->id != FLASH_LED_SWITCH) {
+		led->flash_node[led->num_leds - 1].trigger |=
+						(0x80 >> flash_node->id);
+		if (flash_node->id == FLASH_LED_0)
+			led->flash_node[led->num_leds - 1].prgm_current =
+						flash_node->prgm_current;
+		else if (flash_node->id == FLASH_LED_1)
+			led->flash_node[led->num_leds - 1].prgm_current2 =
+						flash_node->prgm_current;
+	}
+
+	if (flash_node->type == TORCH) {
+		rc = qpnp_led_masked_write(led,
+			FLASH_LED_UNLOCK_SECURE(led->base),
+			FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+		if (rc) {
+			dev_err(&led->pdev->dev, "Secure reg write failed\n");
+			goto exit_flash_led_work;
+		}
+
+		rc = qpnp_led_masked_write(led,
+			FLASH_TORCH(led->base),
+			FLASH_TORCH_MASK, FLASH_LED_TORCH_ENABLE);
+		if (rc) {
+			dev_err(&led->pdev->dev, "Torch reg write failed\n");
+			goto exit_flash_led_work;
+		}
+
+		if (flash_node->id == FLASH_LED_SWITCH) {
+			val = (u8)(flash_node->prgm_current *
+						FLASH_TORCH_MAX_LEVEL
+						/ flash_node->max_current);
+			rc = qpnp_led_masked_write(led,
+						led->current_addr,
+						FLASH_CURRENT_MASK, val);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Torch reg write failed\n");
+				goto exit_flash_led_work;
+			}
+
+			val = (u8)(flash_node->prgm_current2 *
+						FLASH_TORCH_MAX_LEVEL
+						/ flash_node->max_current);
+			rc = qpnp_led_masked_write(led,
+					led->current2_addr,
+					FLASH_CURRENT_MASK, val);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Torch reg write failed\n");
+				goto exit_flash_led_work;
+			}
+		} else {
+			val = (u8)(flash_node->prgm_current *
+						FLASH_TORCH_MAX_LEVEL /
+						flash_node->max_current);
+			if (flash_node->id == FLASH_LED_0) {
+				rc = qpnp_led_masked_write(led,
+						led->current_addr,
+						FLASH_CURRENT_MASK, val);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"current reg write failed\n");
+					goto exit_flash_led_work;
+				}
+			} else {
+				rc = qpnp_led_masked_write(led,
+						led->current2_addr,
+						FLASH_CURRENT_MASK, val);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"current reg write failed\n");
+					goto exit_flash_led_work;
+				}
+			}
+		}
+
+		rc = qpnp_led_masked_write(led,
+			FLASH_MAX_CURRENT(led->base),
+			FLASH_CURRENT_MASK, FLASH_TORCH_MAX_LEVEL);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"Max current reg write failed\n");
+			goto exit_flash_led_work;
+		}
+
+		rc = qpnp_led_masked_write(led,
+			FLASH_MODULE_ENABLE_CTRL(led->base),
+			FLASH_MODULE_ENABLE_MASK, FLASH_MODULE_ENABLE);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Module enable reg write failed\n");
+			goto exit_flash_led_work;
+		}
+
+		if (led->pdata->hdrm_sns_ch0_en ||
+						led->pdata->hdrm_sns_ch1_en) {
+			if (flash_node->id == FLASH_LED_SWITCH) {
+				rc = qpnp_led_masked_write(led,
+					FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+					FLASH_LED_HDRM_SNS_ENABLE_MASK,
+					flash_node->trigger &
+					FLASH_LED0_TRIGGER ?
+					FLASH_LED_HDRM_SNS_ENABLE :
+					FLASH_LED_HDRM_SNS_DISABLE);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Headroom sense enable failed\n");
+					goto exit_flash_led_work;
+				}
+
+				rc = qpnp_led_masked_write(led,
+					FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+					FLASH_LED_HDRM_SNS_ENABLE_MASK,
+					flash_node->trigger &
+					FLASH_LED1_TRIGGER ?
+					FLASH_LED_HDRM_SNS_ENABLE :
+					FLASH_LED_HDRM_SNS_DISABLE);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Headroom sense enable failed\n");
+					goto exit_flash_led_work;
+				}
+			} else if (flash_node->id == FLASH_LED_0) {
+				rc = qpnp_led_masked_write(led,
+					FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+					FLASH_LED_HDRM_SNS_ENABLE_MASK,
+					FLASH_LED_HDRM_SNS_ENABLE);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Headroom sense disable failed\n");
+					goto exit_flash_led_work;
+				}
+			} else if (flash_node->id == FLASH_LED_1) {
+				rc = qpnp_led_masked_write(led,
+					FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+					FLASH_LED_HDRM_SNS_ENABLE_MASK,
+					FLASH_LED_HDRM_SNS_ENABLE);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Headroom sense disable failed\n");
+					goto exit_flash_led_work;
+				}
+			}
+		}
+
+		rc = qpnp_led_masked_write(led,
+			FLASH_LED_STROBE_CTRL(led->base),
+			(flash_node->id == FLASH_LED_SWITCH ? FLASH_STROBE_MASK
+						| FLASH_LED_STROBE_TYPE_HW
+							: flash_node->trigger |
+						FLASH_LED_STROBE_TYPE_HW),
+							flash_node->trigger);
+		if (rc) {
+			dev_err(&led->pdev->dev, "Strobe reg write failed\n");
+			goto exit_flash_led_work;
+		}
+	} else if (flash_node->type == FLASH) {
+		if (flash_node->trigger & FLASH_LED0_TRIGGER)
+			max_curr_avail_ma += flash_node->max_current;
+		if (flash_node->trigger & FLASH_LED1_TRIGGER)
+			max_curr_avail_ma += flash_node->max_current;
+
+		psy_prop.intval = true;
+		rc = power_supply_set_property(led->battery_psy,
+						POWER_SUPPLY_PROP_FLASH_ACTIVE,
+								&psy_prop);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Failed to setup OTG pulse skip enable\n");
+			goto exit_flash_led_work;
+		}
+
+		if (led->pdata->power_detect_en ||
+					led->pdata->die_current_derate_en) {
+			if (led->battery_psy) {
+				power_supply_get_property(led->battery_psy,
+					POWER_SUPPLY_PROP_STATUS,
+					&psy_prop);
+				if (psy_prop.intval < 0) {
+					dev_err(&led->pdev->dev,
+						"Invalid battery status\n");
+					goto exit_flash_led_work;
+				}
+
+				if (psy_prop.intval ==
+						POWER_SUPPLY_STATUS_CHARGING)
+					led->charging_enabled = true;
+				else if (psy_prop.intval ==
+					POWER_SUPPLY_STATUS_DISCHARGING
+					|| psy_prop.intval ==
+					POWER_SUPPLY_STATUS_NOT_CHARGING)
+					led->charging_enabled = false;
+			}
+			max_curr_avail_ma =
+				qpnp_flash_led_get_max_avail_current
+							(flash_node, led);
+			if (max_curr_avail_ma < 0) {
+				dev_err(&led->pdev->dev,
+					"Failed to get max avail curr\n");
+				goto exit_flash_led_work;
+			}
+		}
+
+		if (flash_node->id == FLASH_LED_SWITCH) {
+			if (flash_node->trigger & FLASH_LED0_TRIGGER)
+				total_curr_ma += flash_node->prgm_current;
+			if (flash_node->trigger & FLASH_LED1_TRIGGER)
+				total_curr_ma += flash_node->prgm_current2;
+
+			if (max_curr_avail_ma < total_curr_ma) {
+				flash_node->prgm_current =
+					(flash_node->prgm_current *
+					max_curr_avail_ma) / total_curr_ma;
+				flash_node->prgm_current2 =
+					(flash_node->prgm_current2 *
+					max_curr_avail_ma) / total_curr_ma;
+			}
+
+			val = (u8)(flash_node->prgm_current *
+				FLASH_MAX_LEVEL / flash_node->max_current);
+			rc = qpnp_led_masked_write(led,
+				led->current_addr, FLASH_CURRENT_MASK, val);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Current register write failed\n");
+				goto exit_flash_led_work;
+			}
+
+			val = (u8)(flash_node->prgm_current2 *
+				FLASH_MAX_LEVEL / flash_node->max_current);
+			rc = qpnp_led_masked_write(led,
+				led->current2_addr, FLASH_CURRENT_MASK, val);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Current register write failed\n");
+				goto exit_flash_led_work;
+			}
+		} else {
+			if (max_curr_avail_ma < flash_node->prgm_current) {
+				dev_err(&led->pdev->dev,
+					"battery only supprots %d mA\n",
+					max_curr_avail_ma);
+				flash_node->prgm_current =
+					 (u16)max_curr_avail_ma;
+			}
+
+			val = (u8)(flash_node->prgm_current *
+					 FLASH_MAX_LEVEL
+					/ flash_node->max_current);
+			if (flash_node->id == FLASH_LED_0) {
+				rc = qpnp_led_masked_write(
+					led,
+					led->current_addr,
+					FLASH_CURRENT_MASK, val);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"current reg write failed\n");
+					goto exit_flash_led_work;
+				}
+			} else if (flash_node->id == FLASH_LED_1) {
+				rc = qpnp_led_masked_write(
+					led,
+					led->current2_addr,
+					FLASH_CURRENT_MASK, val);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"current reg write failed\n");
+					goto exit_flash_led_work;
+				}
+			}
+		}
+
+		val = (u8)((flash_node->duration - FLASH_DURATION_DIVIDER)
+						/ FLASH_DURATION_DIVIDER);
+		rc = qpnp_led_masked_write(led,
+			FLASH_SAFETY_TIMER(led->base),
+			FLASH_SAFETY_TIMER_MASK, val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Safety timer reg write failed\n");
+			goto exit_flash_led_work;
+		}
+
+		rc = qpnp_led_masked_write(led,
+			FLASH_MAX_CURRENT(led->base),
+			FLASH_CURRENT_MASK, FLASH_MAX_LEVEL);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Max current reg write failed\n");
+			goto exit_flash_led_work;
+		}
+
+		if (!led->charging_enabled) {
+			rc = qpnp_led_masked_write(led,
+				FLASH_MODULE_ENABLE_CTRL(led->base),
+				FLASH_MODULE_ENABLE, FLASH_MODULE_ENABLE);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Module enable reg write failed\n");
+				goto exit_flash_led_work;
+			}
+
+			usleep_range(FLASH_RAMP_UP_DELAY_US_MIN,
+						FLASH_RAMP_UP_DELAY_US_MAX);
+		}
+
+		if (led->revid_data->pmic_subtype == PMI8996_SUBTYPE &&
+						!led->revid_data->rev3) {
+			rc = power_supply_set_property(led->battery_psy,
+						POWER_SUPPLY_PROP_FLASH_TRIGGER,
+							&psy_prop);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+				"Failed to disable charger i/p curr limit\n");
+				goto exit_flash_led_work;
+			}
+		}
+
+		if (led->pdata->hdrm_sns_ch0_en ||
+					led->pdata->hdrm_sns_ch1_en) {
+			if (flash_node->id == FLASH_LED_SWITCH) {
+				rc = qpnp_led_masked_write(led,
+					FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+					FLASH_LED_HDRM_SNS_ENABLE_MASK,
+					(flash_node->trigger &
+					FLASH_LED0_TRIGGER ?
+					FLASH_LED_HDRM_SNS_ENABLE :
+					FLASH_LED_HDRM_SNS_DISABLE));
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Headroom sense enable failed\n");
+					goto exit_flash_led_work;
+				}
+
+				rc = qpnp_led_masked_write(led,
+					FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+					FLASH_LED_HDRM_SNS_ENABLE_MASK,
+					(flash_node->trigger &
+					FLASH_LED1_TRIGGER ?
+					FLASH_LED_HDRM_SNS_ENABLE :
+					FLASH_LED_HDRM_SNS_DISABLE));
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Headroom sense enable failed\n");
+					goto exit_flash_led_work;
+				}
+			} else if (flash_node->id == FLASH_LED_0) {
+				rc = qpnp_led_masked_write(led,
+					FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+					FLASH_LED_HDRM_SNS_ENABLE_MASK,
+					FLASH_LED_HDRM_SNS_ENABLE);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Headroom sense disable failed\n");
+					goto exit_flash_led_work;
+				}
+			} else if (flash_node->id == FLASH_LED_1) {
+				rc = qpnp_led_masked_write(led,
+					FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+					FLASH_LED_HDRM_SNS_ENABLE_MASK,
+					FLASH_LED_HDRM_SNS_ENABLE);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Headroom sense disable failed\n");
+					goto exit_flash_led_work;
+				}
+			}
+		}
+
+		rc = qpnp_led_masked_write(led,
+			FLASH_LED_STROBE_CTRL(led->base),
+			(flash_node->id == FLASH_LED_SWITCH ? FLASH_STROBE_MASK
+						| FLASH_LED_STROBE_TYPE_HW
+							: flash_node->trigger |
+						FLASH_LED_STROBE_TYPE_HW),
+							flash_node->trigger);
+		if (rc) {
+			dev_err(&led->pdev->dev, "Strobe reg write failed\n");
+			goto exit_flash_led_work;
+		}
+
+		if (led->strobe_debug && led->dbg_feature_en) {
+			udelay(2000);
+			rc = regmap_read(led->regmap,
+					 FLASH_LED_FAULT_STATUS(led->base),
+					 &temp);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+				"Unable to read from addr= %x, rc(%d)\n",
+				FLASH_LED_FAULT_STATUS(led->base), rc);
+				goto exit_flash_led_work;
+			}
+			led->fault_reg = temp;
+		}
+	} else {
+		pr_err("Both Torch and Flash cannot be select at same time\n");
+		for (i = 0; i < led->num_leds; i++)
+			led->flash_node[i].flash_on = false;
+		goto turn_off;
+	}
+
+	flash_node->flash_on = true;
+	mutex_unlock(&led->flash_led_lock);
+
+	return;
+
+turn_off:
+	if (led->flash_node[led->num_leds - 1].id == FLASH_LED_SWITCH &&
+					flash_node->id != FLASH_LED_SWITCH)
+		led->flash_node[led->num_leds - 1].trigger &=
+						~(0x80 >> flash_node->id);
+	if (flash_node->type == TORCH) {
+		/*
+		 * Checking LED fault status detects hardware open fault.
+		 * If fault occurs, all subsequent LED enablement requests
+		 * will be rejected to protect hardware.
+		 */
+		rc = regmap_read(led->regmap,
+			FLASH_LED_FAULT_STATUS(led->base), &temp);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Failed to read out fault status register\n");
+			goto exit_flash_led_work;
+		}
+
+		led->open_fault |= (val & FLASH_LED_OPEN_FAULT_DETECTED);
+	}
+
+	rc = qpnp_led_masked_write(led,
+			FLASH_LED_STROBE_CTRL(led->base),
+			(flash_node->id == FLASH_LED_SWITCH ? FLASH_STROBE_MASK
+						| FLASH_LED_STROBE_TYPE_HW
+						: flash_node->trigger
+						| FLASH_LED_STROBE_TYPE_HW),
+						FLASH_LED_DISABLE);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Strobe disable failed\n");
+		goto exit_flash_led_work;
+	}
+
+	usleep_range(FLASH_RAMP_DN_DELAY_US_MIN, FLASH_RAMP_DN_DELAY_US_MAX);
+exit_flash_hdrm_sns:
+	if (led->pdata->hdrm_sns_ch0_en) {
+		if (flash_node->id == FLASH_LED_0 ||
+				flash_node->id == FLASH_LED_SWITCH) {
+			rc = qpnp_led_masked_write(led,
+					FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+					FLASH_LED_HDRM_SNS_ENABLE_MASK,
+					FLASH_LED_HDRM_SNS_DISABLE);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Headroom sense disable failed\n");
+				goto exit_flash_hdrm_sns;
+			}
+		}
+	}
+
+	if (led->pdata->hdrm_sns_ch1_en) {
+		if (flash_node->id == FLASH_LED_1 ||
+				flash_node->id == FLASH_LED_SWITCH) {
+			rc = qpnp_led_masked_write(led,
+					FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+					FLASH_LED_HDRM_SNS_ENABLE_MASK,
+					FLASH_LED_HDRM_SNS_DISABLE);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Headroom sense disable failed\n");
+				goto exit_flash_hdrm_sns;
+			}
+		}
+	}
+exit_flash_led_work:
+	rc = qpnp_flash_led_module_disable(led, flash_node);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Module disable failed\n");
+		goto exit_flash_led_work;
+	}
+error_enable_gpio:
+	if (flash_node->flash_on && flash_node->num_regulators > 0)
+		flash_regulator_enable(led, flash_node, false);
+
+	flash_node->flash_on = false;
+	mutex_unlock(&led->flash_led_lock);
+}
+
+static void qpnp_flash_led_brightness_set(struct led_classdev *led_cdev,
+						enum led_brightness value)
+{
+	struct flash_node_data *flash_node;
+	struct qpnp_flash_led *led;
+
+	flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+	led = dev_get_drvdata(&flash_node->pdev->dev);
+
+	if (value < LED_OFF) {
+		pr_err("Invalid brightness value\n");
+		return;
+	}
+
+	if (value > flash_node->cdev.max_brightness)
+		value = flash_node->cdev.max_brightness;
+
+	flash_node->cdev.brightness = value;
+	if (led->flash_node[led->num_leds - 1].id ==
+						FLASH_LED_SWITCH) {
+		if (flash_node->type == TORCH)
+			led->flash_node[led->num_leds - 1].type = TORCH;
+		else if (flash_node->type == FLASH)
+			led->flash_node[led->num_leds - 1].type = FLASH;
+
+		led->flash_node[led->num_leds - 1].max_current
+						= flash_node->max_current;
+
+		if (flash_node->id == FLASH_LED_0 ||
+					 flash_node->id == FLASH_LED_1) {
+			if (value < FLASH_LED_MIN_CURRENT_MA && value != 0)
+				value = FLASH_LED_MIN_CURRENT_MA;
+
+			flash_node->prgm_current = value;
+			flash_node->flash_on = value ? true : false;
+		} else if (flash_node->id == FLASH_LED_SWITCH) {
+			if (!value) {
+				flash_node->prgm_current = 0;
+				flash_node->prgm_current2 = 0;
+			}
+		}
+	} else {
+		if (value < FLASH_LED_MIN_CURRENT_MA && value != 0)
+			value = FLASH_LED_MIN_CURRENT_MA;
+		flash_node->prgm_current = value;
+	}
+
+	queue_work(led->ordered_workq, &flash_node->work);
+}
+
+static int qpnp_flash_led_init_settings(struct qpnp_flash_led *led)
+{
+	int rc;
+	u8 val, temp_val;
+	uint val_int;
+
+	rc = qpnp_led_masked_write(led,
+			FLASH_MODULE_ENABLE_CTRL(led->base),
+			FLASH_MODULE_ENABLE_MASK,
+			FLASH_LED_MODULE_CTRL_DEFAULT);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Module disable failed\n");
+		return rc;
+	}
+
+	rc = qpnp_led_masked_write(led,
+			FLASH_LED_STROBE_CTRL(led->base),
+			FLASH_STROBE_MASK, FLASH_LED_DISABLE);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Strobe disable failed\n");
+		return rc;
+	}
+
+	rc = qpnp_led_masked_write(led,
+					FLASH_LED_TMR_CTRL(led->base),
+					FLASH_TMR_MASK, FLASH_TMR_SAFETY);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"LED timer ctrl reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	val = (u8)(led->pdata->headroom / FLASH_LED_HEADROOM_DIVIDER -
+						FLASH_LED_HEADROOM_OFFSET);
+	rc = qpnp_led_masked_write(led,
+						FLASH_HEADROOM(led->base),
+						FLASH_HEADROOM_MASK, val);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Headroom reg write failed\n");
+		return rc;
+	}
+
+	val = qpnp_flash_led_get_startup_dly(led->pdata->startup_dly);
+
+	rc = qpnp_led_masked_write(led,
+					FLASH_STARTUP_DELAY(led->base),
+						FLASH_STARTUP_DLY_MASK, val);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Startup delay reg write failed\n");
+		return rc;
+	}
+
+	val = (u8)(led->pdata->clamp_current * FLASH_MAX_LEVEL /
+						FLASH_LED_MAX_CURRENT_MA);
+	rc = qpnp_led_masked_write(led,
+					FLASH_CLAMP_CURRENT(led->base),
+						FLASH_CURRENT_MASK, val);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Clamp current reg write failed\n");
+		return rc;
+	}
+
+	if (led->pdata->pmic_charger_support)
+		val = FLASH_LED_FLASH_HW_VREG_OK;
+	else
+		val = FLASH_LED_FLASH_SW_VREG_OK;
+	rc = qpnp_led_masked_write(led,
+					FLASH_VREG_OK_FORCE(led->base),
+						FLASH_VREG_OK_FORCE_MASK, val);
+	if (rc) {
+		dev_err(&led->pdev->dev, "VREG OK force reg write failed\n");
+		return rc;
+	}
+
+	if (led->pdata->self_check_en)
+		val = FLASH_MODULE_ENABLE;
+	else
+		val = FLASH_LED_DISABLE;
+	rc = qpnp_led_masked_write(led,
+					FLASH_FAULT_DETECT(led->base),
+						FLASH_FAULT_DETECT_MASK, val);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Fault detect reg write failed\n");
+		return rc;
+	}
+
+	val = 0x0;
+	val |= led->pdata->mask3_en << FLASH_LED_MASK3_ENABLE_SHIFT;
+	val |= FLASH_LED_MASK_MODULE_MASK2_ENABLE;
+	rc = qpnp_led_masked_write(led, FLASH_MASK_ENABLE(led->base),
+				FLASH_MASK_MODULE_CONTRL_MASK, val);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Mask module enable failed\n");
+		return rc;
+	}
+
+	rc = regmap_read(led->regmap, FLASH_PERPH_RESET_CTRL(led->base),
+			&val_int);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Unable to read from address %x, rc(%d)\n",
+			FLASH_PERPH_RESET_CTRL(led->base), rc);
+		return -EINVAL;
+	}
+	val = (u8)val_int;
+
+	if (led->pdata->follow_rb_disable) {
+		rc = qpnp_led_masked_write(led,
+				FLASH_LED_UNLOCK_SECURE(led->base),
+				FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+		if (rc) {
+			dev_err(&led->pdev->dev, "Secure reg write failed\n");
+			return -EINVAL;
+		}
+
+		val |= FLASH_FOLLOW_OTST2_RB_MASK;
+		rc = qpnp_led_masked_write(led,
+				FLASH_PERPH_RESET_CTRL(led->base),
+				FLASH_FOLLOW_OTST2_RB_MASK, val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"failed to reset OTST2_RB bit\n");
+			return rc;
+		}
+	} else {
+		rc = qpnp_led_masked_write(led,
+				FLASH_LED_UNLOCK_SECURE(led->base),
+				FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+		if (rc) {
+			dev_err(&led->pdev->dev, "Secure reg write failed\n");
+			return -EINVAL;
+		}
+
+		val &= ~FLASH_FOLLOW_OTST2_RB_MASK;
+		rc = qpnp_led_masked_write(led,
+				FLASH_PERPH_RESET_CTRL(led->base),
+				FLASH_FOLLOW_OTST2_RB_MASK, val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"failed to reset OTST2_RB bit\n");
+			return rc;
+		}
+	}
+
+	if (!led->pdata->thermal_derate_en)
+		val = 0x0;
+	else {
+		val = led->pdata->thermal_derate_en << 7;
+		val |= led->pdata->thermal_derate_rate << 3;
+		val |= (led->pdata->thermal_derate_threshold -
+				FLASH_LED_THERMAL_THRESHOLD_MIN) /
+				FLASH_LED_THERMAL_DEVIDER;
+	}
+	rc = qpnp_led_masked_write(led,
+					FLASH_THERMAL_DRATE(led->base),
+					FLASH_THERMAL_DERATE_MASK, val);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Thermal derate reg write failed\n");
+		return rc;
+	}
+
+	if (!led->pdata->current_ramp_en)
+		val = 0x0;
+	else {
+		val = led->pdata->current_ramp_en << 7;
+		val |= led->pdata->ramp_up_step << 3;
+		val |= led->pdata->ramp_dn_step;
+	}
+	rc = qpnp_led_masked_write(led,
+						FLASH_CURRENT_RAMP(led->base),
+						FLASH_CURRENT_RAMP_MASK, val);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Current ramp reg write failed\n");
+		return rc;
+	}
+
+	if (!led->pdata->vph_pwr_droop_en)
+		val = 0x0;
+	else {
+		val = led->pdata->vph_pwr_droop_en << 7;
+		val |= ((led->pdata->vph_pwr_droop_threshold -
+				FLASH_LED_VPH_DROOP_THRESHOLD_MIN_MV) /
+				FLASH_LED_VPH_DROOP_THRESHOLD_DIVIDER) << 4;
+		temp_val =
+			qpnp_flash_led_get_droop_debounce_time(
+				led->pdata->vph_pwr_droop_debounce_time);
+		if (temp_val == 0xFF) {
+			dev_err(&led->pdev->dev, "Invalid debounce time\n");
+			return temp_val;
+		}
+
+		val |= temp_val;
+	}
+	rc = qpnp_led_masked_write(led,
+						FLASH_VPH_PWR_DROOP(led->base),
+						FLASH_VPH_PWR_DROOP_MASK, val);
+	if (rc) {
+		dev_err(&led->pdev->dev, "VPH PWR droop reg write failed\n");
+		return rc;
+	}
+
+	led->battery_psy = power_supply_get_by_name("battery");
+	if (!led->battery_psy) {
+		dev_err(&led->pdev->dev,
+			"Failed to get battery power supply\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int qpnp_flash_led_parse_each_led_dt(struct qpnp_flash_led *led,
+					struct flash_node_data *flash_node)
+{
+	const char *temp_string;
+	struct device_node *node = flash_node->cdev.dev->of_node;
+	struct device_node *temp = NULL;
+	int rc = 0, num_regs = 0;
+	u32 val;
+
+	rc = of_property_read_string(node, "label", &temp_string);
+	if (!rc) {
+		if (strcmp(temp_string, "flash") == 0)
+			flash_node->type = FLASH;
+		else if (strcmp(temp_string, "torch") == 0)
+			flash_node->type = TORCH;
+		else if (strcmp(temp_string, "switch") == 0)
+			flash_node->type = SWITCH;
+		else {
+			dev_err(&led->pdev->dev, "Wrong flash LED type\n");
+			return -EINVAL;
+		}
+	} else if (rc < 0) {
+		dev_err(&led->pdev->dev, "Unable to read flash type\n");
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,current", &val);
+	if (!rc) {
+		if (val < FLASH_LED_MIN_CURRENT_MA)
+			val = FLASH_LED_MIN_CURRENT_MA;
+		flash_node->prgm_current = val;
+	} else if (rc != -EINVAL) {
+		dev_err(&led->pdev->dev, "Unable to read current\n");
+		return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,id", &val);
+	if (!rc)
+		flash_node->id = (u8)val;
+	else if (rc != -EINVAL) {
+		dev_err(&led->pdev->dev, "Unable to read led ID\n");
+		return rc;
+	}
+
+	if (flash_node->type == SWITCH || flash_node->type == FLASH) {
+		rc = of_property_read_u32(node, "qcom,duration", &val);
+		if (!rc)
+			flash_node->duration = (u16)val;
+		else if (rc != -EINVAL) {
+			dev_err(&led->pdev->dev, "Unable to read duration\n");
+			return rc;
+		}
+	}
+
+	switch (led->peripheral_type) {
+	case FLASH_SUBTYPE_SINGLE:
+		flash_node->trigger = FLASH_LED0_TRIGGER;
+		break;
+	case FLASH_SUBTYPE_DUAL:
+		if (flash_node->id == FLASH_LED_0)
+			flash_node->trigger = FLASH_LED0_TRIGGER;
+		else if (flash_node->id == FLASH_LED_1)
+			flash_node->trigger = FLASH_LED1_TRIGGER;
+		break;
+	default:
+		dev_err(&led->pdev->dev, "Invalid peripheral type\n");
+	}
+
+	while ((temp = of_get_next_child(node, temp))) {
+		if (of_find_property(temp, "regulator-name", NULL))
+			num_regs++;
+	}
+
+	if (num_regs)
+		flash_node->num_regulators = num_regs;
+
+	return rc;
+}
+
+static int qpnp_flash_led_parse_common_dt(
+				struct qpnp_flash_led *led,
+						struct device_node *node)
+{
+	int rc;
+	u32 val, temp_val;
+	const char *temp;
+
+	led->pdata->headroom = FLASH_LED_HEADROOM_DEFAULT_MV;
+	rc = of_property_read_u32(node, "qcom,headroom", &val);
+	if (!rc)
+		led->pdata->headroom = (u16)val;
+	else if (rc != -EINVAL) {
+		dev_err(&led->pdev->dev, "Unable to read headroom\n");
+		return rc;
+	}
+
+	led->pdata->startup_dly = FLASH_LED_STARTUP_DELAY_DEFAULT_US;
+	rc = of_property_read_u32(node, "qcom,startup-dly", &val);
+	if (!rc)
+		led->pdata->startup_dly = (u8)val;
+	else if (rc != -EINVAL) {
+		dev_err(&led->pdev->dev, "Unable to read startup delay\n");
+		return rc;
+	}
+
+	led->pdata->clamp_current = FLASH_LED_CLAMP_CURRENT_DEFAULT_MA;
+	rc = of_property_read_u32(node, "qcom,clamp-current", &val);
+	if (!rc) {
+		if (val < FLASH_LED_MIN_CURRENT_MA)
+			val = FLASH_LED_MIN_CURRENT_MA;
+		led->pdata->clamp_current = (u16)val;
+	} else if (rc != -EINVAL) {
+		dev_err(&led->pdev->dev, "Unable to read clamp current\n");
+		return rc;
+	}
+
+	led->pdata->pmic_charger_support =
+			of_property_read_bool(node,
+						"qcom,pmic-charger-support");
+
+	led->pdata->self_check_en =
+			of_property_read_bool(node, "qcom,self-check-enabled");
+
+	led->pdata->thermal_derate_en =
+			of_property_read_bool(node,
+						"qcom,thermal-derate-enabled");
+
+	if (led->pdata->thermal_derate_en) {
+		led->pdata->thermal_derate_rate =
+				FLASH_LED_THERMAL_DERATE_RATE_DEFAULT_PERCENT;
+		rc = of_property_read_string(node, "qcom,thermal-derate-rate",
+									&temp);
+		if (!rc) {
+			temp_val =
+				qpnp_flash_led_get_thermal_derate_rate(temp);
+			if (temp_val < 0) {
+				dev_err(&led->pdev->dev,
+					"Invalid thermal derate rate\n");
+				return -EINVAL;
+			}
+
+			led->pdata->thermal_derate_rate = (u8)temp_val;
+		} else {
+			dev_err(&led->pdev->dev,
+				"Unable to read thermal derate rate\n");
+			return -EINVAL;
+		}
+
+		led->pdata->thermal_derate_threshold =
+				FLASH_LED_THERMAL_DERATE_THRESHOLD_DEFAULT_C;
+		rc = of_property_read_u32(node, "qcom,thermal-derate-threshold",
+									&val);
+		if (!rc)
+			led->pdata->thermal_derate_threshold = (u8)val;
+		else if (rc != -EINVAL) {
+			dev_err(&led->pdev->dev,
+				"Unable to read thermal derate threshold\n");
+			return rc;
+		}
+	}
+
+	led->pdata->current_ramp_en =
+			of_property_read_bool(node,
+						"qcom,current-ramp-enabled");
+	if (led->pdata->current_ramp_en) {
+		led->pdata->ramp_up_step = FLASH_LED_RAMP_UP_STEP_DEFAULT_US;
+		rc = of_property_read_string(node, "qcom,ramp_up_step", &temp);
+		if (!rc) {
+			temp_val = qpnp_flash_led_get_ramp_step(temp);
+			if (temp_val < 0) {
+				dev_err(&led->pdev->dev,
+					"Invalid ramp up step values\n");
+				return -EINVAL;
+			}
+			led->pdata->ramp_up_step = (u8)temp_val;
+		} else if (rc != -EINVAL) {
+			dev_err(&led->pdev->dev,
+					"Unable to read ramp up steps\n");
+			return rc;
+		}
+
+		led->pdata->ramp_dn_step = FLASH_LED_RAMP_DN_STEP_DEFAULT_US;
+		rc = of_property_read_string(node, "qcom,ramp_dn_step", &temp);
+		if (!rc) {
+			temp_val = qpnp_flash_led_get_ramp_step(temp);
+			if (temp_val < 0) {
+				dev_err(&led->pdev->dev,
+					"Invalid ramp down step values\n");
+				return rc;
+			}
+			led->pdata->ramp_dn_step = (u8)temp_val;
+		} else if (rc != -EINVAL) {
+			dev_err(&led->pdev->dev,
+					"Unable to read ramp down steps\n");
+			return rc;
+		}
+	}
+
+	led->pdata->vph_pwr_droop_en = of_property_read_bool(node,
+						"qcom,vph-pwr-droop-enabled");
+	if (led->pdata->vph_pwr_droop_en) {
+		led->pdata->vph_pwr_droop_threshold =
+				FLASH_LED_VPH_PWR_DROOP_THRESHOLD_DEFAULT_MV;
+		rc = of_property_read_u32(node,
+					"qcom,vph-pwr-droop-threshold", &val);
+		if (!rc) {
+			led->pdata->vph_pwr_droop_threshold = (u16)val;
+		} else if (rc != -EINVAL) {
+			dev_err(&led->pdev->dev,
+				"Unable to read VPH PWR droop threshold\n");
+			return rc;
+		}
+
+		led->pdata->vph_pwr_droop_debounce_time =
+			FLASH_LED_VPH_PWR_DROOP_DEBOUNCE_TIME_DEFAULT_US;
+		rc = of_property_read_u32(node,
+				"qcom,vph-pwr-droop-debounce-time", &val);
+		if (!rc)
+			led->pdata->vph_pwr_droop_debounce_time = (u8)val;
+		else if (rc != -EINVAL) {
+			dev_err(&led->pdev->dev,
+				"Unable to read VPH PWR droop debounce time\n");
+			return rc;
+		}
+	}
+
+	led->pdata->hdrm_sns_ch0_en = of_property_read_bool(node,
+						"qcom,headroom-sense-ch0-enabled");
+
+	led->pdata->hdrm_sns_ch1_en = of_property_read_bool(node,
+						"qcom,headroom-sense-ch1-enabled");
+
+	led->pdata->power_detect_en = of_property_read_bool(node,
+						"qcom,power-detect-enabled");
+
+	led->pdata->mask3_en = of_property_read_bool(node,
+						"qcom,otst2-module-enabled");
+
+	led->pdata->follow_rb_disable = of_property_read_bool(node,
+						"qcom,follow-otst2-rb-disabled");
+
+	led->pdata->die_current_derate_en = of_property_read_bool(node,
+					"qcom,die-current-derate-enabled");
+
+	if (led->pdata->die_current_derate_en) {
+		led->vadc_dev = qpnp_get_vadc(&led->pdev->dev, "die-temp");
+		if (IS_ERR(led->vadc_dev)) {
+			pr_err("VADC channel property Missing\n");
+			return -EINVAL;
+		}
+
+		if (of_find_property(node, "qcom,die-temp-threshold",
+				&led->pdata->temp_threshold_num)) {
+			if (led->pdata->temp_threshold_num > 0) {
+				led->pdata->die_temp_threshold_degc =
+				devm_kzalloc(&led->pdev->dev,
+						led->pdata->temp_threshold_num,
+						GFP_KERNEL);
+
+				if (led->pdata->die_temp_threshold_degc
+								== NULL) {
+					dev_err(&led->pdev->dev,
+					"failed to allocate die temp array\n");
+					return -ENOMEM;
+				}
+				led->pdata->temp_threshold_num /=
+							sizeof(unsigned int);
+
+				rc = of_property_read_u32_array(node,
+						"qcom,die-temp-threshold",
+				led->pdata->die_temp_threshold_degc,
+						led->pdata->temp_threshold_num);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"couldn't read temp threshold rc=%d\n",
+								rc);
+					return rc;
+				}
+			}
+		}
+
+		if (of_find_property(node, "qcom,die-temp-derate-current",
+					&led->pdata->temp_derate_curr_num)) {
+			if (led->pdata->temp_derate_curr_num > 0) {
+				led->pdata->die_temp_derate_curr_ma =
+					devm_kzalloc(&led->pdev->dev,
+					led->pdata->temp_derate_curr_num,
+					GFP_KERNEL);
+				if (led->pdata->die_temp_derate_curr_ma
+								== NULL) {
+					dev_err(&led->pdev->dev,
+						"failed to allocate die derate current array\n");
+					return -ENOMEM;
+				}
+				led->pdata->temp_derate_curr_num /=
+						sizeof(unsigned int);
+
+				rc = of_property_read_u32_array(node,
+						"qcom,die-temp-derate-current",
+				led->pdata->die_temp_derate_curr_ma,
+				led->pdata->temp_derate_curr_num);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"couldn't read temp limits rc =%d\n",
+								rc);
+					return rc;
+				}
+			}
+		}
+		if (led->pdata->temp_threshold_num !=
+					led->pdata->temp_derate_curr_num) {
+			pr_err("Both array size are not same\n");
+			return -EINVAL;
+		}
+	}
+
+	led->pinctrl = devm_pinctrl_get(&led->pdev->dev);
+	if (IS_ERR_OR_NULL(led->pinctrl)) {
+		dev_err(&led->pdev->dev, "Unable to acquire pinctrl\n");
+		led->pinctrl = NULL;
+		return 0;
+	}
+
+	led->gpio_state_active = pinctrl_lookup_state(led->pinctrl,
+							"flash_led_enable");
+	if (IS_ERR_OR_NULL(led->gpio_state_active)) {
+		dev_err(&led->pdev->dev, "Cannot lookup LED active state\n");
+		devm_pinctrl_put(led->pinctrl);
+		led->pinctrl = NULL;
+		return PTR_ERR(led->gpio_state_active);
+	}
+
+	led->gpio_state_suspend = pinctrl_lookup_state(led->pinctrl,
+							"flash_led_disable");
+	if (IS_ERR_OR_NULL(led->gpio_state_suspend)) {
+		dev_err(&led->pdev->dev, "Cannot lookup LED disable state\n");
+		devm_pinctrl_put(led->pinctrl);
+		led->pinctrl = NULL;
+		return PTR_ERR(led->gpio_state_suspend);
+	}
+
+	return 0;
+}
+
+static int qpnp_flash_led_probe(struct platform_device *pdev)
+{
+	struct qpnp_flash_led *led;
+	unsigned int base;
+	struct device_node *node, *temp;
+	struct dentry *root, *file;
+	int rc, i = 0, j, num_leds = 0;
+	u32 val;
+
+	root = NULL;
+	node = pdev->dev.of_node;
+	if (node == NULL) {
+		dev_info(&pdev->dev, "No flash device defined\n");
+		return -ENODEV;
+	}
+
+	rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+	if (rc < 0) {
+		dev_err(&pdev->dev,
+			"Couldn't find reg in node = %s rc = %d\n",
+			pdev->dev.of_node->full_name, rc);
+		return rc;
+	}
+
+	led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!led->regmap) {
+		dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+		return -EINVAL;
+	}
+
+	led->base = base;
+	led->pdev = pdev;
+	led->current_addr = FLASH_LED0_CURRENT(led->base);
+	led->current2_addr = FLASH_LED1_CURRENT(led->base);
+
+	led->pdata = devm_kzalloc(&pdev->dev, sizeof(*led->pdata), GFP_KERNEL);
+	if (!led->pdata)
+		return -ENOMEM;
+
+	led->peripheral_type = (u8)qpnp_flash_led_get_peripheral_type(led);
+	if (led->peripheral_type < 0) {
+		dev_err(&pdev->dev, "Failed to get peripheral type\n");
+		return rc;
+	}
+
+	rc = qpnp_flash_led_parse_common_dt(led, node);
+	if (rc) {
+		dev_err(&pdev->dev,
+			"Failed to get common config for flash LEDs\n");
+		return rc;
+	}
+
+	rc = qpnp_flash_led_init_settings(led);
+	if (rc) {
+		dev_err(&pdev->dev, "Failed to initialize flash LED\n");
+		return rc;
+	}
+
+	rc = qpnp_get_pmic_revid(led);
+	if (rc)
+		return rc;
+
+	temp = NULL;
+	while ((temp = of_get_next_child(node, temp)))
+		num_leds++;
+
+	if (!num_leds)
+		return -ECHILD;
+
+	led->flash_node = devm_kzalloc(&pdev->dev,
+			(sizeof(struct flash_node_data) * num_leds),
+			GFP_KERNEL);
+	if (!led->flash_node) {
+		dev_err(&pdev->dev, "Unable to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	mutex_init(&led->flash_led_lock);
+
+	led->ordered_workq = alloc_ordered_workqueue("flash_led_workqueue", 0);
+	if (!led->ordered_workq) {
+		dev_err(&pdev->dev, "Failed to allocate ordered workqueue\n");
+		return -ENOMEM;
+	}
+
+	for_each_child_of_node(node, temp) {
+		led->flash_node[i].cdev.brightness_set =
+						qpnp_flash_led_brightness_set;
+		led->flash_node[i].cdev.brightness_get =
+						qpnp_flash_led_brightness_get;
+		led->flash_node[i].pdev = pdev;
+
+		INIT_WORK(&led->flash_node[i].work, qpnp_flash_led_work);
+		rc = of_property_read_string(temp, "qcom,led-name",
+						&led->flash_node[i].cdev.name);
+		if (rc < 0) {
+			dev_err(&led->pdev->dev,
+					"Unable to read flash name\n");
+			return rc;
+		}
+
+		rc = of_property_read_string(temp, "qcom,default-led-trigger",
+				&led->flash_node[i].cdev.default_trigger);
+		if (rc < 0) {
+			dev_err(&led->pdev->dev,
+					"Unable to read trigger name\n");
+			return rc;
+		}
+
+		rc = of_property_read_u32(temp, "qcom,max-current", &val);
+		if (!rc) {
+			if (val < FLASH_LED_MIN_CURRENT_MA)
+				val = FLASH_LED_MIN_CURRENT_MA;
+			led->flash_node[i].max_current = (u16)val;
+			led->flash_node[i].cdev.max_brightness = val;
+		} else {
+			dev_err(&led->pdev->dev,
+					"Unable to read max current\n");
+			return rc;
+		}
+		rc = led_classdev_register(&pdev->dev,
+						&led->flash_node[i].cdev);
+		if (rc) {
+			dev_err(&pdev->dev, "Unable to register led\n");
+			goto error_led_register;
+		}
+
+		led->flash_node[i].cdev.dev->of_node = temp;
+
+		rc = qpnp_flash_led_parse_each_led_dt(led, &led->flash_node[i]);
+		if (rc) {
+			dev_err(&pdev->dev,
+				"Failed to parse config for each LED\n");
+			goto error_led_register;
+		}
+
+		if (led->flash_node[i].num_regulators) {
+			rc = flash_regulator_parse_dt(led, &led->flash_node[i]);
+			if (rc) {
+				dev_err(&pdev->dev,
+					"Unable to parse regulator data\n");
+				goto error_led_register;
+			}
+
+			rc = flash_regulator_setup(led, &led->flash_node[i],
+									true);
+			if (rc) {
+				dev_err(&pdev->dev,
+					"Unable to set up regulator\n");
+				goto error_led_register;
+			}
+		}
+
+		for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++) {
+			rc =
+			sysfs_create_file(&led->flash_node[i].cdev.dev->kobj,
+					&qpnp_flash_led_attrs[j].attr);
+			if (rc)
+				goto error_led_register;
+		}
+
+		i++;
+	}
+
+	led->num_leds = i;
+
+	root = debugfs_create_dir("flashLED", NULL);
+	if (IS_ERR_OR_NULL(root)) {
+		pr_err("Error creating top level directory err%ld",
+			(long)root);
+		if (PTR_ERR(root) == -ENODEV)
+			pr_err("debugfs is not enabled in kernel");
+		goto error_led_debugfs;
+	}
+
+	led->dbgfs_root = root;
+	file = debugfs_create_file("enable_debug", 0600, root, led,
+					&flash_led_dfs_dbg_feature_fops);
+	if (!file) {
+		pr_err("error creating 'enable_debug' entry\n");
+		goto error_led_debugfs;
+	}
+
+	file = debugfs_create_file("latched", 0600, root, led,
+					&flash_led_dfs_latched_reg_fops);
+	if (!file) {
+		pr_err("error creating 'latched' entry\n");
+		goto error_led_debugfs;
+	}
+
+	file = debugfs_create_file("strobe", 0600, root, led,
+					&flash_led_dfs_strobe_reg_fops);
+	if (!file) {
+		pr_err("error creating 'strobe' entry\n");
+		goto error_led_debugfs;
+	}
+
+	dev_set_drvdata(&pdev->dev, led);
+
+	return 0;
+
+error_led_debugfs:
+	i = led->num_leds - 1;
+	j = ARRAY_SIZE(qpnp_flash_led_attrs) - 1;
+error_led_register:
+	for (; i >= 0; i--) {
+		for (; j >= 0; j--)
+			sysfs_remove_file(&led->flash_node[i].cdev.dev->kobj,
+						&qpnp_flash_led_attrs[j].attr);
+		j = ARRAY_SIZE(qpnp_flash_led_attrs) - 1;
+		led_classdev_unregister(&led->flash_node[i].cdev);
+	}
+	debugfs_remove_recursive(root);
+	mutex_destroy(&led->flash_led_lock);
+	destroy_workqueue(led->ordered_workq);
+
+	return rc;
+}
+
+static int qpnp_flash_led_remove(struct platform_device *pdev)
+{
+	struct qpnp_flash_led *led  = dev_get_drvdata(&pdev->dev);
+	int i, j;
+
+	for (i = led->num_leds - 1; i >= 0; i--) {
+		if (led->flash_node[i].reg_data) {
+			if (led->flash_node[i].flash_on)
+				flash_regulator_enable(led,
+						&led->flash_node[i], false);
+			flash_regulator_setup(led, &led->flash_node[i],
+								false);
+		}
+		for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++)
+			sysfs_remove_file(&led->flash_node[i].cdev.dev->kobj,
+						&qpnp_flash_led_attrs[j].attr);
+		led_classdev_unregister(&led->flash_node[i].cdev);
+	}
+	debugfs_remove_recursive(led->dbgfs_root);
+	mutex_destroy(&led->flash_led_lock);
+	destroy_workqueue(led->ordered_workq);
+
+	return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+	{ .compatible = "qcom,qpnp-flash-led",},
+	{ },
+};
+
+static struct platform_driver qpnp_flash_led_driver = {
+	.driver		= {
+		.name		= "qcom,qpnp-flash-led",
+		.of_match_table	= spmi_match_table,
+	},
+	.probe		= qpnp_flash_led_probe,
+	.remove		= qpnp_flash_led_remove,
+};
+
+static int __init qpnp_flash_led_init(void)
+{
+	return platform_driver_register(&qpnp_flash_led_driver);
+}
+late_initcall(qpnp_flash_led_init);
+
+static void __exit qpnp_flash_led_exit(void)
+{
+	platform_driver_unregister(&qpnp_flash_led_driver);
+}
+module_exit(qpnp_flash_led_exit);
+
+MODULE_DESCRIPTION("QPNP Flash LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("leds:leds-qpnp-flash");
diff --git a/drivers/leds/leds-qpnp-wled.c b/drivers/leds/leds-qpnp-wled.c
new file mode 100644
index 0000000..1e24c79
--- /dev/null
+++ b/drivers/leds/leds-qpnp-wled.c
@@ -0,0 +1,2221 @@
+/* Copyright (c) 2014-2017, 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/init.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/errno.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/leds-qpnp-wled.h>
+#include <linux/qpnp/qpnp-revid.h>
+
+/* base addresses */
+#define QPNP_WLED_CTRL_BASE		"qpnp-wled-ctrl-base"
+#define QPNP_WLED_SINK_BASE		"qpnp-wled-sink-base"
+
+/* ctrl registers */
+#define QPNP_WLED_FAULT_STATUS(b)	(b + 0x08)
+#define QPNP_WLED_EN_REG(b)		(b + 0x46)
+#define QPNP_WLED_FDBK_OP_REG(b)	(b + 0x48)
+#define QPNP_WLED_VREF_REG(b)		(b + 0x49)
+#define QPNP_WLED_BOOST_DUTY_REG(b)	(b + 0x4B)
+#define QPNP_WLED_SWITCH_FREQ_REG(b)	(b + 0x4C)
+#define QPNP_WLED_OVP_REG(b)		(b + 0x4D)
+#define QPNP_WLED_ILIM_REG(b)		(b + 0x4E)
+#define QPNP_WLED_AMOLED_VOUT_REG(b)	(b + 0x4F)
+#define QPNP_WLED_SOFTSTART_RAMP_DLY(b) (b + 0x53)
+#define QPNP_WLED_VLOOP_COMP_RES_REG(b)	(b + 0x55)
+#define QPNP_WLED_VLOOP_COMP_GM_REG(b)	(b + 0x56)
+#define QPNP_WLED_PSM_CTRL_REG(b)	(b + 0x5B)
+#define QPNP_WLED_LCD_AUTO_PFM_REG(b)	(b + 0x5C)
+#define QPNP_WLED_SC_PRO_REG(b)		(b + 0x5E)
+#define QPNP_WLED_SWIRE_AVDD_REG(b)	(b + 0x5F)
+#define QPNP_WLED_CTRL_SPARE_REG(b)	(b + 0xDF)
+#define QPNP_WLED_TEST1_REG(b)		(b + 0xE2)
+#define QPNP_WLED_TEST4_REG(b)		(b + 0xE5)
+#define QPNP_WLED_REF_7P7_TRIM_REG(b)	(b + 0xF2)
+
+#define QPNP_WLED_7P7_TRIM_MASK		GENMASK(3, 0)
+#define QPNP_WLED_EN_MASK		0x7F
+#define QPNP_WLED_EN_SHIFT		7
+#define QPNP_WLED_FDBK_OP_MASK		0xF8
+#define QPNP_WLED_VREF_MASK		GENMASK(3, 0)
+
+#define QPNP_WLED_VLOOP_COMP_RES_MASK			0xF0
+#define QPNP_WLED_VLOOP_COMP_RES_OVERWRITE		0x80
+#define QPNP_WLED_LOOP_COMP_RES_DFLT_AMOLED_KOHM	320
+#define QPNP_WLED_LOOP_COMP_RES_STEP_KOHM		20
+#define QPNP_WLED_LOOP_COMP_RES_MIN_KOHM		20
+#define QPNP_WLED_LOOP_COMP_RES_MAX_KOHM		320
+#define QPNP_WLED_VLOOP_COMP_GM_MASK			GENMASK(3, 0)
+#define QPNP_WLED_VLOOP_COMP_GM_OVERWRITE		0x80
+#define QPNP_WLED_VLOOP_COMP_AUTO_GM_EN			BIT(6)
+#define QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_MASK	GENMASK(5, 4)
+#define QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_SHIFT	4
+#define QPNP_WLED_LOOP_EA_GM_DFLT_AMOLED_PMI8994	0x03
+#define QPNP_WLED_LOOP_GM_DFLT_AMOLED_PMI8998		0x09
+#define QPNP_WLED_LOOP_GM_DFLT_WLED			0x09
+#define QPNP_WLED_LOOP_EA_GM_MIN			0x0
+#define QPNP_WLED_LOOP_EA_GM_MAX			0xF
+#define QPNP_WLED_LOOP_AUTO_GM_THRESH_MAX		3
+#define QPNP_WLED_LOOP_AUTO_GM_DFLT_THRESH		1
+#define QPNP_WLED_VREF_PSM_MASK				0xF8
+#define QPNP_WLED_VREF_PSM_STEP_MV			50
+#define QPNP_WLED_VREF_PSM_MIN_MV			400
+#define QPNP_WLED_VREF_PSM_MAX_MV			750
+#define QPNP_WLED_VREF_PSM_DFLT_AMOLED_MV		450
+#define QPNP_WLED_PSM_CTRL_OVERWRITE			0x80
+#define QPNP_WLED_LCD_AUTO_PFM_DFLT_THRESH		1
+#define QPNP_WLED_LCD_AUTO_PFM_THRESH_MAX		0xF
+#define QPNP_WLED_LCD_AUTO_PFM_EN_SHIFT			7
+#define QPNP_WLED_LCD_AUTO_PFM_EN_BIT			BIT(7)
+#define QPNP_WLED_LCD_AUTO_PFM_THRESH_MASK		GENMASK(3, 0)
+
+#define QPNP_WLED_ILIM_MASK		GENMASK(2, 0)
+#define QPNP_WLED_ILIM_OVERWRITE	BIT(7)
+#define PMI8994_WLED_ILIM_MIN_MA	105
+#define PMI8994_WLED_ILIM_MAX_MA	1980
+#define PMI8994_WLED_DFLT_ILIM_MA	980
+#define PMI8994_AMOLED_DFLT_ILIM_MA	385
+#define PMI8998_WLED_ILIM_MAX_MA	1500
+#define PMI8998_WLED_DFLT_ILIM_MA	970
+#define PMI8998_AMOLED_DFLT_ILIM_MA	620
+#define QPNP_WLED_BOOST_DUTY_MASK	0xFC
+#define QPNP_WLED_BOOST_DUTY_STEP_NS	52
+#define QPNP_WLED_BOOST_DUTY_MIN_NS	26
+#define QPNP_WLED_BOOST_DUTY_MAX_NS	156
+#define QPNP_WLED_DEF_BOOST_DUTY_NS	104
+#define QPNP_WLED_SWITCH_FREQ_MASK	0x70
+#define QPNP_WLED_SWITCH_FREQ_800_KHZ	800
+#define QPNP_WLED_SWITCH_FREQ_1600_KHZ	1600
+#define QPNP_WLED_SWITCH_FREQ_OVERWRITE 0x80
+#define QPNP_WLED_OVP_MASK		GENMASK(1, 0)
+#define QPNP_WLED_TEST4_EN_DEB_BYPASS_ILIM_BIT	BIT(6)
+#define QPNP_WLED_TEST4_EN_SH_FOR_SS_BIT	BIT(5)
+#define QPNP_WLED_TEST4_EN_CLAMP_BIT		BIT(4)
+#define QPNP_WLED_TEST4_EN_SOFT_START_BIT	BIT(1)
+#define QPNP_WLED_TEST4_EN_VREF_UP			\
+		(QPNP_WLED_TEST4_EN_SH_FOR_SS_BIT |	\
+		QPNP_WLED_TEST4_EN_CLAMP_BIT |		\
+		QPNP_WLED_TEST4_EN_SOFT_START_BIT)
+#define QPNP_WLED_TEST4_EN_IIND_UP	0x1
+
+/* sink registers */
+#define QPNP_WLED_CURR_SINK_REG(b)	(b + 0x46)
+#define QPNP_WLED_SYNC_REG(b)		(b + 0x47)
+#define QPNP_WLED_MOD_REG(b)		(b + 0x4A)
+#define QPNP_WLED_HYB_THRES_REG(b)	(b + 0x4B)
+#define QPNP_WLED_MOD_EN_REG(b, n)	(b + 0x50 + (n * 0x10))
+#define QPNP_WLED_SYNC_DLY_REG(b, n)	(QPNP_WLED_MOD_EN_REG(b, n) + 0x01)
+#define QPNP_WLED_FS_CURR_REG(b, n)	(QPNP_WLED_MOD_EN_REG(b, n) + 0x02)
+#define QPNP_WLED_CABC_REG(b, n)	(QPNP_WLED_MOD_EN_REG(b, n) + 0x06)
+#define QPNP_WLED_BRIGHT_LSB_REG(b, n)	(QPNP_WLED_MOD_EN_REG(b, n) + 0x07)
+#define QPNP_WLED_BRIGHT_MSB_REG(b, n)	(QPNP_WLED_MOD_EN_REG(b, n) + 0x08)
+#define QPNP_WLED_SINK_TEST5_REG(b)	(b + 0xE6)
+
+#define QPNP_WLED_MOD_FREQ_1200_KHZ	1200
+#define QPNP_WLED_MOD_FREQ_2400_KHZ	2400
+#define QPNP_WLED_MOD_FREQ_9600_KHZ	9600
+#define QPNP_WLED_MOD_FREQ_19200_KHZ	19200
+#define QPNP_WLED_MOD_FREQ_MASK		0x3F
+#define QPNP_WLED_MOD_FREQ_SHIFT	6
+#define QPNP_WLED_ACC_CLK_FREQ_MASK	0xE7
+#define QPNP_WLED_ACC_CLK_FREQ_SHIFT	3
+#define QPNP_WLED_PHASE_STAG_MASK	0xDF
+#define QPNP_WLED_PHASE_STAG_SHIFT	5
+#define QPNP_WLED_DIM_RES_MASK		0xFD
+#define QPNP_WLED_DIM_RES_SHIFT		1
+#define QPNP_WLED_DIM_HYB_MASK		0xFB
+#define QPNP_WLED_DIM_HYB_SHIFT		2
+#define QPNP_WLED_DIM_ANA_MASK		0xFE
+#define QPNP_WLED_HYB_THRES_MASK	0xF8
+#define QPNP_WLED_HYB_THRES_MIN		78
+#define QPNP_WLED_DEF_HYB_THRES		625
+#define QPNP_WLED_HYB_THRES_MAX		10000
+#define QPNP_WLED_MOD_EN_MASK		0x7F
+#define QPNP_WLED_MOD_EN_SHFT		7
+#define QPNP_WLED_MOD_EN		1
+#define QPNP_WLED_GATE_DRV_MASK		0xFE
+#define QPNP_WLED_SYNC_DLY_MASK		0xF8
+#define QPNP_WLED_SYNC_DLY_MIN_US	0
+#define QPNP_WLED_SYNC_DLY_MAX_US	1400
+#define QPNP_WLED_SYNC_DLY_STEP_US	200
+#define QPNP_WLED_DEF_SYNC_DLY_US	400
+#define QPNP_WLED_FS_CURR_MASK		0xF0
+#define QPNP_WLED_FS_CURR_MIN_UA	0
+#define QPNP_WLED_FS_CURR_MAX_UA	30000
+#define QPNP_WLED_FS_CURR_STEP_UA	2500
+#define QPNP_WLED_CABC_MASK		0x7F
+#define QPNP_WLED_CABC_SHIFT		7
+#define QPNP_WLED_CURR_SINK_SHIFT	4
+#define QPNP_WLED_BRIGHT_LSB_MASK	0xFF
+#define QPNP_WLED_BRIGHT_MSB_SHIFT	8
+#define QPNP_WLED_BRIGHT_MSB_MASK	0x0F
+#define QPNP_WLED_SYNC			0x0F
+#define QPNP_WLED_SYNC_RESET		0x00
+
+#define QPNP_WLED_SINK_TEST5_HYB	0x14
+#define QPNP_WLED_SINK_TEST5_DIG	0x1E
+#define QPNP_WLED_SINK_TEST5_HVG_PULL_STR_BIT	BIT(3)
+
+#define QPNP_WLED_SWITCH_FREQ_800_KHZ_CODE	0x0B
+#define QPNP_WLED_SWITCH_FREQ_1600_KHZ_CODE	0x05
+
+#define QPNP_WLED_DISP_SEL_REG(b)	(b + 0x44)
+#define QPNP_WLED_MODULE_RDY_REG(b)	(b + 0x45)
+#define QPNP_WLED_MODULE_EN_REG(b)	(b + 0x46)
+#define QPNP_WLED_MODULE_RDY_MASK	0x7F
+#define QPNP_WLED_MODULE_RDY_SHIFT	7
+#define QPNP_WLED_MODULE_EN_MASK	BIT(7)
+#define QPNP_WLED_MODULE_EN_SHIFT	7
+#define QPNP_WLED_DISP_SEL_MASK		0x7F
+#define QPNP_WLED_DISP_SEL_SHIFT	7
+#define QPNP_WLED_EN_SC_DEB_CYCLES_MASK	0x79
+#define QPNP_WLED_EN_DEB_CYCLES_MASK	0xF9
+#define QPNP_WLED_EN_SC_SHIFT		7
+#define QPNP_WLED_SC_PRO_EN_DSCHGR	0x8
+#define QPNP_WLED_SC_DEB_CYCLES_MIN     2
+#define QPNP_WLED_SC_DEB_CYCLES_MAX     16
+#define QPNP_WLED_SC_DEB_CYCLES_SUB     2
+#define QPNP_WLED_SC_DEB_CYCLES_DFLT    4
+#define QPNP_WLED_EXT_FET_DTEST2	0x09
+
+#define QPNP_WLED_SEC_ACCESS_REG(b)    (b + 0xD0)
+#define QPNP_WLED_SEC_UNLOCK           0xA5
+
+#define QPNP_WLED_MAX_STRINGS		4
+#define WLED_MAX_LEVEL_4095		4095
+#define QPNP_WLED_RAMP_DLY_MS		20
+#define QPNP_WLED_TRIGGER_NONE		"none"
+#define QPNP_WLED_STR_SIZE		20
+#define QPNP_WLED_MIN_MSLEEP		20
+#define QPNP_WLED_SC_DLY_MS		20
+
+#define NUM_SUPPORTED_AVDD_VOLTAGES	6
+#define QPNP_WLED_DFLT_AVDD_MV		7600
+#define QPNP_WLED_AVDD_MIN_MV		5650
+#define QPNP_WLED_AVDD_MAX_MV		7900
+#define QPNP_WLED_AVDD_STEP_MV		150
+#define QPNP_WLED_AVDD_MIN_TRIM_VAL	0x0
+#define QPNP_WLED_AVDD_MAX_TRIM_VAL	0xF
+#define QPNP_WLED_AVDD_SEL_SPMI_BIT	BIT(7)
+#define QPNP_WLED_AVDD_SET_BIT		BIT(4)
+
+#define NUM_SUPPORTED_OVP_THRESHOLDS	4
+#define NUM_SUPPORTED_ILIM_THRESHOLDS	8
+
+#define QPNP_WLED_AVDD_MV_TO_REG(val) \
+		((val - QPNP_WLED_AVDD_MIN_MV) / QPNP_WLED_AVDD_STEP_MV)
+
+/* output feedback mode */
+enum qpnp_wled_fdbk_op {
+	QPNP_WLED_FDBK_AUTO,
+	QPNP_WLED_FDBK_WLED1,
+	QPNP_WLED_FDBK_WLED2,
+	QPNP_WLED_FDBK_WLED3,
+	QPNP_WLED_FDBK_WLED4,
+};
+
+/* dimming modes */
+enum qpnp_wled_dim_mode {
+	QPNP_WLED_DIM_ANALOG,
+	QPNP_WLED_DIM_DIGITAL,
+	QPNP_WLED_DIM_HYBRID,
+};
+
+/* wled ctrl debug registers */
+static u8 qpnp_wled_ctrl_dbg_regs[] = {
+	0x44, 0x46, 0x48, 0x49, 0x4b, 0x4c, 0x4d, 0x4e, 0x50, 0x51, 0x52, 0x53,
+	0x54, 0x55, 0x56, 0x57, 0x58, 0x5a, 0x5b, 0x5d, 0x5e, 0xe2
+};
+
+/* wled sink debug registers */
+static u8 qpnp_wled_sink_dbg_regs[] = {
+	0x46, 0x47, 0x48, 0x4a, 0x4b,
+	0x50, 0x51, 0x52, 0x53,	0x56, 0x57, 0x58,
+	0x60, 0x61, 0x62, 0x63,	0x66, 0x67, 0x68,
+	0x70, 0x71, 0x72, 0x73,	0x76, 0x77, 0x78,
+	0x80, 0x81, 0x82, 0x83,	0x86, 0x87, 0x88,
+	0xe6,
+};
+
+static int qpnp_wled_avdd_target_voltages[NUM_SUPPORTED_AVDD_VOLTAGES] = {
+	7900, 7600, 7300, 6400, 6100, 5800,
+};
+
+static u8 qpnp_wled_ovp_reg_settings[NUM_SUPPORTED_AVDD_VOLTAGES] = {
+	0x0, 0x0, 0x1, 0x2, 0x2, 0x3,
+};
+
+static int qpnp_wled_avdd_trim_adjustments[NUM_SUPPORTED_AVDD_VOLTAGES] = {
+	3, 0, -2, 7, 3, 3,
+};
+
+static int qpnp_wled_ovp_thresholds_pmi8994[NUM_SUPPORTED_OVP_THRESHOLDS] = {
+	31000, 29500, 19400, 17800,
+};
+
+static int qpnp_wled_ovp_thresholds_pmi8998[NUM_SUPPORTED_OVP_THRESHOLDS] = {
+	31100, 29600, 19600, 18100,
+};
+
+static int qpnp_wled_ilim_settings_pmi8994[NUM_SUPPORTED_ILIM_THRESHOLDS] = {
+	105, 385, 660, 980, 1150, 1420, 1700, 1980,
+};
+
+static int qpnp_wled_ilim_settings_pmi8998[NUM_SUPPORTED_ILIM_THRESHOLDS] = {
+	105, 280, 450, 620, 970, 1150, 1300, 1500,
+};
+
+struct wled_vref_setting {
+	u32 min_uv;
+	u32 max_uv;
+	u32 step_uv;
+	u32 default_uv;
+};
+
+static struct wled_vref_setting vref_setting_pmi8994 = {
+	300000, 675000, 25000, 350000,
+};
+static struct wled_vref_setting vref_setting_pmi8998 = {
+	60000, 397500, 22500, 127500,
+};
+
+/**
+ *  qpnp_wled - wed data structure
+ *  @ cdev - led class device
+ *  @ pdev - platform device
+ *  @ work - worker for led operation
+ *  @ lock - mutex lock for exclusive access
+ *  @ fdbk_op - output feedback mode
+ *  @ dim_mode - dimming mode
+ *  @ ovp_irq - over voltage protection irq
+ *  @ sc_irq - short circuit irq
+ *  @ sc_cnt - short circuit irq count
+ *  @ avdd_target_voltage_mv - target voltage for AVDD module in mV
+ *  @ ctrl_base - base address for wled ctrl
+ *  @ sink_base - base address for wled sink
+ *  @ mod_freq_khz - modulator frequency in KHZ
+ *  @ hyb_thres - threshold for hybrid dimming
+ *  @ sync_dly_us - sync delay in us
+ *  @ vref_uv - ref voltage in uv
+ *  @ vref_psm_mv - ref psm voltage in mv
+ *  @ loop_comp_res_kohm - control to select the compensation resistor
+ *  @ loop_ea_gm - control to select the gm for the gm stage in control loop
+ *  @ sc_deb_cycles - debounce time for short circuit detection
+ *  @ switch_freq_khz - switching frequency in KHZ
+ *  @ ovp_mv - over voltage protection in mv
+ *  @ ilim_ma - current limiter in ma
+ *  @ boost_duty_ns - boost duty cycle in ns
+ *  @ fs_curr_ua - full scale current in ua
+ *  @ ramp_ms - delay between ramp steps in ms
+ *  @ ramp_step - ramp step size
+ *  @ cons_sync_write_delay_us - delay between two consecutive writes to SYNC
+ *  @ strings - supported list of strings
+ *  @ num_strings - number of strings
+ *  @ loop_auto_gm_thresh - the clamping level for auto gm
+ *  @ lcd_auto_pfm_thresh - the threshold for lcd auto pfm mode
+ *  @ loop_auto_gm_en - select if auto gm is enabled
+ *  @ lcd_auto_pfm_en - select if auto pfm is enabled in lcd mode
+ *  @ avdd_mode_spmi - enable avdd programming via spmi
+ *  @ en_9b_dim_res - enable or disable 9bit dimming
+ *  @ en_phase_stag - enable or disable phase staggering
+ *  @ en_cabc - enable or disable cabc
+ *  @ disp_type_amoled - type of display: LCD/AMOLED
+ *  @ en_ext_pfet_sc_pro - enable sc protection on external pfet
+ */
+struct qpnp_wled {
+	struct led_classdev	cdev;
+	struct platform_device	*pdev;
+	struct regmap		*regmap;
+	struct pmic_revid_data	*pmic_rev_id;
+	struct work_struct	work;
+	struct mutex		lock;
+	struct mutex		bus_lock;
+	enum qpnp_wled_fdbk_op	fdbk_op;
+	enum qpnp_wled_dim_mode	dim_mode;
+	int			ovp_irq;
+	int			sc_irq;
+	u32			sc_cnt;
+	u32			avdd_target_voltage_mv;
+	u16			ctrl_base;
+	u16			sink_base;
+	u16			mod_freq_khz;
+	u16			hyb_thres;
+	u16			sync_dly_us;
+	u32			vref_uv;
+	u16			vref_psm_mv;
+	u16			loop_comp_res_kohm;
+	u16			loop_ea_gm;
+	u16			sc_deb_cycles;
+	u16			switch_freq_khz;
+	u16			ovp_mv;
+	u16			ilim_ma;
+	u16			boost_duty_ns;
+	u16			fs_curr_ua;
+	u16			ramp_ms;
+	u16			ramp_step;
+	u16			cons_sync_write_delay_us;
+	u8			strings[QPNP_WLED_MAX_STRINGS];
+	u8			num_strings;
+	u8			loop_auto_gm_thresh;
+	u8			lcd_auto_pfm_thresh;
+	bool			loop_auto_gm_en;
+	bool			lcd_auto_pfm_en;
+	bool			avdd_mode_spmi;
+	bool			en_9b_dim_res;
+	bool			en_phase_stag;
+	bool			en_cabc;
+	bool			disp_type_amoled;
+	bool			en_ext_pfet_sc_pro;
+	bool			prev_state;
+	bool			ovp_irq_disabled;
+};
+
+/* helper to read a pmic register */
+static int qpnp_wled_read_reg(struct qpnp_wled *wled, u16 addr, u8 *data)
+{
+	int rc;
+	uint val;
+
+	rc = regmap_read(wled->regmap, addr, &val);
+	if (rc < 0) {
+		dev_err(&wled->pdev->dev,
+			"Error reading address: %x(%d)\n", addr, rc);
+		return rc;
+	}
+
+	*data = (u8)val;
+	return 0;
+}
+
+/* helper to write a pmic register */
+static int qpnp_wled_write_reg(struct qpnp_wled *wled, u16 addr, u8 data)
+{
+	int rc;
+
+	mutex_lock(&wled->bus_lock);
+	rc = regmap_write(wled->regmap, addr, data);
+	if (rc < 0) {
+		dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n",
+			addr, rc);
+		goto out;
+	}
+
+	dev_dbg(&wled->pdev->dev, "wrote: WLED_0x%x = 0x%x\n", addr, data);
+out:
+	mutex_unlock(&wled->bus_lock);
+	return rc;
+}
+
+static int qpnp_wled_masked_write_reg(struct qpnp_wled *wled, u16 addr,
+					u8 mask, u8 data)
+{
+	int rc;
+
+	mutex_lock(&wled->bus_lock);
+	rc = regmap_update_bits(wled->regmap, addr, mask, data);
+	if (rc < 0) {
+		dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n",
+			addr, rc);
+		goto out;
+	}
+
+	dev_dbg(&wled->pdev->dev, "wrote: WLED_0x%x = 0x%x\n", addr, data);
+out:
+	mutex_unlock(&wled->bus_lock);
+	return rc;
+}
+
+static int qpnp_wled_sec_write_reg(struct qpnp_wled *wled, u16 addr, u8 data)
+{
+	int rc;
+	u8 reg = QPNP_WLED_SEC_UNLOCK;
+	u16 base_addr = addr & 0xFF00;
+
+	mutex_lock(&wled->bus_lock);
+	rc = regmap_write(wled->regmap, QPNP_WLED_SEC_ACCESS_REG(base_addr),
+			reg);
+	if (rc < 0) {
+		dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n",
+			QPNP_WLED_SEC_ACCESS_REG(base_addr), rc);
+		goto out;
+	}
+
+	rc = regmap_write(wled->regmap, addr, data);
+	if (rc < 0) {
+		dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n",
+			addr, rc);
+		goto out;
+	}
+
+	dev_dbg(&wled->pdev->dev, "wrote: WLED_0x%x = 0x%x\n", addr, data);
+out:
+	mutex_unlock(&wled->bus_lock);
+	return rc;
+}
+
+static int qpnp_wled_swire_avdd_config(struct qpnp_wled *wled)
+{
+	int rc;
+	u8 val;
+
+	if (wled->pmic_rev_id->pmic_subtype != PMI8998_SUBTYPE &&
+		wled->pmic_rev_id->pmic_subtype != PM660L_SUBTYPE)
+		return 0;
+
+	if (!wled->disp_type_amoled || wled->avdd_mode_spmi)
+		return 0;
+
+	val = QPNP_WLED_AVDD_MV_TO_REG(wled->avdd_target_voltage_mv);
+	rc = qpnp_wled_write_reg(wled,
+			QPNP_WLED_SWIRE_AVDD_REG(wled->ctrl_base), val);
+	return rc;
+}
+
+static int qpnp_wled_sync_reg_toggle(struct qpnp_wled *wled)
+{
+	int rc;
+	u8 reg;
+
+	/* sync */
+	reg = QPNP_WLED_SYNC;
+	rc = qpnp_wled_write_reg(wled, QPNP_WLED_SYNC_REG(wled->sink_base),
+			reg);
+	if (rc < 0)
+		return rc;
+
+	if (wled->cons_sync_write_delay_us)
+		usleep_range(wled->cons_sync_write_delay_us,
+				wled->cons_sync_write_delay_us + 1);
+
+	reg = QPNP_WLED_SYNC_RESET;
+	rc = qpnp_wled_write_reg(wled, QPNP_WLED_SYNC_REG(wled->sink_base),
+			reg);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/* set wled to a level of brightness */
+static int qpnp_wled_set_level(struct qpnp_wled *wled, int level)
+{
+	int i, rc;
+	u8 reg;
+
+	/* set brightness registers */
+	for (i = 0; i < wled->num_strings; i++) {
+		reg = level & QPNP_WLED_BRIGHT_LSB_MASK;
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_BRIGHT_LSB_REG(wled->sink_base,
+					wled->strings[i]), reg);
+		if (rc < 0)
+			return rc;
+
+		reg = level >> QPNP_WLED_BRIGHT_MSB_SHIFT;
+		reg = reg & QPNP_WLED_BRIGHT_MSB_MASK;
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_BRIGHT_MSB_REG(wled->sink_base,
+					wled->strings[i]), reg);
+		if (rc < 0)
+			return rc;
+	}
+
+	rc = qpnp_wled_sync_reg_toggle(wled);
+	if (rc < 0) {
+		dev_err(&wled->pdev->dev, "Failed to toggle sync reg %d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int qpnp_wled_module_en(struct qpnp_wled *wled,
+				u16 base_addr, bool state)
+{
+	int rc;
+
+	rc = qpnp_wled_masked_write_reg(wled,
+			QPNP_WLED_MODULE_EN_REG(base_addr),
+			QPNP_WLED_MODULE_EN_MASK,
+			state << QPNP_WLED_MODULE_EN_SHIFT);
+	if (rc < 0)
+		return rc;
+
+	if (wled->ovp_irq > 0) {
+		if (state && wled->ovp_irq_disabled) {
+			/*
+			 * Wait for at least 10ms before enabling OVP fault
+			 * interrupt after enabling the module so that soft
+			 * start is completed. Keep OVP interrupt disabled
+			 * when the module is disabled.
+			 */
+			usleep_range(10000, 11000);
+			enable_irq(wled->ovp_irq);
+			wled->ovp_irq_disabled = false;
+		} else if (!state && !wled->ovp_irq_disabled) {
+			disable_irq(wled->ovp_irq);
+			wled->ovp_irq_disabled = true;
+		}
+	}
+
+	return 0;
+}
+
+/* sysfs store function for ramp */
+static ssize_t qpnp_wled_ramp_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(dev);
+	int i, rc;
+
+	mutex_lock(&wled->lock);
+
+	if (!wled->cdev.brightness) {
+		rc = qpnp_wled_module_en(wled, wled->ctrl_base, true);
+		if (rc) {
+			dev_err(&wled->pdev->dev, "wled enable failed\n");
+			goto unlock_mutex;
+		}
+	}
+
+	/* ramp up */
+	for (i = 0; i <= wled->cdev.max_brightness;) {
+		rc = qpnp_wled_set_level(wled, i);
+		if (rc) {
+			dev_err(&wled->pdev->dev, "wled set level failed\n");
+			goto restore_brightness;
+		}
+
+		if (wled->ramp_ms < QPNP_WLED_MIN_MSLEEP)
+			usleep_range(wled->ramp_ms * USEC_PER_MSEC,
+					wled->ramp_ms * USEC_PER_MSEC);
+		else
+			msleep(wled->ramp_ms);
+
+		if (i == wled->cdev.max_brightness)
+			break;
+
+		i += wled->ramp_step;
+		if (i > wled->cdev.max_brightness)
+			i = wled->cdev.max_brightness;
+	}
+
+	/* ramp down */
+	for (i = wled->cdev.max_brightness; i >= 0;) {
+		rc = qpnp_wled_set_level(wled, i);
+		if (rc) {
+			dev_err(&wled->pdev->dev, "wled set level failed\n");
+			goto restore_brightness;
+		}
+
+		if (wled->ramp_ms < QPNP_WLED_MIN_MSLEEP)
+			usleep_range(wled->ramp_ms * USEC_PER_MSEC,
+					wled->ramp_ms * USEC_PER_MSEC);
+		else
+			msleep(wled->ramp_ms);
+
+		if (i == 0)
+			break;
+
+		i -= wled->ramp_step;
+		if (i < 0)
+			i = 0;
+	}
+
+	dev_info(&wled->pdev->dev, "wled ramp complete\n");
+
+restore_brightness:
+	/* restore the old brightness */
+	qpnp_wled_set_level(wled, wled->cdev.brightness);
+	if (!wled->cdev.brightness) {
+		rc = qpnp_wled_module_en(wled, wled->ctrl_base, false);
+		if (rc)
+			dev_err(&wled->pdev->dev, "wled enable failed\n");
+	}
+unlock_mutex:
+	mutex_unlock(&wled->lock);
+
+	return count;
+}
+
+static int qpnp_wled_dump_regs(struct qpnp_wled *wled, u16 base_addr,
+				u8 dbg_regs[], u8 size, char *label,
+				int count, char *buf)
+{
+	int i, rc;
+	u8 reg;
+
+	for (i = 0; i < size; i++) {
+		rc = qpnp_wled_read_reg(wled, base_addr + dbg_regs[i], &reg);
+		if (rc < 0)
+			return rc;
+
+		count += snprintf(buf + count, PAGE_SIZE - count,
+				"%s: REG_0x%x = 0x%x\n", label,
+				base_addr + dbg_regs[i], reg);
+
+		if (count >= PAGE_SIZE)
+			return PAGE_SIZE - 1;
+	}
+
+	return count;
+}
+
+/* sysfs show function for debug registers */
+static ssize_t qpnp_wled_dump_regs_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(dev);
+	int count = 0;
+
+	count = qpnp_wled_dump_regs(wled, wled->ctrl_base,
+			qpnp_wled_ctrl_dbg_regs,
+			ARRAY_SIZE(qpnp_wled_ctrl_dbg_regs),
+			"wled_ctrl", count, buf);
+
+	if (count < 0 || count == PAGE_SIZE - 1)
+		return count;
+
+	count = qpnp_wled_dump_regs(wled, wled->sink_base,
+			qpnp_wled_sink_dbg_regs,
+			ARRAY_SIZE(qpnp_wled_sink_dbg_regs),
+			"wled_sink", count, buf);
+
+	if (count < 0 || count == PAGE_SIZE - 1)
+		return count;
+
+	return count;
+}
+
+/* sysfs show function for ramp delay in each step */
+static ssize_t qpnp_wled_ramp_ms_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", wled->ramp_ms);
+}
+
+/* sysfs store function for ramp delay in each step */
+static ssize_t qpnp_wled_ramp_ms_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(dev);
+	int data, rc;
+
+	rc = kstrtoint(buf, 10, &data);
+	if (rc)
+		return rc;
+
+	wled->ramp_ms = data;
+	return count;
+}
+
+/* sysfs show function for ramp step */
+static ssize_t qpnp_wled_ramp_step_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", wled->ramp_step);
+}
+
+/* sysfs store function for ramp step */
+static ssize_t qpnp_wled_ramp_step_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(dev);
+	int data, rc;
+
+	rc = kstrtoint(buf, 10, &data);
+	if (rc)
+		return rc;
+
+	wled->ramp_step = data;
+	return count;
+}
+
+/* sysfs show function for dim mode */
+static ssize_t qpnp_wled_dim_mode_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(dev);
+	char *str;
+
+	if (wled->dim_mode == QPNP_WLED_DIM_ANALOG)
+		str = "analog";
+	else if (wled->dim_mode == QPNP_WLED_DIM_DIGITAL)
+		str = "digital";
+	else
+		str = "hybrid";
+
+	return snprintf(buf, PAGE_SIZE, "%s\n", str);
+}
+
+/* sysfs store function for dim mode*/
+static ssize_t qpnp_wled_dim_mode_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(dev);
+	char str[QPNP_WLED_STR_SIZE + 1];
+	int rc, temp;
+	u8 reg;
+
+	if (snprintf(str, QPNP_WLED_STR_SIZE, "%s", buf) > QPNP_WLED_STR_SIZE)
+		return -EINVAL;
+
+	if (strcmp(str, "analog") == 0)
+		temp = QPNP_WLED_DIM_ANALOG;
+	else if (strcmp(str, "digital") == 0)
+		temp = QPNP_WLED_DIM_DIGITAL;
+	else
+		temp = QPNP_WLED_DIM_HYBRID;
+
+	if (temp == wled->dim_mode)
+		return count;
+
+	rc = qpnp_wled_read_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), &reg);
+	if (rc < 0)
+		return rc;
+
+	if (temp == QPNP_WLED_DIM_HYBRID) {
+		reg &= QPNP_WLED_DIM_HYB_MASK;
+		reg |= (1 << QPNP_WLED_DIM_HYB_SHIFT);
+	} else {
+		reg &= QPNP_WLED_DIM_HYB_MASK;
+		reg |= (0 << QPNP_WLED_DIM_HYB_SHIFT);
+		reg &= QPNP_WLED_DIM_ANA_MASK;
+		reg |= temp;
+	}
+
+	rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), reg);
+	if (rc)
+		return rc;
+
+	wled->dim_mode = temp;
+
+	return count;
+}
+
+/* sysfs show function for full scale current in ua*/
+static ssize_t qpnp_wled_fs_curr_ua_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", wled->fs_curr_ua);
+}
+
+/* sysfs store function for full scale current in ua*/
+static ssize_t qpnp_wled_fs_curr_ua_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(dev);
+	int data, i, rc, temp;
+	u8 reg;
+
+	rc = kstrtoint(buf, 10, &data);
+	if (rc)
+		return rc;
+
+	for (i = 0; i < wled->num_strings; i++) {
+		if (data < QPNP_WLED_FS_CURR_MIN_UA)
+			data = QPNP_WLED_FS_CURR_MIN_UA;
+		else if (data > QPNP_WLED_FS_CURR_MAX_UA)
+			data = QPNP_WLED_FS_CURR_MAX_UA;
+
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_FS_CURR_REG(wled->sink_base,
+					wled->strings[i]), &reg);
+		if (rc < 0)
+			return rc;
+		reg &= QPNP_WLED_FS_CURR_MASK;
+		temp = data / QPNP_WLED_FS_CURR_STEP_UA;
+		reg |= temp;
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_FS_CURR_REG(wled->sink_base,
+					wled->strings[i]), reg);
+		if (rc)
+			return rc;
+	}
+
+	wled->fs_curr_ua = data;
+
+	rc = qpnp_wled_sync_reg_toggle(wled);
+	if (rc < 0) {
+		dev_err(&wled->pdev->dev, "Failed to toggle sync reg %d\n", rc);
+		return rc;
+	}
+
+	return count;
+}
+
+/* sysfs attributes exported by wled */
+static struct device_attribute qpnp_wled_attrs[] = {
+	__ATTR(dump_regs, 0664, qpnp_wled_dump_regs_show, NULL),
+	__ATTR(dim_mode, 0664, qpnp_wled_dim_mode_show,
+		qpnp_wled_dim_mode_store),
+	__ATTR(fs_curr_ua, 0664, qpnp_wled_fs_curr_ua_show,
+		qpnp_wled_fs_curr_ua_store),
+	__ATTR(start_ramp, 0664, NULL, qpnp_wled_ramp_store),
+	__ATTR(ramp_ms, 0664, qpnp_wled_ramp_ms_show, qpnp_wled_ramp_ms_store),
+	__ATTR(ramp_step, 0664, qpnp_wled_ramp_step_show,
+		qpnp_wled_ramp_step_store),
+};
+
+/* worker for setting wled brightness */
+static void qpnp_wled_work(struct work_struct *work)
+{
+	struct qpnp_wled *wled;
+	int level, rc;
+
+	wled = container_of(work, struct qpnp_wled, work);
+
+	level = wled->cdev.brightness;
+
+	mutex_lock(&wled->lock);
+
+	if (level) {
+		rc = qpnp_wled_set_level(wled, level);
+		if (rc) {
+			dev_err(&wled->pdev->dev, "wled set level failed\n");
+			goto unlock_mutex;
+		}
+	}
+
+	if (!!level != wled->prev_state) {
+		if (!!level) {
+			/*
+			 * For AMOLED display in pmi8998, SWIRE_AVDD_DEFAULT has
+			 * to be reconfigured every time the module is enabled.
+			 */
+			rc = qpnp_wled_swire_avdd_config(wled);
+			if (rc < 0) {
+				pr_err("Write to SWIRE_AVDD_DEFAULT register failed rc:%d\n",
+					rc);
+				goto unlock_mutex;
+			}
+		}
+
+		rc = qpnp_wled_module_en(wled, wled->ctrl_base, !!level);
+		if (rc) {
+			dev_err(&wled->pdev->dev, "wled %sable failed\n",
+						level ? "en" : "dis");
+			goto unlock_mutex;
+		}
+	}
+
+	wled->prev_state = !!level;
+unlock_mutex:
+	mutex_unlock(&wled->lock);
+}
+
+/* get api registered with led classdev for wled brightness */
+static enum led_brightness qpnp_wled_get(struct led_classdev *led_cdev)
+{
+	struct qpnp_wled *wled;
+
+	wled = container_of(led_cdev, struct qpnp_wled, cdev);
+
+	return wled->cdev.brightness;
+}
+
+/* set api registered with led classdev for wled brightness */
+static void qpnp_wled_set(struct led_classdev *led_cdev,
+				enum led_brightness level)
+{
+	struct qpnp_wled *wled;
+
+	wled = container_of(led_cdev, struct qpnp_wled, cdev);
+
+	if (level < LED_OFF)
+		level = LED_OFF;
+	else if (level > wled->cdev.max_brightness)
+		level = wled->cdev.max_brightness;
+
+	wled->cdev.brightness = level;
+	schedule_work(&wled->work);
+}
+
+static int qpnp_wled_set_disp(struct qpnp_wled *wled, u16 base_addr)
+{
+	int rc;
+	u8 reg;
+
+	/* display type */
+	rc = qpnp_wled_read_reg(wled, QPNP_WLED_DISP_SEL_REG(base_addr), &reg);
+	if (rc < 0)
+		return rc;
+
+	reg &= QPNP_WLED_DISP_SEL_MASK;
+	reg |= (wled->disp_type_amoled << QPNP_WLED_DISP_SEL_SHIFT);
+
+	rc = qpnp_wled_sec_write_reg(wled, QPNP_WLED_DISP_SEL_REG(base_addr),
+			reg);
+	if (rc)
+		return rc;
+
+	if (wled->disp_type_amoled) {
+		/* Configure the PSM CTRL register for AMOLED */
+		if (wled->vref_psm_mv < QPNP_WLED_VREF_PSM_MIN_MV)
+			wled->vref_psm_mv = QPNP_WLED_VREF_PSM_MIN_MV;
+		else if (wled->vref_psm_mv > QPNP_WLED_VREF_PSM_MAX_MV)
+			wled->vref_psm_mv = QPNP_WLED_VREF_PSM_MAX_MV;
+
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_PSM_CTRL_REG(wled->ctrl_base), &reg);
+		if (rc < 0)
+			return rc;
+
+		reg &= QPNP_WLED_VREF_PSM_MASK;
+		reg |= ((wled->vref_psm_mv - QPNP_WLED_VREF_PSM_MIN_MV)/
+			QPNP_WLED_VREF_PSM_STEP_MV);
+		reg |= QPNP_WLED_PSM_CTRL_OVERWRITE;
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_PSM_CTRL_REG(wled->ctrl_base), reg);
+		if (rc)
+			return rc;
+
+		/* Configure the VLOOP COMP RES register for AMOLED */
+		if (wled->loop_comp_res_kohm < QPNP_WLED_LOOP_COMP_RES_MIN_KOHM)
+			wled->loop_comp_res_kohm =
+					QPNP_WLED_LOOP_COMP_RES_MIN_KOHM;
+		else if (wled->loop_comp_res_kohm >
+					QPNP_WLED_LOOP_COMP_RES_MAX_KOHM)
+			wled->loop_comp_res_kohm =
+					QPNP_WLED_LOOP_COMP_RES_MAX_KOHM;
+
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_VLOOP_COMP_RES_REG(wled->ctrl_base),
+				&reg);
+		if (rc < 0)
+			return rc;
+
+		reg &= QPNP_WLED_VLOOP_COMP_RES_MASK;
+		reg |= ((wled->loop_comp_res_kohm -
+				 QPNP_WLED_LOOP_COMP_RES_MIN_KOHM)/
+				 QPNP_WLED_LOOP_COMP_RES_STEP_KOHM);
+		reg |= QPNP_WLED_VLOOP_COMP_RES_OVERWRITE;
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_VLOOP_COMP_RES_REG(wled->ctrl_base),
+				reg);
+		if (rc)
+			return rc;
+
+		/* Configure the CTRL TEST4 register for AMOLED */
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_TEST4_REG(wled->ctrl_base), &reg);
+		if (rc < 0)
+			return rc;
+
+		reg |= QPNP_WLED_TEST4_EN_IIND_UP;
+		rc = qpnp_wled_sec_write_reg(wled,
+				QPNP_WLED_TEST4_REG(base_addr), reg);
+		if (rc)
+			return rc;
+	} else {
+		/*
+		 * enable VREF_UP to avoid false ovp on low brightness for LCD
+		 */
+		reg = QPNP_WLED_TEST4_EN_VREF_UP
+				| QPNP_WLED_TEST4_EN_DEB_BYPASS_ILIM_BIT;
+		rc = qpnp_wled_sec_write_reg(wled,
+				QPNP_WLED_TEST4_REG(base_addr), reg);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
+/* ovp irq handler */
+static irqreturn_t qpnp_wled_ovp_irq_handler(int irq, void *_wled)
+{
+	struct qpnp_wled *wled = _wled;
+	int rc;
+	u8 val;
+
+	rc = qpnp_wled_read_reg(wled,
+			QPNP_WLED_FAULT_STATUS(wled->ctrl_base), &val);
+	if (rc < 0) {
+		pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	pr_err("WLED OVP fault detected, fault_status= %x\n", val);
+	return IRQ_HANDLED;
+}
+
+/* short circuit irq handler */
+static irqreturn_t qpnp_wled_sc_irq_handler(int irq, void *_wled)
+{
+	struct qpnp_wled *wled = _wled;
+	int rc;
+	u8 val;
+
+	rc = qpnp_wled_read_reg(wled,
+			QPNP_WLED_FAULT_STATUS(wled->ctrl_base), &val);
+	if (rc < 0) {
+		pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	pr_err("WLED short circuit detected %d times fault_status=%x\n",
+		++wled->sc_cnt, val);
+	mutex_lock(&wled->lock);
+	qpnp_wled_module_en(wled, wled->ctrl_base, false);
+	msleep(QPNP_WLED_SC_DLY_MS);
+	qpnp_wled_module_en(wled, wled->ctrl_base, true);
+	mutex_unlock(&wled->lock);
+
+	return IRQ_HANDLED;
+}
+
+static bool is_avdd_trim_adjustment_required(struct qpnp_wled *wled)
+{
+	int rc;
+	u8 reg = 0;
+
+	/*
+	 * AVDD trim adjustment is not required for pmi8998/pm660l and not
+	 * supported for pmi8994.
+	 */
+	if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+		wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE ||
+		wled->pmic_rev_id->pmic_subtype == PMI8994_SUBTYPE)
+		return false;
+
+	/*
+	 * Configure TRIM_REG only if disp_type_amoled and it has
+	 * not already been programmed by bootloader.
+	 */
+	if (!wled->disp_type_amoled)
+		return false;
+
+	rc = qpnp_wled_read_reg(wled,
+			QPNP_WLED_CTRL_SPARE_REG(wled->ctrl_base), &reg);
+	if (rc < 0)
+		return false;
+
+	return !(reg & QPNP_WLED_AVDD_SET_BIT);
+}
+
+static int qpnp_wled_gm_config(struct qpnp_wled *wled)
+{
+	int rc;
+	u8 mask = 0, reg = 0;
+
+	/* Configure the LOOP COMP GM register */
+	if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+			wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+		if (wled->loop_auto_gm_en)
+			reg |= QPNP_WLED_VLOOP_COMP_AUTO_GM_EN;
+
+		if (wled->loop_auto_gm_thresh >
+				QPNP_WLED_LOOP_AUTO_GM_THRESH_MAX)
+			wled->loop_auto_gm_thresh =
+				QPNP_WLED_LOOP_AUTO_GM_THRESH_MAX;
+
+		reg |= wled->loop_auto_gm_thresh <<
+			QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_SHIFT;
+		mask |= QPNP_WLED_VLOOP_COMP_AUTO_GM_EN |
+			QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_MASK;
+	}
+
+	if (wled->loop_ea_gm < QPNP_WLED_LOOP_EA_GM_MIN)
+		wled->loop_ea_gm = QPNP_WLED_LOOP_EA_GM_MIN;
+	else if (wled->loop_ea_gm > QPNP_WLED_LOOP_EA_GM_MAX)
+		wled->loop_ea_gm = QPNP_WLED_LOOP_EA_GM_MAX;
+
+	reg |= wled->loop_ea_gm | QPNP_WLED_VLOOP_COMP_GM_OVERWRITE;
+	mask |= QPNP_WLED_VLOOP_COMP_GM_MASK |
+		QPNP_WLED_VLOOP_COMP_GM_OVERWRITE;
+
+	rc = qpnp_wled_masked_write_reg(wled,
+			QPNP_WLED_VLOOP_COMP_GM_REG(wled->ctrl_base), mask,
+			reg);
+	if (rc)
+		pr_err("write VLOOP_COMP_GM_REG failed, rc=%d]\n", rc);
+
+	return rc;
+}
+
+static int qpnp_wled_ovp_config(struct qpnp_wled *wled)
+{
+	int rc, i, *ovp_table;
+	u8 reg;
+
+	/*
+	 * Configure the OVP register based on ovp_mv only if display type is
+	 * not AMOLED.
+	 */
+	if (wled->disp_type_amoled)
+		return 0;
+
+	if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+		wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+		ovp_table = qpnp_wled_ovp_thresholds_pmi8998;
+	else
+		ovp_table = qpnp_wled_ovp_thresholds_pmi8994;
+
+	for (i = 0; i < NUM_SUPPORTED_OVP_THRESHOLDS; i++) {
+		if (wled->ovp_mv == ovp_table[i])
+			break;
+	}
+
+	if (i == NUM_SUPPORTED_OVP_THRESHOLDS) {
+		dev_err(&wled->pdev->dev,
+			"Invalid ovp threshold specified in device tree\n");
+		return -EINVAL;
+	}
+
+	reg = i & QPNP_WLED_OVP_MASK;
+	rc = qpnp_wled_masked_write_reg(wled,
+			QPNP_WLED_OVP_REG(wled->ctrl_base),
+			QPNP_WLED_OVP_MASK, reg);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static int qpnp_wled_avdd_trim_config(struct qpnp_wled *wled)
+{
+	int rc, i;
+	u8 reg;
+
+	for (i = 0; i < NUM_SUPPORTED_AVDD_VOLTAGES; i++) {
+		if (wled->avdd_target_voltage_mv ==
+				qpnp_wled_avdd_target_voltages[i])
+			break;
+	}
+
+	if (i == NUM_SUPPORTED_AVDD_VOLTAGES) {
+		dev_err(&wled->pdev->dev,
+			"Invalid avdd target voltage specified in device tree\n");
+		return -EINVAL;
+	}
+
+	/* Update WLED_OVP register based on desired target voltage */
+	reg = qpnp_wled_ovp_reg_settings[i];
+	rc = qpnp_wled_masked_write_reg(wled,
+			QPNP_WLED_OVP_REG(wled->ctrl_base),
+			QPNP_WLED_OVP_MASK, reg);
+	if (rc)
+		return rc;
+
+	/* Update WLED_TRIM register based on desired target voltage */
+	rc = qpnp_wled_read_reg(wled,
+			QPNP_WLED_REF_7P7_TRIM_REG(wled->ctrl_base), &reg);
+	if (rc)
+		return rc;
+
+	reg += qpnp_wled_avdd_trim_adjustments[i];
+	if ((s8)reg < QPNP_WLED_AVDD_MIN_TRIM_VAL ||
+			(s8)reg > QPNP_WLED_AVDD_MAX_TRIM_VAL) {
+		dev_dbg(&wled->pdev->dev,
+			 "adjusted trim %d is not within range, capping it\n",
+			 (s8)reg);
+		if ((s8)reg < QPNP_WLED_AVDD_MIN_TRIM_VAL)
+			reg = QPNP_WLED_AVDD_MIN_TRIM_VAL;
+		else
+			reg = QPNP_WLED_AVDD_MAX_TRIM_VAL;
+	}
+
+	reg &= QPNP_WLED_7P7_TRIM_MASK;
+	rc = qpnp_wled_sec_write_reg(wled,
+			QPNP_WLED_REF_7P7_TRIM_REG(wled->ctrl_base), reg);
+	if (rc < 0)
+		dev_err(&wled->pdev->dev, "Write to 7P7_TRIM register failed, rc=%d\n",
+			rc);
+	return rc;
+}
+
+static int qpnp_wled_avdd_mode_config(struct qpnp_wled *wled)
+{
+	int rc;
+	u8 reg = 0;
+
+	/*
+	 * At present, configuring the mode to SPMI/SWIRE for controlling
+	 * AVDD voltage is available only in pmi8998/pm660l.
+	 */
+	if (wled->pmic_rev_id->pmic_subtype != PMI8998_SUBTYPE &&
+		wled->pmic_rev_id->pmic_subtype != PM660L_SUBTYPE)
+		return 0;
+
+	/* AMOLED_VOUT should be configured for AMOLED */
+	if (!wled->disp_type_amoled)
+		return 0;
+
+	/* Configure avdd register */
+	if (wled->avdd_target_voltage_mv > QPNP_WLED_AVDD_MAX_MV) {
+		dev_dbg(&wled->pdev->dev, "Capping avdd target voltage to %d\n",
+			QPNP_WLED_AVDD_MAX_MV);
+		wled->avdd_target_voltage_mv = QPNP_WLED_AVDD_MAX_MV;
+	} else if (wled->avdd_target_voltage_mv < QPNP_WLED_AVDD_MIN_MV) {
+		dev_info(&wled->pdev->dev, "Capping avdd target voltage to %d\n",
+			QPNP_WLED_AVDD_MIN_MV);
+		wled->avdd_target_voltage_mv = QPNP_WLED_AVDD_MIN_MV;
+	}
+
+	if (wled->avdd_mode_spmi) {
+		reg = QPNP_WLED_AVDD_MV_TO_REG(wled->avdd_target_voltage_mv);
+		reg |= QPNP_WLED_AVDD_SEL_SPMI_BIT;
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_AMOLED_VOUT_REG(wled->ctrl_base),
+				reg);
+		if (rc < 0)
+			pr_err("Write to AMOLED_VOUT register failed, rc=%d\n",
+				rc);
+	} else {
+		rc = qpnp_wled_swire_avdd_config(wled);
+		if (rc < 0)
+			pr_err("Write to SWIRE_AVDD_DEFAULT register failed rc:%d\n",
+				rc);
+	}
+
+	return rc;
+}
+
+static int qpnp_wled_ilim_config(struct qpnp_wled *wled)
+{
+	int rc, i, *ilim_table;
+	u8 reg;
+
+	if (wled->ilim_ma < PMI8994_WLED_ILIM_MIN_MA)
+		wled->ilim_ma = PMI8994_WLED_ILIM_MIN_MA;
+
+	if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+		wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+		ilim_table = qpnp_wled_ilim_settings_pmi8998;
+		if (wled->ilim_ma > PMI8998_WLED_ILIM_MAX_MA)
+			wled->ilim_ma = PMI8998_WLED_ILIM_MAX_MA;
+	} else {
+		ilim_table = qpnp_wled_ilim_settings_pmi8994;
+		if (wled->ilim_ma > PMI8994_WLED_ILIM_MAX_MA)
+			wled->ilim_ma = PMI8994_WLED_ILIM_MAX_MA;
+	}
+
+	for (i = 0; i < NUM_SUPPORTED_ILIM_THRESHOLDS; i++) {
+		if (wled->ilim_ma == ilim_table[i])
+			break;
+	}
+
+	if (i == NUM_SUPPORTED_ILIM_THRESHOLDS) {
+		dev_err(&wled->pdev->dev,
+			"Invalid ilim threshold specified in device tree\n");
+		return -EINVAL;
+	}
+
+	reg = (i & QPNP_WLED_ILIM_MASK) | QPNP_WLED_ILIM_OVERWRITE;
+	rc = qpnp_wled_masked_write_reg(wled,
+			QPNP_WLED_ILIM_REG(wled->ctrl_base),
+			QPNP_WLED_ILIM_MASK | QPNP_WLED_ILIM_OVERWRITE, reg);
+	if (rc < 0)
+		dev_err(&wled->pdev->dev, "Write to ILIM register failed, rc=%d\n",
+			rc);
+	return rc;
+}
+
+static int qpnp_wled_vref_config(struct qpnp_wled *wled)
+{
+
+	struct wled_vref_setting vref_setting;
+	int rc;
+	u8 reg = 0;
+
+	if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+			wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+		vref_setting = vref_setting_pmi8998;
+	else
+		vref_setting = vref_setting_pmi8994;
+
+	if (wled->vref_uv < vref_setting.min_uv)
+		wled->vref_uv = vref_setting.min_uv;
+	else if (wled->vref_uv > vref_setting.max_uv)
+		wled->vref_uv = vref_setting.max_uv;
+
+	reg |= DIV_ROUND_CLOSEST(wled->vref_uv - vref_setting.min_uv,
+					vref_setting.step_uv);
+
+	rc = qpnp_wled_masked_write_reg(wled,
+			QPNP_WLED_VREF_REG(wled->ctrl_base),
+			QPNP_WLED_VREF_MASK, reg);
+	if (rc)
+		pr_err("Write VREF_REG failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+/* Configure WLED registers */
+static int qpnp_wled_config(struct qpnp_wled *wled)
+{
+	int rc, i, temp;
+	u8 reg = 0;
+
+	/* Configure display type */
+	rc = qpnp_wled_set_disp(wled, wled->ctrl_base);
+	if (rc < 0)
+		return rc;
+
+	/* Configure the FEEDBACK OUTPUT register */
+	rc = qpnp_wled_read_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base),
+			&reg);
+	if (rc < 0)
+		return rc;
+	reg &= QPNP_WLED_FDBK_OP_MASK;
+	reg |= wled->fdbk_op;
+	rc = qpnp_wled_write_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base),
+			reg);
+	if (rc)
+		return rc;
+
+	/* Configure the VREF register */
+	rc = qpnp_wled_vref_config(wled);
+	if (rc < 0) {
+		pr_err("Error in configuring wled vref, rc=%d\n", rc);
+		return rc;
+	}
+
+	/* Configure VLOOP_COMP_GM register */
+	rc = qpnp_wled_gm_config(wled);
+	if (rc < 0) {
+		pr_err("Error in configureing wled gm, rc=%d\n", rc);
+		return rc;
+	}
+
+	/* Configure the ILIM register */
+	rc = qpnp_wled_ilim_config(wled);
+	if (rc < 0) {
+		pr_err("Error in configuring wled ilim, rc=%d\n", rc);
+		return rc;
+	}
+
+	/* Configure auto PFM mode for LCD mode only */
+	if ((wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+		wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+		&& !wled->disp_type_amoled) {
+		reg = 0;
+		reg |= wled->lcd_auto_pfm_thresh;
+		reg |= wled->lcd_auto_pfm_en <<
+			QPNP_WLED_LCD_AUTO_PFM_EN_SHIFT;
+		rc = qpnp_wled_masked_write_reg(wled,
+				QPNP_WLED_LCD_AUTO_PFM_REG(wled->ctrl_base),
+				QPNP_WLED_LCD_AUTO_PFM_EN_BIT |
+				QPNP_WLED_LCD_AUTO_PFM_THRESH_MASK, reg);
+		if (rc < 0) {
+			pr_err("Write LCD_AUTO_PFM failed, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	/* Configure the Soft start Ramp delay: for AMOLED - 0,for LCD - 2 */
+	reg = (wled->disp_type_amoled) ? 0 : 2;
+	rc = qpnp_wled_write_reg(wled,
+			QPNP_WLED_SOFTSTART_RAMP_DLY(wled->ctrl_base), reg);
+	if (rc)
+		return rc;
+
+	/* Configure the MAX BOOST DUTY register */
+	if (wled->boost_duty_ns < QPNP_WLED_BOOST_DUTY_MIN_NS)
+		wled->boost_duty_ns = QPNP_WLED_BOOST_DUTY_MIN_NS;
+	else if (wled->boost_duty_ns > QPNP_WLED_BOOST_DUTY_MAX_NS)
+		wled->boost_duty_ns = QPNP_WLED_BOOST_DUTY_MAX_NS;
+
+	rc = qpnp_wled_read_reg(wled, QPNP_WLED_BOOST_DUTY_REG(wled->ctrl_base),
+			&reg);
+	if (rc < 0)
+		return rc;
+	reg &= QPNP_WLED_BOOST_DUTY_MASK;
+	reg |= (wled->boost_duty_ns / QPNP_WLED_BOOST_DUTY_STEP_NS);
+	rc = qpnp_wled_write_reg(wled,
+			QPNP_WLED_BOOST_DUTY_REG(wled->ctrl_base), reg);
+	if (rc)
+		return rc;
+
+	/* Configure the SWITCHING FREQ register */
+	if (wled->switch_freq_khz == QPNP_WLED_SWITCH_FREQ_1600_KHZ)
+		temp = QPNP_WLED_SWITCH_FREQ_1600_KHZ_CODE;
+	else
+		temp = QPNP_WLED_SWITCH_FREQ_800_KHZ_CODE;
+
+	rc = qpnp_wled_read_reg(wled,
+			QPNP_WLED_SWITCH_FREQ_REG(wled->ctrl_base), &reg);
+	if (rc < 0)
+		return rc;
+	reg &= QPNP_WLED_SWITCH_FREQ_MASK;
+	reg |= (temp | QPNP_WLED_SWITCH_FREQ_OVERWRITE);
+	rc = qpnp_wled_write_reg(wled,
+			QPNP_WLED_SWITCH_FREQ_REG(wled->ctrl_base), reg);
+	if (rc)
+		return rc;
+
+	rc = qpnp_wled_ovp_config(wled);
+	if (rc < 0) {
+		pr_err("Error in configuring OVP threshold, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (is_avdd_trim_adjustment_required(wled)) {
+		rc = qpnp_wled_avdd_trim_config(wled);
+		if (rc < 0)
+			return rc;
+	}
+
+	rc = qpnp_wled_avdd_mode_config(wled);
+	if (rc < 0)
+		return rc;
+
+	/* Configure the MODULATION register */
+	if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_1200_KHZ) {
+		wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_1200_KHZ;
+		temp = 3;
+	} else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_2400_KHZ) {
+		wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_2400_KHZ;
+		temp = 2;
+	} else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_9600_KHZ) {
+		wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ;
+		temp = 1;
+	} else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_19200_KHZ) {
+		wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_19200_KHZ;
+		temp = 0;
+	} else {
+		wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ;
+		temp = 1;
+	}
+
+	rc = qpnp_wled_read_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), &reg);
+	if (rc < 0)
+		return rc;
+	reg &= QPNP_WLED_MOD_FREQ_MASK;
+	reg |= (temp << QPNP_WLED_MOD_FREQ_SHIFT);
+
+	reg &= QPNP_WLED_PHASE_STAG_MASK;
+	reg |= (wled->en_phase_stag << QPNP_WLED_PHASE_STAG_SHIFT);
+
+	reg &= QPNP_WLED_ACC_CLK_FREQ_MASK;
+	reg |= (temp << QPNP_WLED_ACC_CLK_FREQ_SHIFT);
+
+	reg &= QPNP_WLED_DIM_RES_MASK;
+	reg |= (wled->en_9b_dim_res << QPNP_WLED_DIM_RES_SHIFT);
+
+	if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) {
+		reg &= QPNP_WLED_DIM_HYB_MASK;
+		reg |= (1 << QPNP_WLED_DIM_HYB_SHIFT);
+	} else {
+		reg &= QPNP_WLED_DIM_HYB_MASK;
+		reg |= (0 << QPNP_WLED_DIM_HYB_SHIFT);
+		reg &= QPNP_WLED_DIM_ANA_MASK;
+		reg |= wled->dim_mode;
+	}
+
+	rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), reg);
+	if (rc)
+		return rc;
+
+	/* Configure the HYBRID THRESHOLD register */
+	if (wled->hyb_thres < QPNP_WLED_HYB_THRES_MIN)
+		wled->hyb_thres = QPNP_WLED_HYB_THRES_MIN;
+	else if (wled->hyb_thres > QPNP_WLED_HYB_THRES_MAX)
+		wled->hyb_thres = QPNP_WLED_HYB_THRES_MAX;
+
+	rc = qpnp_wled_read_reg(wled, QPNP_WLED_HYB_THRES_REG(wled->sink_base),
+			&reg);
+	if (rc < 0)
+		return rc;
+	reg &= QPNP_WLED_HYB_THRES_MASK;
+	temp = fls(wled->hyb_thres / QPNP_WLED_HYB_THRES_MIN) - 1;
+	reg |= temp;
+	rc = qpnp_wled_write_reg(wled, QPNP_WLED_HYB_THRES_REG(wled->sink_base),
+			reg);
+	if (rc)
+		return rc;
+
+	/* Configure TEST5 register */
+	if (wled->dim_mode == QPNP_WLED_DIM_DIGITAL) {
+		reg = QPNP_WLED_SINK_TEST5_DIG;
+	} else {
+		reg = QPNP_WLED_SINK_TEST5_HYB;
+		if (wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+			reg |= QPNP_WLED_SINK_TEST5_HVG_PULL_STR_BIT;
+	}
+
+	rc = qpnp_wled_sec_write_reg(wled,
+			QPNP_WLED_SINK_TEST5_REG(wled->sink_base), reg);
+	if (rc)
+		return rc;
+
+	/* disable all current sinks and enable selected strings */
+	reg = 0x00;
+	rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base),
+			reg);
+
+	for (i = 0; i < wled->num_strings; i++) {
+		if (wled->strings[i] >= QPNP_WLED_MAX_STRINGS) {
+			dev_err(&wled->pdev->dev, "Invalid string number\n");
+			return -EINVAL;
+		}
+
+		/* MODULATOR */
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_MOD_EN_REG(wled->sink_base,
+					wled->strings[i]), &reg);
+		if (rc < 0)
+			return rc;
+		reg &= QPNP_WLED_MOD_EN_MASK;
+		reg |= (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT);
+
+		if (wled->dim_mode == QPNP_WLED_DIM_HYBRID)
+			reg &= QPNP_WLED_GATE_DRV_MASK;
+		else
+			reg |= ~QPNP_WLED_GATE_DRV_MASK;
+
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_MOD_EN_REG(wled->sink_base,
+					wled->strings[i]), reg);
+		if (rc)
+			return rc;
+
+		/* SYNC DELAY */
+		if (wled->sync_dly_us > QPNP_WLED_SYNC_DLY_MAX_US)
+			wled->sync_dly_us = QPNP_WLED_SYNC_DLY_MAX_US;
+
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_SYNC_DLY_REG(wled->sink_base,
+					wled->strings[i]), &reg);
+		if (rc < 0)
+			return rc;
+		reg &= QPNP_WLED_SYNC_DLY_MASK;
+		temp = wled->sync_dly_us / QPNP_WLED_SYNC_DLY_STEP_US;
+		reg |= temp;
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_SYNC_DLY_REG(wled->sink_base,
+					wled->strings[i]), reg);
+		if (rc)
+			return rc;
+
+		/* FULL SCALE CURRENT */
+		if (wled->fs_curr_ua > QPNP_WLED_FS_CURR_MAX_UA)
+			wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA;
+
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_FS_CURR_REG(wled->sink_base,
+					wled->strings[i]), &reg);
+		if (rc < 0)
+			return rc;
+		reg &= QPNP_WLED_FS_CURR_MASK;
+		temp = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA;
+		reg |= temp;
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_FS_CURR_REG(wled->sink_base,
+					wled->strings[i]), reg);
+		if (rc)
+			return rc;
+
+		/* CABC */
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_CABC_REG(wled->sink_base,
+					wled->strings[i]), &reg);
+		if (rc < 0)
+			return rc;
+		reg &= QPNP_WLED_CABC_MASK;
+		reg |= (wled->en_cabc << QPNP_WLED_CABC_SHIFT);
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_CABC_REG(wled->sink_base,
+					wled->strings[i]), reg);
+		if (rc)
+			return rc;
+
+		/* Enable CURRENT SINK */
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_CURR_SINK_REG(wled->sink_base), &reg);
+		if (rc < 0)
+			return rc;
+		temp = wled->strings[i] + QPNP_WLED_CURR_SINK_SHIFT;
+		reg |= (1 << temp);
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_CURR_SINK_REG(wled->sink_base), reg);
+		if (rc)
+			return rc;
+	}
+
+	rc = qpnp_wled_sync_reg_toggle(wled);
+	if (rc < 0) {
+		dev_err(&wled->pdev->dev, "Failed to toggle sync reg %d\n", rc);
+		return rc;
+	}
+
+	/* setup ovp and sc irqs */
+	if (wled->ovp_irq >= 0) {
+		rc = devm_request_threaded_irq(&wled->pdev->dev, wled->ovp_irq,
+				NULL, qpnp_wled_ovp_irq_handler, IRQF_ONESHOT,
+				"qpnp_wled_ovp_irq", wled);
+		if (rc < 0) {
+			dev_err(&wled->pdev->dev,
+				"Unable to request ovp(%d) IRQ(err:%d)\n",
+				wled->ovp_irq, rc);
+			return rc;
+		}
+	}
+
+	if (wled->sc_irq >= 0) {
+		wled->sc_cnt = 0;
+		rc = devm_request_threaded_irq(&wled->pdev->dev, wled->sc_irq,
+				NULL, qpnp_wled_sc_irq_handler, IRQF_ONESHOT,
+				"qpnp_wled_sc_irq", wled);
+		if (rc < 0) {
+			dev_err(&wled->pdev->dev,
+				"Unable to request sc(%d) IRQ(err:%d)\n",
+				wled->sc_irq, rc);
+			return rc;
+		}
+
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_SC_PRO_REG(wled->ctrl_base), &reg);
+		if (rc < 0)
+			return rc;
+		reg &= QPNP_WLED_EN_SC_DEB_CYCLES_MASK;
+		reg |= 1 << QPNP_WLED_EN_SC_SHIFT;
+
+		if (wled->sc_deb_cycles < QPNP_WLED_SC_DEB_CYCLES_MIN)
+			wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MIN;
+		else if (wled->sc_deb_cycles > QPNP_WLED_SC_DEB_CYCLES_MAX)
+			wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MAX;
+		temp = fls(wled->sc_deb_cycles) - QPNP_WLED_SC_DEB_CYCLES_SUB;
+		reg |= (temp << 1);
+
+		if (wled->disp_type_amoled)
+			reg |= QPNP_WLED_SC_PRO_EN_DSCHGR;
+
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_SC_PRO_REG(wled->ctrl_base), reg);
+		if (rc)
+			return rc;
+
+		if (wled->en_ext_pfet_sc_pro) {
+			reg = QPNP_WLED_EXT_FET_DTEST2;
+			rc = qpnp_wled_sec_write_reg(wled,
+					QPNP_WLED_TEST1_REG(wled->ctrl_base),
+					reg);
+			if (rc)
+				return rc;
+		}
+	} else {
+		rc = qpnp_wled_read_reg(wled,
+				QPNP_WLED_SC_PRO_REG(wled->ctrl_base), &reg);
+		if (rc < 0)
+			return rc;
+		reg &= QPNP_WLED_EN_DEB_CYCLES_MASK;
+
+		if (wled->sc_deb_cycles < QPNP_WLED_SC_DEB_CYCLES_MIN)
+			wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MIN;
+		else if (wled->sc_deb_cycles > QPNP_WLED_SC_DEB_CYCLES_MAX)
+			wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MAX;
+		temp = fls(wled->sc_deb_cycles) - QPNP_WLED_SC_DEB_CYCLES_SUB;
+		reg |= (temp << 1);
+
+		rc = qpnp_wled_write_reg(wled,
+				QPNP_WLED_SC_PRO_REG(wled->ctrl_base), reg);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
+/* parse wled dtsi parameters */
+static int qpnp_wled_parse_dt(struct qpnp_wled *wled)
+{
+	struct platform_device *pdev = wled->pdev;
+	struct property *prop;
+	const char *temp_str;
+	u32 temp_val;
+	int rc, i;
+	u8 *strings;
+
+	wled->cdev.name = "wled";
+	rc = of_property_read_string(pdev->dev.of_node,
+			"linux,name", &wled->cdev.name);
+	if (rc && (rc != -EINVAL)) {
+		dev_err(&pdev->dev, "Unable to read led name\n");
+		return rc;
+	}
+
+	wled->cdev.default_trigger = QPNP_WLED_TRIGGER_NONE;
+	rc = of_property_read_string(pdev->dev.of_node, "linux,default-trigger",
+					&wled->cdev.default_trigger);
+	if (rc && (rc != -EINVAL)) {
+		dev_err(&pdev->dev, "Unable to read led trigger\n");
+		return rc;
+	}
+
+	wled->disp_type_amoled = of_property_read_bool(pdev->dev.of_node,
+				"qcom,disp-type-amoled");
+	if (wled->disp_type_amoled) {
+		wled->vref_psm_mv = QPNP_WLED_VREF_PSM_DFLT_AMOLED_MV;
+		rc = of_property_read_u32(pdev->dev.of_node,
+				"qcom,vref-psm-mv", &temp_val);
+		if (!rc) {
+			wled->vref_psm_mv = temp_val;
+		} else if (rc != -EINVAL) {
+			dev_err(&pdev->dev, "Unable to read vref-psm\n");
+			return rc;
+		}
+
+		wled->loop_comp_res_kohm =
+			QPNP_WLED_LOOP_COMP_RES_DFLT_AMOLED_KOHM;
+		rc = of_property_read_u32(pdev->dev.of_node,
+				"qcom,loop-comp-res-kohm", &temp_val);
+		if (!rc) {
+			wled->loop_comp_res_kohm = temp_val;
+		} else if (rc != -EINVAL) {
+			dev_err(&pdev->dev, "Unable to read loop-comp-res-kohm\n");
+			return rc;
+		}
+
+		wled->avdd_mode_spmi = of_property_read_bool(pdev->dev.of_node,
+				"qcom,avdd-mode-spmi");
+
+		wled->avdd_target_voltage_mv = QPNP_WLED_DFLT_AVDD_MV;
+		rc = of_property_read_u32(pdev->dev.of_node,
+				"qcom,avdd-target-voltage-mv", &temp_val);
+		if (!rc) {
+			wled->avdd_target_voltage_mv = temp_val;
+		} else if (rc != -EINVAL) {
+			dev_err(&pdev->dev, "Unable to read avdd target voltage\n");
+			return rc;
+		}
+	}
+
+	if (wled->disp_type_amoled) {
+		if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+			wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+			wled->loop_ea_gm =
+				QPNP_WLED_LOOP_GM_DFLT_AMOLED_PMI8998;
+		else
+			wled->loop_ea_gm =
+				QPNP_WLED_LOOP_EA_GM_DFLT_AMOLED_PMI8994;
+	} else {
+		wled->loop_ea_gm = QPNP_WLED_LOOP_GM_DFLT_WLED;
+	}
+
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,loop-ea-gm", &temp_val);
+	if (!rc) {
+		wled->loop_ea_gm = temp_val;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read loop-ea-gm\n");
+		return rc;
+	}
+
+	if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+		wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+		wled->loop_auto_gm_en =
+			of_property_read_bool(pdev->dev.of_node,
+					"qcom,loop-auto-gm-en");
+		wled->loop_auto_gm_thresh = QPNP_WLED_LOOP_AUTO_GM_DFLT_THRESH;
+		rc = of_property_read_u8(pdev->dev.of_node,
+				"qcom,loop-auto-gm-thresh",
+				&wled->loop_auto_gm_thresh);
+		if (rc && rc != -EINVAL) {
+			dev_err(&pdev->dev,
+				"Unable to read loop-auto-gm-thresh\n");
+			return rc;
+		}
+	}
+
+	if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+		wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+
+		if (wled->pmic_rev_id->rev4 == PMI8998_V2P0_REV4)
+			wled->lcd_auto_pfm_en = false;
+		else
+			wled->lcd_auto_pfm_en = true;
+
+		wled->lcd_auto_pfm_thresh = QPNP_WLED_LCD_AUTO_PFM_DFLT_THRESH;
+		rc = of_property_read_u8(pdev->dev.of_node,
+				"qcom,lcd-auto-pfm-thresh",
+				&wled->lcd_auto_pfm_thresh);
+		if (rc && rc != -EINVAL) {
+			dev_err(&pdev->dev,
+				"Unable to read lcd-auto-pfm-thresh\n");
+			return rc;
+		}
+
+		if (wled->lcd_auto_pfm_thresh >
+				QPNP_WLED_LCD_AUTO_PFM_THRESH_MAX)
+			wled->lcd_auto_pfm_thresh =
+				QPNP_WLED_LCD_AUTO_PFM_THRESH_MAX;
+	}
+
+	wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_DFLT;
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,sc-deb-cycles", &temp_val);
+	if (!rc) {
+		wled->sc_deb_cycles = temp_val;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read sc debounce cycles\n");
+		return rc;
+	}
+
+	wled->fdbk_op = QPNP_WLED_FDBK_AUTO;
+	rc = of_property_read_string(pdev->dev.of_node,
+			"qcom,fdbk-output", &temp_str);
+	if (!rc) {
+		if (strcmp(temp_str, "wled1") == 0)
+			wled->fdbk_op = QPNP_WLED_FDBK_WLED1;
+		else if (strcmp(temp_str, "wled2") == 0)
+			wled->fdbk_op = QPNP_WLED_FDBK_WLED2;
+		else if (strcmp(temp_str, "wled3") == 0)
+			wled->fdbk_op = QPNP_WLED_FDBK_WLED3;
+		else if (strcmp(temp_str, "wled4") == 0)
+			wled->fdbk_op = QPNP_WLED_FDBK_WLED4;
+		else
+			wled->fdbk_op = QPNP_WLED_FDBK_AUTO;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read feedback output\n");
+		return rc;
+	}
+
+	if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+			wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+		wled->vref_uv = vref_setting_pmi8998.default_uv;
+	else
+		wled->vref_uv = vref_setting_pmi8994.default_uv;
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,vref-uv", &temp_val);
+	if (!rc) {
+		wled->vref_uv = temp_val;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read vref\n");
+		return rc;
+	}
+
+	wled->switch_freq_khz = QPNP_WLED_SWITCH_FREQ_800_KHZ;
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,switch-freq-khz", &temp_val);
+	if (!rc) {
+		wled->switch_freq_khz = temp_val;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read switch freq\n");
+		return rc;
+	}
+
+	if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+		wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+		wled->ovp_mv = 29600;
+	else
+		wled->ovp_mv = 29500;
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,ovp-mv", &temp_val);
+	if (!rc) {
+		wled->ovp_mv = temp_val;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read ovp\n");
+		return rc;
+	}
+
+	if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+		wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+		if (wled->disp_type_amoled)
+			wled->ilim_ma = PMI8998_AMOLED_DFLT_ILIM_MA;
+		else
+			wled->ilim_ma = PMI8998_WLED_DFLT_ILIM_MA;
+	} else {
+		if (wled->disp_type_amoled)
+			wled->ilim_ma = PMI8994_AMOLED_DFLT_ILIM_MA;
+		else
+			wled->ilim_ma = PMI8994_WLED_DFLT_ILIM_MA;
+	}
+
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,ilim-ma", &temp_val);
+	if (!rc) {
+		wled->ilim_ma = temp_val;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read ilim\n");
+		return rc;
+	}
+
+	wled->boost_duty_ns = QPNP_WLED_DEF_BOOST_DUTY_NS;
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,boost-duty-ns", &temp_val);
+	if (!rc) {
+		wled->boost_duty_ns = temp_val;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read boost duty\n");
+		return rc;
+	}
+
+	wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ;
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,mod-freq-khz", &temp_val);
+	if (!rc) {
+		wled->mod_freq_khz = temp_val;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read modulation freq\n");
+		return rc;
+	}
+
+	wled->dim_mode = QPNP_WLED_DIM_HYBRID;
+	rc = of_property_read_string(pdev->dev.of_node,
+			"qcom,dim-mode", &temp_str);
+	if (!rc) {
+		if (strcmp(temp_str, "analog") == 0)
+			wled->dim_mode = QPNP_WLED_DIM_ANALOG;
+		else if (strcmp(temp_str, "digital") == 0)
+			wled->dim_mode = QPNP_WLED_DIM_DIGITAL;
+		else
+			wled->dim_mode = QPNP_WLED_DIM_HYBRID;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read dim mode\n");
+		return rc;
+	}
+
+	if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) {
+		wled->hyb_thres = QPNP_WLED_DEF_HYB_THRES;
+		rc = of_property_read_u32(pdev->dev.of_node,
+				"qcom,hyb-thres", &temp_val);
+		if (!rc) {
+			wled->hyb_thres = temp_val;
+		} else if (rc != -EINVAL) {
+			dev_err(&pdev->dev, "Unable to read hyb threshold\n");
+			return rc;
+		}
+	}
+
+	wled->sync_dly_us = QPNP_WLED_DEF_SYNC_DLY_US;
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,sync-dly-us", &temp_val);
+	if (!rc) {
+		wled->sync_dly_us = temp_val;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read sync delay\n");
+		return rc;
+	}
+
+	wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA;
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,fs-curr-ua", &temp_val);
+	if (!rc) {
+		wled->fs_curr_ua = temp_val;
+	} else if (rc != -EINVAL) {
+		dev_err(&pdev->dev, "Unable to read full scale current\n");
+		return rc;
+	}
+
+	wled->cons_sync_write_delay_us = 0;
+	rc = of_property_read_u32(pdev->dev.of_node,
+			"qcom,cons-sync-write-delay-us", &temp_val);
+	if (!rc)
+		wled->cons_sync_write_delay_us = temp_val;
+
+	wled->en_9b_dim_res = of_property_read_bool(pdev->dev.of_node,
+			"qcom,en-9b-dim-res");
+	wled->en_phase_stag = of_property_read_bool(pdev->dev.of_node,
+			"qcom,en-phase-stag");
+	wled->en_cabc = of_property_read_bool(pdev->dev.of_node,
+			"qcom,en-cabc");
+
+	prop = of_find_property(pdev->dev.of_node,
+			"qcom,led-strings-list", &temp_val);
+	if (!prop || !temp_val || temp_val > QPNP_WLED_MAX_STRINGS) {
+		dev_err(&pdev->dev, "Invalid strings info, use default");
+		wled->num_strings = QPNP_WLED_MAX_STRINGS;
+		for (i = 0; i < wled->num_strings; i++)
+			wled->strings[i] = i;
+	} else {
+		wled->num_strings = temp_val;
+		strings = prop->value;
+		for (i = 0; i < wled->num_strings; ++i)
+			wled->strings[i] = strings[i];
+	}
+
+	wled->ovp_irq = platform_get_irq_byname(pdev, "ovp-irq");
+	if (wled->ovp_irq < 0)
+		dev_dbg(&pdev->dev, "ovp irq is not used\n");
+
+	wled->sc_irq = platform_get_irq_byname(pdev, "sc-irq");
+	if (wled->sc_irq < 0)
+		dev_dbg(&pdev->dev, "sc irq is not used\n");
+
+	wled->en_ext_pfet_sc_pro = of_property_read_bool(pdev->dev.of_node,
+					"qcom,en-ext-pfet-sc-pro");
+
+	return 0;
+}
+
+static int qpnp_wled_probe(struct platform_device *pdev)
+{
+	struct qpnp_wled *wled;
+	struct device_node *revid_node;
+	int rc = 0, i;
+	const __be32 *prop;
+
+	wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
+	if (!wled)
+		return -ENOMEM;
+		wled->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+		if (!wled->regmap) {
+			dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+			return -EINVAL;
+		}
+
+	wled->pdev = pdev;
+
+	revid_node = of_parse_phandle(pdev->dev.of_node, "qcom,pmic-revid", 0);
+	if (!revid_node) {
+		pr_err("Missing qcom,pmic-revid property - driver failed\n");
+		return -EINVAL;
+	}
+
+	wled->pmic_rev_id = get_revid_data(revid_node);
+	if (IS_ERR_OR_NULL(wled->pmic_rev_id)) {
+		pr_err("Unable to get pmic_revid rc=%ld\n",
+			PTR_ERR(wled->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;
+	}
+
+	pr_debug("PMIC subtype %d Digital major %d\n",
+		wled->pmic_rev_id->pmic_subtype, wled->pmic_rev_id->rev4);
+
+	prop = of_get_address_by_name(pdev->dev.of_node, QPNP_WLED_SINK_BASE,
+			0, 0);
+	if (!prop) {
+		dev_err(&pdev->dev, "Couldnt find sink's addr rc %d\n", rc);
+		return rc;
+	}
+	wled->sink_base = be32_to_cpu(*prop);
+
+	prop = of_get_address_by_name(pdev->dev.of_node, QPNP_WLED_CTRL_BASE,
+			0, 0);
+	if (!prop) {
+		dev_err(&pdev->dev, "Couldnt find ctrl's addr rc = %d\n", rc);
+		return rc;
+	}
+	wled->ctrl_base = be32_to_cpu(*prop);
+
+	dev_set_drvdata(&pdev->dev, wled);
+
+	rc = qpnp_wled_parse_dt(wled);
+	if (rc) {
+		dev_err(&pdev->dev, "DT parsing failed\n");
+		return rc;
+	}
+
+	mutex_init(&wled->bus_lock);
+	rc = qpnp_wled_config(wled);
+	if (rc) {
+		dev_err(&pdev->dev, "wled config failed\n");
+		return rc;
+	}
+
+	mutex_init(&wled->lock);
+	INIT_WORK(&wled->work, qpnp_wled_work);
+	wled->ramp_ms = QPNP_WLED_RAMP_DLY_MS;
+	wled->ramp_step = 1;
+
+	wled->cdev.brightness_set = qpnp_wled_set;
+	wled->cdev.brightness_get = qpnp_wled_get;
+
+	wled->cdev.max_brightness = WLED_MAX_LEVEL_4095;
+
+	rc = led_classdev_register(&pdev->dev, &wled->cdev);
+	if (rc) {
+		dev_err(&pdev->dev, "wled registration failed(%d)\n", rc);
+		goto wled_register_fail;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(qpnp_wled_attrs); i++) {
+		rc = sysfs_create_file(&wled->cdev.dev->kobj,
+				&qpnp_wled_attrs[i].attr);
+		if (rc < 0) {
+			dev_err(&pdev->dev, "sysfs creation failed\n");
+			goto sysfs_fail;
+		}
+	}
+
+	return 0;
+
+sysfs_fail:
+	for (i--; i >= 0; i--)
+		sysfs_remove_file(&wled->cdev.dev->kobj,
+				&qpnp_wled_attrs[i].attr);
+	led_classdev_unregister(&wled->cdev);
+wled_register_fail:
+	cancel_work_sync(&wled->work);
+	mutex_destroy(&wled->lock);
+	return rc;
+}
+
+static int qpnp_wled_remove(struct platform_device *pdev)
+{
+	struct qpnp_wled *wled = dev_get_drvdata(&pdev->dev);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(qpnp_wled_attrs); i++)
+		sysfs_remove_file(&wled->cdev.dev->kobj,
+				&qpnp_wled_attrs[i].attr);
+
+	led_classdev_unregister(&wled->cdev);
+	cancel_work_sync(&wled->work);
+	mutex_destroy(&wled->lock);
+
+	return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+	{ .compatible = "qcom,qpnp-wled",},
+	{ },
+};
+
+static struct platform_driver qpnp_wled_driver = {
+	.driver		= {
+		.name		= "qcom,qpnp-wled",
+		.of_match_table	= spmi_match_table,
+	},
+	.probe		= qpnp_wled_probe,
+	.remove		= qpnp_wled_remove,
+};
+
+static int __init qpnp_wled_init(void)
+{
+	return platform_driver_register(&qpnp_wled_driver);
+}
+module_init(qpnp_wled_init);
+
+static void __exit qpnp_wled_exit(void)
+{
+	platform_driver_unregister(&qpnp_wled_driver);
+}
+module_exit(qpnp_wled_exit);
+
+MODULE_DESCRIPTION("QPNP WLED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("leds:leds-qpnp-wled");
diff --git a/drivers/leds/leds-qpnp.c b/drivers/leds/leds-qpnp.c
new file mode 100644
index 0000000..85a6be8
--- /dev/null
+++ b/drivers/leds/leds-qpnp.c
@@ -0,0 +1,4188 @@
+/* Copyright (c) 2012-2015, 2017, 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/kernel.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/of_platform.h>
+#include <linux/of_device.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/qpnp/pwm.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+
+#define WLED_MOD_EN_REG(base, n)	(base + 0x60 + n*0x10)
+#define WLED_IDAC_DLY_REG(base, n)	(WLED_MOD_EN_REG(base, n) + 0x01)
+#define WLED_FULL_SCALE_REG(base, n)	(WLED_IDAC_DLY_REG(base, n) + 0x01)
+#define WLED_MOD_SRC_SEL_REG(base, n)	(WLED_FULL_SCALE_REG(base, n) + 0x01)
+
+/* wled control registers */
+#define WLED_OVP_INT_STATUS(base)		(base + 0x10)
+#define WLED_BRIGHTNESS_CNTL_LSB(base, n)	(base + 0x40 + 2*n)
+#define WLED_BRIGHTNESS_CNTL_MSB(base, n)	(base + 0x41 + 2*n)
+#define WLED_MOD_CTRL_REG(base)			(base + 0x46)
+#define WLED_SYNC_REG(base)			(base + 0x47)
+#define WLED_FDBCK_CTRL_REG(base)		(base + 0x48)
+#define WLED_SWITCHING_FREQ_REG(base)		(base + 0x4C)
+#define WLED_OVP_CFG_REG(base)			(base + 0x4D)
+#define WLED_BOOST_LIMIT_REG(base)		(base + 0x4E)
+#define WLED_CURR_SINK_REG(base)		(base + 0x4F)
+#define WLED_HIGH_POLE_CAP_REG(base)		(base + 0x58)
+#define WLED_CURR_SINK_MASK		0xE0
+#define WLED_CURR_SINK_SHFT		0x05
+#define WLED_DISABLE_ALL_SINKS		0x00
+#define WLED_DISABLE_1_2_SINKS		0x80
+#define WLED_SWITCH_FREQ_MASK		0x0F
+#define WLED_OVP_VAL_MASK		0x03
+#define WLED_OVP_INT_MASK		0x02
+#define WLED_OVP_VAL_BIT_SHFT		0x00
+#define WLED_BOOST_LIMIT_MASK		0x07
+#define WLED_BOOST_LIMIT_BIT_SHFT	0x00
+#define WLED_BOOST_ON			0x80
+#define WLED_BOOST_OFF			0x00
+#define WLED_EN_MASK			0x80
+#define WLED_NO_MASK			0x00
+#define WLED_CP_SELECT_MAX		0x03
+#define WLED_CP_SELECT_MASK		0x02
+#define WLED_USE_EXT_GEN_MOD_SRC	0x01
+#define WLED_CTL_DLY_STEP		200
+#define WLED_CTL_DLY_MAX		1400
+#define WLED_MAX_CURR			25
+#define WLED_NO_CURRENT			0x00
+#define WLED_OVP_DELAY			1000
+#define WLED_OVP_DELAY_INT		200
+#define WLED_OVP_DELAY_LOOP		100
+#define WLED_MSB_MASK			0x0F
+#define WLED_MAX_CURR_MASK		0x1F
+#define WLED_OP_FDBCK_MASK		0x07
+#define WLED_OP_FDBCK_BIT_SHFT		0x00
+#define WLED_OP_FDBCK_DEFAULT		0x00
+
+#define WLED_SET_ILIM_CODE		0x01
+
+#define WLED_MAX_LEVEL			4095
+#define WLED_8_BIT_MASK			0xFF
+#define WLED_4_BIT_MASK			0x0F
+#define WLED_8_BIT_SHFT			0x08
+#define WLED_MAX_DUTY_CYCLE		0xFFF
+
+#define WLED_SYNC_VAL			0x07
+#define WLED_SYNC_RESET_VAL		0x00
+
+#define PMIC_VER_8026			0x04
+#define PMIC_VER_8941			0x01
+#define PMIC_VERSION_REG		0x0105
+
+#define WLED_DEFAULT_STRINGS		0x01
+#define WLED_THREE_STRINGS		0x03
+#define WLED_MAX_TRIES			5
+#define WLED_DEFAULT_OVP_VAL		0x02
+#define WLED_BOOST_LIM_DEFAULT		0x03
+#define WLED_CP_SEL_DEFAULT		0x00
+#define WLED_CTRL_DLY_DEFAULT		0x00
+#define WLED_SWITCH_FREQ_DEFAULT	0x0B
+
+#define FLASH_SAFETY_TIMER(base)	(base + 0x40)
+#define FLASH_MAX_CURR(base)		(base + 0x41)
+#define FLASH_LED_0_CURR(base)		(base + 0x42)
+#define FLASH_LED_1_CURR(base)		(base + 0x43)
+#define FLASH_CLAMP_CURR(base)		(base + 0x44)
+#define FLASH_LED_TMR_CTRL(base)	(base + 0x48)
+#define FLASH_HEADROOM(base)		(base + 0x4A)
+#define FLASH_STARTUP_DELAY(base)	(base + 0x4B)
+#define FLASH_MASK_ENABLE(base)		(base + 0x4C)
+#define FLASH_VREG_OK_FORCE(base)	(base + 0x4F)
+#define FLASH_ENABLE_CONTROL(base)	(base + 0x46)
+#define FLASH_LED_STROBE_CTRL(base)	(base + 0x47)
+#define FLASH_WATCHDOG_TMR(base)	(base + 0x49)
+#define FLASH_FAULT_DETECT(base)	(base + 0x51)
+#define FLASH_PERIPHERAL_SUBTYPE(base)	(base + 0x05)
+#define FLASH_CURRENT_RAMP(base)	(base + 0x54)
+
+#define FLASH_MAX_LEVEL			0x4F
+#define TORCH_MAX_LEVEL			0x0F
+#define	FLASH_NO_MASK			0x00
+
+#define FLASH_MASK_1			0x20
+#define FLASH_MASK_REG_MASK		0xE0
+#define FLASH_HEADROOM_MASK		0x03
+#define FLASH_SAFETY_TIMER_MASK		0x7F
+#define FLASH_CURRENT_MASK		0xFF
+#define FLASH_MAX_CURRENT_MASK		0x7F
+#define FLASH_TMR_MASK			0x03
+#define FLASH_TMR_WATCHDOG		0x03
+#define FLASH_TMR_SAFETY		0x00
+#define FLASH_FAULT_DETECT_MASK		0X80
+#define FLASH_HW_VREG_OK		0x40
+#define FLASH_SW_VREG_OK                0x80
+#define FLASH_VREG_MASK			0xC0
+#define FLASH_STARTUP_DLY_MASK		0x02
+#define FLASH_CURRENT_RAMP_MASK		0xBF
+
+#define FLASH_ENABLE_ALL		0xE0
+#define FLASH_ENABLE_MODULE		0x80
+#define FLASH_ENABLE_MODULE_MASK	0x80
+#define FLASH_DISABLE_ALL		0x00
+#define FLASH_ENABLE_MASK		0xE0
+#define FLASH_ENABLE_LED_0		0xC0
+#define FLASH_ENABLE_LED_1		0xA0
+#define FLASH_INIT_MASK			0xE0
+#define	FLASH_SELFCHECK_ENABLE		0x80
+#define FLASH_WATCHDOG_MASK		0x1F
+#define FLASH_RAMP_STEP_27US		0xBF
+
+#define FLASH_HW_SW_STROBE_SEL_MASK	0x04
+#define FLASH_STROBE_MASK		0xC7
+#define FLASH_LED_0_OUTPUT		0x80
+#define FLASH_LED_1_OUTPUT		0x40
+#define FLASH_TORCH_OUTPUT		0xC0
+
+#define FLASH_CURRENT_PRGM_MIN		1
+#define FLASH_CURRENT_PRGM_SHIFT	1
+#define FLASH_CURRENT_MAX		0x4F
+#define FLASH_CURRENT_TORCH		0x07
+
+#define FLASH_DURATION_200ms		0x13
+#define TORCH_DURATION_12s		0x0A
+#define FLASH_CLAMP_200mA		0x0F
+
+#define FLASH_SUBTYPE_DUAL		0x01
+#define FLASH_SUBTYPE_SINGLE		0x02
+
+#define FLASH_RAMP_UP_DELAY_US		1000
+#define FLASH_RAMP_DN_DELAY_US		2160
+
+#define LED_TRIGGER_DEFAULT		"none"
+
+#define RGB_LED_SRC_SEL(base)		(base + 0x45)
+#define RGB_LED_EN_CTL(base)		(base + 0x46)
+#define RGB_LED_ATC_CTL(base)		(base + 0x47)
+
+#define RGB_MAX_LEVEL			LED_FULL
+#define RGB_LED_ENABLE_RED		0x80
+#define RGB_LED_ENABLE_GREEN		0x40
+#define RGB_LED_ENABLE_BLUE		0x20
+#define RGB_LED_SOURCE_VPH_PWR		0x01
+#define RGB_LED_ENABLE_MASK		0xE0
+#define RGB_LED_SRC_MASK		0x03
+#define QPNP_LED_PWM_FLAGS	(PM_PWM_LUT_LOOP | PM_PWM_LUT_RAMP_UP)
+#define QPNP_LUT_RAMP_STEP_DEFAULT	255
+#define	PWM_LUT_MAX_SIZE		63
+#define	PWM_GPLED_LUT_MAX_SIZE		31
+#define RGB_LED_DISABLE			0x00
+
+#define MPP_MAX_LEVEL			LED_FULL
+#define LED_MPP_MODE_CTRL(base)		(base + 0x40)
+#define LED_MPP_VIN_CTRL(base)		(base + 0x41)
+#define LED_MPP_EN_CTRL(base)		(base + 0x46)
+#define LED_MPP_SINK_CTRL(base)		(base + 0x4C)
+
+#define LED_MPP_CURRENT_MIN		5
+#define LED_MPP_CURRENT_MAX		40
+#define LED_MPP_VIN_CTRL_DEFAULT	0
+#define LED_MPP_CURRENT_PER_SETTING	5
+#define LED_MPP_SOURCE_SEL_DEFAULT	LED_MPP_MODE_ENABLE
+
+#define LED_MPP_SINK_MASK		0x07
+#define LED_MPP_MODE_MASK		0x7F
+#define LED_MPP_VIN_MASK		0x03
+#define LED_MPP_EN_MASK			0x80
+#define LED_MPP_SRC_MASK		0x0F
+#define LED_MPP_MODE_CTRL_MASK		0x70
+
+#define LED_MPP_MODE_SINK		(0x06 << 4)
+#define LED_MPP_MODE_ENABLE		0x01
+#define LED_MPP_MODE_OUTPUT		0x10
+#define LED_MPP_MODE_DISABLE		0x00
+#define LED_MPP_EN_ENABLE		0x80
+#define LED_MPP_EN_DISABLE		0x00
+
+#define MPP_SOURCE_DTEST1		0x08
+
+#define GPIO_MAX_LEVEL			LED_FULL
+#define LED_GPIO_MODE_CTRL(base)	(base + 0x40)
+#define LED_GPIO_VIN_CTRL(base)		(base + 0x41)
+#define LED_GPIO_EN_CTRL(base)		(base + 0x46)
+
+#define LED_GPIO_VIN_CTRL_DEFAULT	0
+#define LED_GPIO_SOURCE_SEL_DEFAULT	LED_GPIO_MODE_ENABLE
+
+#define LED_GPIO_MODE_MASK		0x3F
+#define LED_GPIO_VIN_MASK		0x0F
+#define LED_GPIO_EN_MASK		0x80
+#define LED_GPIO_SRC_MASK		0x0F
+#define LED_GPIO_MODE_CTRL_MASK		0x30
+
+#define LED_GPIO_MODE_ENABLE	0x01
+#define LED_GPIO_MODE_DISABLE	0x00
+#define LED_GPIO_MODE_OUTPUT		0x10
+#define LED_GPIO_EN_ENABLE		0x80
+#define LED_GPIO_EN_DISABLE		0x00
+
+#define KPDBL_MAX_LEVEL			LED_FULL
+#define KPDBL_ROW_SRC_SEL(base)		(base + 0x40)
+#define KPDBL_ENABLE(base)		(base + 0x46)
+#define KPDBL_ROW_SRC(base)		(base + 0xE5)
+
+#define KPDBL_ROW_SRC_SEL_VAL_MASK	0x0F
+#define KPDBL_ROW_SCAN_EN_MASK		0x80
+#define KPDBL_ROW_SCAN_VAL_MASK		0x0F
+#define KPDBL_ROW_SCAN_EN_SHIFT		7
+#define KPDBL_MODULE_EN			0x80
+#define KPDBL_MODULE_DIS		0x00
+#define KPDBL_MODULE_EN_MASK		0x80
+#define NUM_KPDBL_LEDS			4
+#define KPDBL_MASTER_BIT_INDEX		0
+
+/**
+ * enum qpnp_leds - QPNP supported led ids
+ * @QPNP_ID_WLED - White led backlight
+ */
+enum qpnp_leds {
+	QPNP_ID_WLED = 0,
+	QPNP_ID_FLASH1_LED0,
+	QPNP_ID_FLASH1_LED1,
+	QPNP_ID_RGB_RED,
+	QPNP_ID_RGB_GREEN,
+	QPNP_ID_RGB_BLUE,
+	QPNP_ID_LED_MPP,
+	QPNP_ID_KPDBL,
+	QPNP_ID_LED_GPIO,
+	QPNP_ID_MAX,
+};
+
+/* current boost limit */
+enum wled_current_boost_limit {
+	WLED_CURR_LIMIT_105mA,
+	WLED_CURR_LIMIT_385mA,
+	WLED_CURR_LIMIT_525mA,
+	WLED_CURR_LIMIT_805mA,
+	WLED_CURR_LIMIT_980mA,
+	WLED_CURR_LIMIT_1260mA,
+	WLED_CURR_LIMIT_1400mA,
+	WLED_CURR_LIMIT_1680mA,
+};
+
+/* over voltage protection threshold */
+enum wled_ovp_threshold {
+	WLED_OVP_35V,
+	WLED_OVP_32V,
+	WLED_OVP_29V,
+	WLED_OVP_27V,
+};
+
+enum flash_headroom {
+	HEADROOM_250mV = 0,
+	HEADROOM_300mV,
+	HEADROOM_400mV,
+	HEADROOM_500mV,
+};
+
+enum flash_startup_dly {
+	DELAY_10us = 0,
+	DELAY_32us,
+	DELAY_64us,
+	DELAY_128us,
+};
+
+enum led_mode {
+	PWM_MODE = 0,
+	LPG_MODE,
+	MANUAL_MODE,
+};
+
+static u8 wled_debug_regs[] = {
+	/* brightness registers */
+	0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
+	/* common registers */
+	0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+	/* LED1 */
+	0x60, 0x61, 0x62, 0x63, 0x66,
+	/* LED2 */
+	0x70, 0x71, 0x72, 0x73, 0x76,
+	/* LED3 */
+	0x80, 0x81, 0x82, 0x83, 0x86,
+};
+
+static u8 flash_debug_regs[] = {
+	0x40, 0x41, 0x42, 0x43, 0x44, 0x48, 0x49, 0x4b, 0x4c,
+	0x4f, 0x46, 0x47,
+};
+
+static u8 rgb_pwm_debug_regs[] = {
+	0x45, 0x46, 0x47,
+};
+
+static u8 mpp_debug_regs[] = {
+	0x40, 0x41, 0x42, 0x45, 0x46, 0x4c,
+};
+
+static u8 kpdbl_debug_regs[] = {
+	0x40, 0x46, 0xb1, 0xb3, 0xb4, 0xe5,
+};
+
+static u8 gpio_debug_regs[] = {
+	0x40, 0x41, 0x42, 0x45, 0x46,
+};
+
+/**
+ *  pwm_config_data - pwm configuration data
+ *  @lut_params - lut parameters to be used by pwm driver
+ *  @pwm_device - pwm device
+ *  @pwm_period_us - period for pwm, in us
+ *  @mode - mode the led operates in
+ *  @old_duty_pcts - storage for duty pcts that may need to be reused
+ *  @default_mode - default mode of LED as set in device tree
+ *  @use_blink - use blink sysfs entry
+ *  @blinking - device is currently blinking w/LPG mode
+ */
+struct pwm_config_data {
+	struct lut_params	lut_params;
+	struct pwm_device	*pwm_dev;
+	u32			pwm_period_us;
+	struct pwm_duty_cycles	*duty_cycles;
+	int	*old_duty_pcts;
+	u8	mode;
+	u8	default_mode;
+	bool	pwm_enabled;
+	bool use_blink;
+	bool blinking;
+};
+
+/**
+ *  wled_config_data - wled configuration data
+ *  @num_strings - number of wled strings to be configured
+ *  @num_physical_strings - physical number of strings supported
+ *  @ovp_val - over voltage protection threshold
+ *  @boost_curr_lim - boot current limit
+ *  @cp_select - high pole capacitance
+ *  @ctrl_delay_us - delay in activation of led
+ *  @dig_mod_gen_en - digital module generator
+ *  @cs_out_en - current sink output enable
+ *  @op_fdbck - selection of output as feedback for the boost
+ */
+struct wled_config_data {
+	u8	num_strings;
+	u8	num_physical_strings;
+	u8	ovp_val;
+	u8	boost_curr_lim;
+	u8	cp_select;
+	u8	ctrl_delay_us;
+	u8	switch_freq;
+	u8	op_fdbck;
+	u8	pmic_version;
+	bool	dig_mod_gen_en;
+	bool	cs_out_en;
+};
+
+/**
+ *  mpp_config_data - mpp configuration data
+ *  @pwm_cfg - device pwm configuration
+ *  @current_setting - current setting, 5ma-40ma in 5ma increments
+ *  @source_sel - source selection
+ *  @mode_ctrl - mode control
+ *  @vin_ctrl - input control
+ *  @min_brightness - minimum brightness supported
+ *  @pwm_mode - pwm mode in use
+ *  @max_uV - maximum regulator voltage
+ *  @min_uV - minimum regulator voltage
+ *  @mpp_reg - regulator to power mpp based LED
+ *  @enable - flag indicating LED on or off
+ */
+struct mpp_config_data {
+	struct pwm_config_data	*pwm_cfg;
+	u8	current_setting;
+	u8	source_sel;
+	u8	mode_ctrl;
+	u8	vin_ctrl;
+	u8	min_brightness;
+	u8 pwm_mode;
+	u32	max_uV;
+	u32	min_uV;
+	struct regulator *mpp_reg;
+	bool	enable;
+};
+
+/**
+ *  flash_config_data - flash configuration data
+ *  @current_prgm - current to be programmed, scaled by max level
+ *  @clamp_curr - clamp current to use
+ *  @headroom - headroom value to use
+ *  @duration - duration of the flash
+ *  @enable_module - enable address for particular flash
+ *  @trigger_flash - trigger flash
+ *  @startup_dly - startup delay for flash
+ *  @strobe_type - select between sw and hw strobe
+ *  @peripheral_subtype - module peripheral subtype
+ *  @current_addr - address to write for current
+ *  @second_addr - address of secondary flash to be written
+ *  @safety_timer - enable safety timer or watchdog timer
+ *  @torch_enable - enable flash LED torch mode
+ *  @flash_reg_get - flash regulator attached or not
+ *  @flash_wa_reg_get - workaround regulator attached or not
+ *  @flash_on - flash status, on or off
+ *  @torch_on - torch status, on or off
+ *  @vreg_ok - specifies strobe type, sw or hw
+ *  @no_smbb_support - specifies if smbb boost is not required and there is a
+    single regulator for both flash and torch
+ *  @flash_boost_reg - boost regulator for flash
+ *  @torch_boost_reg - boost regulator for torch
+ *  @flash_wa_reg - flash regulator for wa
+ */
+struct flash_config_data {
+	u8	current_prgm;
+	u8	clamp_curr;
+	u8	headroom;
+	u8	duration;
+	u8	enable_module;
+	u8	trigger_flash;
+	u8	startup_dly;
+	u8	strobe_type;
+	u8	peripheral_subtype;
+	u16	current_addr;
+	u16	second_addr;
+	bool	safety_timer;
+	bool	torch_enable;
+	bool	flash_reg_get;
+	bool    flash_wa_reg_get;
+	bool	flash_on;
+	bool	torch_on;
+	bool	vreg_ok;
+	bool    no_smbb_support;
+	struct regulator *flash_boost_reg;
+	struct regulator *torch_boost_reg;
+	struct regulator *flash_wa_reg;
+};
+
+/**
+ *  kpdbl_config_data - kpdbl configuration data
+ *  @pwm_cfg - device pwm configuration
+ *  @mode - running mode: pwm or lut
+ *  @row_id - row id of the led
+ *  @row_src_vbst - 0 for vph_pwr and 1 for vbst
+ *  @row_src_en - enable row source
+ *  @always_on - always on row
+ *  @lut_params - lut parameters to be used by pwm driver
+ *  @duty_cycles - duty cycles for lut
+ *  @pwm_mode - pwm mode in use
+ */
+struct kpdbl_config_data {
+	struct pwm_config_data	*pwm_cfg;
+	u32	row_id;
+	bool	row_src_vbst;
+	bool	row_src_en;
+	bool	always_on;
+	struct pwm_duty_cycles  *duty_cycles;
+	struct lut_params	lut_params;
+	u8	pwm_mode;
+};
+
+/**
+ *  rgb_config_data - rgb configuration data
+ *  @pwm_cfg - device pwm configuration
+ *  @enable - bits to enable led
+ */
+struct rgb_config_data {
+	struct pwm_config_data	*pwm_cfg;
+	u8	enable;
+};
+
+/**
+ *  gpio_config_data - gpio configuration data
+ *  @source_sel - source selection
+ *  @mode_ctrl - mode control
+ *  @vin_ctrl - input control
+ *  @enable - flag indicating LED on or off
+ */
+struct gpio_config_data {
+	u8	source_sel;
+	u8	mode_ctrl;
+	u8	vin_ctrl;
+	bool	enable;
+};
+
+/**
+ * struct qpnp_led_data - internal led data structure
+ * @led_classdev - led class device
+ * @delayed_work - delayed work for turning off the LED
+ * @workqueue - dedicated workqueue to handle concurrency
+ * @work - workqueue for led
+ * @id - led index
+ * @base_reg - base register given in device tree
+ * @lock - to protect the transactions
+ * @reg - cached value of led register
+ * @num_leds - number of leds in the module
+ * @max_current - maximum current supported by LED
+ * @default_on - true: default state max, false, default state 0
+ * @turn_off_delay_ms - number of msec before turning off the LED
+ */
+struct qpnp_led_data {
+	struct led_classdev		cdev;
+	struct platform_device		*pdev;
+	struct regmap			*regmap;
+	struct delayed_work		dwork;
+	struct workqueue_struct		*workqueue;
+	struct work_struct		work;
+	int				id;
+	u16				base;
+	u8				reg;
+	u8				num_leds;
+	struct mutex			lock;
+	struct wled_config_data		*wled_cfg;
+	struct flash_config_data	*flash_cfg;
+	struct kpdbl_config_data	*kpdbl_cfg;
+	struct rgb_config_data		*rgb_cfg;
+	struct mpp_config_data		*mpp_cfg;
+	struct gpio_config_data		*gpio_cfg;
+	int				max_current;
+	bool				default_on;
+	bool				in_order_command_processing;
+	int				turn_off_delay_ms;
+};
+
+static DEFINE_MUTEX(flash_lock);
+static struct pwm_device *kpdbl_master;
+static u32 kpdbl_master_period_us;
+DECLARE_BITMAP(kpdbl_leds_in_use, NUM_KPDBL_LEDS);
+static bool is_kpdbl_master_turn_on;
+
+static int
+qpnp_led_masked_write(struct qpnp_led_data *led, u16 addr, u8 mask, u8 val)
+{
+	int rc;
+
+	rc = regmap_update_bits(led->regmap, addr, mask, val);
+	if (rc)
+		dev_err(&led->pdev->dev,
+			"Unable to regmap_update_bits to addr=%x, rc(%d)\n",
+			addr, rc);
+	return rc;
+}
+
+static void qpnp_dump_regs(struct qpnp_led_data *led, u8 regs[], u8 array_size)
+{
+	int i;
+	u8 val;
+
+	pr_debug("===== %s LED register dump start =====\n", led->cdev.name);
+	for (i = 0; i < array_size; i++) {
+		regmap_bulk_read(led->regmap, led->base + regs[i], &val,
+				 sizeof(val));
+		pr_debug("%s: 0x%x = 0x%x\n", led->cdev.name,
+					led->base + regs[i], val);
+	}
+	pr_debug("===== %s LED register dump end =====\n", led->cdev.name);
+}
+
+static int qpnp_wled_sync(struct qpnp_led_data *led)
+{
+	int rc;
+	u8 val;
+
+	/* sync */
+	val = WLED_SYNC_VAL;
+	rc = regmap_write(led->regmap, WLED_SYNC_REG(led->base), val);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+				"WLED set sync reg failed(%d)\n", rc);
+		return rc;
+	}
+
+	val = WLED_SYNC_RESET_VAL;
+	rc = regmap_write(led->regmap, WLED_SYNC_REG(led->base), val);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+				"WLED reset sync reg failed(%d)\n", rc);
+		return rc;
+	}
+	return 0;
+}
+
+static int qpnp_wled_set(struct qpnp_led_data *led)
+{
+	int rc, duty, level, tries = 0;
+	u8 val, i, num_wled_strings;
+	uint sink_val, ilim_val, ovp_val;
+
+	num_wled_strings = led->wled_cfg->num_strings;
+
+	level = led->cdev.brightness;
+
+	if (level > WLED_MAX_LEVEL)
+		level = WLED_MAX_LEVEL;
+	if (level == 0) {
+		for (i = 0; i < num_wled_strings; i++) {
+			rc = qpnp_led_masked_write(led,
+				WLED_FULL_SCALE_REG(led->base, i),
+				WLED_MAX_CURR_MASK, WLED_NO_CURRENT);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Write max current failure (%d)\n",
+					rc);
+				return rc;
+			}
+		}
+
+		rc = qpnp_wled_sync(led);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED sync failed(%d)\n", rc);
+			return rc;
+		}
+
+		rc = regmap_read(led->regmap, WLED_CURR_SINK_REG(led->base),
+				 &sink_val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED read sink reg failed(%d)\n", rc);
+			return rc;
+		}
+
+		if (led->wled_cfg->pmic_version == PMIC_VER_8026) {
+			val = WLED_DISABLE_ALL_SINKS;
+			rc = regmap_write(led->regmap,
+					  WLED_CURR_SINK_REG(led->base), val);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"WLED write sink reg failed(%d)\n", rc);
+				return rc;
+			}
+
+			usleep_range(WLED_OVP_DELAY, WLED_OVP_DELAY + 10);
+		} else if (led->wled_cfg->pmic_version == PMIC_VER_8941) {
+			if (led->wled_cfg->num_physical_strings <=
+					WLED_THREE_STRINGS) {
+				val = WLED_DISABLE_1_2_SINKS;
+				rc = regmap_write(led->regmap,
+						  WLED_CURR_SINK_REG(led->base),
+						  val);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"WLED write sink reg failed");
+					return rc;
+				}
+
+				rc = regmap_read(led->regmap,
+					 WLED_BOOST_LIMIT_REG(led->base),
+					 &ilim_val);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"Unable to read boost reg");
+				}
+				val = WLED_SET_ILIM_CODE;
+				rc = regmap_write(led->regmap,
+					  WLED_BOOST_LIMIT_REG(led->base),
+					  val);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"WLED write sink reg failed");
+					return rc;
+				}
+				usleep_range(WLED_OVP_DELAY,
+					     WLED_OVP_DELAY + 10);
+			} else {
+				val = WLED_DISABLE_ALL_SINKS;
+				rc = regmap_write(led->regmap,
+						  WLED_CURR_SINK_REG(led->base),
+						  val);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"WLED write sink reg failed");
+					return rc;
+				}
+
+				msleep(WLED_OVP_DELAY_INT);
+				while (tries < WLED_MAX_TRIES) {
+					rc = regmap_read(led->regmap,
+						 WLED_OVP_INT_STATUS(led->base),
+						 &ovp_val);
+					if (rc) {
+						dev_err(&led->pdev->dev,
+						"Unable to read boost reg");
+					}
+
+					if (ovp_val & WLED_OVP_INT_MASK)
+						break;
+
+					msleep(WLED_OVP_DELAY_LOOP);
+					tries++;
+				}
+				usleep_range(WLED_OVP_DELAY,
+					     WLED_OVP_DELAY + 10);
+			}
+		}
+
+		val = WLED_BOOST_OFF;
+		rc = regmap_write(led->regmap, WLED_MOD_CTRL_REG(led->base),
+				  val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED write ctrl reg failed(%d)\n", rc);
+			return rc;
+		}
+
+		for (i = 0; i < num_wled_strings; i++) {
+			rc = qpnp_led_masked_write(led,
+				WLED_FULL_SCALE_REG(led->base, i),
+				WLED_MAX_CURR_MASK, (u8)led->max_current);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Write max current failure (%d)\n",
+					rc);
+				return rc;
+			}
+		}
+
+		rc = qpnp_wled_sync(led);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED sync failed(%d)\n", rc);
+			return rc;
+		}
+
+		if (led->wled_cfg->pmic_version == PMIC_VER_8941) {
+			if (led->wled_cfg->num_physical_strings <=
+					WLED_THREE_STRINGS) {
+				rc = regmap_write(led->regmap,
+					  WLED_BOOST_LIMIT_REG(led->base),
+					  ilim_val);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"WLED write sink reg failed");
+					return rc;
+				}
+			} else {
+				/* restore OVP to original value */
+				rc = regmap_write(led->regmap,
+						  WLED_OVP_CFG_REG(led->base),
+						  *&led->wled_cfg->ovp_val);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"WLED write sink reg failed");
+					return rc;
+				}
+			}
+		}
+
+		/* re-enable all sinks */
+		rc = regmap_write(led->regmap, WLED_CURR_SINK_REG(led->base),
+				  sink_val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED write sink reg failed(%d)\n", rc);
+			return rc;
+		}
+
+	} else {
+		val = WLED_BOOST_ON;
+		rc = regmap_write(led->regmap, WLED_MOD_CTRL_REG(led->base),
+				  val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED write ctrl reg failed(%d)\n", rc);
+			return rc;
+		}
+	}
+
+	duty = (WLED_MAX_DUTY_CYCLE * level) / WLED_MAX_LEVEL;
+
+	/* program brightness control registers */
+	for (i = 0; i < num_wled_strings; i++) {
+		rc = qpnp_led_masked_write(led,
+			WLED_BRIGHTNESS_CNTL_MSB(led->base, i), WLED_MSB_MASK,
+			(duty >> WLED_8_BIT_SHFT) & WLED_4_BIT_MASK);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED set brightness MSB failed(%d)\n", rc);
+			return rc;
+		}
+		val = duty & WLED_8_BIT_MASK;
+		rc = regmap_write(led->regmap,
+				  WLED_BRIGHTNESS_CNTL_LSB(led->base, i), val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED set brightness LSB failed(%d)\n", rc);
+			return rc;
+		}
+	}
+
+	rc = qpnp_wled_sync(led);
+	if (rc) {
+		dev_err(&led->pdev->dev, "WLED sync failed(%d)\n", rc);
+		return rc;
+	}
+	return 0;
+}
+
+static int qpnp_mpp_set(struct qpnp_led_data *led)
+{
+	int rc;
+	u8 val;
+	int duty_us, duty_ns, period_us;
+
+	if (led->cdev.brightness) {
+		if (led->mpp_cfg->mpp_reg && !led->mpp_cfg->enable) {
+			rc = regulator_set_voltage(led->mpp_cfg->mpp_reg,
+					led->mpp_cfg->min_uV,
+					led->mpp_cfg->max_uV);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Regulator voltage set failed rc=%d\n",
+									rc);
+				return rc;
+			}
+
+			rc = regulator_enable(led->mpp_cfg->mpp_reg);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Regulator enable failed(%d)\n", rc);
+				goto err_reg_enable;
+			}
+		}
+
+		led->mpp_cfg->enable = true;
+
+		if (led->cdev.brightness < led->mpp_cfg->min_brightness) {
+			dev_warn(&led->pdev->dev, "brightness is less than supported, set to minimum supported\n");
+			led->cdev.brightness = led->mpp_cfg->min_brightness;
+		}
+
+		if (led->mpp_cfg->pwm_mode != MANUAL_MODE) {
+			if (!led->mpp_cfg->pwm_cfg->blinking) {
+				led->mpp_cfg->pwm_cfg->mode =
+					led->mpp_cfg->pwm_cfg->default_mode;
+				led->mpp_cfg->pwm_mode =
+					led->mpp_cfg->pwm_cfg->default_mode;
+			}
+		}
+		if (led->mpp_cfg->pwm_mode == PWM_MODE) {
+			/*config pwm for brightness scaling*/
+			period_us = led->mpp_cfg->pwm_cfg->pwm_period_us;
+			if (period_us > INT_MAX / NSEC_PER_USEC) {
+				duty_us = (period_us * led->cdev.brightness) /
+					LED_FULL;
+				rc = pwm_config_us(
+					led->mpp_cfg->pwm_cfg->pwm_dev,
+					duty_us,
+					period_us);
+			} else {
+				duty_ns = ((period_us * NSEC_PER_USEC) /
+					LED_FULL) * led->cdev.brightness;
+				rc = pwm_config(
+					led->mpp_cfg->pwm_cfg->pwm_dev,
+					duty_ns,
+					period_us * NSEC_PER_USEC);
+			}
+			if (rc < 0) {
+				dev_err(&led->pdev->dev, "Failed to configure pwm for new values\n");
+				goto err_mpp_reg_write;
+			}
+		}
+
+		if (led->mpp_cfg->pwm_mode != MANUAL_MODE)
+			pwm_enable(led->mpp_cfg->pwm_cfg->pwm_dev);
+		else {
+			if (led->cdev.brightness < LED_MPP_CURRENT_MIN)
+				led->cdev.brightness = LED_MPP_CURRENT_MIN;
+			else {
+				/*
+				 * PMIC supports LED intensity from 5mA - 40mA
+				 * in steps of 5mA. Brightness is rounded to
+				 * 5mA or nearest lower supported values
+				 */
+				led->cdev.brightness /= LED_MPP_CURRENT_MIN;
+				led->cdev.brightness *= LED_MPP_CURRENT_MIN;
+			}
+
+			val = (led->cdev.brightness / LED_MPP_CURRENT_MIN) - 1;
+
+			rc = qpnp_led_masked_write(led,
+					LED_MPP_SINK_CTRL(led->base),
+					LED_MPP_SINK_MASK, val);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Failed to write sink control reg\n");
+				goto err_mpp_reg_write;
+			}
+		}
+
+		val = (led->mpp_cfg->source_sel & LED_MPP_SRC_MASK) |
+			(led->mpp_cfg->mode_ctrl & LED_MPP_MODE_CTRL_MASK);
+
+		rc = qpnp_led_masked_write(led,
+			LED_MPP_MODE_CTRL(led->base), LED_MPP_MODE_MASK,
+			val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"Failed to write led mode reg\n");
+			goto err_mpp_reg_write;
+		}
+
+		rc = qpnp_led_masked_write(led,
+				LED_MPP_EN_CTRL(led->base), LED_MPP_EN_MASK,
+				LED_MPP_EN_ENABLE);
+		if (rc) {
+			dev_err(&led->pdev->dev, "Failed to write led enable reg\n");
+			goto err_mpp_reg_write;
+		}
+	} else {
+		if (led->mpp_cfg->pwm_mode != MANUAL_MODE) {
+			led->mpp_cfg->pwm_cfg->mode =
+				led->mpp_cfg->pwm_cfg->default_mode;
+			led->mpp_cfg->pwm_mode =
+				led->mpp_cfg->pwm_cfg->default_mode;
+			pwm_disable(led->mpp_cfg->pwm_cfg->pwm_dev);
+		}
+		rc = qpnp_led_masked_write(led,
+					LED_MPP_MODE_CTRL(led->base),
+					LED_MPP_MODE_MASK,
+					LED_MPP_MODE_DISABLE);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"Failed to write led mode reg\n");
+			goto err_mpp_reg_write;
+		}
+
+		rc = qpnp_led_masked_write(led,
+					LED_MPP_EN_CTRL(led->base),
+					LED_MPP_EN_MASK,
+					LED_MPP_EN_DISABLE);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"Failed to write led enable reg\n");
+			goto err_mpp_reg_write;
+		}
+
+		if (led->mpp_cfg->mpp_reg && led->mpp_cfg->enable) {
+			rc = regulator_disable(led->mpp_cfg->mpp_reg);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"MPP regulator disable failed(%d)\n",
+					rc);
+				return rc;
+			}
+
+			rc = regulator_set_voltage(led->mpp_cfg->mpp_reg,
+						0, led->mpp_cfg->max_uV);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"MPP regulator voltage set failed(%d)\n",
+					rc);
+				return rc;
+			}
+		}
+
+		led->mpp_cfg->enable = false;
+	}
+
+	if (led->mpp_cfg->pwm_mode != MANUAL_MODE)
+		led->mpp_cfg->pwm_cfg->blinking = false;
+	qpnp_dump_regs(led, mpp_debug_regs, ARRAY_SIZE(mpp_debug_regs));
+
+	return 0;
+
+err_mpp_reg_write:
+	if (led->mpp_cfg->mpp_reg)
+		regulator_disable(led->mpp_cfg->mpp_reg);
+err_reg_enable:
+	if (led->mpp_cfg->mpp_reg)
+		regulator_set_voltage(led->mpp_cfg->mpp_reg, 0,
+							led->mpp_cfg->max_uV);
+	led->mpp_cfg->enable = false;
+
+	return rc;
+}
+
+static int qpnp_gpio_set(struct qpnp_led_data *led)
+{
+	int rc, val;
+
+	if (led->cdev.brightness) {
+		val = (led->gpio_cfg->source_sel & LED_GPIO_SRC_MASK) |
+			(led->gpio_cfg->mode_ctrl & LED_GPIO_MODE_CTRL_MASK);
+
+		rc = qpnp_led_masked_write(led,
+			 LED_GPIO_MODE_CTRL(led->base),
+			 LED_GPIO_MODE_MASK,
+			 val);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"Failed to write led mode reg\n");
+			goto err_gpio_reg_write;
+		}
+
+		rc = qpnp_led_masked_write(led,
+			 LED_GPIO_EN_CTRL(led->base),
+			 LED_GPIO_EN_MASK,
+			 LED_GPIO_EN_ENABLE);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"Failed to write led enable reg\n");
+			goto err_gpio_reg_write;
+		}
+
+		led->gpio_cfg->enable = true;
+	} else {
+		rc = qpnp_led_masked_write(led,
+				LED_GPIO_MODE_CTRL(led->base),
+				LED_GPIO_MODE_MASK,
+				LED_GPIO_MODE_DISABLE);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"Failed to write led mode reg\n");
+			goto err_gpio_reg_write;
+		}
+
+		rc = qpnp_led_masked_write(led,
+				LED_GPIO_EN_CTRL(led->base),
+				LED_GPIO_EN_MASK,
+				LED_GPIO_EN_DISABLE);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+					"Failed to write led enable reg\n");
+			goto err_gpio_reg_write;
+		}
+
+		led->gpio_cfg->enable = false;
+	}
+
+	qpnp_dump_regs(led, gpio_debug_regs, ARRAY_SIZE(gpio_debug_regs));
+
+	return 0;
+
+err_gpio_reg_write:
+	led->gpio_cfg->enable = false;
+
+	return rc;
+}
+
+static int qpnp_flash_regulator_operate(struct qpnp_led_data *led, bool on)
+{
+	int rc, i;
+	struct qpnp_led_data *led_array;
+	bool regulator_on = false;
+
+	led_array = dev_get_drvdata(&led->pdev->dev);
+	if (!led_array) {
+		dev_err(&led->pdev->dev, "Unable to get LED array\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < led->num_leds; i++)
+		regulator_on |= led_array[i].flash_cfg->flash_on;
+
+	if (!on)
+		goto regulator_turn_off;
+
+	if (!regulator_on && !led->flash_cfg->flash_on) {
+		for (i = 0; i < led->num_leds; i++) {
+			if (led_array[i].flash_cfg->flash_reg_get) {
+				if (led_array[i].flash_cfg->flash_wa_reg_get) {
+					rc = regulator_enable(
+						led_array[i].flash_cfg->
+							flash_wa_reg);
+					if (rc) {
+						dev_err(&led->pdev->dev, "Flash wa regulator enable failed(%d)\n",
+							rc);
+						return rc;
+					}
+				}
+
+				rc = regulator_enable(
+				       led_array[i].flash_cfg->flash_boost_reg);
+				if (rc) {
+					if (led_array[i].flash_cfg->
+							flash_wa_reg_get)
+						/*
+						 * Disable flash wa regulator
+						 * when flash boost regulator
+						 * enable fails
+						 */
+						regulator_disable(
+							led_array[i].flash_cfg->
+								flash_wa_reg);
+					dev_err(&led->pdev->dev, "Flash boost regulator enable failed(%d)\n",
+						rc);
+					return rc;
+				}
+				led->flash_cfg->flash_on = true;
+			}
+			break;
+		}
+	}
+
+	return 0;
+
+regulator_turn_off:
+	if (regulator_on && led->flash_cfg->flash_on) {
+		for (i = 0; i < led->num_leds; i++) {
+			if (led_array[i].flash_cfg->flash_reg_get) {
+				rc = qpnp_led_masked_write(led,
+					FLASH_ENABLE_CONTROL(led->base),
+					FLASH_ENABLE_MASK,
+					FLASH_DISABLE_ALL);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"Enable reg write failed(%d)\n",
+						rc);
+				}
+
+				rc = regulator_disable(
+				       led_array[i].flash_cfg->flash_boost_reg);
+				if (rc) {
+					dev_err(&led->pdev->dev, "Flash boost regulator disable failed(%d)\n",
+						rc);
+					return rc;
+				}
+				if (led_array[i].flash_cfg->flash_wa_reg_get) {
+					rc = regulator_disable(
+						led_array[i].flash_cfg->
+							flash_wa_reg);
+					if (rc) {
+						dev_err(&led->pdev->dev, "Flash_wa regulator disable failed(%d)\n",
+							rc);
+						return rc;
+					}
+				}
+				led->flash_cfg->flash_on = false;
+			}
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_torch_regulator_operate(struct qpnp_led_data *led, bool on)
+{
+	int rc;
+
+	if (!on)
+		goto regulator_turn_off;
+
+	if (!led->flash_cfg->torch_on) {
+		rc = regulator_enable(led->flash_cfg->torch_boost_reg);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Regulator enable failed(%d)\n", rc);
+				return rc;
+		}
+		led->flash_cfg->torch_on = true;
+	}
+	return 0;
+
+regulator_turn_off:
+	if (led->flash_cfg->torch_on) {
+		rc = qpnp_led_masked_write(led,	FLASH_ENABLE_CONTROL(led->base),
+				FLASH_ENABLE_MODULE_MASK, FLASH_DISABLE_ALL);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Enable reg write failed(%d)\n", rc);
+		}
+
+		rc = regulator_disable(led->flash_cfg->torch_boost_reg);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Regulator disable failed(%d)\n", rc);
+			return rc;
+		}
+		led->flash_cfg->torch_on = false;
+	}
+	return 0;
+}
+
+static int qpnp_flash_set(struct qpnp_led_data *led)
+{
+	int rc, error;
+	int val = led->cdev.brightness;
+
+	if (led->flash_cfg->torch_enable)
+		led->flash_cfg->current_prgm =
+			(val * TORCH_MAX_LEVEL / led->max_current);
+	else
+		led->flash_cfg->current_prgm =
+			(val * FLASH_MAX_LEVEL / led->max_current);
+
+	/* Set led current */
+	if (val > 0) {
+		if (led->flash_cfg->torch_enable) {
+			if (led->flash_cfg->peripheral_subtype ==
+							FLASH_SUBTYPE_DUAL) {
+				if (!led->flash_cfg->no_smbb_support)
+					rc = qpnp_torch_regulator_operate(led,
+									true);
+				else
+					rc = qpnp_flash_regulator_operate(led,
+									true);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Torch regulator operate failed(%d)\n",
+					rc);
+					return rc;
+				}
+			} else if (led->flash_cfg->peripheral_subtype ==
+							FLASH_SUBTYPE_SINGLE) {
+				rc = qpnp_flash_regulator_operate(led, true);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Flash regulator operate failed(%d)\n",
+					rc);
+					goto error_flash_set;
+				}
+			}
+
+			qpnp_led_masked_write(led, FLASH_MAX_CURR(led->base),
+				FLASH_CURRENT_MASK,
+				TORCH_MAX_LEVEL);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Max current reg write failed(%d)\n",
+					rc);
+				goto error_reg_write;
+			}
+
+			qpnp_led_masked_write(led,
+				FLASH_LED_TMR_CTRL(led->base),
+				FLASH_TMR_MASK,
+				FLASH_TMR_WATCHDOG);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Timer control reg write failed(%d)\n",
+					rc);
+				goto error_reg_write;
+			}
+
+			rc = qpnp_led_masked_write(led,
+				led->flash_cfg->current_addr,
+				FLASH_CURRENT_MASK,
+				led->flash_cfg->current_prgm);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Current reg write failed(%d)\n", rc);
+				goto error_reg_write;
+			}
+
+			rc = qpnp_led_masked_write(led,
+				led->flash_cfg->second_addr,
+				FLASH_CURRENT_MASK,
+				led->flash_cfg->current_prgm);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"2nd Current reg write failed(%d)\n",
+					rc);
+				goto error_reg_write;
+			}
+
+			qpnp_led_masked_write(led,
+				FLASH_WATCHDOG_TMR(led->base),
+				FLASH_WATCHDOG_MASK,
+				led->flash_cfg->duration);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Max current reg write failed(%d)\n",
+					rc);
+				goto error_reg_write;
+			}
+
+			rc = qpnp_led_masked_write(led,
+				FLASH_ENABLE_CONTROL(led->base),
+				FLASH_ENABLE_MASK,
+				led->flash_cfg->enable_module);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Enable reg write failed(%d)\n",
+					rc);
+				goto error_reg_write;
+			}
+
+			if (!led->flash_cfg->strobe_type)
+				led->flash_cfg->trigger_flash &=
+						~FLASH_HW_SW_STROBE_SEL_MASK;
+			else
+				led->flash_cfg->trigger_flash |=
+						FLASH_HW_SW_STROBE_SEL_MASK;
+
+			rc = qpnp_led_masked_write(led,
+				FLASH_LED_STROBE_CTRL(led->base),
+				led->flash_cfg->trigger_flash,
+				led->flash_cfg->trigger_flash);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"LED %d strobe reg write failed(%d)\n",
+					led->id, rc);
+				goto error_reg_write;
+			}
+		} else {
+			rc = qpnp_flash_regulator_operate(led, true);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Flash regulator operate failed(%d)\n",
+					rc);
+				goto error_flash_set;
+			}
+
+			qpnp_led_masked_write(led,
+				FLASH_LED_TMR_CTRL(led->base),
+				FLASH_TMR_MASK,
+				FLASH_TMR_SAFETY);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Timer control reg write failed(%d)\n",
+					rc);
+				goto error_reg_write;
+			}
+
+			/* Set flash safety timer */
+			rc = qpnp_led_masked_write(led,
+				FLASH_SAFETY_TIMER(led->base),
+				FLASH_SAFETY_TIMER_MASK,
+				led->flash_cfg->duration);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Safety timer reg write failed(%d)\n",
+					rc);
+				goto error_flash_set;
+			}
+
+			/* Set max current */
+			rc = qpnp_led_masked_write(led,
+				FLASH_MAX_CURR(led->base), FLASH_CURRENT_MASK,
+				FLASH_MAX_LEVEL);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Max current reg write failed(%d)\n",
+					rc);
+				goto error_flash_set;
+			}
+
+			/* Set clamp current */
+			rc = qpnp_led_masked_write(led,
+				FLASH_CLAMP_CURR(led->base),
+				FLASH_CURRENT_MASK,
+				led->flash_cfg->clamp_curr);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Clamp current reg write failed(%d)\n",
+					rc);
+				goto error_flash_set;
+			}
+
+			rc = qpnp_led_masked_write(led,
+				led->flash_cfg->current_addr,
+				FLASH_CURRENT_MASK,
+				led->flash_cfg->current_prgm);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Current reg write failed(%d)\n", rc);
+				goto error_flash_set;
+			}
+
+			rc = qpnp_led_masked_write(led,
+				FLASH_ENABLE_CONTROL(led->base),
+				led->flash_cfg->enable_module,
+				led->flash_cfg->enable_module);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Enable reg write failed(%d)\n", rc);
+				goto error_flash_set;
+			}
+
+			/*
+			 * Add 1ms delay for bharger enter stable state
+			 */
+			usleep_range(FLASH_RAMP_UP_DELAY_US,
+				     FLASH_RAMP_UP_DELAY_US + 10);
+
+			if (!led->flash_cfg->strobe_type)
+				led->flash_cfg->trigger_flash &=
+						~FLASH_HW_SW_STROBE_SEL_MASK;
+			else
+				led->flash_cfg->trigger_flash |=
+						FLASH_HW_SW_STROBE_SEL_MASK;
+
+			rc = qpnp_led_masked_write(led,
+				FLASH_LED_STROBE_CTRL(led->base),
+				led->flash_cfg->trigger_flash,
+				led->flash_cfg->trigger_flash);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+				"LED %d strobe reg write failed(%d)\n",
+				led->id, rc);
+				goto error_flash_set;
+			}
+		}
+	} else {
+		rc = qpnp_led_masked_write(led,
+			FLASH_LED_STROBE_CTRL(led->base),
+			led->flash_cfg->trigger_flash,
+			FLASH_DISABLE_ALL);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"LED %d flash write failed(%d)\n", led->id, rc);
+			if (led->flash_cfg->torch_enable)
+				goto error_torch_set;
+			else
+				goto error_flash_set;
+		}
+
+		if (led->flash_cfg->torch_enable) {
+			if (led->flash_cfg->peripheral_subtype ==
+							FLASH_SUBTYPE_DUAL) {
+				if (!led->flash_cfg->no_smbb_support)
+					rc = qpnp_torch_regulator_operate(led,
+									false);
+				else
+					rc = qpnp_flash_regulator_operate(led,
+									false);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"Torch regulator operate failed(%d)\n",
+						rc);
+					return rc;
+				}
+			} else if (led->flash_cfg->peripheral_subtype ==
+							FLASH_SUBTYPE_SINGLE) {
+				rc = qpnp_flash_regulator_operate(led, false);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+						"Flash regulator operate failed(%d)\n",
+						rc);
+					return rc;
+				}
+			}
+		} else {
+			/*
+			 * Disable module after ramp down complete for stable
+			 * behavior
+			 */
+			usleep_range(FLASH_RAMP_UP_DELAY_US,
+				     FLASH_RAMP_UP_DELAY_US + 10);
+
+			rc = qpnp_led_masked_write(led,
+				FLASH_ENABLE_CONTROL(led->base),
+				led->flash_cfg->enable_module &
+				~FLASH_ENABLE_MODULE_MASK,
+				FLASH_DISABLE_ALL);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Enable reg write failed(%d)\n", rc);
+				if (led->flash_cfg->torch_enable)
+					goto error_torch_set;
+				else
+					goto error_flash_set;
+			}
+
+			rc = qpnp_flash_regulator_operate(led, false);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Flash regulator operate failed(%d)\n",
+					rc);
+				return rc;
+			}
+		}
+	}
+
+	qpnp_dump_regs(led, flash_debug_regs, ARRAY_SIZE(flash_debug_regs));
+
+	return 0;
+
+error_reg_write:
+	if (led->flash_cfg->peripheral_subtype == FLASH_SUBTYPE_SINGLE)
+		goto error_flash_set;
+
+error_torch_set:
+	if (!led->flash_cfg->no_smbb_support)
+		error = qpnp_torch_regulator_operate(led, false);
+	else
+		error = qpnp_flash_regulator_operate(led, false);
+	if (error) {
+		dev_err(&led->pdev->dev,
+			"Torch regulator operate failed(%d)\n", rc);
+		return error;
+	}
+	return rc;
+
+error_flash_set:
+	error = qpnp_flash_regulator_operate(led, false);
+	if (error) {
+		dev_err(&led->pdev->dev,
+			"Flash regulator operate failed(%d)\n", rc);
+		return error;
+	}
+	return rc;
+}
+
+static int qpnp_kpdbl_set(struct qpnp_led_data *led)
+{
+	int rc;
+	int duty_us, duty_ns, period_us;
+
+	if (led->cdev.brightness) {
+		if (!led->kpdbl_cfg->pwm_cfg->blinking)
+			led->kpdbl_cfg->pwm_cfg->mode =
+				led->kpdbl_cfg->pwm_cfg->default_mode;
+
+		if (bitmap_empty(kpdbl_leds_in_use, NUM_KPDBL_LEDS)) {
+			rc = qpnp_led_masked_write(led, KPDBL_ENABLE(led->base),
+					KPDBL_MODULE_EN_MASK, KPDBL_MODULE_EN);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+					"Enable reg write failed(%d)\n", rc);
+				return rc;
+			}
+		}
+
+		/* On some platforms, GPLED1 channel should always be enabled
+		 * for the other GPLEDs 2/3/4 to glow. Before enabling GPLED
+		 * 2/3/4, first check if GPLED1 is already enabled. If GPLED1
+		 * channel is not enabled, then enable the GPLED1 channel but
+		 * with a 0 brightness
+		 */
+		if (!led->kpdbl_cfg->always_on &&
+			!test_bit(KPDBL_MASTER_BIT_INDEX, kpdbl_leds_in_use) &&
+						kpdbl_master) {
+			rc = pwm_config_us(kpdbl_master, 0,
+					kpdbl_master_period_us);
+			if (rc < 0) {
+				dev_err(&led->pdev->dev,
+					"pwm config failed\n");
+				return rc;
+			}
+
+			rc = pwm_enable(kpdbl_master);
+			if (rc < 0) {
+				dev_err(&led->pdev->dev,
+					"pwm enable failed\n");
+				return rc;
+			}
+			set_bit(KPDBL_MASTER_BIT_INDEX,
+						kpdbl_leds_in_use);
+		}
+
+		if (led->kpdbl_cfg->pwm_cfg->mode == PWM_MODE) {
+			period_us = led->kpdbl_cfg->pwm_cfg->pwm_period_us;
+			if (period_us > INT_MAX / NSEC_PER_USEC) {
+				duty_us = (period_us * led->cdev.brightness) /
+					KPDBL_MAX_LEVEL;
+				rc = pwm_config_us(
+					led->kpdbl_cfg->pwm_cfg->pwm_dev,
+					duty_us,
+					period_us);
+			} else {
+				duty_ns = ((period_us * NSEC_PER_USEC) /
+					KPDBL_MAX_LEVEL) * led->cdev.brightness;
+				rc = pwm_config(
+					led->kpdbl_cfg->pwm_cfg->pwm_dev,
+					duty_ns,
+					period_us * NSEC_PER_USEC);
+			}
+			if (rc < 0) {
+				dev_err(&led->pdev->dev,
+					"pwm config failed\n");
+				return rc;
+			}
+		}
+
+		rc = pwm_enable(led->kpdbl_cfg->pwm_cfg->pwm_dev);
+		if (rc < 0) {
+			dev_err(&led->pdev->dev, "pwm enable failed\n");
+			return rc;
+		}
+
+		set_bit(led->kpdbl_cfg->row_id, kpdbl_leds_in_use);
+
+		/* is_kpdbl_master_turn_on will be set to true when GPLED1
+		 * channel is enabled and has a valid brightness value
+		 */
+		if (led->kpdbl_cfg->always_on)
+			is_kpdbl_master_turn_on = true;
+
+	} else {
+		led->kpdbl_cfg->pwm_cfg->mode =
+			led->kpdbl_cfg->pwm_cfg->default_mode;
+
+		/* Before disabling GPLED1, check if any other GPLED 2/3/4 is
+		 * on. If any of the other GPLED 2/3/4 is on, then have the
+		 * GPLED1 channel enabled with 0 brightness.
+		 */
+		if (led->kpdbl_cfg->always_on) {
+			if (bitmap_weight(kpdbl_leds_in_use,
+						NUM_KPDBL_LEDS) > 1) {
+				rc = pwm_config_us(
+					led->kpdbl_cfg->pwm_cfg->pwm_dev, 0,
+					led->kpdbl_cfg->pwm_cfg->pwm_period_us);
+				if (rc < 0) {
+					dev_err(&led->pdev->dev,
+						"pwm config failed\n");
+					return rc;
+				}
+
+				rc = pwm_enable(led->kpdbl_cfg->pwm_cfg->
+							pwm_dev);
+				if (rc < 0) {
+					dev_err(&led->pdev->dev,
+						"pwm enable failed\n");
+					return rc;
+				}
+			} else {
+				if (kpdbl_master) {
+					pwm_disable(kpdbl_master);
+					clear_bit(KPDBL_MASTER_BIT_INDEX,
+						kpdbl_leds_in_use);
+					rc = qpnp_led_masked_write(
+						led, KPDBL_ENABLE(led->base),
+						KPDBL_MODULE_EN_MASK,
+						KPDBL_MODULE_DIS);
+					if (rc) {
+						dev_err(&led->pdev->dev, "Failed to write led enable reg\n");
+						return rc;
+					}
+				}
+			}
+			is_kpdbl_master_turn_on = false;
+		} else {
+			pwm_disable(led->kpdbl_cfg->pwm_cfg->pwm_dev);
+			clear_bit(led->kpdbl_cfg->row_id, kpdbl_leds_in_use);
+			if (bitmap_weight(kpdbl_leds_in_use,
+				NUM_KPDBL_LEDS) == 1 && kpdbl_master &&
+						!is_kpdbl_master_turn_on) {
+				pwm_disable(kpdbl_master);
+				clear_bit(KPDBL_MASTER_BIT_INDEX,
+					kpdbl_leds_in_use);
+				rc = qpnp_led_masked_write(
+					led, KPDBL_ENABLE(led->base),
+					KPDBL_MODULE_EN_MASK, KPDBL_MODULE_DIS);
+				if (rc) {
+					dev_err(&led->pdev->dev,
+					"Failed to write led enable reg\n");
+					return rc;
+				}
+				is_kpdbl_master_turn_on = false;
+			}
+		}
+	}
+
+	led->kpdbl_cfg->pwm_cfg->blinking = false;
+
+	qpnp_dump_regs(led, kpdbl_debug_regs, ARRAY_SIZE(kpdbl_debug_regs));
+
+	return 0;
+}
+
+static int qpnp_rgb_set(struct qpnp_led_data *led)
+{
+	int rc;
+	int duty_us, duty_ns, period_us;
+
+	if (led->cdev.brightness) {
+		if (!led->rgb_cfg->pwm_cfg->blinking)
+			led->rgb_cfg->pwm_cfg->mode =
+				led->rgb_cfg->pwm_cfg->default_mode;
+		if (led->rgb_cfg->pwm_cfg->mode == PWM_MODE) {
+			period_us = led->rgb_cfg->pwm_cfg->pwm_period_us;
+			if (period_us > INT_MAX / NSEC_PER_USEC) {
+				duty_us = (period_us * led->cdev.brightness) /
+					LED_FULL;
+				rc = pwm_config_us(
+					led->rgb_cfg->pwm_cfg->pwm_dev,
+					duty_us,
+					period_us);
+			} else {
+				duty_ns = ((period_us * NSEC_PER_USEC) /
+					LED_FULL) * led->cdev.brightness;
+				rc = pwm_config(
+					led->rgb_cfg->pwm_cfg->pwm_dev,
+					duty_ns,
+					period_us * NSEC_PER_USEC);
+			}
+			if (rc < 0) {
+				dev_err(&led->pdev->dev,
+					"pwm config failed\n");
+				return rc;
+			}
+		}
+		rc = qpnp_led_masked_write(led,
+			RGB_LED_EN_CTL(led->base),
+			led->rgb_cfg->enable, led->rgb_cfg->enable);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Failed to write led enable reg\n");
+			return rc;
+		}
+
+		if (led->rgb_cfg->pwm_cfg->pwm_enabled) {
+			pwm_disable(led->rgb_cfg->pwm_cfg->pwm_dev);
+			led->rgb_cfg->pwm_cfg->pwm_enabled = 0;
+		}
+
+		rc = pwm_enable(led->rgb_cfg->pwm_cfg->pwm_dev);
+		if (!rc)
+			led->rgb_cfg->pwm_cfg->pwm_enabled = 1;
+	} else {
+		led->rgb_cfg->pwm_cfg->mode =
+			led->rgb_cfg->pwm_cfg->default_mode;
+		pwm_disable(led->rgb_cfg->pwm_cfg->pwm_dev);
+		led->rgb_cfg->pwm_cfg->pwm_enabled = 0;
+		rc = qpnp_led_masked_write(led,
+			RGB_LED_EN_CTL(led->base),
+			led->rgb_cfg->enable, RGB_LED_DISABLE);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Failed to write led enable reg\n");
+			return rc;
+		}
+	}
+
+	led->rgb_cfg->pwm_cfg->blinking = false;
+	qpnp_dump_regs(led, rgb_pwm_debug_regs, ARRAY_SIZE(rgb_pwm_debug_regs));
+
+	return 0;
+}
+
+static void qpnp_led_set(struct led_classdev *led_cdev,
+				enum led_brightness value)
+{
+	struct qpnp_led_data *led;
+
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+	if (value < LED_OFF) {
+		dev_err(&led->pdev->dev, "Invalid brightness value\n");
+		return;
+	}
+
+	if (value > led->cdev.max_brightness)
+		value = led->cdev.max_brightness;
+
+	led->cdev.brightness = value;
+	if (led->in_order_command_processing)
+		queue_work(led->workqueue, &led->work);
+	else
+		schedule_work(&led->work);
+}
+
+static void __qpnp_led_work(struct qpnp_led_data *led,
+				enum led_brightness value)
+{
+	int rc;
+
+	if (led->id == QPNP_ID_FLASH1_LED0 || led->id == QPNP_ID_FLASH1_LED1)
+		mutex_lock(&flash_lock);
+	else
+		mutex_lock(&led->lock);
+
+	switch (led->id) {
+	case QPNP_ID_WLED:
+		rc = qpnp_wled_set(led);
+		if (rc < 0)
+			dev_err(&led->pdev->dev,
+				"WLED set brightness failed (%d)\n", rc);
+		break;
+	case QPNP_ID_FLASH1_LED0:
+	case QPNP_ID_FLASH1_LED1:
+		rc = qpnp_flash_set(led);
+		if (rc < 0)
+			dev_err(&led->pdev->dev,
+				"FLASH set brightness failed (%d)\n", rc);
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		rc = qpnp_rgb_set(led);
+		if (rc < 0)
+			dev_err(&led->pdev->dev,
+				"RGB set brightness failed (%d)\n", rc);
+		break;
+	case QPNP_ID_LED_MPP:
+		rc = qpnp_mpp_set(led);
+		if (rc < 0)
+			dev_err(&led->pdev->dev,
+					"MPP set brightness failed (%d)\n", rc);
+		break;
+	case QPNP_ID_LED_GPIO:
+		rc = qpnp_gpio_set(led);
+		if (rc < 0)
+			dev_err(&led->pdev->dev,
+					"GPIO set brightness failed (%d)\n",
+					rc);
+		break;
+	case QPNP_ID_KPDBL:
+		rc = qpnp_kpdbl_set(led);
+		if (rc < 0)
+			dev_err(&led->pdev->dev,
+				"KPDBL set brightness failed (%d)\n", rc);
+		break;
+	default:
+		dev_err(&led->pdev->dev, "Invalid LED(%d)\n", led->id);
+		break;
+	}
+	if (led->id == QPNP_ID_FLASH1_LED0 || led->id == QPNP_ID_FLASH1_LED1)
+		mutex_unlock(&flash_lock);
+	else
+		mutex_unlock(&led->lock);
+
+}
+
+static void qpnp_led_work(struct work_struct *work)
+{
+	struct qpnp_led_data *led = container_of(work,
+					struct qpnp_led_data, work);
+
+	__qpnp_led_work(led, led->cdev.brightness);
+}
+
+static int qpnp_led_set_max_brightness(struct qpnp_led_data *led)
+{
+	switch (led->id) {
+	case QPNP_ID_WLED:
+		led->cdev.max_brightness = WLED_MAX_LEVEL;
+		break;
+	case QPNP_ID_FLASH1_LED0:
+	case QPNP_ID_FLASH1_LED1:
+		led->cdev.max_brightness = led->max_current;
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		led->cdev.max_brightness = RGB_MAX_LEVEL;
+		break;
+	case QPNP_ID_LED_MPP:
+		if (led->mpp_cfg->pwm_mode == MANUAL_MODE)
+			led->cdev.max_brightness = led->max_current;
+		else
+			led->cdev.max_brightness = MPP_MAX_LEVEL;
+		break;
+	case QPNP_ID_LED_GPIO:
+			led->cdev.max_brightness = led->max_current;
+		break;
+	case QPNP_ID_KPDBL:
+		led->cdev.max_brightness = KPDBL_MAX_LEVEL;
+		break;
+	default:
+		dev_err(&led->pdev->dev, "Invalid LED(%d)\n", led->id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static enum led_brightness qpnp_led_get(struct led_classdev *led_cdev)
+{
+	struct qpnp_led_data *led;
+
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+	return led->cdev.brightness;
+}
+
+static void qpnp_led_turn_off_delayed(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct qpnp_led_data *led
+		= container_of(dwork, struct qpnp_led_data, dwork);
+
+	led->cdev.brightness = LED_OFF;
+	qpnp_led_set(&led->cdev, led->cdev.brightness);
+}
+
+static void qpnp_led_turn_off(struct qpnp_led_data *led)
+{
+	INIT_DELAYED_WORK(&led->dwork, qpnp_led_turn_off_delayed);
+	schedule_delayed_work(&led->dwork,
+		msecs_to_jiffies(led->turn_off_delay_ms));
+}
+
+static int qpnp_wled_init(struct qpnp_led_data *led)
+{
+	int rc, i;
+	u8 num_wled_strings, val = 0;
+
+	num_wled_strings = led->wled_cfg->num_strings;
+
+	/* verify ranges */
+	if (led->wled_cfg->ovp_val > WLED_OVP_27V) {
+		dev_err(&led->pdev->dev, "Invalid ovp value\n");
+		return -EINVAL;
+	}
+
+	if (led->wled_cfg->boost_curr_lim > WLED_CURR_LIMIT_1680mA) {
+		dev_err(&led->pdev->dev, "Invalid boost current limit\n");
+		return -EINVAL;
+	}
+
+	if (led->wled_cfg->cp_select > WLED_CP_SELECT_MAX) {
+		dev_err(&led->pdev->dev, "Invalid pole capacitance\n");
+		return -EINVAL;
+	}
+
+	if (led->max_current > WLED_MAX_CURR) {
+		dev_err(&led->pdev->dev, "Invalid max current\n");
+		return -EINVAL;
+	}
+
+	if ((led->wled_cfg->ctrl_delay_us % WLED_CTL_DLY_STEP) ||
+		(led->wled_cfg->ctrl_delay_us > WLED_CTL_DLY_MAX)) {
+		dev_err(&led->pdev->dev, "Invalid control delay\n");
+		return -EINVAL;
+	}
+
+	/* program over voltage protection threshold */
+	rc = qpnp_led_masked_write(led, WLED_OVP_CFG_REG(led->base),
+		WLED_OVP_VAL_MASK,
+		(led->wled_cfg->ovp_val << WLED_OVP_VAL_BIT_SHFT));
+	if (rc) {
+		dev_err(&led->pdev->dev,
+				"WLED OVP reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* program current boost limit */
+	rc = qpnp_led_masked_write(led, WLED_BOOST_LIMIT_REG(led->base),
+		WLED_BOOST_LIMIT_MASK, led->wled_cfg->boost_curr_lim);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+				"WLED boost limit reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* program output feedback */
+	rc = qpnp_led_masked_write(led, WLED_FDBCK_CTRL_REG(led->base),
+		WLED_OP_FDBCK_MASK,
+		(led->wled_cfg->op_fdbck << WLED_OP_FDBCK_BIT_SHFT));
+	if (rc) {
+		dev_err(&led->pdev->dev,
+				"WLED fdbck ctrl reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* program switch frequency */
+	rc = qpnp_led_masked_write(led,
+		WLED_SWITCHING_FREQ_REG(led->base),
+		WLED_SWITCH_FREQ_MASK, led->wled_cfg->switch_freq);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"WLED switch freq reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* program current sink */
+	if (led->wled_cfg->cs_out_en) {
+		for (i = 0; i < led->wled_cfg->num_strings; i++)
+			val |= 1 << i;
+		rc = qpnp_led_masked_write(led, WLED_CURR_SINK_REG(led->base),
+			WLED_CURR_SINK_MASK, (val << WLED_CURR_SINK_SHFT));
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED curr sink reg write failed(%d)\n", rc);
+			return rc;
+		}
+	}
+
+	/* program high pole capacitance */
+	rc = qpnp_led_masked_write(led, WLED_HIGH_POLE_CAP_REG(led->base),
+		WLED_CP_SELECT_MASK, led->wled_cfg->cp_select);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+				"WLED pole cap reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* program modulator, current mod src and cabc */
+	for (i = 0; i < num_wled_strings; i++) {
+		rc = qpnp_led_masked_write(led, WLED_MOD_EN_REG(led->base, i),
+			WLED_NO_MASK, WLED_EN_MASK);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED mod enable reg write failed(%d)\n", rc);
+			return rc;
+		}
+
+		if (led->wled_cfg->dig_mod_gen_en) {
+			rc = qpnp_led_masked_write(led,
+				WLED_MOD_SRC_SEL_REG(led->base, i),
+				WLED_NO_MASK, WLED_USE_EXT_GEN_MOD_SRC);
+			if (rc) {
+				dev_err(&led->pdev->dev,
+				"WLED dig mod en reg write failed(%d)\n", rc);
+			}
+		}
+
+		rc = qpnp_led_masked_write(led,
+			WLED_FULL_SCALE_REG(led->base, i), WLED_MAX_CURR_MASK,
+			(u8)led->max_current);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"WLED max current reg write failed(%d)\n", rc);
+			return rc;
+		}
+
+	}
+
+	/* Reset WLED enable register */
+	rc = qpnp_led_masked_write(led, WLED_MOD_CTRL_REG(led->base),
+		WLED_8_BIT_MASK, WLED_BOOST_OFF);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"WLED write ctrl reg failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* dump wled registers */
+	qpnp_dump_regs(led, wled_debug_regs, ARRAY_SIZE(wled_debug_regs));
+
+	return 0;
+}
+
+static ssize_t led_mode_store(struct device *dev,
+			struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct qpnp_led_data *led;
+	unsigned long state;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret = -EINVAL;
+
+	ret = kstrtoul(buf, 10, &state);
+	if (ret)
+		return ret;
+
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+	/* '1' to enable torch mode; '0' to switch to flash mode */
+	if (state == 1)
+		led->flash_cfg->torch_enable = true;
+	else
+		led->flash_cfg->torch_enable = false;
+
+	return count;
+}
+
+static ssize_t led_strobe_type_store(struct device *dev,
+			struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct qpnp_led_data *led;
+	unsigned long state;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret = -EINVAL;
+
+	ret = kstrtoul(buf, 10, &state);
+	if (ret)
+		return ret;
+
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+	/* '0' for sw strobe; '1' for hw strobe */
+	if (state == 1)
+		led->flash_cfg->strobe_type = 1;
+	else
+		led->flash_cfg->strobe_type = 0;
+
+	return count;
+}
+
+static int qpnp_pwm_init(struct pwm_config_data *pwm_cfg,
+					struct platform_device *pdev,
+					const char *name)
+{
+	int rc, start_idx, idx_len, lut_max_size;
+
+	if (pwm_cfg->pwm_dev) {
+		if (pwm_cfg->mode == LPG_MODE) {
+			start_idx =
+			pwm_cfg->duty_cycles->start_idx;
+			idx_len =
+			pwm_cfg->duty_cycles->num_duty_pcts;
+
+			if (strnstr(name, "kpdbl", sizeof("kpdbl")))
+				lut_max_size = PWM_GPLED_LUT_MAX_SIZE;
+			else
+				lut_max_size = PWM_LUT_MAX_SIZE;
+
+			if (idx_len >= lut_max_size && start_idx) {
+				dev_err(&pdev->dev,
+					"Wrong LUT size or index\n");
+				return -EINVAL;
+			}
+
+			if ((start_idx + idx_len) > lut_max_size) {
+				dev_err(&pdev->dev, "Exceed LUT limit\n");
+				return -EINVAL;
+			}
+			rc = pwm_lut_config(pwm_cfg->pwm_dev,
+				pwm_cfg->pwm_period_us,
+				pwm_cfg->duty_cycles->duty_pcts,
+				pwm_cfg->lut_params);
+			if (rc < 0) {
+				dev_err(&pdev->dev, "Failed to configure pwm LUT\n");
+				return rc;
+			}
+		}
+	} else {
+		dev_err(&pdev->dev, "Invalid PWM device\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static ssize_t pwm_us_store(struct device *dev,
+	struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct qpnp_led_data *led;
+	u32 pwm_us;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret;
+	u32 previous_pwm_us;
+	struct pwm_config_data *pwm_cfg;
+
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+	ret = kstrtou32(buf, 10, &pwm_us);
+	if (ret)
+		return ret;
+
+	switch (led->id) {
+	case QPNP_ID_LED_MPP:
+		pwm_cfg = led->mpp_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		pwm_cfg = led->rgb_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_KPDBL:
+		pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+		break;
+	default:
+		dev_err(&led->pdev->dev, "Invalid LED id type for pwm_us\n");
+		return -EINVAL;
+	}
+
+	if (pwm_cfg->mode == LPG_MODE)
+		pwm_cfg->blinking = true;
+
+	previous_pwm_us = pwm_cfg->pwm_period_us;
+
+	pwm_cfg->pwm_period_us = pwm_us;
+	pwm_free(pwm_cfg->pwm_dev);
+	ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+	if (ret) {
+		pwm_cfg->pwm_period_us = previous_pwm_us;
+		pwm_free(pwm_cfg->pwm_dev);
+		qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+		qpnp_led_set(&led->cdev, led->cdev.brightness);
+		dev_err(&led->pdev->dev,
+			"Failed to initialize pwm with new pwm_us value\n");
+		return ret;
+	}
+	qpnp_led_set(&led->cdev, led->cdev.brightness);
+	return count;
+}
+
+static ssize_t pause_lo_store(struct device *dev,
+	struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct qpnp_led_data *led;
+	u32 pause_lo;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret;
+	u32 previous_pause_lo;
+	struct pwm_config_data *pwm_cfg;
+
+	ret = kstrtou32(buf, 10, &pause_lo);
+	if (ret)
+		return ret;
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+	switch (led->id) {
+	case QPNP_ID_LED_MPP:
+		pwm_cfg = led->mpp_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		pwm_cfg = led->rgb_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_KPDBL:
+		pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+		break;
+	default:
+		dev_err(&led->pdev->dev,
+			"Invalid LED id type for pause lo\n");
+		return -EINVAL;
+	}
+
+	if (pwm_cfg->mode == LPG_MODE)
+		pwm_cfg->blinking = true;
+
+	previous_pause_lo = pwm_cfg->lut_params.lut_pause_lo;
+
+	pwm_free(pwm_cfg->pwm_dev);
+	pwm_cfg->lut_params.lut_pause_lo = pause_lo;
+	ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+	if (ret) {
+		pwm_cfg->lut_params.lut_pause_lo = previous_pause_lo;
+		pwm_free(pwm_cfg->pwm_dev);
+		qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+		qpnp_led_set(&led->cdev, led->cdev.brightness);
+		dev_err(&led->pdev->dev,
+			"Failed to initialize pwm with new pause lo value\n");
+		return ret;
+	}
+	qpnp_led_set(&led->cdev, led->cdev.brightness);
+	return count;
+}
+
+static ssize_t pause_hi_store(struct device *dev,
+	struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct qpnp_led_data *led;
+	u32 pause_hi;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret;
+	u32 previous_pause_hi;
+	struct pwm_config_data *pwm_cfg;
+
+	ret = kstrtou32(buf, 10, &pause_hi);
+	if (ret)
+		return ret;
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+	switch (led->id) {
+	case QPNP_ID_LED_MPP:
+		pwm_cfg = led->mpp_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		pwm_cfg = led->rgb_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_KPDBL:
+		pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+		break;
+	default:
+		dev_err(&led->pdev->dev,
+			"Invalid LED id type for pause hi\n");
+		return -EINVAL;
+	}
+
+	if (pwm_cfg->mode == LPG_MODE)
+		pwm_cfg->blinking = true;
+
+	previous_pause_hi = pwm_cfg->lut_params.lut_pause_hi;
+
+	pwm_free(pwm_cfg->pwm_dev);
+	pwm_cfg->lut_params.lut_pause_hi = pause_hi;
+	ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+	if (ret) {
+		pwm_cfg->lut_params.lut_pause_hi = previous_pause_hi;
+		pwm_free(pwm_cfg->pwm_dev);
+		qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+		qpnp_led_set(&led->cdev, led->cdev.brightness);
+		dev_err(&led->pdev->dev,
+			"Failed to initialize pwm with new pause hi value\n");
+		return ret;
+	}
+	qpnp_led_set(&led->cdev, led->cdev.brightness);
+	return count;
+}
+
+static ssize_t start_idx_store(struct device *dev,
+	struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct qpnp_led_data *led;
+	u32 start_idx;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret;
+	u32 previous_start_idx;
+	struct pwm_config_data *pwm_cfg;
+
+	ret = kstrtou32(buf, 10, &start_idx);
+	if (ret)
+		return ret;
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+	switch (led->id) {
+	case QPNP_ID_LED_MPP:
+		pwm_cfg = led->mpp_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		pwm_cfg = led->rgb_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_KPDBL:
+		pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+		break;
+	default:
+		dev_err(&led->pdev->dev,
+			"Invalid LED id type for start idx\n");
+		return -EINVAL;
+	}
+
+	if (pwm_cfg->mode == LPG_MODE)
+		pwm_cfg->blinking = true;
+
+	previous_start_idx = pwm_cfg->duty_cycles->start_idx;
+	pwm_cfg->duty_cycles->start_idx = start_idx;
+	pwm_cfg->lut_params.start_idx = pwm_cfg->duty_cycles->start_idx;
+	pwm_free(pwm_cfg->pwm_dev);
+	ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+	if (ret) {
+		pwm_cfg->duty_cycles->start_idx = previous_start_idx;
+		pwm_cfg->lut_params.start_idx = pwm_cfg->duty_cycles->start_idx;
+		pwm_free(pwm_cfg->pwm_dev);
+		qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+		qpnp_led_set(&led->cdev, led->cdev.brightness);
+		dev_err(&led->pdev->dev,
+			"Failed to initialize pwm with new start idx value\n");
+		return ret;
+	}
+	qpnp_led_set(&led->cdev, led->cdev.brightness);
+	return count;
+}
+
+static ssize_t ramp_step_ms_store(struct device *dev,
+	struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct qpnp_led_data *led;
+	u32 ramp_step_ms;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret;
+	u32 previous_ramp_step_ms;
+	struct pwm_config_data *pwm_cfg;
+
+	ret = kstrtou32(buf, 10, &ramp_step_ms);
+	if (ret)
+		return ret;
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+	switch (led->id) {
+	case QPNP_ID_LED_MPP:
+		pwm_cfg = led->mpp_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		pwm_cfg = led->rgb_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_KPDBL:
+		pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+		break;
+	default:
+		dev_err(&led->pdev->dev,
+			"Invalid LED id type for ramp step\n");
+		return -EINVAL;
+	}
+
+	if (pwm_cfg->mode == LPG_MODE)
+		pwm_cfg->blinking = true;
+
+	previous_ramp_step_ms = pwm_cfg->lut_params.ramp_step_ms;
+
+	pwm_free(pwm_cfg->pwm_dev);
+	pwm_cfg->lut_params.ramp_step_ms = ramp_step_ms;
+	ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+	if (ret) {
+		pwm_cfg->lut_params.ramp_step_ms = previous_ramp_step_ms;
+		pwm_free(pwm_cfg->pwm_dev);
+		qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+		qpnp_led_set(&led->cdev, led->cdev.brightness);
+		dev_err(&led->pdev->dev,
+			"Failed to initialize pwm with new ramp step value\n");
+		return ret;
+	}
+	qpnp_led_set(&led->cdev, led->cdev.brightness);
+	return count;
+}
+
+static ssize_t lut_flags_store(struct device *dev,
+	struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct qpnp_led_data *led;
+	u32 lut_flags;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret;
+	u32 previous_lut_flags;
+	struct pwm_config_data *pwm_cfg;
+
+	ret = kstrtou32(buf, 10, &lut_flags);
+	if (ret)
+		return ret;
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+	switch (led->id) {
+	case QPNP_ID_LED_MPP:
+		pwm_cfg = led->mpp_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		pwm_cfg = led->rgb_cfg->pwm_cfg;
+		break;
+	case QPNP_ID_KPDBL:
+		pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+		break;
+	default:
+		dev_err(&led->pdev->dev,
+			"Invalid LED id type for lut flags\n");
+		return -EINVAL;
+	}
+
+	if (pwm_cfg->mode == LPG_MODE)
+		pwm_cfg->blinking = true;
+
+	previous_lut_flags = pwm_cfg->lut_params.flags;
+
+	pwm_free(pwm_cfg->pwm_dev);
+	pwm_cfg->lut_params.flags = lut_flags;
+	ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+	if (ret) {
+		pwm_cfg->lut_params.flags = previous_lut_flags;
+		pwm_free(pwm_cfg->pwm_dev);
+		qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+		qpnp_led_set(&led->cdev, led->cdev.brightness);
+		dev_err(&led->pdev->dev,
+			"Failed to initialize pwm with new lut flags value\n");
+		return ret;
+	}
+	qpnp_led_set(&led->cdev, led->cdev.brightness);
+	return count;
+}
+
+static ssize_t duty_pcts_store(struct device *dev,
+	struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct qpnp_led_data *led;
+	int num_duty_pcts = 0;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	char *buffer;
+	ssize_t ret;
+	int i = 0;
+	int max_duty_pcts;
+	struct pwm_config_data *pwm_cfg;
+	u32 previous_num_duty_pcts;
+	int value;
+	int *previous_duty_pcts;
+
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+	switch (led->id) {
+	case QPNP_ID_LED_MPP:
+		pwm_cfg = led->mpp_cfg->pwm_cfg;
+		max_duty_pcts = PWM_LUT_MAX_SIZE;
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		pwm_cfg = led->rgb_cfg->pwm_cfg;
+		max_duty_pcts = PWM_LUT_MAX_SIZE;
+		break;
+	case QPNP_ID_KPDBL:
+		pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+		max_duty_pcts = PWM_GPLED_LUT_MAX_SIZE;
+		break;
+	default:
+		dev_err(&led->pdev->dev,
+			"Invalid LED id type for duty pcts\n");
+		return -EINVAL;
+	}
+
+	if (pwm_cfg->mode == LPG_MODE)
+		pwm_cfg->blinking = true;
+
+	buffer = (char *)buf;
+
+	for (i = 0; i < max_duty_pcts; i++) {
+		if (buffer == NULL)
+			break;
+		ret = sscanf((const char *)buffer, "%u,%s", &value, buffer);
+		pwm_cfg->old_duty_pcts[i] = value;
+		num_duty_pcts++;
+		if (ret <= 1)
+			break;
+	}
+
+	if (num_duty_pcts >= max_duty_pcts) {
+		dev_err(&led->pdev->dev,
+			"Number of duty pcts given exceeds max (%d)\n",
+			max_duty_pcts);
+		return -EINVAL;
+	}
+
+	previous_num_duty_pcts = pwm_cfg->duty_cycles->num_duty_pcts;
+	previous_duty_pcts = pwm_cfg->duty_cycles->duty_pcts;
+
+	pwm_cfg->duty_cycles->num_duty_pcts = num_duty_pcts;
+	pwm_cfg->duty_cycles->duty_pcts = pwm_cfg->old_duty_pcts;
+	pwm_cfg->old_duty_pcts = previous_duty_pcts;
+	pwm_cfg->lut_params.idx_len = pwm_cfg->duty_cycles->num_duty_pcts;
+
+	pwm_free(pwm_cfg->pwm_dev);
+	ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+	if (ret)
+		goto restore;
+
+	qpnp_led_set(&led->cdev, led->cdev.brightness);
+	return count;
+
+restore:
+	dev_err(&led->pdev->dev,
+		"Failed to initialize pwm with new duty pcts value\n");
+	pwm_cfg->duty_cycles->num_duty_pcts = previous_num_duty_pcts;
+	pwm_cfg->old_duty_pcts = pwm_cfg->duty_cycles->duty_pcts;
+	pwm_cfg->duty_cycles->duty_pcts = previous_duty_pcts;
+	pwm_cfg->lut_params.idx_len = pwm_cfg->duty_cycles->num_duty_pcts;
+	pwm_free(pwm_cfg->pwm_dev);
+	qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+	qpnp_led_set(&led->cdev, led->cdev.brightness);
+	return ret;
+}
+
+static void led_blink(struct qpnp_led_data *led,
+			struct pwm_config_data *pwm_cfg)
+{
+	int rc;
+
+	flush_work(&led->work);
+	mutex_lock(&led->lock);
+	if (pwm_cfg->use_blink) {
+		if (led->cdev.brightness) {
+			pwm_cfg->blinking = true;
+			if (led->id == QPNP_ID_LED_MPP)
+				led->mpp_cfg->pwm_mode = LPG_MODE;
+			else if (led->id == QPNP_ID_KPDBL)
+				led->kpdbl_cfg->pwm_mode = LPG_MODE;
+			pwm_cfg->mode = LPG_MODE;
+		} else {
+			pwm_cfg->blinking = false;
+			pwm_cfg->mode = pwm_cfg->default_mode;
+			if (led->id == QPNP_ID_LED_MPP)
+				led->mpp_cfg->pwm_mode = pwm_cfg->default_mode;
+			else if (led->id == QPNP_ID_KPDBL)
+				led->kpdbl_cfg->pwm_mode =
+						pwm_cfg->default_mode;
+		}
+		pwm_free(pwm_cfg->pwm_dev);
+		qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+		if (led->id == QPNP_ID_RGB_RED || led->id == QPNP_ID_RGB_GREEN
+				|| led->id == QPNP_ID_RGB_BLUE) {
+			rc = qpnp_rgb_set(led);
+			if (rc < 0)
+				dev_err(&led->pdev->dev,
+				"RGB set brightness failed (%d)\n", rc);
+		} else if (led->id == QPNP_ID_LED_MPP) {
+			rc = qpnp_mpp_set(led);
+			if (rc < 0)
+				dev_err(&led->pdev->dev,
+				"MPP set brightness failed (%d)\n", rc);
+		} else if (led->id == QPNP_ID_KPDBL) {
+			rc = qpnp_kpdbl_set(led);
+			if (rc < 0)
+				dev_err(&led->pdev->dev,
+				"KPDBL set brightness failed (%d)\n", rc);
+		}
+	}
+	mutex_unlock(&led->lock);
+}
+
+static ssize_t blink_store(struct device *dev,
+	struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct qpnp_led_data *led;
+	unsigned long blinking;
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	ssize_t ret = -EINVAL;
+
+	ret = kstrtoul(buf, 10, &blinking);
+	if (ret)
+		return ret;
+	led = container_of(led_cdev, struct qpnp_led_data, cdev);
+	led->cdev.brightness = blinking ? led->cdev.max_brightness : 0;
+
+	switch (led->id) {
+	case QPNP_ID_LED_MPP:
+		led_blink(led, led->mpp_cfg->pwm_cfg);
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		led_blink(led, led->rgb_cfg->pwm_cfg);
+		break;
+	case QPNP_ID_KPDBL:
+		led_blink(led, led->kpdbl_cfg->pwm_cfg);
+		break;
+	default:
+		dev_err(&led->pdev->dev, "Invalid LED id type for blink\n");
+		return -EINVAL;
+	}
+	return count;
+}
+
+static DEVICE_ATTR(led_mode, 0664, NULL, led_mode_store);
+static DEVICE_ATTR(strobe, 0664, NULL, led_strobe_type_store);
+static DEVICE_ATTR(pwm_us, 0664, NULL, pwm_us_store);
+static DEVICE_ATTR(pause_lo, 0664, NULL, pause_lo_store);
+static DEVICE_ATTR(pause_hi, 0664, NULL, pause_hi_store);
+static DEVICE_ATTR(start_idx, 0664, NULL, start_idx_store);
+static DEVICE_ATTR(ramp_step_ms, 0664, NULL, ramp_step_ms_store);
+static DEVICE_ATTR(lut_flags, 0664, NULL, lut_flags_store);
+static DEVICE_ATTR(duty_pcts, 0664, NULL, duty_pcts_store);
+static DEVICE_ATTR(blink, 0664, NULL, blink_store);
+
+static struct attribute *led_attrs[] = {
+	&dev_attr_led_mode.attr,
+	&dev_attr_strobe.attr,
+	NULL
+};
+
+static const struct attribute_group led_attr_group = {
+	.attrs = led_attrs,
+};
+
+static struct attribute *pwm_attrs[] = {
+	&dev_attr_pwm_us.attr,
+	NULL
+};
+
+static struct attribute *lpg_attrs[] = {
+	&dev_attr_pause_lo.attr,
+	&dev_attr_pause_hi.attr,
+	&dev_attr_start_idx.attr,
+	&dev_attr_ramp_step_ms.attr,
+	&dev_attr_lut_flags.attr,
+	&dev_attr_duty_pcts.attr,
+	NULL
+};
+
+static struct attribute *blink_attrs[] = {
+	&dev_attr_blink.attr,
+	NULL
+};
+
+static const struct attribute_group pwm_attr_group = {
+	.attrs = pwm_attrs,
+};
+
+static const struct attribute_group lpg_attr_group = {
+	.attrs = lpg_attrs,
+};
+
+static const struct attribute_group blink_attr_group = {
+	.attrs = blink_attrs,
+};
+
+static int qpnp_flash_init(struct qpnp_led_data *led)
+{
+	int rc;
+
+	led->flash_cfg->flash_on = false;
+
+	rc = qpnp_led_masked_write(led,
+		FLASH_LED_STROBE_CTRL(led->base),
+		FLASH_STROBE_MASK, FLASH_DISABLE_ALL);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"LED %d flash write failed(%d)\n", led->id, rc);
+		return rc;
+	}
+
+	/* Disable flash LED module */
+	rc = qpnp_led_masked_write(led, FLASH_ENABLE_CONTROL(led->base),
+		FLASH_ENABLE_MASK, FLASH_DISABLE_ALL);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Enable reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	if (led->flash_cfg->torch_enable)
+		return 0;
+
+	/* Set headroom */
+	rc = qpnp_led_masked_write(led, FLASH_HEADROOM(led->base),
+		FLASH_HEADROOM_MASK, led->flash_cfg->headroom);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Headroom reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* Set startup delay */
+	rc = qpnp_led_masked_write(led,
+		FLASH_STARTUP_DELAY(led->base), FLASH_STARTUP_DLY_MASK,
+		led->flash_cfg->startup_dly);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Startup delay reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* Set timer control - safety or watchdog */
+	if (led->flash_cfg->safety_timer) {
+		rc = qpnp_led_masked_write(led,
+			FLASH_LED_TMR_CTRL(led->base),
+			FLASH_TMR_MASK, FLASH_TMR_SAFETY);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"LED timer ctrl reg write failed(%d)\n",
+				rc);
+			return rc;
+		}
+	}
+
+	/* Set Vreg force */
+	if (led->flash_cfg->vreg_ok)
+		rc = qpnp_led_masked_write(led,	FLASH_VREG_OK_FORCE(led->base),
+			FLASH_VREG_MASK, FLASH_SW_VREG_OK);
+	else
+		rc = qpnp_led_masked_write(led, FLASH_VREG_OK_FORCE(led->base),
+			FLASH_VREG_MASK, FLASH_HW_VREG_OK);
+
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Vreg OK reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* Set self fault check */
+	rc = qpnp_led_masked_write(led, FLASH_FAULT_DETECT(led->base),
+		FLASH_FAULT_DETECT_MASK, FLASH_SELFCHECK_ENABLE);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Fault detect reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* Set mask enable */
+	rc = qpnp_led_masked_write(led, FLASH_MASK_ENABLE(led->base),
+		FLASH_MASK_REG_MASK, FLASH_MASK_1);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Mask enable reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	/* Set current ramp */
+	rc = qpnp_led_masked_write(led, FLASH_CURRENT_RAMP(led->base),
+		FLASH_CURRENT_RAMP_MASK, FLASH_RAMP_STEP_27US);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Current ramp reg write failed(%d)\n", rc);
+		return rc;
+	}
+
+	led->flash_cfg->strobe_type = 0;
+
+	/* dump flash registers */
+	qpnp_dump_regs(led, flash_debug_regs, ARRAY_SIZE(flash_debug_regs));
+
+	return 0;
+}
+
+static int qpnp_kpdbl_init(struct qpnp_led_data *led)
+{
+	int rc;
+	uint val;
+
+	/* select row source - vbst or vph */
+	rc = regmap_read(led->regmap, KPDBL_ROW_SRC_SEL(led->base), &val);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Unable to read from addr=%x, rc(%d)\n",
+			KPDBL_ROW_SRC_SEL(led->base), rc);
+		return rc;
+	}
+
+	if (led->kpdbl_cfg->row_src_vbst)
+		val |= 1 << led->kpdbl_cfg->row_id;
+	else
+		val &= ~(1 << led->kpdbl_cfg->row_id);
+
+	rc = regmap_write(led->regmap, KPDBL_ROW_SRC_SEL(led->base), val);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Unable to read from addr=%x, rc(%d)\n",
+			KPDBL_ROW_SRC_SEL(led->base), rc);
+		return rc;
+	}
+
+	/* row source enable */
+	rc = regmap_read(led->regmap, KPDBL_ROW_SRC(led->base), &val);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Unable to read from addr=%x, rc(%d)\n",
+			KPDBL_ROW_SRC(led->base), rc);
+		return rc;
+	}
+
+	if (led->kpdbl_cfg->row_src_en)
+		val |= KPDBL_ROW_SCAN_EN_MASK | (1 << led->kpdbl_cfg->row_id);
+	else
+		val &= ~(1 << led->kpdbl_cfg->row_id);
+
+	rc = regmap_write(led->regmap, KPDBL_ROW_SRC(led->base), val);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Unable to write to addr=%x, rc(%d)\n",
+			KPDBL_ROW_SRC(led->base), rc);
+		return rc;
+	}
+
+	/* enable module */
+	rc = qpnp_led_masked_write(led, KPDBL_ENABLE(led->base),
+		KPDBL_MODULE_EN_MASK, KPDBL_MODULE_EN);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Enable module write failed(%d)\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_pwm_init(led->kpdbl_cfg->pwm_cfg, led->pdev,
+				led->cdev.name);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Failed to initialize pwm\n");
+		return rc;
+	}
+
+	if (led->kpdbl_cfg->always_on) {
+		kpdbl_master = led->kpdbl_cfg->pwm_cfg->pwm_dev;
+		kpdbl_master_period_us = led->kpdbl_cfg->pwm_cfg->pwm_period_us;
+	}
+
+	/* dump kpdbl registers */
+	qpnp_dump_regs(led, kpdbl_debug_regs, ARRAY_SIZE(kpdbl_debug_regs));
+
+	return 0;
+}
+
+static int qpnp_rgb_init(struct qpnp_led_data *led)
+{
+	int rc;
+
+	rc = qpnp_led_masked_write(led, RGB_LED_SRC_SEL(led->base),
+		RGB_LED_SRC_MASK, RGB_LED_SOURCE_VPH_PWR);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Failed to write led source select register\n");
+		return rc;
+	}
+
+	rc = qpnp_pwm_init(led->rgb_cfg->pwm_cfg, led->pdev, led->cdev.name);
+	if (rc) {
+		dev_err(&led->pdev->dev, "Failed to initialize pwm\n");
+		return rc;
+	}
+	/* Initialize led for use in auto trickle charging mode */
+	rc = qpnp_led_masked_write(led, RGB_LED_ATC_CTL(led->base),
+		led->rgb_cfg->enable, led->rgb_cfg->enable);
+
+	return 0;
+}
+
+static int qpnp_mpp_init(struct qpnp_led_data *led)
+{
+	int rc;
+	u8 val;
+
+
+	if (led->max_current < LED_MPP_CURRENT_MIN ||
+		led->max_current > LED_MPP_CURRENT_MAX) {
+		dev_err(&led->pdev->dev,
+			"max current for mpp is not valid\n");
+		return -EINVAL;
+	}
+
+	val = (led->mpp_cfg->current_setting / LED_MPP_CURRENT_PER_SETTING) - 1;
+
+	if (val < 0)
+		val = 0;
+
+	rc = qpnp_led_masked_write(led, LED_MPP_VIN_CTRL(led->base),
+		LED_MPP_VIN_MASK, led->mpp_cfg->vin_ctrl);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Failed to write led vin control reg\n");
+		return rc;
+	}
+
+	rc = qpnp_led_masked_write(led, LED_MPP_SINK_CTRL(led->base),
+		LED_MPP_SINK_MASK, val);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Failed to write sink control reg\n");
+		return rc;
+	}
+
+	if (led->mpp_cfg->pwm_mode != MANUAL_MODE) {
+		rc = qpnp_pwm_init(led->mpp_cfg->pwm_cfg, led->pdev,
+					led->cdev.name);
+		if (rc) {
+			dev_err(&led->pdev->dev,
+				"Failed to initialize pwm\n");
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_gpio_init(struct qpnp_led_data *led)
+{
+	int rc;
+
+	rc = qpnp_led_masked_write(led, LED_GPIO_VIN_CTRL(led->base),
+		LED_GPIO_VIN_MASK, led->gpio_cfg->vin_ctrl);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Failed to write led vin control reg\n");
+		return rc;
+	}
+
+	return 0;
+}
+
+static int qpnp_led_initialize(struct qpnp_led_data *led)
+{
+	int rc = 0;
+
+	switch (led->id) {
+	case QPNP_ID_WLED:
+		rc = qpnp_wled_init(led);
+		if (rc)
+			dev_err(&led->pdev->dev,
+				"WLED initialize failed(%d)\n", rc);
+		break;
+	case QPNP_ID_FLASH1_LED0:
+	case QPNP_ID_FLASH1_LED1:
+		rc = qpnp_flash_init(led);
+		if (rc)
+			dev_err(&led->pdev->dev,
+				"FLASH initialize failed(%d)\n", rc);
+		break;
+	case QPNP_ID_RGB_RED:
+	case QPNP_ID_RGB_GREEN:
+	case QPNP_ID_RGB_BLUE:
+		rc = qpnp_rgb_init(led);
+		if (rc)
+			dev_err(&led->pdev->dev,
+				"RGB initialize failed(%d)\n", rc);
+		break;
+	case QPNP_ID_LED_MPP:
+		rc = qpnp_mpp_init(led);
+		if (rc)
+			dev_err(&led->pdev->dev,
+				"MPP initialize failed(%d)\n", rc);
+		break;
+	case QPNP_ID_LED_GPIO:
+		rc = qpnp_gpio_init(led);
+		if (rc)
+			dev_err(&led->pdev->dev,
+				"GPIO initialize failed(%d)\n", rc);
+		break;
+	case QPNP_ID_KPDBL:
+		rc = qpnp_kpdbl_init(led);
+		if (rc)
+			dev_err(&led->pdev->dev,
+				"KPDBL initialize failed(%d)\n", rc);
+		break;
+	default:
+		dev_err(&led->pdev->dev, "Invalid LED(%d)\n", led->id);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int qpnp_get_common_configs(struct qpnp_led_data *led,
+				struct device_node *node)
+{
+	int rc;
+	u32 val;
+	const char *temp_string;
+
+	led->cdev.default_trigger = LED_TRIGGER_DEFAULT;
+	rc = of_property_read_string(node, "linux,default-trigger",
+		&temp_string);
+	if (!rc)
+		led->cdev.default_trigger = temp_string;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->default_on = false;
+	rc = of_property_read_string(node, "qcom,default-state",
+		&temp_string);
+	if (!rc) {
+		if (strcmp(temp_string, "on") == 0)
+			led->default_on = true;
+	} else if (rc != -EINVAL)
+		return rc;
+
+	led->turn_off_delay_ms = 0;
+	rc = of_property_read_u32(node, "qcom,turn-off-delay-ms", &val);
+	if (!rc)
+		led->turn_off_delay_ms = val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	return 0;
+}
+
+/*
+ * Handlers for alternative sources of platform_data
+ */
+static int qpnp_get_config_wled(struct qpnp_led_data *led,
+				struct device_node *node)
+{
+	u32 val;
+	uint tmp;
+	int rc;
+
+	led->wled_cfg = devm_kzalloc(&led->pdev->dev,
+				sizeof(struct wled_config_data), GFP_KERNEL);
+	if (!led->wled_cfg)
+		return -ENOMEM;
+
+	rc = regmap_read(led->regmap, PMIC_VERSION_REG, &tmp);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Unable to read pmic ver, rc(%d)\n", rc);
+	}
+	led->wled_cfg->pmic_version = (u8)tmp;
+
+	led->wled_cfg->num_strings = WLED_DEFAULT_STRINGS;
+	rc = of_property_read_u32(node, "qcom,num-strings", &val);
+	if (!rc)
+		led->wled_cfg->num_strings = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->wled_cfg->num_physical_strings = led->wled_cfg->num_strings;
+	rc = of_property_read_u32(node, "qcom,num-physical-strings", &val);
+	if (!rc)
+		led->wled_cfg->num_physical_strings = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->wled_cfg->ovp_val = WLED_DEFAULT_OVP_VAL;
+	rc = of_property_read_u32(node, "qcom,ovp-val", &val);
+	if (!rc)
+		led->wled_cfg->ovp_val = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->wled_cfg->boost_curr_lim = WLED_BOOST_LIM_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,boost-curr-lim", &val);
+	if (!rc)
+		led->wled_cfg->boost_curr_lim = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->wled_cfg->cp_select = WLED_CP_SEL_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,cp-sel", &val);
+	if (!rc)
+		led->wled_cfg->cp_select = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->wled_cfg->ctrl_delay_us = WLED_CTRL_DLY_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,ctrl-delay-us", &val);
+	if (!rc)
+		led->wled_cfg->ctrl_delay_us = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->wled_cfg->op_fdbck = WLED_OP_FDBCK_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,op-fdbck", &val);
+	if (!rc)
+		led->wled_cfg->op_fdbck = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->wled_cfg->switch_freq = WLED_SWITCH_FREQ_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,switch-freq", &val);
+	if (!rc)
+		led->wled_cfg->switch_freq = (u8) val;
+	else if (rc != -EINVAL)
+		return rc;
+
+	led->wled_cfg->dig_mod_gen_en =
+		of_property_read_bool(node, "qcom,dig-mod-gen-en");
+
+	led->wled_cfg->cs_out_en =
+		of_property_read_bool(node, "qcom,cs-out-en");
+
+	return 0;
+}
+
+static int qpnp_get_config_flash(struct qpnp_led_data *led,
+				struct device_node *node, bool *reg_set)
+{
+	int rc;
+	u32 val;
+	uint tmp;
+
+	led->flash_cfg = devm_kzalloc(&led->pdev->dev,
+				sizeof(struct flash_config_data), GFP_KERNEL);
+	if (!led->flash_cfg)
+		return -ENOMEM;
+
+	rc = regmap_read(led->regmap, FLASH_PERIPHERAL_SUBTYPE(led->base),
+			&tmp);
+	if (rc) {
+		dev_err(&led->pdev->dev,
+			"Unable to read from addr=%x, rc(%d)\n",
+			FLASH_PERIPHERAL_SUBTYPE(led->base), rc);
+	}
+	led->flash_cfg->peripheral_subtype = (u8)tmp;
+
+	led->flash_cfg->torch_enable =
+		of_property_read_bool(node, "qcom,torch-enable");
+
+	led->flash_cfg->no_smbb_support =
+		of_property_read_bool(node, "qcom,no-smbb-support");
+
+	if (of_find_property(of_get_parent(node), "flash-wa-supply",
+					NULL) && (!*reg_set)) {
+		led->flash_cfg->flash_wa_reg =
+			devm_regulator_get(&led->pdev->dev, "flash-wa");
+		if (IS_ERR_OR_NULL(led->flash_cfg->flash_wa_reg)) {
+			rc = PTR_ERR(led->flash_cfg->flash_wa_reg);
+			if (rc != EPROBE_DEFER) {
+				dev_err(&led->pdev->dev,
+					"Flash wa regulator get failed(%d)\n",
+					rc);
+			}
+		} else {
+			led->flash_cfg->flash_wa_reg_get = true;
+		}
+	}
+
+	if (led->id == QPNP_ID_FLASH1_LED0) {
+		led->flash_cfg->enable_module = FLASH_ENABLE_LED_0;
+		led->flash_cfg->current_addr = FLASH_LED_0_CURR(led->base);
+		led->flash_cfg->trigger_flash = FLASH_LED_0_OUTPUT;
+		if (!*reg_set) {
+			led->flash_cfg->flash_boost_reg =
+				regulator_get(&led->pdev->dev,
+							"flash-boost");
+			if (IS_ERR(led->flash_cfg->flash_boost_reg)) {
+				rc = PTR_ERR(led->flash_cfg->flash_boost_reg);
+				dev_err(&led->pdev->dev,
+					"Regulator get failed(%d)\n", rc);
+				goto error_get_flash_reg;
+			}
+			led->flash_cfg->flash_reg_get = true;
+			*reg_set = true;
+		} else
+			led->flash_cfg->flash_reg_get = false;
+
+		if (led->flash_cfg->torch_enable) {
+			led->flash_cfg->second_addr =
+						FLASH_LED_1_CURR(led->base);
+		}
+	} else if (led->id == QPNP_ID_FLASH1_LED1) {
+		led->flash_cfg->enable_module = FLASH_ENABLE_LED_1;
+		led->flash_cfg->current_addr = FLASH_LED_1_CURR(led->base);
+		led->flash_cfg->trigger_flash = FLASH_LED_1_OUTPUT;
+		if (!*reg_set) {
+			led->flash_cfg->flash_boost_reg =
+					regulator_get(&led->pdev->dev,
+								"flash-boost");
+			if (IS_ERR(led->flash_cfg->flash_boost_reg)) {
+				rc = PTR_ERR(led->flash_cfg->flash_boost_reg);
+				dev_err(&led->pdev->dev,
+					"Regulator get failed(%d)\n", rc);
+				goto error_get_flash_reg;
+			}
+			led->flash_cfg->flash_reg_get = true;
+			*reg_set = true;
+		} else
+			led->flash_cfg->flash_reg_get = false;
+
+		if (led->flash_cfg->torch_enable) {
+			led->flash_cfg->second_addr =
+						FLASH_LED_0_CURR(led->base);
+		}
+	} else {
+		dev_err(&led->pdev->dev, "Unknown flash LED name given\n");
+		return -EINVAL;
+	}
+
+	if (led->flash_cfg->torch_enable) {
+		if (of_find_property(of_get_parent(node), "torch-boost-supply",
+									NULL)) {
+			if (!led->flash_cfg->no_smbb_support) {
+				led->flash_cfg->torch_boost_reg =
+					regulator_get(&led->pdev->dev,
+								"torch-boost");
+				if (IS_ERR(led->flash_cfg->torch_boost_reg)) {
+					rc = PTR_ERR(led->flash_cfg->
+							torch_boost_reg);
+					dev_err(&led->pdev->dev,
+					"Torch regulator get failed(%d)\n", rc);
+					goto error_get_torch_reg;
+				}
+			}
+			led->flash_cfg->enable_module = FLASH_ENABLE_MODULE;
+		} else
+			led->flash_cfg->enable_module = FLASH_ENABLE_ALL;
+		led->flash_cfg->trigger_flash = FLASH_TORCH_OUTPUT;
+
+		rc = of_property_read_u32(node, "qcom,duration", &val);
+		if (!rc)
+			led->flash_cfg->duration = ((u8) val) - 2;
+		else if (rc == -EINVAL)
+			led->flash_cfg->duration = TORCH_DURATION_12s;
+		else {
+			if (led->flash_cfg->peripheral_subtype ==
+							FLASH_SUBTYPE_SINGLE)
+				goto error_get_flash_reg;
+			else if (led->flash_cfg->peripheral_subtype ==
+							FLASH_SUBTYPE_DUAL)
+				goto error_get_torch_reg;
+		}
+
+		rc = of_property_read_u32(node, "qcom,current", &val);
+		if (!rc)
+			led->flash_cfg->current_prgm = (val *
+				TORCH_MAX_LEVEL / led->max_current);
+		else {
+			if (led->flash_cfg->peripheral_subtype ==
+							FLASH_SUBTYPE_SINGLE)
+				goto error_get_flash_reg;
+			else if (led->flash_cfg->peripheral_subtype ==
+							FLASH_SUBTYPE_DUAL)
+				goto error_get_torch_reg;
+			goto error_get_torch_reg;
+		}
+
+		return 0;
+	}
+
+	rc = of_property_read_u32(node, "qcom,duration", &val);
+	if (!rc)
+		led->flash_cfg->duration = (u8)((val - 10) / 10);
+	else if (rc == -EINVAL)
+		led->flash_cfg->duration = FLASH_DURATION_200ms;
+	else
+		goto error_get_flash_reg;
+
+	rc = of_property_read_u32(node, "qcom,current", &val);
+	if (!rc)
+		led->flash_cfg->current_prgm = val * FLASH_MAX_LEVEL
+						/ led->max_current;
+	else
+		goto error_get_flash_reg;
+
+	rc = of_property_read_u32(node, "qcom,headroom", &val);
+	if (!rc)
+		led->flash_cfg->headroom = (u8) val;
+	else if (rc == -EINVAL)
+		led->flash_cfg->headroom = HEADROOM_500mV;
+	else
+		goto error_get_flash_reg;
+
+	rc = of_property_read_u32(node, "qcom,clamp-curr", &val);
+	if (!rc)
+		led->flash_cfg->clamp_curr = (val *
+				FLASH_MAX_LEVEL / led->max_current);
+	else if (rc == -EINVAL)
+		led->flash_cfg->clamp_curr = FLASH_CLAMP_200mA;
+	else
+		goto error_get_flash_reg;
+
+	rc = of_property_read_u32(node, "qcom,startup-dly", &val);
+	if (!rc)
+		led->flash_cfg->startup_dly = (u8) val;
+	else if (rc == -EINVAL)
+		led->flash_cfg->startup_dly = DELAY_128us;
+	else
+		goto error_get_flash_reg;
+
+	led->flash_cfg->safety_timer =
+		of_property_read_bool(node, "qcom,safety-timer");
+
+	led->flash_cfg->vreg_ok =
+		of_property_read_bool(node, "qcom,sw_vreg_ok");
+
+	return 0;
+
+error_get_torch_reg:
+	if (led->flash_cfg->no_smbb_support)
+		regulator_put(led->flash_cfg->flash_boost_reg);
+	else
+		regulator_put(led->flash_cfg->torch_boost_reg);
+
+error_get_flash_reg:
+	regulator_put(led->flash_cfg->flash_boost_reg);
+	return rc;
+
+}
+
+static int qpnp_get_config_pwm(struct pwm_config_data *pwm_cfg,
+				struct platform_device *pdev,
+				struct device_node *node)
+{
+	struct property *prop;
+	int rc, i, lut_max_size;
+	u32 val;
+	u8 *temp_cfg;
+	const char *led_label;
+
+	pwm_cfg->pwm_dev = of_pwm_get(node, NULL);
+
+	if (IS_ERR(pwm_cfg->pwm_dev)) {
+		rc = PTR_ERR(pwm_cfg->pwm_dev);
+		dev_err(&pdev->dev, "Cannot get PWM device rc:(%d)\n", rc);
+		pwm_cfg->pwm_dev = NULL;
+		return rc;
+	}
+
+	if (pwm_cfg->mode != MANUAL_MODE) {
+		rc = of_property_read_u32(node, "qcom,pwm-us", &val);
+		if (!rc)
+			pwm_cfg->pwm_period_us = val;
+		else
+			return rc;
+	}
+
+	pwm_cfg->use_blink =
+		of_property_read_bool(node, "qcom,use-blink");
+
+	if (pwm_cfg->mode == LPG_MODE || pwm_cfg->use_blink) {
+		pwm_cfg->duty_cycles =
+			devm_kzalloc(&pdev->dev,
+			sizeof(struct pwm_duty_cycles), GFP_KERNEL);
+		if (!pwm_cfg->duty_cycles) {
+			dev_err(&pdev->dev, "Unable to allocate memory\n");
+			rc = -ENOMEM;
+			goto bad_lpg_params;
+		}
+
+		prop = of_find_property(node, "qcom,duty-pcts",
+			&pwm_cfg->duty_cycles->num_duty_pcts);
+		if (!prop) {
+			dev_err(&pdev->dev, "Looking up property node qcom,duty-pcts failed\n");
+			rc =  -ENODEV;
+			goto bad_lpg_params;
+		} else if (!pwm_cfg->duty_cycles->num_duty_pcts) {
+			dev_err(&pdev->dev, "Invalid length of duty pcts\n");
+			rc =  -EINVAL;
+			goto bad_lpg_params;
+		}
+
+		rc = of_property_read_string(node, "label", &led_label);
+
+		if (rc < 0) {
+			dev_err(&pdev->dev,
+				"Failure reading label, rc = %d\n", rc);
+			return rc;
+		}
+
+		if (strcmp(led_label, "kpdbl") == 0)
+			lut_max_size = PWM_GPLED_LUT_MAX_SIZE;
+		else
+			lut_max_size = PWM_LUT_MAX_SIZE;
+
+		pwm_cfg->duty_cycles->duty_pcts =
+			devm_kzalloc(&pdev->dev,
+			sizeof(int) * lut_max_size,
+			GFP_KERNEL);
+		if (!pwm_cfg->duty_cycles->duty_pcts) {
+			dev_err(&pdev->dev, "Unable to allocate memory\n");
+			rc = -ENOMEM;
+			goto bad_lpg_params;
+		}
+
+		pwm_cfg->old_duty_pcts =
+			devm_kzalloc(&pdev->dev,
+			sizeof(int) * lut_max_size,
+			GFP_KERNEL);
+		if (!pwm_cfg->old_duty_pcts) {
+			dev_err(&pdev->dev, "Unable to allocate memory\n");
+			rc = -ENOMEM;
+			goto bad_lpg_params;
+		}
+
+		temp_cfg = devm_kzalloc(&pdev->dev,
+				pwm_cfg->duty_cycles->num_duty_pcts *
+				sizeof(u8), GFP_KERNEL);
+		if (!temp_cfg) {
+			dev_err(&pdev->dev, "Failed to allocate memory for duty pcts\n");
+			rc = -ENOMEM;
+			goto bad_lpg_params;
+		}
+
+		memcpy(temp_cfg, prop->value,
+			pwm_cfg->duty_cycles->num_duty_pcts);
+
+		for (i = 0; i < pwm_cfg->duty_cycles->num_duty_pcts; i++)
+			pwm_cfg->duty_cycles->duty_pcts[i] =
+				(int) temp_cfg[i];
+
+		rc = of_property_read_u32(node, "qcom,start-idx", &val);
+		if (!rc) {
+			pwm_cfg->lut_params.start_idx = val;
+			pwm_cfg->duty_cycles->start_idx = val;
+		} else
+			goto bad_lpg_params;
+
+		pwm_cfg->lut_params.lut_pause_hi = 0;
+		rc = of_property_read_u32(node, "qcom,pause-hi", &val);
+		if (!rc)
+			pwm_cfg->lut_params.lut_pause_hi = val;
+		else if (rc != -EINVAL)
+			goto bad_lpg_params;
+
+		pwm_cfg->lut_params.lut_pause_lo = 0;
+		rc = of_property_read_u32(node, "qcom,pause-lo", &val);
+		if (!rc)
+			pwm_cfg->lut_params.lut_pause_lo = val;
+		else if (rc != -EINVAL)
+			goto bad_lpg_params;
+
+		pwm_cfg->lut_params.ramp_step_ms =
+				QPNP_LUT_RAMP_STEP_DEFAULT;
+		rc = of_property_read_u32(node, "qcom,ramp-step-ms", &val);
+		if (!rc)
+			pwm_cfg->lut_params.ramp_step_ms = val;
+		else if (rc != -EINVAL)
+			goto bad_lpg_params;
+
+		pwm_cfg->lut_params.flags = QPNP_LED_PWM_FLAGS;
+		rc = of_property_read_u32(node, "qcom,lut-flags", &val);
+		if (!rc)
+			pwm_cfg->lut_params.flags = (u8) val;
+		else if (rc != -EINVAL)
+			goto bad_lpg_params;
+
+		pwm_cfg->lut_params.idx_len =
+			pwm_cfg->duty_cycles->num_duty_pcts;
+	}
+	return 0;
+
+bad_lpg_params:
+	pwm_cfg->use_blink = false;
+	if (pwm_cfg->mode == PWM_MODE) {
+		dev_err(&pdev->dev, "LPG parameters not set for blink mode, defaulting to PWM mode\n");
+		return 0;
+	}
+	return rc;
+};
+
+static int qpnp_led_get_mode(const char *mode)
+{
+	if (strcmp(mode, "manual") == 0)
+		return MANUAL_MODE;
+	else if (strcmp(mode, "pwm") == 0)
+		return PWM_MODE;
+	else if (strcmp(mode, "lpg") == 0)
+		return LPG_MODE;
+	else
+		return -EINVAL;
+};
+
+static int qpnp_get_config_kpdbl(struct qpnp_led_data *led,
+				struct device_node *node)
+{
+	int rc;
+	u32 val;
+	u8 led_mode;
+	const char *mode;
+
+	led->kpdbl_cfg = devm_kzalloc(&led->pdev->dev,
+				sizeof(struct kpdbl_config_data), GFP_KERNEL);
+	if (!led->kpdbl_cfg)
+		return -ENOMEM;
+
+	rc = of_property_read_string(node, "qcom,mode", &mode);
+	if (!rc) {
+		led_mode = qpnp_led_get_mode(mode);
+		if ((led_mode == MANUAL_MODE) || (led_mode == -EINVAL)) {
+			dev_err(&led->pdev->dev, "Selected mode not supported for kpdbl.\n");
+			return -EINVAL;
+		}
+		led->kpdbl_cfg->pwm_cfg = devm_kzalloc(&led->pdev->dev,
+					sizeof(struct pwm_config_data),
+					GFP_KERNEL);
+		if (!led->kpdbl_cfg->pwm_cfg)
+			return -ENOMEM;
+
+		led->kpdbl_cfg->pwm_cfg->mode = led_mode;
+		led->kpdbl_cfg->pwm_cfg->default_mode = led_mode;
+	} else {
+		return rc;
+	}
+
+	rc = qpnp_get_config_pwm(led->kpdbl_cfg->pwm_cfg, led->pdev,  node);
+	if (rc < 0)
+		return rc;
+
+	rc = of_property_read_u32(node, "qcom,row-id", &val);
+	if (!rc)
+		led->kpdbl_cfg->row_id = val;
+	else
+		return rc;
+
+	led->kpdbl_cfg->row_src_vbst =
+			of_property_read_bool(node, "qcom,row-src-vbst");
+
+	led->kpdbl_cfg->row_src_en =
+			of_property_read_bool(node, "qcom,row-src-en");
+
+	led->kpdbl_cfg->always_on =
+			of_property_read_bool(node, "qcom,always-on");
+
+	return 0;
+}
+
+static int qpnp_get_config_rgb(struct qpnp_led_data *led,
+				struct device_node *node)
+{
+	int rc;
+	u8 led_mode;
+	const char *mode;
+
+	led->rgb_cfg = devm_kzalloc(&led->pdev->dev,
+				sizeof(struct rgb_config_data), GFP_KERNEL);
+	if (!led->rgb_cfg)
+		return -ENOMEM;
+
+	if (led->id == QPNP_ID_RGB_RED)
+		led->rgb_cfg->enable = RGB_LED_ENABLE_RED;
+	else if (led->id == QPNP_ID_RGB_GREEN)
+		led->rgb_cfg->enable = RGB_LED_ENABLE_GREEN;
+	else if (led->id == QPNP_ID_RGB_BLUE)
+		led->rgb_cfg->enable = RGB_LED_ENABLE_BLUE;
+	else
+		return -EINVAL;
+
+	rc = of_property_read_string(node, "qcom,mode", &mode);
+	if (!rc) {
+		led_mode = qpnp_led_get_mode(mode);
+		if ((led_mode == MANUAL_MODE) || (led_mode == -EINVAL)) {
+			dev_err(&led->pdev->dev, "Selected mode not supported for rgb\n");
+			return -EINVAL;
+		}
+		led->rgb_cfg->pwm_cfg = devm_kzalloc(&led->pdev->dev,
+					sizeof(struct pwm_config_data),
+					GFP_KERNEL);
+		if (!led->rgb_cfg->pwm_cfg) {
+			dev_err(&led->pdev->dev,
+				"Unable to allocate memory\n");
+			return -ENOMEM;
+		}
+		led->rgb_cfg->pwm_cfg->mode = led_mode;
+		led->rgb_cfg->pwm_cfg->default_mode = led_mode;
+	} else {
+		return rc;
+	}
+
+	rc = qpnp_get_config_pwm(led->rgb_cfg->pwm_cfg, led->pdev, node);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int qpnp_get_config_mpp(struct qpnp_led_data *led,
+		struct device_node *node)
+{
+	int rc;
+	u32 val;
+	u8 led_mode;
+	const char *mode;
+
+	led->mpp_cfg = devm_kzalloc(&led->pdev->dev,
+			sizeof(struct mpp_config_data), GFP_KERNEL);
+	if (!led->mpp_cfg)
+		return -ENOMEM;
+
+	if (of_find_property(of_get_parent(node), "mpp-power-supply", NULL)) {
+		led->mpp_cfg->mpp_reg =
+				regulator_get(&led->pdev->dev,
+							"mpp-power");
+		if (IS_ERR(led->mpp_cfg->mpp_reg)) {
+			rc = PTR_ERR(led->mpp_cfg->mpp_reg);
+			dev_err(&led->pdev->dev,
+				"MPP regulator get failed(%d)\n", rc);
+			return rc;
+		}
+	}
+
+	if (led->mpp_cfg->mpp_reg) {
+		rc = of_property_read_u32(of_get_parent(node),
+					"qcom,mpp-power-max-voltage", &val);
+		if (!rc)
+			led->mpp_cfg->max_uV = val;
+		else
+			goto err_config_mpp;
+
+		rc = of_property_read_u32(of_get_parent(node),
+					"qcom,mpp-power-min-voltage", &val);
+		if (!rc)
+			led->mpp_cfg->min_uV = val;
+		else
+			goto err_config_mpp;
+	} else {
+		rc = of_property_read_u32(of_get_parent(node),
+					"qcom,mpp-power-max-voltage", &val);
+		if (!rc)
+			dev_warn(&led->pdev->dev, "No regulator specified\n");
+
+		rc = of_property_read_u32(of_get_parent(node),
+					"qcom,mpp-power-min-voltage", &val);
+		if (!rc)
+			dev_warn(&led->pdev->dev, "No regulator specified\n");
+	}
+
+	led->mpp_cfg->current_setting = LED_MPP_CURRENT_MIN;
+	rc = of_property_read_u32(node, "qcom,current-setting", &val);
+	if (!rc) {
+		if (led->mpp_cfg->current_setting < LED_MPP_CURRENT_MIN)
+			led->mpp_cfg->current_setting = LED_MPP_CURRENT_MIN;
+		else if (led->mpp_cfg->current_setting > LED_MPP_CURRENT_MAX)
+			led->mpp_cfg->current_setting = LED_MPP_CURRENT_MAX;
+		else
+			led->mpp_cfg->current_setting = (u8) val;
+	} else if (rc != -EINVAL)
+		goto err_config_mpp;
+
+	led->mpp_cfg->source_sel = LED_MPP_SOURCE_SEL_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,source-sel", &val);
+	if (!rc)
+		led->mpp_cfg->source_sel = (u8) val;
+	else if (rc != -EINVAL)
+		goto err_config_mpp;
+
+	led->mpp_cfg->mode_ctrl = LED_MPP_MODE_SINK;
+	rc = of_property_read_u32(node, "qcom,mode-ctrl", &val);
+	if (!rc)
+		led->mpp_cfg->mode_ctrl = (u8) val;
+	else if (rc != -EINVAL)
+		goto err_config_mpp;
+
+	led->mpp_cfg->vin_ctrl = LED_MPP_VIN_CTRL_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,vin-ctrl", &val);
+	if (!rc)
+		led->mpp_cfg->vin_ctrl = (u8) val;
+	else if (rc != -EINVAL)
+		goto err_config_mpp;
+
+	led->mpp_cfg->min_brightness = 0;
+	rc = of_property_read_u32(node, "qcom,min-brightness", &val);
+	if (!rc)
+		led->mpp_cfg->min_brightness = (u8) val;
+	else if (rc != -EINVAL)
+		goto err_config_mpp;
+
+	rc = of_property_read_string(node, "qcom,mode", &mode);
+	if (!rc) {
+		led_mode = qpnp_led_get_mode(mode);
+		led->mpp_cfg->pwm_mode = led_mode;
+		if (led_mode == MANUAL_MODE)
+			return MANUAL_MODE;
+		else if (led_mode == -EINVAL) {
+			dev_err(&led->pdev->dev, "Selected mode not supported for mpp\n");
+			rc = -EINVAL;
+			goto err_config_mpp;
+		}
+		led->mpp_cfg->pwm_cfg = devm_kzalloc(&led->pdev->dev,
+					sizeof(struct pwm_config_data),
+					GFP_KERNEL);
+		if (!led->mpp_cfg->pwm_cfg) {
+			dev_err(&led->pdev->dev,
+				"Unable to allocate memory\n");
+			rc = -ENOMEM;
+			goto err_config_mpp;
+		}
+		led->mpp_cfg->pwm_cfg->mode = led_mode;
+		led->mpp_cfg->pwm_cfg->default_mode = led_mode;
+	} else {
+		return rc;
+	}
+
+	rc = qpnp_get_config_pwm(led->mpp_cfg->pwm_cfg, led->pdev, node);
+	if (rc < 0)
+		goto err_config_mpp;
+
+	return 0;
+
+err_config_mpp:
+	if (led->mpp_cfg->mpp_reg)
+		regulator_put(led->mpp_cfg->mpp_reg);
+	return rc;
+}
+
+static int qpnp_get_config_gpio(struct qpnp_led_data *led,
+		struct device_node *node)
+{
+	int rc;
+	u32 val;
+
+	led->gpio_cfg = devm_kzalloc(&led->pdev->dev,
+			sizeof(struct gpio_config_data), GFP_KERNEL);
+	if (!led->gpio_cfg)
+		return -ENOMEM;
+
+	led->gpio_cfg->source_sel = LED_GPIO_SOURCE_SEL_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,source-sel", &val);
+	if (!rc)
+		led->gpio_cfg->source_sel = (u8) val;
+	else if (rc != -EINVAL)
+		goto err_config_gpio;
+
+	led->gpio_cfg->mode_ctrl = LED_GPIO_MODE_OUTPUT;
+	rc = of_property_read_u32(node, "qcom,mode-ctrl", &val);
+	if (!rc)
+		led->gpio_cfg->mode_ctrl = (u8) val;
+	else if (rc != -EINVAL)
+		goto err_config_gpio;
+
+	led->gpio_cfg->vin_ctrl = LED_GPIO_VIN_CTRL_DEFAULT;
+	rc = of_property_read_u32(node, "qcom,vin-ctrl", &val);
+	if (!rc)
+		led->gpio_cfg->vin_ctrl = (u8) val;
+	else if (rc != -EINVAL)
+		goto err_config_gpio;
+
+	return 0;
+
+err_config_gpio:
+	return rc;
+}
+
+static int qpnp_leds_probe(struct platform_device *pdev)
+{
+	struct qpnp_led_data *led, *led_array;
+	unsigned int base;
+	struct device_node *node, *temp;
+	int rc, i, num_leds = 0, parsed_leds = 0;
+	const char *led_label;
+	bool regulator_probe = false;
+
+	node = pdev->dev.of_node;
+	if (node == NULL)
+		return -ENODEV;
+
+	temp = NULL;
+	while ((temp = of_get_next_child(node, temp)))
+		num_leds++;
+
+	if (!num_leds)
+		return -ECHILD;
+
+	led_array = devm_kcalloc(&pdev->dev, num_leds, sizeof(*led_array),
+				GFP_KERNEL);
+	if (!led_array)
+		return -ENOMEM;
+
+	for_each_child_of_node(node, temp) {
+		led = &led_array[parsed_leds];
+		led->num_leds = num_leds;
+		led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+		if (!led->regmap) {
+			dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+			return -EINVAL;
+		}
+		led->pdev = pdev;
+
+		rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+		if (rc < 0) {
+			dev_err(&pdev->dev,
+				"Couldn't find reg in node = %s rc = %d\n",
+				pdev->dev.of_node->full_name, rc);
+			goto fail_id_check;
+		}
+		led->base = base;
+
+		rc = of_property_read_string(temp, "label", &led_label);
+		if (rc < 0) {
+			dev_err(&led->pdev->dev,
+				"Failure reading label, rc = %d\n", rc);
+			goto fail_id_check;
+		}
+
+		rc = of_property_read_string(temp, "linux,name",
+			&led->cdev.name);
+		if (rc < 0) {
+			dev_err(&led->pdev->dev,
+				"Failure reading led name, rc = %d\n", rc);
+			goto fail_id_check;
+		}
+
+		rc = of_property_read_u32(temp, "qcom,max-current",
+			&led->max_current);
+		if (rc < 0) {
+			dev_err(&led->pdev->dev,
+				"Failure reading max_current, rc =  %d\n", rc);
+			goto fail_id_check;
+		}
+
+		rc = of_property_read_u32(temp, "qcom,id", &led->id);
+		if (rc < 0) {
+			dev_err(&led->pdev->dev,
+				"Failure reading led id, rc =  %d\n", rc);
+			goto fail_id_check;
+		}
+
+		rc = qpnp_get_common_configs(led, temp);
+		if (rc) {
+			dev_err(&led->pdev->dev, "Failure reading common led configuration, rc = %d\n",
+				rc);
+			goto fail_id_check;
+		}
+
+		led->cdev.brightness_set    = qpnp_led_set;
+		led->cdev.brightness_get    = qpnp_led_get;
+
+		if (strcmp(led_label, "wled") == 0) {
+			rc = qpnp_get_config_wled(led, temp);
+			if (rc < 0) {
+				dev_err(&led->pdev->dev,
+					"Unable to read wled config data\n");
+				goto fail_id_check;
+			}
+		} else if (strcmp(led_label, "flash") == 0) {
+			if (!of_find_property(node, "flash-boost-supply", NULL))
+				regulator_probe = true;
+			rc = qpnp_get_config_flash(led, temp, &regulator_probe);
+			if (rc < 0) {
+				dev_err(&led->pdev->dev,
+					"Unable to read flash config data\n");
+				goto fail_id_check;
+			}
+		} else if (strcmp(led_label, "rgb") == 0) {
+			rc = qpnp_get_config_rgb(led, temp);
+			if (rc < 0) {
+				dev_err(&led->pdev->dev,
+					"Unable to read rgb config data\n");
+				goto fail_id_check;
+			}
+		} else if (strcmp(led_label, "mpp") == 0) {
+			rc = qpnp_get_config_mpp(led, temp);
+			if (rc < 0) {
+				dev_err(&led->pdev->dev,
+						"Unable to read mpp config data\n");
+				goto fail_id_check;
+			}
+		} else if (strcmp(led_label, "gpio") == 0) {
+			rc = qpnp_get_config_gpio(led, temp);
+			if (rc < 0) {
+				dev_err(&led->pdev->dev,
+						"Unable to read gpio config data\n");
+				goto fail_id_check;
+			}
+		} else if (strcmp(led_label, "kpdbl") == 0) {
+			bitmap_zero(kpdbl_leds_in_use, NUM_KPDBL_LEDS);
+			is_kpdbl_master_turn_on = false;
+			rc = qpnp_get_config_kpdbl(led, temp);
+			if (rc < 0) {
+				dev_err(&led->pdev->dev,
+					"Unable to read kpdbl config data\n");
+				goto fail_id_check;
+			}
+		} else {
+			dev_err(&led->pdev->dev, "No LED matching label\n");
+			rc = -EINVAL;
+			goto fail_id_check;
+		}
+
+		if (led->id != QPNP_ID_FLASH1_LED0 &&
+					led->id != QPNP_ID_FLASH1_LED1)
+			mutex_init(&led->lock);
+
+		led->in_order_command_processing = of_property_read_bool
+				(temp, "qcom,in-order-command-processing");
+
+		if (led->in_order_command_processing) {
+			/*
+			 * the command order from user space needs to be
+			 * maintained use ordered workqueue to prevent
+			 * concurrency
+			 */
+			led->workqueue = alloc_ordered_workqueue
+							("led_workqueue", 0);
+			if (!led->workqueue) {
+				rc = -ENOMEM;
+				goto fail_id_check;
+			}
+		}
+
+		INIT_WORK(&led->work, qpnp_led_work);
+
+		rc =  qpnp_led_initialize(led);
+		if (rc < 0)
+			goto fail_id_check;
+
+		rc = qpnp_led_set_max_brightness(led);
+		if (rc < 0)
+			goto fail_id_check;
+
+		rc = led_classdev_register(&pdev->dev, &led->cdev);
+		if (rc) {
+			dev_err(&pdev->dev,
+				"unable to register led %d,rc=%d\n",
+						 led->id, rc);
+			goto fail_id_check;
+		}
+
+		if (led->id == QPNP_ID_FLASH1_LED0 ||
+			led->id == QPNP_ID_FLASH1_LED1) {
+			rc = sysfs_create_group(&led->cdev.dev->kobj,
+							&led_attr_group);
+			if (rc)
+				goto fail_id_check;
+
+		}
+
+		if (led->id == QPNP_ID_LED_MPP) {
+			if (!led->mpp_cfg->pwm_cfg)
+				break;
+			if (led->mpp_cfg->pwm_cfg->mode == PWM_MODE) {
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&pwm_attr_group);
+				if (rc)
+					goto fail_id_check;
+			}
+			if (led->mpp_cfg->pwm_cfg->use_blink) {
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&blink_attr_group);
+				if (rc)
+					goto fail_id_check;
+
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&lpg_attr_group);
+				if (rc)
+					goto fail_id_check;
+			} else if (led->mpp_cfg->pwm_cfg->mode == LPG_MODE) {
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&lpg_attr_group);
+				if (rc)
+					goto fail_id_check;
+			}
+		} else if ((led->id == QPNP_ID_RGB_RED) ||
+			(led->id == QPNP_ID_RGB_GREEN) ||
+			(led->id == QPNP_ID_RGB_BLUE)) {
+			if (led->rgb_cfg->pwm_cfg->mode == PWM_MODE) {
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&pwm_attr_group);
+				if (rc)
+					goto fail_id_check;
+			}
+			if (led->rgb_cfg->pwm_cfg->use_blink) {
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&blink_attr_group);
+				if (rc)
+					goto fail_id_check;
+
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&lpg_attr_group);
+				if (rc)
+					goto fail_id_check;
+			} else if (led->rgb_cfg->pwm_cfg->mode == LPG_MODE) {
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&lpg_attr_group);
+				if (rc)
+					goto fail_id_check;
+			}
+		} else if (led->id == QPNP_ID_KPDBL) {
+			if (led->kpdbl_cfg->pwm_cfg->mode == PWM_MODE) {
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&pwm_attr_group);
+				if (rc)
+					goto fail_id_check;
+			}
+			if (led->kpdbl_cfg->pwm_cfg->use_blink) {
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&blink_attr_group);
+				if (rc)
+					goto fail_id_check;
+
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&lpg_attr_group);
+				if (rc)
+					goto fail_id_check;
+			} else if (led->kpdbl_cfg->pwm_cfg->mode == LPG_MODE) {
+				rc = sysfs_create_group(&led->cdev.dev->kobj,
+					&lpg_attr_group);
+				if (rc)
+					goto fail_id_check;
+			}
+		}
+
+		/* configure default state */
+		if (led->default_on) {
+			led->cdev.brightness = led->cdev.max_brightness;
+			__qpnp_led_work(led, led->cdev.brightness);
+			if (led->turn_off_delay_ms > 0)
+				qpnp_led_turn_off(led);
+		} else
+			led->cdev.brightness = LED_OFF;
+
+		parsed_leds++;
+	}
+	dev_set_drvdata(&pdev->dev, led_array);
+	return 0;
+
+fail_id_check:
+	for (i = 0; i < parsed_leds; i++) {
+		if (led_array[i].id != QPNP_ID_FLASH1_LED0 &&
+				led_array[i].id != QPNP_ID_FLASH1_LED1)
+			mutex_destroy(&led_array[i].lock);
+		if (led_array[i].in_order_command_processing)
+			destroy_workqueue(led_array[i].workqueue);
+		led_classdev_unregister(&led_array[i].cdev);
+	}
+
+	return rc;
+}
+
+static int qpnp_leds_remove(struct platform_device *pdev)
+{
+	struct qpnp_led_data *led_array  = dev_get_drvdata(&pdev->dev);
+	int i, parsed_leds = led_array->num_leds;
+
+	for (i = 0; i < parsed_leds; i++) {
+		cancel_work_sync(&led_array[i].work);
+		if (led_array[i].id != QPNP_ID_FLASH1_LED0 &&
+				led_array[i].id != QPNP_ID_FLASH1_LED1)
+			mutex_destroy(&led_array[i].lock);
+
+		if (led_array[i].in_order_command_processing)
+			destroy_workqueue(led_array[i].workqueue);
+		led_classdev_unregister(&led_array[i].cdev);
+		switch (led_array[i].id) {
+		case QPNP_ID_WLED:
+			break;
+		case QPNP_ID_FLASH1_LED0:
+		case QPNP_ID_FLASH1_LED1:
+			if (led_array[i].flash_cfg->flash_reg_get)
+				regulator_put(
+				       led_array[i].flash_cfg->flash_boost_reg);
+			if (led_array[i].flash_cfg->torch_enable)
+				if (!led_array[i].flash_cfg->no_smbb_support)
+					regulator_put(led_array[i].
+					flash_cfg->torch_boost_reg);
+			sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&led_attr_group);
+			break;
+		case QPNP_ID_RGB_RED:
+		case QPNP_ID_RGB_GREEN:
+		case QPNP_ID_RGB_BLUE:
+			if (led_array[i].rgb_cfg->pwm_cfg->mode == PWM_MODE)
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&pwm_attr_group);
+			if (led_array[i].rgb_cfg->pwm_cfg->use_blink) {
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&blink_attr_group);
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&lpg_attr_group);
+			} else if (led_array[i].rgb_cfg->pwm_cfg->mode
+				   == LPG_MODE)
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&lpg_attr_group);
+			break;
+		case QPNP_ID_LED_MPP:
+			if (!led_array[i].mpp_cfg->pwm_cfg)
+				break;
+			if (led_array[i].mpp_cfg->pwm_cfg->mode == PWM_MODE)
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&pwm_attr_group);
+			if (led_array[i].mpp_cfg->pwm_cfg->use_blink) {
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&blink_attr_group);
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&lpg_attr_group);
+			} else if (led_array[i].mpp_cfg->pwm_cfg->mode
+				   == LPG_MODE)
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&lpg_attr_group);
+			if (led_array[i].mpp_cfg->mpp_reg)
+				regulator_put(led_array[i].mpp_cfg->mpp_reg);
+			break;
+		case QPNP_ID_KPDBL:
+			if (led_array[i].kpdbl_cfg->pwm_cfg->mode == PWM_MODE)
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&pwm_attr_group);
+			if (led_array[i].kpdbl_cfg->pwm_cfg->use_blink) {
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&blink_attr_group);
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&lpg_attr_group);
+			} else if (led_array[i].kpdbl_cfg->pwm_cfg->mode
+				   == LPG_MODE)
+				sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+							&lpg_attr_group);
+			break;
+		default:
+			dev_err(&led_array->pdev->dev,
+					"Invalid LED(%d)\n",
+					led_array[i].id);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id spmi_match_table[] = {
+	{ .compatible = "qcom,leds-qpnp",},
+	{ },
+};
+#else
+#define spmi_match_table NULL
+#endif
+
+static struct platform_driver qpnp_leds_driver = {
+	.driver		= {
+		.name		= "qcom,leds-qpnp",
+		.of_match_table	= spmi_match_table,
+	},
+	.probe		= qpnp_leds_probe,
+	.remove		= qpnp_leds_remove,
+};
+
+static int __init qpnp_led_init(void)
+{
+	return platform_driver_register(&qpnp_leds_driver);
+}
+module_init(qpnp_led_init);
+
+static void __exit qpnp_led_exit(void)
+{
+	platform_driver_unregister(&qpnp_leds_driver);
+}
+module_exit(qpnp_led_exit);
+
+MODULE_DESCRIPTION("QPNP LEDs driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("leds:leds-qpnp");
+
diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index 7d38e6b..dbb9a36 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -21,6 +21,22 @@
 	return led_cdev->brightness;
 }
 
+static inline struct led_classdev *trigger_to_lcdev(struct led_trigger *trig)
+{
+	struct led_classdev *led_cdev;
+
+	read_lock(&trig->leddev_list_lock);
+	list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) {
+		if (!strcmp(led_cdev->default_trigger, trig->name)) {
+			read_unlock(&trig->leddev_list_lock);
+			return led_cdev;
+		}
+	}
+
+	read_unlock(&trig->leddev_list_lock);
+	return NULL;
+}
+
 void led_init_core(struct led_classdev *led_cdev);
 void led_stop_software_blink(struct led_classdev *led_cdev);
 void led_set_brightness_nopm(struct led_classdev *led_cdev,
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 5f6083d1..1239e68 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -775,6 +775,17 @@
 	  Say M here if you want to include support for PM8921 chip as a module.
 	  This will build a module called "pm8921-core".
 
+config MFD_I2C_PMIC
+	tristate "QTI I2C PMIC support"
+	depends on I2C && OF
+	select IRQ_DOMAIN
+	select REGMAP_I2C
+	help
+	  This enables support for controlling Qualcomm Technologies, Inc.
+	  PMICs over I2C. The driver controls interrupts, and provides register
+	  access for all of the device's peripherals.  Some QTI PMIC chips
+	  support communication over both I2C and SPMI.
+
 config MFD_QCOM_RPM
 	tristate "Qualcomm Resource Power Manager (RPM)"
 	depends on ARCH_QCOM && OF
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index cdae17f..b2fe74b 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -173,6 +173,7 @@
 obj-$(CONFIG_MFD_CS5535)	+= cs5535-mfd.o
 obj-$(CONFIG_MFD_OMAP_USB_HOST)	+= omap-usb-host.o omap-usb-tll.o
 obj-$(CONFIG_MFD_PM8921_CORE) 	+= pm8921-core.o ssbi.o
+obj-$(CONFIG_MFD_I2C_PMIC)	+= qcom-i2c-pmic.o
 obj-$(CONFIG_MFD_QCOM_RPM)	+= qcom_rpm.o
 obj-$(CONFIG_MFD_SPMI_PMIC)	+= qcom-spmi-pmic.o
 obj-$(CONFIG_TPS65911_COMPARATOR)	+= tps65911-comparator.o
diff --git a/drivers/mfd/qcom-i2c-pmic.c b/drivers/mfd/qcom-i2c-pmic.c
new file mode 100644
index 0000000..590e4c1
--- /dev/null
+++ b/drivers/mfd/qcom-i2c-pmic.c
@@ -0,0 +1,681 @@
+/* Copyright (c) 2016-2017 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) "I2C PMIC: %s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define I2C_INTR_STATUS_BASE	0x0550
+#define INT_RT_STS_OFFSET	0x10
+#define INT_SET_TYPE_OFFSET	0x11
+#define INT_POL_HIGH_OFFSET	0x12
+#define INT_POL_LOW_OFFSET	0x13
+#define INT_LATCHED_CLR_OFFSET	0x14
+#define INT_EN_SET_OFFSET	0x15
+#define INT_EN_CLR_OFFSET	0x16
+#define INT_LATCHED_STS_OFFSET	0x18
+#define INT_PENDING_STS_OFFSET	0x19
+#define INT_MID_SEL_OFFSET	0x1A
+#define INT_MID_SEL_MASK	GENMASK(1, 0)
+#define INT_PRIORITY_OFFSET	0x1B
+#define INT_PRIORITY_BIT	BIT(0)
+
+enum {
+	IRQ_SET_TYPE = 0,
+	IRQ_POL_HIGH,
+	IRQ_POL_LOW,
+	IRQ_LATCHED_CLR, /* not needed but makes life easy */
+	IRQ_EN_SET,
+	IRQ_MAX_REGS,
+};
+
+struct i2c_pmic_periph {
+	void		*data;
+	u16		addr;
+	u8		cached[IRQ_MAX_REGS];
+	u8		synced[IRQ_MAX_REGS];
+	u8		wake;
+	struct mutex	lock;
+};
+
+struct i2c_pmic {
+	struct device		*dev;
+	struct regmap		*regmap;
+	struct irq_domain	*domain;
+	struct i2c_pmic_periph	*periph;
+	struct pinctrl		*pinctrl;
+	const char		*pinctrl_name;
+	int			num_periphs;
+};
+
+static void i2c_pmic_irq_bus_lock(struct irq_data *d)
+{
+	struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+
+	mutex_lock(&periph->lock);
+}
+
+static void i2c_pmic_sync_type_polarity(struct i2c_pmic *chip,
+			       struct i2c_pmic_periph *periph)
+{
+	int rc;
+
+	/* did any irq type change? */
+	if (periph->cached[IRQ_SET_TYPE] ^ periph->synced[IRQ_SET_TYPE]) {
+		rc = regmap_write(chip->regmap,
+				  periph->addr | INT_SET_TYPE_OFFSET,
+				  periph->cached[IRQ_SET_TYPE]);
+		if (rc < 0) {
+			pr_err("Couldn't set periph 0x%04x irqs 0x%02x type rc=%d\n",
+				periph->addr, periph->cached[IRQ_SET_TYPE], rc);
+			return;
+		}
+
+		periph->synced[IRQ_SET_TYPE] = periph->cached[IRQ_SET_TYPE];
+	}
+
+	/* did any polarity high change? */
+	if (periph->cached[IRQ_POL_HIGH] ^ periph->synced[IRQ_POL_HIGH]) {
+		rc = regmap_write(chip->regmap,
+				  periph->addr | INT_POL_HIGH_OFFSET,
+				  periph->cached[IRQ_POL_HIGH]);
+		if (rc < 0) {
+			pr_err("Couldn't set periph 0x%04x irqs 0x%02x polarity high rc=%d\n",
+				periph->addr, periph->cached[IRQ_POL_HIGH], rc);
+			return;
+		}
+
+		periph->synced[IRQ_POL_HIGH] = periph->cached[IRQ_POL_HIGH];
+	}
+
+	/* did any polarity low change? */
+	if (periph->cached[IRQ_POL_LOW] ^ periph->synced[IRQ_POL_LOW]) {
+		rc = regmap_write(chip->regmap,
+				  periph->addr | INT_POL_LOW_OFFSET,
+				  periph->cached[IRQ_POL_LOW]);
+		if (rc < 0) {
+			pr_err("Couldn't set periph 0x%04x irqs 0x%02x polarity low rc=%d\n",
+				periph->addr, periph->cached[IRQ_POL_LOW], rc);
+			return;
+		}
+
+		periph->synced[IRQ_POL_LOW] = periph->cached[IRQ_POL_LOW];
+	}
+}
+
+static void i2c_pmic_sync_enable(struct i2c_pmic *chip,
+				 struct i2c_pmic_periph *periph)
+{
+	u8 en_set, en_clr;
+	int rc;
+
+	/* determine which irqs were enabled and which were disabled */
+	en_clr = periph->synced[IRQ_EN_SET] & ~periph->cached[IRQ_EN_SET];
+	en_set = ~periph->synced[IRQ_EN_SET] & periph->cached[IRQ_EN_SET];
+
+	/* were any irqs disabled? */
+	if (en_clr) {
+		rc = regmap_write(chip->regmap,
+				  periph->addr | INT_EN_CLR_OFFSET, en_clr);
+		if (rc < 0) {
+			pr_err("Couldn't disable periph 0x%04x irqs 0x%02x rc=%d\n",
+				periph->addr, en_clr, rc);
+			return;
+		}
+	}
+
+	/* were any irqs enabled? */
+	if (en_set) {
+		rc = regmap_write(chip->regmap,
+				  periph->addr | INT_EN_SET_OFFSET, en_set);
+		if (rc < 0) {
+			pr_err("Couldn't enable periph 0x%04x irqs 0x%02x rc=%d\n",
+				periph->addr, en_set, rc);
+			return;
+		}
+	}
+
+	/* irq enabled status was written to hardware */
+	periph->synced[IRQ_EN_SET] = periph->cached[IRQ_EN_SET];
+}
+
+static void i2c_pmic_irq_bus_sync_unlock(struct irq_data *d)
+{
+	struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+	struct i2c_pmic *chip = periph->data;
+
+	i2c_pmic_sync_type_polarity(chip, periph);
+	i2c_pmic_sync_enable(chip, periph);
+	mutex_unlock(&periph->lock);
+}
+
+static void i2c_pmic_irq_disable(struct irq_data *d)
+{
+	struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+
+	periph->cached[IRQ_EN_SET] &= ~d->hwirq & 0xFF;
+}
+
+static void i2c_pmic_irq_enable(struct irq_data *d)
+{
+	struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+
+	periph->cached[IRQ_EN_SET] |= d->hwirq & 0xFF;
+}
+
+static int i2c_pmic_irq_set_type(struct irq_data *d, unsigned int irq_type)
+{
+	struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+
+	switch (irq_type) {
+	case IRQ_TYPE_EDGE_RISING:
+		periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF;
+		periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF;
+		periph->cached[IRQ_POL_LOW] &= ~d->hwirq & 0xFF;
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF;
+		periph->cached[IRQ_POL_HIGH] &= ~d->hwirq & 0xFF;
+		periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF;
+		break;
+	case IRQ_TYPE_EDGE_BOTH:
+		periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF;
+		periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF;
+		periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF;
+		break;
+	case IRQ_TYPE_LEVEL_HIGH:
+		periph->cached[IRQ_SET_TYPE] &= ~d->hwirq & 0xFF;
+		periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF;
+		periph->cached[IRQ_POL_LOW] &= ~d->hwirq & 0xFF;
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+		periph->cached[IRQ_SET_TYPE] &= ~d->hwirq & 0xFF;
+		periph->cached[IRQ_POL_HIGH] &= ~d->hwirq & 0xFF;
+		periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF;
+		break;
+	default:
+		pr_err("irq type 0x%04x is not supported\n", irq_type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int i2c_pmic_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+	struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+
+	if (on)
+		periph->wake |= d->hwirq & 0xFF;
+	else
+		periph->wake &= ~d->hwirq & 0xFF;
+
+	return 0;
+}
+#else
+#define i2c_pmic_irq_set_wake NULL
+#endif
+
+static struct irq_chip i2c_pmic_irq_chip = {
+	.name			= "i2c_pmic_irq_chip",
+	.irq_bus_lock		= i2c_pmic_irq_bus_lock,
+	.irq_bus_sync_unlock	= i2c_pmic_irq_bus_sync_unlock,
+	.irq_disable		= i2c_pmic_irq_disable,
+	.irq_enable		= i2c_pmic_irq_enable,
+	.irq_set_type		= i2c_pmic_irq_set_type,
+	.irq_set_wake		= i2c_pmic_irq_set_wake,
+};
+
+static struct i2c_pmic_periph *i2c_pmic_find_periph(struct i2c_pmic *chip,
+						    irq_hw_number_t hwirq)
+{
+	int i;
+
+	for (i = 0; i < chip->num_periphs; i++)
+		if (chip->periph[i].addr == (hwirq & 0xFF00))
+			return &chip->periph[i];
+
+	pr_err_ratelimited("Couldn't find periph struct for hwirq 0x%04lx\n",
+			   hwirq);
+	return NULL;
+}
+
+static int i2c_pmic_domain_map(struct irq_domain *d, unsigned int virq,
+			irq_hw_number_t hwirq)
+{
+	struct i2c_pmic *chip = d->host_data;
+	struct i2c_pmic_periph *periph = i2c_pmic_find_periph(chip, hwirq);
+
+	if (!periph)
+		return -ENODEV;
+
+	irq_set_chip_data(virq, periph);
+	irq_set_chip_and_handler(virq, &i2c_pmic_irq_chip, handle_level_irq);
+	irq_set_nested_thread(virq, 1);
+	irq_set_noprobe(virq);
+	return 0;
+}
+
+static int i2c_pmic_domain_xlate(struct irq_domain *d,
+				 struct device_node *ctrlr, const u32 *intspec,
+				 unsigned int intsize, unsigned long *out_hwirq,
+				 unsigned int *out_type)
+{
+	if (intsize != 3)
+		return -EINVAL;
+
+	if (intspec[0] > 0xFF || intspec[1] > 0x7 ||
+					intspec[2] > IRQ_TYPE_SENSE_MASK)
+		return -EINVAL;
+
+	/*
+	 * Interrupt specifiers are triplets
+	 * <peripheral-address, irq-number, IRQ_TYPE_*>
+	 *
+	 * peripheral-address - The base address of the peripheral
+	 * irq-number	      - The zero based bit position of the peripheral's
+	 *			interrupt registers corresponding to the irq
+	 *			where the LSB is 0 and the MSB is 7
+	 * IRQ_TYPE_*	      - Please refer to linux/irq.h
+	 */
+	*out_hwirq = intspec[0] << 8 | BIT(intspec[1]);
+	*out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;
+
+	return 0;
+}
+
+static const struct irq_domain_ops i2c_pmic_domain_ops = {
+	.map	= i2c_pmic_domain_map,
+	.xlate	= i2c_pmic_domain_xlate,
+};
+
+static void i2c_pmic_irq_ack_now(struct i2c_pmic *chip, u16 hwirq)
+{
+	int rc;
+
+	rc = regmap_write(chip->regmap,
+			  (hwirq & 0xFF00) | INT_LATCHED_CLR_OFFSET,
+			  hwirq & 0xFF);
+	if (rc < 0)
+		pr_err_ratelimited("Couldn't ack 0x%04x rc=%d\n", hwirq, rc);
+}
+
+static void i2c_pmic_irq_disable_now(struct i2c_pmic *chip, u16 hwirq)
+{
+	struct i2c_pmic_periph *periph = i2c_pmic_find_periph(chip, hwirq);
+	int rc;
+
+	if (!periph)
+		return;
+
+	mutex_lock(&periph->lock);
+	periph->cached[IRQ_EN_SET] &= ~hwirq & 0xFF;
+
+	rc = regmap_write(chip->regmap,
+			  (hwirq & 0xFF00) | INT_EN_CLR_OFFSET,
+			  hwirq & 0xFF);
+	if (rc < 0) {
+		pr_err_ratelimited("Couldn't disable irq 0x%04x rc=%d\n",
+				   hwirq, rc);
+		goto unlock;
+	}
+
+	periph->synced[IRQ_EN_SET] = periph->cached[IRQ_EN_SET];
+
+unlock:
+	mutex_unlock(&periph->lock);
+}
+
+static void i2c_pmic_periph_status_handler(struct i2c_pmic *chip,
+					   u16 periph_address, u8 periph_status)
+{
+	unsigned int hwirq, virq;
+	int i;
+
+	while (periph_status) {
+		i = ffs(periph_status) - 1;
+		periph_status &= ~BIT(i);
+		hwirq = periph_address | BIT(i);
+		virq = irq_find_mapping(chip->domain, hwirq);
+		if (virq == 0) {
+			pr_err_ratelimited("Couldn't find mapping; disabling 0x%04x\n",
+					   hwirq);
+			i2c_pmic_irq_disable_now(chip, hwirq);
+			continue;
+		}
+
+		handle_nested_irq(virq);
+		i2c_pmic_irq_ack_now(chip, hwirq);
+	}
+}
+
+static void i2c_pmic_summary_status_handler(struct i2c_pmic *chip,
+					    struct i2c_pmic_periph *periph,
+					    u8 summary_status)
+{
+	unsigned int periph_status;
+	int rc, i;
+
+	while (summary_status) {
+		i = ffs(summary_status) - 1;
+		summary_status &= ~BIT(i);
+
+		rc = regmap_read(chip->regmap,
+				 periph[i].addr | INT_LATCHED_STS_OFFSET,
+				 &periph_status);
+		if (rc < 0) {
+			pr_err_ratelimited("Couldn't read 0x%04x | INT_LATCHED_STS rc=%d\n",
+					   periph[i].addr, rc);
+			continue;
+		}
+
+		i2c_pmic_periph_status_handler(chip, periph[i].addr,
+					       periph_status);
+	}
+}
+
+static irqreturn_t i2c_pmic_irq_handler(int irq, void *dev_id)
+{
+	struct i2c_pmic *chip = dev_id;
+	struct i2c_pmic_periph *periph;
+	unsigned int summary_status;
+	int rc, i;
+
+	for (i = 0; i < DIV_ROUND_UP(chip->num_periphs, BITS_PER_BYTE); i++) {
+		rc = regmap_read(chip->regmap, I2C_INTR_STATUS_BASE + i,
+				&summary_status);
+		if (rc < 0) {
+			pr_err_ratelimited("Couldn't read I2C_INTR_STATUS%d rc=%d\n",
+					   i, rc);
+			continue;
+		}
+
+		if (summary_status == 0)
+			continue;
+
+		periph = &chip->periph[i * 8];
+		i2c_pmic_summary_status_handler(chip, periph, summary_status);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int i2c_pmic_parse_dt(struct i2c_pmic *chip)
+{
+	struct device_node *node = chip->dev->of_node;
+	int rc, i;
+	u32 temp;
+
+	if (!node) {
+		pr_err("missing device tree\n");
+		return -EINVAL;
+	}
+
+	chip->num_periphs = of_property_count_u32_elems(node,
+							"qcom,periph-map");
+	if (chip->num_periphs < 0) {
+		pr_err("missing qcom,periph-map property rc=%d\n",
+			chip->num_periphs);
+		return chip->num_periphs;
+	}
+
+	if (chip->num_periphs == 0) {
+		pr_err("qcom,periph-map must contain at least one address\n");
+		return -EINVAL;
+	}
+
+	chip->periph = devm_kcalloc(chip->dev, chip->num_periphs,
+				     sizeof(*chip->periph), GFP_KERNEL);
+	if (!chip->periph)
+		return -ENOMEM;
+
+	for (i = 0; i < chip->num_periphs; i++) {
+		rc = of_property_read_u32_index(node, "qcom,periph-map",
+						i, &temp);
+		if (rc < 0) {
+			pr_err("Couldn't read qcom,periph-map[%d] rc=%d\n",
+			       i, rc);
+			return rc;
+		}
+
+		chip->periph[i].addr = (u16)(temp << 8);
+		chip->periph[i].data = chip;
+		mutex_init(&chip->periph[i].lock);
+	}
+
+	of_property_read_string(node, "pinctrl-names", &chip->pinctrl_name);
+
+	return rc;
+}
+
+#define MAX_I2C_RETRIES	3
+static int i2c_pmic_read(struct regmap *map, unsigned int reg, void *val,
+			size_t val_count)
+{
+	int rc, retries = 0;
+
+	do {
+		rc = regmap_bulk_read(map, reg, val, val_count);
+	} while (rc == -ENOTCONN && retries++ < MAX_I2C_RETRIES);
+
+	if (retries > 1)
+		pr_err("i2c_pmic_read failed for %d retries, rc = %d\n",
+			retries - 1, rc);
+
+	return rc;
+}
+
+static int i2c_pmic_determine_initial_status(struct i2c_pmic *chip)
+{
+	int rc, i;
+
+	for (i = 0; i < chip->num_periphs; i++) {
+		rc = i2c_pmic_read(chip->regmap,
+				chip->periph[i].addr | INT_SET_TYPE_OFFSET,
+				chip->periph[i].cached, IRQ_MAX_REGS);
+		if (rc < 0) {
+			pr_err("Couldn't read irq data rc=%d\n", rc);
+			return rc;
+		}
+
+		memcpy(chip->periph[i].synced, chip->periph[i].cached,
+		       IRQ_MAX_REGS * sizeof(*chip->periph[i].synced));
+	}
+
+	return 0;
+}
+
+static struct regmap_config i2c_pmic_regmap_config = {
+	.reg_bits	= 16,
+	.val_bits	= 8,
+	.max_register	= 0xFFFF,
+};
+
+static int i2c_pmic_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct i2c_pmic *chip;
+	int rc = 0;
+
+	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->dev = &client->dev;
+	chip->regmap = devm_regmap_init_i2c(client, &i2c_pmic_regmap_config);
+	if (!chip->regmap)
+		return -ENODEV;
+
+	i2c_set_clientdata(client, chip);
+	if (!of_property_read_bool(chip->dev->of_node, "interrupt-controller"))
+		goto probe_children;
+
+	chip->domain = irq_domain_add_tree(client->dev.of_node,
+					   &i2c_pmic_domain_ops, chip);
+	if (!chip->domain) {
+		rc = -ENOMEM;
+		goto cleanup;
+	}
+
+	rc = i2c_pmic_parse_dt(chip);
+	if (rc < 0) {
+		pr_err("Couldn't parse device tree rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = i2c_pmic_determine_initial_status(chip);
+	if (rc < 0) {
+		pr_err("Couldn't determine initial status rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	if (chip->pinctrl_name) {
+		chip->pinctrl = devm_pinctrl_get_select(chip->dev,
+							chip->pinctrl_name);
+		if (IS_ERR(chip->pinctrl)) {
+			pr_err("Couldn't select %s pinctrl rc=%ld\n",
+				chip->pinctrl_name, PTR_ERR(chip->pinctrl));
+			rc = PTR_ERR(chip->pinctrl);
+			goto cleanup;
+		}
+	}
+
+	rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+				       i2c_pmic_irq_handler,
+				       IRQF_ONESHOT | IRQF_SHARED,
+				       "i2c_pmic_stat_irq", chip);
+	if (rc < 0) {
+		pr_err("Couldn't request irq %d rc=%d\n", client->irq, rc);
+		goto cleanup;
+	}
+
+	enable_irq_wake(client->irq);
+
+probe_children:
+	of_platform_populate(chip->dev->of_node, NULL, NULL, chip->dev);
+	pr_info("I2C PMIC probe successful\n");
+	return rc;
+
+cleanup:
+	if (chip->domain)
+		irq_domain_remove(chip->domain);
+	i2c_set_clientdata(client, NULL);
+	return rc;
+}
+
+static int i2c_pmic_remove(struct i2c_client *client)
+{
+	struct i2c_pmic *chip = i2c_get_clientdata(client);
+
+	of_platform_depopulate(chip->dev);
+	if (chip->domain)
+		irq_domain_remove(chip->domain);
+	i2c_set_clientdata(client, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int i2c_pmic_suspend(struct device *dev)
+{
+	struct i2c_pmic *chip = dev_get_drvdata(dev);
+	struct i2c_pmic_periph *periph;
+	int rc = 0, i;
+
+	for (i = 0; i < chip->num_periphs; i++) {
+		periph = &chip->periph[i];
+
+		rc = regmap_write(chip->regmap,
+				  periph->addr | INT_EN_CLR_OFFSET, 0xFF);
+		if (rc < 0) {
+			pr_err_ratelimited("Couldn't clear 0x%04x irqs rc=%d\n",
+				periph->addr, rc);
+			continue;
+		}
+
+		rc = regmap_write(chip->regmap,
+				  periph->addr | INT_EN_SET_OFFSET,
+				  periph->wake);
+		if (rc < 0)
+			pr_err_ratelimited("Couldn't enable 0x%04x wake irqs 0x%02x rc=%d\n",
+			       periph->addr, periph->wake, rc);
+	}
+
+	return rc;
+}
+
+static int i2c_pmic_resume(struct device *dev)
+{
+	struct i2c_pmic *chip = dev_get_drvdata(dev);
+	struct i2c_pmic_periph *periph;
+	int rc = 0, i;
+
+	for (i = 0; i < chip->num_periphs; i++) {
+		periph = &chip->periph[i];
+
+		rc = regmap_write(chip->regmap,
+				  periph->addr | INT_EN_CLR_OFFSET, 0xFF);
+		if (rc < 0) {
+			pr_err("Couldn't clear 0x%04x irqs rc=%d\n",
+				periph->addr, rc);
+			continue;
+		}
+
+		rc = regmap_write(chip->regmap,
+				  periph->addr | INT_EN_SET_OFFSET,
+				  periph->synced[IRQ_EN_SET]);
+		if (rc < 0)
+			pr_err("Couldn't restore 0x%04x synced irqs 0x%02x rc=%d\n",
+			       periph->addr, periph->synced[IRQ_EN_SET], rc);
+	}
+
+	return rc;
+}
+#endif
+static SIMPLE_DEV_PM_OPS(i2c_pmic_pm_ops, i2c_pmic_suspend, i2c_pmic_resume);
+
+static const struct of_device_id i2c_pmic_match_table[] = {
+	{ .compatible = "qcom,i2c-pmic", },
+	{ },
+};
+
+static const struct i2c_device_id i2c_pmic_id[] = {
+	{ "i2c-pmic", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, i2c_pmic_id);
+
+static struct i2c_driver i2c_pmic_driver = {
+	.driver		= {
+		.name		= "i2c_pmic",
+		.owner		= THIS_MODULE,
+		.pm		= &i2c_pmic_pm_ops,
+		.of_match_table	= i2c_pmic_match_table,
+	},
+	.probe		= i2c_pmic_probe,
+	.remove		= i2c_pmic_remove,
+	.id_table	= i2c_pmic_id,
+};
+
+module_i2c_driver(i2c_pmic_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("i2c:i2c_pmic");
diff --git a/drivers/platform/msm/Kconfig b/drivers/platform/msm/Kconfig
index a08b100..7018007 100644
--- a/drivers/platform/msm/Kconfig
+++ b/drivers/platform/msm/Kconfig
@@ -93,4 +93,24 @@
 	default n
 	help
 	  No-Data-Path BAM is used to improve BAM performance.
+
+config QPNP_COINCELL
+	tristate "QPNP coincell charger support"
+	depends on SPMI
+	help
+	  This driver supports the QPNP coincell peripheral found inside of
+	  Qualcomm Technologies, Inc. QPNP PMIC devices.  The coincell charger
+	  provides a means to charge a coincell battery or backup capacitor
+	  which is used to maintain PMIC register state when the main battery is
+	  removed from the mobile device.
+
+config QPNP_REVID
+	tristate "QPNP Revision ID Peripheral"
+	depends on SPMI
+	help
+	  Say 'y' here to include support for the Qualcomm Technologies, Inc.
+	  QPNP REVID peripheral. REVID prints out the PMIC type and revision
+	  numbers in the kernel log along with the PMIC option status. The PMIC
+	  type is mapped to a QTI chip part number and logged as well.
+
 endmenu
diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile
index 7eebfcb5..85c4673 100644
--- a/drivers/platform/msm/Makefile
+++ b/drivers/platform/msm/Makefile
@@ -5,3 +5,7 @@
 obj-$(CONFIG_IPA) += ipa/
 obj-$(CONFIG_IPA3) += ipa/
 obj-$(CONFIG_SPS) += sps/
+
+obj-$(CONFIG_QPNP_COINCELL) += qpnp-coincell.o
+obj-$(CONFIG_QPNP_REVID) += qpnp-revid.o
+
diff --git a/drivers/platform/msm/qpnp-coincell.c b/drivers/platform/msm/qpnp-coincell.c
new file mode 100644
index 0000000..b427f43
--- /dev/null
+++ b/drivers/platform/msm/qpnp-coincell.c
@@ -0,0 +1,283 @@
+/* Copyright (c) 2013-2015, 2017, 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) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#define QPNP_COINCELL_DRIVER_NAME "qcom,qpnp-coincell"
+
+struct qpnp_coincell {
+	struct platform_device	*pdev;
+	struct regmap		*regmap;
+	u16			base_addr;
+};
+
+#define QPNP_COINCELL_REG_TYPE		0x04
+#define QPNP_COINCELL_REG_SUBTYPE	0x05
+#define QPNP_COINCELL_REG_RSET		0x44
+#define QPNP_COINCELL_REG_VSET		0x45
+#define QPNP_COINCELL_REG_ENABLE	0x46
+
+#define QPNP_COINCELL_TYPE		0x02
+#define QPNP_COINCELL_SUBTYPE		0x20
+#define QPNP_COINCELL_ENABLE		0x80
+#define QPNP_COINCELL_DISABLE		0x00
+
+static const int qpnp_rset_map[] = {2100, 1700, 1200, 800};
+static const int qpnp_vset_map[] = {2500, 3200, 3100, 3000};
+
+static int qpnp_coincell_set_resistance(struct qpnp_coincell *chip, int rset)
+{
+	int i, rc;
+	u8 reg;
+
+	for (i = 0; i < ARRAY_SIZE(qpnp_rset_map); i++)
+		if (rset == qpnp_rset_map[i])
+			break;
+
+	if (i >= ARRAY_SIZE(qpnp_rset_map)) {
+		pr_err("invalid rset=%d value\n", rset);
+		return -EINVAL;
+	}
+
+	reg = i;
+	rc = regmap_write(chip->regmap,
+			  chip->base_addr + QPNP_COINCELL_REG_RSET, reg);
+	if (rc)
+		dev_err(&chip->pdev->dev,
+			"%s: could not write to RSET register, rc=%d\n",
+			__func__, rc);
+
+	return rc;
+}
+
+static int qpnp_coincell_set_voltage(struct qpnp_coincell *chip, int vset)
+{
+	int i, rc;
+	u8 reg;
+
+	for (i = 0; i < ARRAY_SIZE(qpnp_vset_map); i++)
+		if (vset == qpnp_vset_map[i])
+			break;
+
+	if (i >= ARRAY_SIZE(qpnp_vset_map)) {
+		pr_err("invalid vset=%d value\n", vset);
+		return -EINVAL;
+	}
+
+	reg = i;
+	rc = regmap_write(chip->regmap,
+			  chip->base_addr + QPNP_COINCELL_REG_VSET, reg);
+	if (rc)
+		dev_err(&chip->pdev->dev,
+			"%s: could not write to VSET register, rc=%d\n",
+			__func__, rc);
+
+	return rc;
+}
+
+static int qpnp_coincell_set_charge(struct qpnp_coincell *chip, bool enabled)
+{
+	int rc;
+	u8 reg;
+
+	reg = enabled ? QPNP_COINCELL_ENABLE : QPNP_COINCELL_DISABLE;
+	rc = regmap_write(chip->regmap,
+			  chip->base_addr + QPNP_COINCELL_REG_ENABLE, reg);
+	if (rc)
+		dev_err(&chip->pdev->dev,
+			"%s: could not write to ENABLE register, rc=%d\n",
+			__func__, rc);
+
+	return rc;
+}
+
+static void qpnp_coincell_charger_show_state(struct qpnp_coincell *chip)
+{
+	int rc, rset, vset, temp;
+	bool enabled;
+	u8 reg[QPNP_COINCELL_REG_ENABLE - QPNP_COINCELL_REG_RSET + 1];
+
+	rc = regmap_bulk_read(chip->regmap,
+			      chip->base_addr + QPNP_COINCELL_REG_RSET, reg,
+			      ARRAY_SIZE(reg));
+	if (rc) {
+		dev_err(&chip->pdev->dev,
+			"%s: could not read RSET register, rc=%d\n",
+			__func__, rc);
+		return;
+	}
+
+	temp = reg[QPNP_COINCELL_REG_RSET - QPNP_COINCELL_REG_RSET];
+	if (temp >= ARRAY_SIZE(qpnp_rset_map)) {
+		dev_err(&chip->pdev->dev,
+			"unknown RSET=0x%02X register value\n",
+			temp);
+		return;
+	}
+	rset = qpnp_rset_map[temp];
+
+	temp = reg[QPNP_COINCELL_REG_VSET - QPNP_COINCELL_REG_RSET];
+	if (temp >= ARRAY_SIZE(qpnp_vset_map)) {
+		dev_err(&chip->pdev->dev,
+			"unknown VSET=0x%02X register value\n",
+			temp);
+		return;
+	}
+	vset = qpnp_vset_map[temp];
+
+	temp = reg[QPNP_COINCELL_REG_ENABLE - QPNP_COINCELL_REG_RSET];
+	enabled = temp & QPNP_COINCELL_ENABLE;
+
+	pr_info("enabled=%c, voltage=%d mV, resistance=%d ohm\n",
+		(enabled ? 'Y' : 'N'), vset, rset);
+}
+
+static int qpnp_coincell_check_type(struct qpnp_coincell *chip)
+{
+	int rc;
+	u8 type[2];
+
+	rc = regmap_bulk_read(chip->regmap,
+			      chip->base_addr + QPNP_COINCELL_REG_TYPE, type,
+			      2);
+	if (rc) {
+		dev_err(&chip->pdev->dev,
+			"%s: could not read type register, rc=%d\n",
+			__func__, rc);
+		return rc;
+	}
+
+	if (type[0] != QPNP_COINCELL_TYPE || type[1] != QPNP_COINCELL_SUBTYPE) {
+		dev_err(&chip->pdev->dev,
+			"%s: invalid type=0x%02X or subtype=0x%02X register value\n",
+			__func__, type[0], type[1]);
+		return -ENODEV;
+	}
+
+	return rc;
+}
+
+static int qpnp_coincell_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct qpnp_coincell *chip;
+	unsigned int base;
+	u32 temp;
+	int rc = 0;
+
+	if (!node) {
+		dev_err(&pdev->dev, "%s: device node missing\n", __func__);
+		return -ENODEV;
+	}
+
+	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) {
+		dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+		return -EINVAL;
+	}
+	chip->pdev = pdev;
+
+	rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+	if (rc < 0) {
+		dev_err(&pdev->dev,
+			"Couldn't find reg in node = %s rc = %d\n",
+			pdev->dev.of_node->full_name, rc);
+		return rc;
+	}
+	chip->base_addr = base;
+
+	rc = qpnp_coincell_check_type(chip);
+	if (rc)
+		return rc;
+
+	rc = of_property_read_u32(node, "qcom,rset-ohms", &temp);
+	if (!rc) {
+		rc = qpnp_coincell_set_resistance(chip, temp);
+		if (rc)
+			return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,vset-millivolts", &temp);
+	if (!rc) {
+		rc = qpnp_coincell_set_voltage(chip, temp);
+		if (rc)
+			return rc;
+	}
+
+	rc = of_property_read_u32(node, "qcom,charge-enable", &temp);
+	if (!rc) {
+		rc = qpnp_coincell_set_charge(chip, temp);
+		if (rc)
+			return rc;
+	}
+
+	qpnp_coincell_charger_show_state(chip);
+
+	return 0;
+}
+
+static int qpnp_coincell_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static const struct of_device_id qpnp_coincell_match_table[] = {
+	{ .compatible = QPNP_COINCELL_DRIVER_NAME, },
+	{}
+};
+
+static const struct platform_device_id qpnp_coincell_id[] = {
+	{ QPNP_COINCELL_DRIVER_NAME, 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(spmi, qpnp_coincell_id);
+
+static struct platform_driver qpnp_coincell_driver = {
+	.driver	= {
+		.name		= QPNP_COINCELL_DRIVER_NAME,
+		.of_match_table	= qpnp_coincell_match_table,
+		.owner		= THIS_MODULE,
+	},
+	.probe		= qpnp_coincell_probe,
+	.remove		= qpnp_coincell_remove,
+	.id_table	= qpnp_coincell_id,
+};
+
+static int __init qpnp_coincell_init(void)
+{
+	return platform_driver_register(&qpnp_coincell_driver);
+}
+
+static void __exit qpnp_coincell_exit(void)
+{
+	platform_driver_unregister(&qpnp_coincell_driver);
+}
+
+MODULE_DESCRIPTION("QPNP PMIC coincell charger driver");
+MODULE_LICENSE("GPL v2");
+
+module_init(qpnp_coincell_init);
+module_exit(qpnp_coincell_exit);
diff --git a/drivers/platform/msm/qpnp-revid.c b/drivers/platform/msm/qpnp-revid.c
new file mode 100644
index 0000000..9ea0b40
--- /dev/null
+++ b/drivers/platform/msm/qpnp-revid.c
@@ -0,0 +1,270 @@
+/* Copyright (c) 2013-2017, 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/slab.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/err.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/of.h>
+
+#define REVID_REVISION1	0x0
+#define REVID_REVISION2	0x1
+#define REVID_REVISION3	0x2
+#define REVID_REVISION4	0x3
+#define REVID_TYPE	0x4
+#define REVID_SUBTYPE	0x5
+#define REVID_STATUS1	0x8
+#define REVID_SPARE_0	0x60
+#define REVID_FAB_ID	0xf2
+
+#define QPNP_REVID_DEV_NAME "qcom,qpnp-revid"
+
+static const char *const pmic_names[] = {
+	[0] =	"Unknown PMIC",
+	[PM8941_SUBTYPE] = "PM8941",
+	[PM8841_SUBTYPE] = "PM8841",
+	[PM8019_SUBTYPE] = "PM8019",
+	[PM8226_SUBTYPE] = "PM8226",
+	[PM8110_SUBTYPE] = "PM8110",
+	[PMA8084_SUBTYPE] = "PMA8084",
+	[PMI8962_SUBTYPE] = "PMI8962",
+	[PMD9635_SUBTYPE] = "PMD9635",
+	[PM8994_SUBTYPE] = "PM8994",
+	[PMI8994_SUBTYPE] = "PMI8994",
+	[PM8916_SUBTYPE] = "PM8916",
+	[PM8004_SUBTYPE] = "PM8004",
+	[PM8909_SUBTYPE] = "PM8909",
+	[PM2433_SUBTYPE] = "PM2433",
+	[PMD9655_SUBTYPE] = "PMD9655",
+	[PM8950_SUBTYPE] = "PM8950",
+	[PMI8950_SUBTYPE] = "PMI8950",
+	[PMK8001_SUBTYPE] = "PMK8001",
+	[PMI8996_SUBTYPE] = "PMI8996",
+	[PM8998_SUBTYPE] = "PM8998",
+	[PMI8998_SUBTYPE] = "PMI8998",
+	[PM8005_SUBTYPE] = "PM8005",
+	[PM8937_SUBTYPE] = "PM8937",
+	[PM660L_SUBTYPE] = "PM660L",
+	[PM660_SUBTYPE] = "PM660",
+	[PMI8937_SUBTYPE] = "PMI8937",
+};
+
+struct revid_chip {
+	struct list_head	link;
+	struct device_node	*dev_node;
+	struct pmic_revid_data	data;
+};
+
+static LIST_HEAD(revid_chips);
+static DEFINE_MUTEX(revid_chips_lock);
+
+static const struct of_device_id qpnp_revid_match_table[] = {
+	{ .compatible = QPNP_REVID_DEV_NAME },
+	{}
+};
+
+static u8 qpnp_read_byte(struct regmap *regmap, u16 addr)
+{
+	int rc;
+	int val;
+
+	rc = regmap_read(regmap, addr, &val);
+	if (rc) {
+		pr_err("read failed rc=%d\n", rc);
+		return 0;
+	}
+	return (u8)val;
+}
+
+/**
+ * get_revid_data - Return the revision information of PMIC
+ * @dev_node: Pointer to the revid peripheral of the PMIC for which
+ *		revision information is seeked
+ *
+ * CONTEXT: Should be called in non atomic context
+ *
+ * RETURNS: pointer to struct pmic_revid_data filled with the information
+ *		about the PMIC revision
+ */
+struct pmic_revid_data *get_revid_data(struct device_node *dev_node)
+{
+	struct revid_chip *revid_chip;
+
+	if (!dev_node)
+		return ERR_PTR(-EINVAL);
+
+	mutex_lock(&revid_chips_lock);
+	list_for_each_entry(revid_chip, &revid_chips, link) {
+		if (dev_node == revid_chip->dev_node) {
+			mutex_unlock(&revid_chips_lock);
+			return &revid_chip->data;
+		}
+	}
+	mutex_unlock(&revid_chips_lock);
+	return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL(get_revid_data);
+
+#define PM8941_PERIPHERAL_SUBTYPE	0x01
+#define PM8226_PERIPHERAL_SUBTYPE	0x04
+#define PMD9655_PERIPHERAL_SUBTYPE	0x0F
+#define PMI8950_PERIPHERAL_SUBTYPE	0x11
+#define PMI8937_PERIPHERAL_SUBTYPE	0x37
+static size_t build_pmic_string(char *buf, size_t n, int sid,
+		u8 subtype, u8 rev1, u8 rev2, u8 rev3, u8 rev4)
+{
+	size_t pos = 0;
+	/*
+	 * In early versions of PM8941 and PM8226, the major revision number
+	 * started incrementing from 0 (eg 0 = v1.0, 1 = v2.0).
+	 * Increment the major revision number here if the chip is an early
+	 * version of PM8941 or PM8226.
+	 */
+	if (((int)subtype == PM8941_PERIPHERAL_SUBTYPE
+			|| (int)subtype == PM8226_PERIPHERAL_SUBTYPE)
+			&& rev4 < 0x02)
+		rev4++;
+
+	pos += snprintf(buf + pos, n - pos, "PMIC@SID%d", sid);
+	if (subtype >= ARRAY_SIZE(pmic_names) || subtype == 0)
+		pos += snprintf(buf + pos, n - pos, ": %s (subtype: 0x%02X)",
+				pmic_names[0], subtype);
+	else
+		pos += snprintf(buf + pos, n - pos, ": %s",
+				pmic_names[subtype]);
+	pos += snprintf(buf + pos, n - pos, " v%d.%d", rev4, rev3);
+	if (rev2 || rev1)
+		pos += snprintf(buf + pos, n - pos, ".%d", rev2);
+	if (rev1)
+		pos += snprintf(buf + pos, n - pos, ".%d", rev1);
+	return pos;
+}
+
+#define PMIC_PERIPHERAL_TYPE		0x51
+#define PMIC_STRING_MAXLENGTH		80
+static int qpnp_revid_probe(struct platform_device *pdev)
+{
+	u8 rev1, rev2, rev3, rev4, pmic_type, pmic_subtype, pmic_status;
+	u8 option1, option2, option3, option4, spare0, fab_id;
+	unsigned int base;
+	int rc;
+	char pmic_string[PMIC_STRING_MAXLENGTH] = {'\0'};
+	struct revid_chip *revid_chip;
+	struct regmap *regmap;
+
+	regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!regmap) {
+		dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+	if (rc < 0) {
+		dev_err(&pdev->dev,
+			"Couldn't find reg in node = %s rc = %d\n",
+			pdev->dev.of_node->full_name, rc);
+		return rc;
+	}
+	pmic_type = qpnp_read_byte(regmap, base + REVID_TYPE);
+	if (pmic_type != PMIC_PERIPHERAL_TYPE) {
+		pr_err("Invalid REVID peripheral type: %02X\n", pmic_type);
+		return -EINVAL;
+	}
+
+	rev1 = qpnp_read_byte(regmap, base + REVID_REVISION1);
+	rev2 = qpnp_read_byte(regmap, base + REVID_REVISION2);
+	rev3 = qpnp_read_byte(regmap, base + REVID_REVISION3);
+	rev4 = qpnp_read_byte(regmap, base + REVID_REVISION4);
+
+	pmic_subtype = qpnp_read_byte(regmap, base + REVID_SUBTYPE);
+	if (pmic_subtype != PMD9655_PERIPHERAL_SUBTYPE)
+		pmic_status = qpnp_read_byte(regmap, base + REVID_STATUS1);
+	else
+		pmic_status = 0;
+
+	/* special case for PMI8937 */
+	if (pmic_subtype == PMI8950_PERIPHERAL_SUBTYPE) {
+		/* read spare register */
+		spare0 = qpnp_read_byte(regmap, base + REVID_SPARE_0);
+		if (spare0)
+			pmic_subtype = PMI8937_PERIPHERAL_SUBTYPE;
+	}
+
+	if (of_property_read_bool(pdev->dev.of_node, "qcom,fab-id-valid"))
+		fab_id = qpnp_read_byte(regmap, base + REVID_FAB_ID);
+	else
+		fab_id = -EINVAL;
+
+	revid_chip = devm_kzalloc(&pdev->dev, sizeof(struct revid_chip),
+						GFP_KERNEL);
+	if (!revid_chip)
+		return -ENOMEM;
+
+	revid_chip->dev_node = pdev->dev.of_node;
+	revid_chip->data.rev1 = rev1;
+	revid_chip->data.rev2 = rev2;
+	revid_chip->data.rev3 = rev3;
+	revid_chip->data.rev4 = rev4;
+	revid_chip->data.pmic_subtype = pmic_subtype;
+	revid_chip->data.pmic_type = pmic_type;
+	revid_chip->data.fab_id = fab_id;
+
+	if (pmic_subtype < ARRAY_SIZE(pmic_names))
+		revid_chip->data.pmic_name = pmic_names[pmic_subtype];
+	else
+		revid_chip->data.pmic_name = pmic_names[0];
+
+	mutex_lock(&revid_chips_lock);
+	list_add(&revid_chip->link, &revid_chips);
+	mutex_unlock(&revid_chips_lock);
+
+	option1 = pmic_status & 0x3;
+	option2 = (pmic_status >> 2) & 0x3;
+	option3 = (pmic_status >> 4) & 0x3;
+	option4 = (pmic_status >> 6) & 0x3;
+
+	build_pmic_string(pmic_string, PMIC_STRING_MAXLENGTH,
+			  to_spmi_device(pdev->dev.parent)->usid,
+			pmic_subtype, rev1, rev2, rev3, rev4);
+	pr_info("%s options: %d, %d, %d, %d\n",
+			pmic_string, option1, option2, option3, option4);
+	return 0;
+}
+
+static struct platform_driver qpnp_revid_driver = {
+	.probe	= qpnp_revid_probe,
+	.driver	= {
+		.name		= QPNP_REVID_DEV_NAME,
+		.owner		= THIS_MODULE,
+		.of_match_table	= qpnp_revid_match_table,
+	},
+};
+
+static int __init qpnp_revid_init(void)
+{
+	return platform_driver_register(&qpnp_revid_driver);
+}
+
+static void __exit qpnp_revid_exit(void)
+{
+	return platform_driver_unregister(&qpnp_revid_driver);
+}
+
+subsys_initcall(qpnp_revid_init);
+module_exit(qpnp_revid_exit);
+
+MODULE_DESCRIPTION("QPNP REVID DRIVER");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" QPNP_REVID_DEV_NAME);
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index bf01288..e7d13ae 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -329,6 +329,16 @@
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-pxa.
 
+config PWM_QPNP
+	tristate "Qualcomm Technologies, Inc. QPNP LPG/PWM support"
+	depends on SPMI
+	help
+	  This driver supports PWM/LPG devices in Qualcomm Technologies, Inc.
+	  PMIC chips which comply with QPNP.  QPNP is an SPMI based PMIC
+	  implementation.  These devices support Pulse Width Modulation output
+	  with user generated patterns. They share a lookup table with size of
+	  64 entries.
+
 config PWM_RCAR
 	tristate "Renesas R-Car PWM support"
 	depends on ARCH_RENESAS || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 1194c54..24c1baf 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -31,6 +31,7 @@
 obj-$(CONFIG_PWM_PCA9685)	+= pwm-pca9685.o
 obj-$(CONFIG_PWM_PUV3)		+= pwm-puv3.o
 obj-$(CONFIG_PWM_PXA)		+= pwm-pxa.o
+obj-$(CONFIG_PWM_QPNP)		+= pwm-qpnp.o
 obj-$(CONFIG_PWM_RCAR)		+= pwm-rcar.o
 obj-$(CONFIG_PWM_RENESAS_TPU)	+= pwm-renesas-tpu.o
 obj-$(CONFIG_PWM_ROCKCHIP)	+= pwm-rockchip.o
diff --git a/drivers/pwm/pwm-qpnp.c b/drivers/pwm/pwm-qpnp.c
new file mode 100644
index 0000000..86f08d2
--- /dev/null
+++ b/drivers/pwm/pwm-qpnp.c
@@ -0,0 +1,2218 @@
+/* Copyright (c) 2012-2017, 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.
+ */
+/*
+ * Qualcomm Technologies, Inc. QPNP Pulse Width Modulation (PWM) driver
+ *
+ * The HW module is also called LPG (Light Pattern Generator).
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/radix-tree.h>
+#include <linux/qpnp/pwm.h>
+
+#define QPNP_LPG_DRIVER_NAME	"qcom,qpnp-pwm"
+#define QPNP_LPG_CHANNEL_BASE	"qpnp-lpg-channel-base"
+#define QPNP_LPG_LUT_BASE	"qpnp-lpg-lut-base"
+
+#define QPNP_PWM_MODE_ONLY_SUB_TYPE	0x0B
+#define QPNP_LPG_CHAN_SUB_TYPE		0x2
+#define QPNP_LPG_S_CHAN_SUB_TYPE	0x11
+
+/* LPG Control for LPG_PATTERN_CONFIG */
+#define QPNP_RAMP_DIRECTION_SHIFT	4
+#define QPNP_RAMP_DIRECTION_MASK	0x10
+#define QPNP_PATTERN_REPEAT_SHIFT	3
+#define QPNP_PATTERN_REPEAT_MASK	0x08
+#define QPNP_RAMP_TOGGLE_SHIFT		2
+#define QPNP_RAMP_TOGGLE_MASK		0x04
+#define QPNP_EN_PAUSE_HI_SHIFT		1
+#define QPNP_EN_PAUSE_HI_MASK		0x02
+#define QPNP_EN_PAUSE_LO_MASK		0x01
+
+/* LPG Control for LPG_PWM_SIZE_CLK */
+#define QPNP_PWM_SIZE_SHIFT_SUB_TYPE		2
+#define QPNP_PWM_SIZE_MASK_SUB_TYPE		0x4
+#define QPNP_PWM_FREQ_CLK_SELECT_MASK_SUB_TYPE	0x03
+#define QPNP_PWM_SIZE_9_BIT_SUB_TYPE		0x01
+
+#define QPNP_SET_PWM_CLK_SUB_TYPE(val, clk, pwm_size) \
+do { \
+	val = (clk + 1) & QPNP_PWM_FREQ_CLK_SELECT_MASK_SUB_TYPE; \
+	val |= (((pwm_size > 6 ? QPNP_PWM_SIZE_9_BIT_SUB_TYPE : 0) << \
+		QPNP_PWM_SIZE_SHIFT_SUB_TYPE) & QPNP_PWM_SIZE_MASK_SUB_TYPE); \
+} while (0)
+
+#define QPNP_GET_PWM_SIZE_SUB_TYPE(reg) ((reg & QPNP_PWM_SIZE_MASK_SUB_TYPE) \
+				>> QPNP_PWM_SIZE_SHIFT_SUB_TYPE)
+
+#define QPNP_PWM_SIZE_SHIFT			4
+#define QPNP_PWM_SIZE_MASK			0x30
+#define QPNP_PWM_FREQ_CLK_SELECT_MASK		0x03
+#define QPNP_MIN_PWM_BIT_SIZE		6
+#define QPNP_MAX_PWM_BIT_SIZE		9
+#define QPNP_PWM_SIZES_SUPPORTED	10
+
+#define QPNP_SET_PWM_CLK(val, clk, pwm_size) \
+do { \
+	val = (clk + 1) & QPNP_PWM_FREQ_CLK_SELECT_MASK; \
+	val |= (((pwm_size - QPNP_MIN_PWM_BIT_SIZE) << \
+		QPNP_PWM_SIZE_SHIFT) & QPNP_PWM_SIZE_MASK); \
+} while (0)
+
+#define QPNP_GET_PWM_SIZE(reg) ((reg & QPNP_PWM_SIZE_MASK) \
+				>> QPNP_PWM_SIZE_SHIFT)
+
+/* LPG Control for LPG_PWM_FREQ_PREDIV_CLK */
+#define QPNP_PWM_FREQ_PRE_DIVIDE_SHIFT		5
+#define QPNP_PWM_FREQ_PRE_DIVIDE_MASK		0x60
+#define QPNP_PWM_FREQ_EXP_MASK			0x07
+
+#define QPNP_SET_PWM_FREQ_PREDIV(val, pre_div, pre_div_exp) \
+do { \
+	val = (pre_div << QPNP_PWM_FREQ_PRE_DIVIDE_SHIFT) & \
+				QPNP_PWM_FREQ_PRE_DIVIDE_MASK;	\
+	val |= (pre_div_exp & QPNP_PWM_FREQ_EXP_MASK);	\
+} while (0)
+
+/* LPG Control for LPG_PWM_TYPE_CONFIG */
+#define QPNP_EN_GLITCH_REMOVAL_SHIFT		5
+#define QPNP_EN_GLITCH_REMOVAL_MASK		0x20
+#define QPNP_EN_FULL_SCALE_SHIFT		3
+#define QPNP_EN_FULL_SCALE_MASK			0x08
+#define QPNP_EN_PHASE_STAGGER_SHIFT		2
+#define QPNP_EN_PHASE_STAGGER_MASK		0x04
+#define QPNP_PHASE_STAGGER_MASK			0x03
+
+/* LPG Control for PWM_VALUE_LSB */
+#define QPNP_PWM_VALUE_LSB_MASK			0xFF
+
+/* LPG Control for PWM_VALUE_MSB */
+#define QPNP_PWM_VALUE_MSB_SHIFT		8
+#define QPNP_PWM_VALUE_MSB_MASK			0x01
+
+/* LPG Control for ENABLE_CONTROL */
+#define QPNP_EN_PWM_HIGH_SHIFT			7
+#define QPNP_EN_PWM_HIGH_MASK			0x80
+#define QPNP_EN_PWM_LO_SHIFT			6
+#define QPNP_EN_PWM_LO_MASK			0x40
+#define QPNP_EN_PWM_OUTPUT_SHIFT		5
+#define QPNP_EN_PWM_OUTPUT_MASK			0x20
+#define QPNP_PWM_SRC_SELECT_SHIFT		2
+#define QPNP_PWM_SRC_SELECT_MASK		0x04
+#define QPNP_PWM_EN_RAMP_GEN_SHIFT		1
+#define QPNP_PWM_EN_RAMP_GEN_MASK		0x02
+
+/* LPG Control for PWM_SYNC */
+#define QPNP_PWM_SYNC_VALUE			0x01
+#define QPNP_PWM_SYNC_MASK			0x01
+
+/* LPG Control for RAMP_CONTROL */
+#define QPNP_RAMP_START_MASK			0x01
+
+#define QPNP_ENABLE_LUT_V0(value) (value |= QPNP_RAMP_START_MASK)
+#define QPNP_DISABLE_LUT_V0(value) (value &= ~QPNP_RAMP_START_MASK)
+#define QPNP_ENABLE_LUT_V1(value, id) (value |= BIT(id))
+
+/* LPG Control for RAMP_STEP_DURATION_LSB */
+#define QPNP_RAMP_STEP_DURATION_LSB_MASK	0xFF
+
+/* LPG Control for RAMP_STEP_DURATION_MSB */
+#define QPNP_RAMP_STEP_DURATION_MSB_SHIFT	8
+#define QPNP_RAMP_STEP_DURATION_MSB_MASK	0x01
+
+#define QPNP_PWM_1KHZ				1024
+#define QPNP_GET_RAMP_STEP_DURATION(ramp_time_ms) \
+		((ramp_time_ms * QPNP_PWM_1KHZ) / 1000)
+
+/* LPG Control for PAUSE_HI_MULTIPLIER_LSB */
+#define QPNP_PAUSE_HI_MULTIPLIER_LSB_MASK	0xFF
+
+/* LPG Control for PAUSE_HI_MULTIPLIER_MSB */
+#define QPNP_PAUSE_HI_MULTIPLIER_MSB_SHIFT	8
+#define QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK	0x1F
+
+/* LPG Control for PAUSE_LO_MULTIPLIER_LSB */
+#define QPNP_PAUSE_LO_MULTIPLIER_LSB_MASK	0xFF
+
+/* LPG Control for PAUSE_LO_MULTIPLIER_MSB */
+#define QPNP_PAUSE_LO_MULTIPLIER_MSB_SHIFT	8
+#define QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK	0x1F
+
+/* LPG Control for HI_INDEX */
+#define QPNP_HI_INDEX_MASK			0x3F
+
+/* LPG Control for LO_INDEX */
+#define QPNP_LO_INDEX_MASK			0x3F
+
+/* LPG DTEST */
+#define QPNP_LPG_DTEST_LINE_MAX			4
+#define QPNP_LPG_DTEST_OUTPUT_MAX		5
+#define QPNP_LPG_DTEST_OUTPUT_MASK		0x07
+
+/* PWM DTEST */
+#define QPNP_PWM_DTEST_LINE_MAX			2
+#define QPNP_PWM_DTEST_OUTPUT_MAX		2
+#define QPNP_PWM_DTEST_OUTPUT_MASK		0x03
+
+#define NUM_CLOCKS				3
+#define QPNP_PWM_M_MAX				7
+#define NSEC_1024HZ	(NSEC_PER_SEC / 1024)
+#define NSEC_32768HZ	(NSEC_PER_SEC / 32768)
+#define NSEC_19P2MHZ	(NSEC_PER_SEC / 19200000)
+
+#define NUM_LPG_PRE_DIVIDE	4
+
+#define PRE_DIVIDE_1		1
+#define PRE_DIVIDE_3		3
+#define PRE_DIVIDE_5		5
+#define PRE_DIVIDE_6		6
+
+#define SPMI_LPG_REG_BASE_OFFSET	0x40
+#define SPMI_LPG_REVISION2_OFFSET	0x1
+#define SPMI_LPG_REV1_RAMP_CONTROL_OFFSET	0x86
+#define SPMI_LPG_SUB_TYPE_OFFSET	0x5
+#define SPMI_LPG_PWM_SYNC		0x7
+#define SPMI_LPG_REG_ADDR(b, n)	(b + SPMI_LPG_REG_BASE_OFFSET + (n))
+#define SPMI_MAX_BUF_LEN	8
+
+#define QPNP_PWM_LUT_NOT_SUPPORTED	0x1
+
+/* Supported PWM sizes */
+#define QPNP_PWM_SIZE_6_BIT		6
+#define QPNP_PWM_SIZE_7_BIT		7
+#define QPNP_PWM_SIZE_8_BIT		8
+#define QPNP_PWM_SIZE_9_BIT		9
+
+#define QPNP_PWM_SIZE_6_9_BIT		0x9
+#define QPNP_PWM_SIZE_7_8_BIT		0x6
+#define QPNP_PWM_SIZE_6_7_9_BIT		0xB
+
+/*
+ * Registers that don't need to be cached are defined below from an offset
+ * of SPMI_LPG_REG_BASE_OFFSET.
+ */
+#define QPNP_LPG_SEC_ACCESS		0x90
+#define QPNP_LPG_DTEST			0xA2
+
+/* Supported time levels */
+enum time_level {
+	LVL_NSEC,
+	LVL_USEC,
+};
+
+/* LPG revisions */
+enum qpnp_lpg_revision {
+	QPNP_LPG_REVISION_0 = 0x0,
+	QPNP_LPG_REVISION_1 = 0x1,
+};
+
+/* LPG LUT MODE STATE */
+enum qpnp_lut_state {
+	QPNP_LUT_ENABLE = 0x0,
+	QPNP_LUT_DISABLE = 0x1,
+};
+
+/* PWM MODE STATE */
+enum qpnp_pwm_state {
+	QPNP_PWM_ENABLE = 0x0,
+	QPNP_PWM_DISABLE = 0x1,
+};
+
+/* SPMI LPG registers */
+enum qpnp_lpg_registers_list {
+	QPNP_LPG_PATTERN_CONFIG,
+	QPNP_LPG_PWM_SIZE_CLK,
+	QPNP_LPG_PWM_FREQ_PREDIV_CLK,
+	QPNP_LPG_PWM_TYPE_CONFIG,
+	QPNP_PWM_VALUE_LSB,
+	QPNP_PWM_VALUE_MSB,
+	QPNP_ENABLE_CONTROL,
+	QPNP_RAMP_CONTROL,
+	QPNP_RAMP_STEP_DURATION_LSB = QPNP_RAMP_CONTROL + 9,
+	QPNP_RAMP_STEP_DURATION_MSB,
+	QPNP_PAUSE_HI_MULTIPLIER_LSB,
+	QPNP_PAUSE_HI_MULTIPLIER_MSB,
+	QPNP_PAUSE_LO_MULTIPLIER_LSB,
+	QPNP_PAUSE_LO_MULTIPLIER_MSB,
+	QPNP_HI_INDEX,
+	QPNP_LO_INDEX,
+	QPNP_TOTAL_LPG_SPMI_REGISTERS
+};
+
+/*
+ * Formula from HSID,
+ * pause_time (hi/lo) = (pause_cnt- 1)*(ramp_ms)
+ * OR,
+ * pause_cnt = (pause_time / ramp_ms) + 1
+ */
+#define QPNP_SET_PAUSE_CNT(to_pause_cnt, from_pause, ramp_ms) \
+	(to_pause_cnt = (from_pause / (ramp_ms ? ramp_ms : 1)) + 1)
+
+
+static unsigned int pt_t[NUM_LPG_PRE_DIVIDE][NUM_CLOCKS] = {
+	{	PRE_DIVIDE_1 * NSEC_1024HZ,
+		PRE_DIVIDE_1 * NSEC_32768HZ,
+		PRE_DIVIDE_1 * NSEC_19P2MHZ,
+	},
+	{	PRE_DIVIDE_3 * NSEC_1024HZ,
+		PRE_DIVIDE_3 * NSEC_32768HZ,
+		PRE_DIVIDE_3 * NSEC_19P2MHZ,
+	},
+	{	PRE_DIVIDE_5 * NSEC_1024HZ,
+		PRE_DIVIDE_5 * NSEC_32768HZ,
+		PRE_DIVIDE_5 * NSEC_19P2MHZ,
+	},
+	{	PRE_DIVIDE_6 * NSEC_1024HZ,
+		PRE_DIVIDE_6 * NSEC_32768HZ,
+		PRE_DIVIDE_6 * NSEC_19P2MHZ,
+	},
+};
+
+struct qpnp_lut_config {
+	u8	*duty_pct_list;
+	int	list_len;
+	int	ramp_index;
+	int	lo_index;
+	int	hi_index;
+	int	lut_pause_hi_cnt;
+	int	lut_pause_lo_cnt;
+	int	ramp_step_ms;
+	bool	ramp_direction;
+	bool	pattern_repeat;
+	bool	ramp_toggle;
+	bool	enable_pause_hi;
+	bool	enable_pause_lo;
+};
+
+struct qpnp_lpg_config {
+	struct qpnp_lut_config	lut_config;
+	u16			base_addr;
+	u16			lut_base_addr;
+	u16			lut_size;
+};
+
+struct _qpnp_pwm_config {
+	int				pwm_value;
+	int				pwm_period;	/* in microseconds */
+	int				pwm_duty;	/* in microseconds */
+	struct pwm_period_config	period;
+	int				supported_sizes;
+	int				force_pwm_size;
+};
+
+/* Public facing structure */
+struct qpnp_pwm_chip {
+	struct platform_device	*pdev;
+	struct regmap		*regmap;
+	struct pwm_chip         chip;
+	bool			enabled;
+	struct _qpnp_pwm_config	pwm_config;
+	struct	qpnp_lpg_config	lpg_config;
+	spinlock_t		lpg_lock;
+	enum qpnp_lpg_revision	revision;
+	u8			sub_type;
+	u32			flags;
+	u8	qpnp_lpg_registers[QPNP_TOTAL_LPG_SPMI_REGISTERS];
+	int			channel_id;
+	const char		*channel_owner;
+	u32			dtest_line;
+	u32			dtest_output;
+	bool			in_test_mode;
+};
+
+/* Internal functions */
+static inline struct qpnp_pwm_chip *qpnp_pwm_from_pwm_dev(
+					struct pwm_device *pwm)
+{
+	return container_of(pwm->chip, struct qpnp_pwm_chip, chip);
+}
+
+static inline struct qpnp_pwm_chip *qpnp_pwm_from_pwm_chip(
+					struct pwm_chip *chip)
+{
+	return container_of(chip, struct qpnp_pwm_chip, chip);
+}
+
+static inline void qpnp_set_pattern_config(u8 *val,
+			struct qpnp_lut_config *lut_config)
+{
+	*val = lut_config->enable_pause_lo & QPNP_EN_PAUSE_LO_MASK;
+	*val |= (lut_config->enable_pause_hi << QPNP_EN_PAUSE_HI_SHIFT) &
+						QPNP_EN_PAUSE_HI_MASK;
+	*val |= (lut_config->ramp_toggle << QPNP_RAMP_TOGGLE_SHIFT) &
+						QPNP_RAMP_TOGGLE_MASK;
+	*val |= (lut_config->pattern_repeat << QPNP_PATTERN_REPEAT_SHIFT) &
+						QPNP_PATTERN_REPEAT_MASK;
+	*val |= (lut_config->ramp_direction << QPNP_RAMP_DIRECTION_SHIFT) &
+						QPNP_RAMP_DIRECTION_MASK;
+}
+
+static inline void qpnp_set_pwm_type_config(u8 *val, bool glitch,
+			bool full_scale, bool en_phase, bool phase)
+{
+	*val = phase;
+	*val |= (en_phase << QPNP_EN_PHASE_STAGGER_SHIFT) &
+				QPNP_EN_PHASE_STAGGER_MASK;
+	*val |= (full_scale << QPNP_EN_FULL_SCALE_SHIFT) &
+				QPNP_EN_FULL_SCALE_MASK;
+	*val |= (glitch << QPNP_EN_GLITCH_REMOVAL_SHIFT) &
+				QPNP_EN_GLITCH_REMOVAL_MASK;
+}
+
+static int qpnp_set_control(struct qpnp_pwm_chip *chip, bool pwm_hi,
+		bool pwm_lo, bool pwm_out, bool pwm_src, bool ramp_gen)
+{
+	int value;
+
+	value = (ramp_gen << QPNP_PWM_EN_RAMP_GEN_SHIFT) |
+		(pwm_src << QPNP_PWM_SRC_SELECT_SHIFT) |
+		(pwm_lo << QPNP_EN_PWM_LO_SHIFT) |
+		(pwm_hi << QPNP_EN_PWM_HIGH_SHIFT);
+	if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
+		value |= (pwm_out << QPNP_EN_PWM_OUTPUT_SHIFT);
+	return value;
+}
+
+#define QPNP_ENABLE_LUT_CONTROL(chip) \
+	qpnp_set_control((chip), 0, 0, 0, 0, 1)
+#define QPNP_ENABLE_PWM_CONTROL(chip) \
+	qpnp_set_control((chip), 0, 0, 0, 1, 0)
+#define QPNP_ENABLE_PWM_MODE(chip) \
+	qpnp_set_control((chip), 1, 1, 1, 1, 0)
+#define QPNP_ENABLE_PWM_MODE_GPLED_CHANNEL(chip) \
+	qpnp_set_control((chip), 1, 1, 1, 1, 1)
+#define QPNP_ENABLE_LPG_MODE(chip) \
+	qpnp_set_control((chip), 1, 1, 1, 0, 1)
+#define QPNP_DISABLE_PWM_MODE(chip) \
+	qpnp_set_control((chip), 0, 0, 0, 1, 0)
+#define QPNP_DISABLE_LPG_MODE(chip) \
+	qpnp_set_control((chip), 0, 0, 0, 0, 1)
+#define QPNP_IS_PWM_CONFIG_SELECTED(val) (val & QPNP_PWM_SRC_SELECT_MASK)
+
+#define QPNP_ENABLE_PWM_MODE_ONLY_SUB_TYPE			0x80
+#define QPNP_DISABLE_PWM_MODE_ONLY_SUB_TYPE			0x0
+#define QPNP_PWM_MODE_ONLY_ENABLE_DISABLE_MASK_SUB_TYPE	0x80
+
+static inline void qpnp_convert_to_lut_flags(int *flags,
+				struct qpnp_lut_config *l_config)
+{
+	*flags = ((l_config->ramp_direction ? PM_PWM_LUT_RAMP_UP : 0) |
+		(l_config->pattern_repeat ? PM_PWM_LUT_LOOP : 0)|
+		(l_config->ramp_toggle ? PM_PWM_LUT_REVERSE : 0) |
+		(l_config->enable_pause_hi ? PM_PWM_LUT_PAUSE_HI_EN : 0) |
+		(l_config->enable_pause_lo ? PM_PWM_LUT_PAUSE_LO_EN : 0));
+}
+
+static inline void qpnp_set_lut_params(struct lut_params *l_params,
+		struct qpnp_lut_config *l_config, int s_idx, int size)
+{
+	l_params->start_idx = s_idx;
+	l_params->idx_len = size;
+	l_params->lut_pause_hi = l_config->lut_pause_hi_cnt;
+	l_params->lut_pause_lo = l_config->lut_pause_lo_cnt;
+	l_params->ramp_step_ms = l_config->ramp_step_ms;
+	qpnp_convert_to_lut_flags(&l_params->flags, l_config);
+}
+
+static void qpnp_lpg_save(u8 *u8p, u8 mask, u8 val)
+{
+	*u8p &= ~mask;
+	*u8p |= val & mask;
+}
+
+static int qpnp_lpg_save_and_write(u8 value, u8 mask, u8 *reg, u16 addr,
+				u16 size, struct qpnp_pwm_chip *chip)
+{
+	qpnp_lpg_save(reg, mask, value);
+
+	return regmap_bulk_write(chip->regmap, addr, reg, size);
+}
+
+/*
+ * PWM Frequency = Clock Frequency / (N * T)
+ *	or
+ * PWM Period = Clock Period * (N * T)
+ *	where
+ * N = 2^9 or 2^6 for 9-bit or 6-bit PWM size
+ * T = Pre-divide * 2^m, where m = 0..7 (exponent)
+ *
+ * This is the formula to figure out m for the best pre-divide and clock:
+ * (PWM Period / N) = (Pre-divide * Clock Period) * 2^m
+ */
+static void qpnp_lpg_calc_period(enum time_level tm_lvl,
+				unsigned int period_value,
+				struct qpnp_pwm_chip *chip)
+{
+	int		n, m, clk, div;
+	int		best_m, best_div, best_clk;
+	unsigned int	last_err, cur_err, min_err;
+	unsigned int	tmp_p, period_n;
+	int             supported_sizes = chip->pwm_config.supported_sizes;
+	int		force_pwm_size = chip->pwm_config.force_pwm_size;
+	struct pwm_period_config *period = &chip->pwm_config.period;
+
+	/* PWM Period / N */
+	if (supported_sizes == QPNP_PWM_SIZE_7_8_BIT)
+		n = 7;
+	else
+		n = 6;
+
+	if (tm_lvl == LVL_USEC) {
+		if (period_value < ((unsigned int)(-1) / NSEC_PER_USEC)) {
+			period_n = (period_value * NSEC_PER_USEC) >> n;
+		} else {
+			if (supported_sizes == QPNP_PWM_SIZE_7_8_BIT)
+				n = 8;
+			else
+				n = 9;
+			period_n = (period_value >> n) * NSEC_PER_USEC;
+		}
+	} else {
+		period_n = period_value >> n;
+	}
+
+	if (force_pwm_size != 0) {
+		if (n < force_pwm_size)
+			period_n = period_n >> (force_pwm_size - n);
+		else
+			period_n = period_n << (n - force_pwm_size);
+		n = force_pwm_size;
+		pr_info("LPG channel '%d' pwm size is forced to=%d\n",
+					chip->channel_id, n);
+	}
+
+	min_err = last_err = (unsigned int)(-1);
+	best_m = 0;
+	best_clk = 0;
+	best_div = 0;
+	for (clk = 0; clk < NUM_CLOCKS; clk++) {
+		for (div = 0; div < NUM_LPG_PRE_DIVIDE; div++) {
+			/* period_n = (PWM Period / N) */
+			/* tmp_p = (Pre-divide * Clock Period) * 2^m */
+			tmp_p = pt_t[div][clk];
+			for (m = 0; m <= QPNP_PWM_M_MAX; m++) {
+				if (period_n > tmp_p)
+					cur_err = period_n - tmp_p;
+				else
+					cur_err = tmp_p - period_n;
+
+				if (cur_err < min_err) {
+					min_err = cur_err;
+					best_m = m;
+					best_clk = clk;
+					best_div = div;
+				}
+
+				if (m && cur_err > last_err)
+					/* Break for bigger cur_err */
+					break;
+
+				last_err = cur_err;
+				tmp_p <<= 1;
+			}
+		}
+	}
+
+	/* Adapt to optimal pwm size, the higher the resolution the better */
+	if (!force_pwm_size) {
+		if (supported_sizes == QPNP_PWM_SIZE_7_8_BIT) {
+			if (n == 7 && best_m >= 1) {
+				n += 1;
+				best_m -= 1;
+			}
+		} else if (n == 6) {
+			if (best_m >= 3) {
+				n += 3;
+				best_m -= 3;
+			} else if (best_m >= 1 && (
+				chip->sub_type != QPNP_PWM_MODE_ONLY_SUB_TYPE &&
+				chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)) {
+				n += 1;
+				best_m -= 1;
+			}
+		}
+	}
+
+	period->pwm_size = n;
+	period->clk = best_clk;
+	period->pre_div = best_div;
+	period->pre_div_exp = best_m;
+}
+
+static void qpnp_lpg_calc_pwm_value(struct _qpnp_pwm_config *pwm_config,
+				      unsigned int period_value,
+				      unsigned int duty_value)
+{
+	unsigned int max_pwm_value, tmp;
+
+	/* Figure out pwm_value with overflow handling */
+	tmp = 1 << (sizeof(tmp) * 8 - pwm_config->period.pwm_size);
+	if (duty_value < tmp) {
+		tmp = duty_value << pwm_config->period.pwm_size;
+		pwm_config->pwm_value = tmp / period_value;
+	} else {
+		tmp = period_value >> pwm_config->period.pwm_size;
+		pwm_config->pwm_value = duty_value / tmp;
+	}
+	max_pwm_value = (1 << pwm_config->period.pwm_size) - 1;
+	if (pwm_config->pwm_value > max_pwm_value)
+		pwm_config->pwm_value = max_pwm_value;
+	pr_debug("pwm_value: %d\n", pwm_config->pwm_value);
+}
+
+static int qpnp_lpg_change_table(struct qpnp_pwm_chip *chip,
+					int duty_pct[], int raw_value)
+{
+	unsigned int		pwm_value, max_pwm_value;
+	struct qpnp_lut_config	*lut = &chip->lpg_config.lut_config;
+	int			i, pwm_size, rc = 0;
+	int			burst_size = SPMI_MAX_BUF_LEN;
+	int			list_len = lut->list_len << 1;
+	int			offset = (lut->lo_index << 1) - 2;
+
+	pwm_size = QPNP_GET_PWM_SIZE(
+			chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]) +
+				QPNP_MIN_PWM_BIT_SIZE;
+
+	max_pwm_value = (1 << pwm_size) - 1;
+
+	if (unlikely(lut->list_len != (lut->hi_index - lut->lo_index + 1))) {
+		pr_err("LUT internal Data structure corruption detected\n");
+		pr_err("LUT list size: %d\n", lut->list_len);
+		pr_err("However, index size is: %d\n",
+				(lut->hi_index - lut->lo_index + 1));
+		return -EINVAL;
+	}
+
+	for (i = 0; i < lut->list_len; i++) {
+		if (raw_value)
+			pwm_value = duty_pct[i];
+		else
+			pwm_value = (duty_pct[i] << pwm_size) / 100;
+
+		if (pwm_value > max_pwm_value)
+			pwm_value = max_pwm_value;
+
+		if (chip->pwm_config.supported_sizes == QPNP_PWM_SIZE_7_8_BIT) {
+			lut->duty_pct_list[i] = pwm_value;
+		} else {
+			lut->duty_pct_list[i*2] = pwm_value;
+			lut->duty_pct_list[(i*2)+1] = (pwm_value >>
+			 QPNP_PWM_VALUE_MSB_SHIFT) & QPNP_PWM_VALUE_MSB_MASK;
+		}
+	}
+
+	/*
+	 * For the Keypad Backlight Lookup Table (KPDBL_LUT),
+	 * offset is lo_index.
+	 */
+	if (chip->pwm_config.supported_sizes == QPNP_PWM_SIZE_7_8_BIT)
+		offset = lut->lo_index;
+
+	/* Write with max allowable burst mode, each entry is of two bytes */
+	for (i = 0; i < list_len; i += burst_size) {
+		if (i + burst_size >= list_len)
+			burst_size = list_len - i;
+		rc = regmap_bulk_write(chip->regmap,
+			       chip->lpg_config.lut_base_addr + offset + i,
+			       lut->duty_pct_list + i,
+			       burst_size);
+	}
+
+	return rc;
+}
+
+static void qpnp_lpg_save_period(struct qpnp_pwm_chip *chip)
+{
+	u8 mask, val;
+	struct _qpnp_pwm_config	*pwm_config = &chip->pwm_config;
+
+	if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE) {
+		QPNP_SET_PWM_CLK_SUB_TYPE(val, pwm_config->period.clk,
+				pwm_config->period.pwm_size);
+		mask = QPNP_PWM_SIZE_MASK_SUB_TYPE |
+				QPNP_PWM_FREQ_CLK_SELECT_MASK_SUB_TYPE;
+	} else {
+		QPNP_SET_PWM_CLK(val, pwm_config->period.clk,
+				pwm_config->period.pwm_size);
+		mask = QPNP_PWM_SIZE_MASK | QPNP_PWM_FREQ_CLK_SELECT_MASK;
+	}
+
+	qpnp_lpg_save(&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK],
+							mask, val);
+
+	QPNP_SET_PWM_FREQ_PREDIV(val, pwm_config->period.pre_div,
+					pwm_config->period.pre_div_exp);
+
+	mask = QPNP_PWM_FREQ_PRE_DIVIDE_MASK | QPNP_PWM_FREQ_EXP_MASK;
+
+	qpnp_lpg_save(&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK],
+								mask, val);
+}
+
+static int qpnp_lpg_save_pwm_value(struct qpnp_pwm_chip *chip)
+{
+	unsigned int		max_pwm_value;
+	int			pwm_size;
+	u8			mask, value;
+	struct _qpnp_pwm_config	*pwm_config = &chip->pwm_config;
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	int rc;
+
+	if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE)
+		pwm_size = QPNP_GET_PWM_SIZE_SUB_TYPE(
+			chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]) ?
+				QPNP_MAX_PWM_BIT_SIZE : QPNP_MIN_PWM_BIT_SIZE;
+	else
+		pwm_size = QPNP_GET_PWM_SIZE(
+			chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]) +
+				QPNP_MIN_PWM_BIT_SIZE;
+
+	max_pwm_value = (1 << pwm_size) - 1;
+
+	if (pwm_config->pwm_value > max_pwm_value)
+		pwm_config->pwm_value = max_pwm_value;
+
+	value = pwm_config->pwm_value;
+	mask = QPNP_PWM_VALUE_LSB_MASK;
+
+	pr_debug("pwm_lsb value:%d\n", value & mask);
+	rc = qpnp_lpg_save_and_write(value, mask,
+			&chip->qpnp_lpg_registers[QPNP_PWM_VALUE_LSB],
+			SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+			QPNP_PWM_VALUE_LSB), 1, chip);
+	if (rc)
+		return rc;
+
+	value = (pwm_config->pwm_value >> QPNP_PWM_VALUE_MSB_SHIFT) &
+					QPNP_PWM_VALUE_MSB_MASK;
+
+	mask = QPNP_PWM_VALUE_MSB_MASK;
+
+	pr_debug("pwm_msb value:%d\n", value);
+	rc = qpnp_lpg_save_and_write(value, mask,
+			&chip->qpnp_lpg_registers[QPNP_PWM_VALUE_MSB],
+			SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+			QPNP_PWM_VALUE_MSB), 1, chip);
+	if (rc)
+		return rc;
+
+	if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE ||
+		chip->sub_type == QPNP_LPG_S_CHAN_SUB_TYPE) {
+		value = QPNP_PWM_SYNC_VALUE & QPNP_PWM_SYNC_MASK;
+		rc = regmap_write(chip->regmap,
+					SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+						SPMI_LPG_PWM_SYNC),
+					value);
+	}
+
+	return rc;
+}
+
+static int qpnp_lpg_configure_pattern(struct qpnp_pwm_chip *chip)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	struct qpnp_lut_config	*lut_config = &lpg_config->lut_config;
+	u8			value, mask;
+
+	qpnp_set_pattern_config(&value, lut_config);
+
+	mask = QPNP_RAMP_DIRECTION_MASK | QPNP_PATTERN_REPEAT_MASK |
+			QPNP_RAMP_TOGGLE_MASK | QPNP_EN_PAUSE_HI_MASK |
+			QPNP_EN_PAUSE_LO_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_LPG_PATTERN_CONFIG],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_LPG_PATTERN_CONFIG), 1, chip);
+}
+
+static int qpnp_lpg_glitch_removal(struct qpnp_pwm_chip *chip, bool enable)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	u8 value, mask;
+
+	qpnp_set_pwm_type_config(&value, enable ? 1 : 0, 0, 0, 0);
+
+	mask = QPNP_EN_GLITCH_REMOVAL_MASK | QPNP_EN_FULL_SCALE_MASK |
+			QPNP_EN_PHASE_STAGGER_MASK | QPNP_PHASE_STAGGER_MASK;
+
+	pr_debug("pwm_type_config: %d\n", value);
+	return qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_LPG_PWM_TYPE_CONFIG],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_LPG_PWM_TYPE_CONFIG), 1, chip);
+}
+
+static int qpnp_lpg_configure_pwm(struct qpnp_pwm_chip *chip)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	int rc;
+
+	pr_debug("pwm_size_clk: %d\n",
+		chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]);
+	rc = regmap_write(chip->regmap,
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_LPG_PWM_SIZE_CLK),
+		*&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]);
+
+	if (rc)
+		return rc;
+
+	pr_debug("pwm_freq_prediv_clk: %d\n",
+		chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK]);
+	rc = regmap_write(chip->regmap,
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_LPG_PWM_FREQ_PREDIV_CLK),
+		*&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK]);
+	if (rc)
+		return rc;
+
+	/* Disable glitch removal when LPG/PWM is configured */
+	rc = qpnp_lpg_glitch_removal(chip, false);
+	if (rc) {
+		pr_err("Error in disabling glitch control, rc=%d\n", rc);
+		return rc;
+	}
+	return rc;
+}
+
+static int qpnp_configure_pwm_control(struct qpnp_pwm_chip *chip)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	u8			value, mask;
+
+	if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE)
+		return 0;
+
+	value = QPNP_ENABLE_PWM_CONTROL(chip);
+
+	mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
+		QPNP_PWM_SRC_SELECT_MASK | QPNP_PWM_EN_RAMP_GEN_MASK;
+	if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
+		mask |= QPNP_EN_PWM_OUTPUT_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_ENABLE_CONTROL), 1, chip);
+
+}
+
+static int qpnp_configure_lpg_control(struct qpnp_pwm_chip *chip)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	u8			value, mask;
+
+	value = QPNP_ENABLE_LUT_CONTROL(chip);
+
+	mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
+		QPNP_PWM_SRC_SELECT_MASK | QPNP_PWM_EN_RAMP_GEN_MASK;
+	if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
+		mask |= QPNP_EN_PWM_OUTPUT_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_ENABLE_CONTROL), 1, chip);
+
+}
+
+static int qpnp_lpg_configure_ramp_step_duration(struct qpnp_pwm_chip *chip)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	struct qpnp_lut_config	lut_config = lpg_config->lut_config;
+	int			rc, value;
+	u8			val, mask;
+
+	value = QPNP_GET_RAMP_STEP_DURATION(lut_config.ramp_step_ms);
+	val = value & QPNP_RAMP_STEP_DURATION_LSB_MASK;
+	mask = QPNP_RAMP_STEP_DURATION_LSB_MASK;
+
+	rc = qpnp_lpg_save_and_write(val, mask,
+		&chip->qpnp_lpg_registers[QPNP_RAMP_STEP_DURATION_LSB],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_RAMP_STEP_DURATION_LSB), 1, chip);
+	if (rc)
+		return rc;
+
+	val = (value >> QPNP_RAMP_STEP_DURATION_MSB_SHIFT) &
+				QPNP_RAMP_STEP_DURATION_MSB_MASK;
+
+	mask = QPNP_RAMP_STEP_DURATION_MSB_MASK;
+
+	return qpnp_lpg_save_and_write(val, mask,
+		&chip->qpnp_lpg_registers[QPNP_RAMP_STEP_DURATION_MSB],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_RAMP_STEP_DURATION_MSB), 1, chip);
+}
+
+static int qpnp_lpg_configure_pause(struct qpnp_pwm_chip *chip)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	struct qpnp_lut_config	lut_config = lpg_config->lut_config;
+	u8			value, mask;
+	int			rc = 0;
+
+	if (lut_config.enable_pause_hi) {
+		value = lut_config.lut_pause_hi_cnt;
+		mask = QPNP_PAUSE_HI_MULTIPLIER_LSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_LSB],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_PAUSE_HI_MULTIPLIER_LSB), 1, chip);
+		if (rc)
+			return rc;
+
+		value = (lut_config.lut_pause_hi_cnt >>
+			QPNP_PAUSE_HI_MULTIPLIER_MSB_SHIFT) &
+					QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK;
+
+		mask = QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_MSB],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_PAUSE_HI_MULTIPLIER_MSB), 1, chip);
+	} else {
+		value = 0;
+		mask = QPNP_PAUSE_HI_MULTIPLIER_LSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_LSB],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_PAUSE_HI_MULTIPLIER_LSB), 1, chip);
+		if (rc)
+			return rc;
+
+		mask = QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_MSB],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_PAUSE_HI_MULTIPLIER_MSB), 1, chip);
+		if (rc)
+			return rc;
+
+	}
+
+	if (lut_config.enable_pause_lo) {
+		value = lut_config.lut_pause_lo_cnt;
+		mask = QPNP_PAUSE_LO_MULTIPLIER_LSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_LSB],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_PAUSE_LO_MULTIPLIER_LSB), 1, chip);
+		if (rc)
+			return rc;
+
+		value = (lut_config.lut_pause_lo_cnt >>
+				QPNP_PAUSE_LO_MULTIPLIER_MSB_SHIFT) &
+					QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK;
+
+		mask = QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_MSB],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_PAUSE_LO_MULTIPLIER_MSB), 1, chip);
+	} else {
+		value = 0;
+		mask = QPNP_PAUSE_LO_MULTIPLIER_LSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_LSB],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_PAUSE_LO_MULTIPLIER_LSB), 1, chip);
+		if (rc)
+			return rc;
+
+		mask = QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_MSB],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_PAUSE_LO_MULTIPLIER_MSB), 1, chip);
+		return rc;
+	}
+
+	return rc;
+}
+
+static int qpnp_lpg_configure_index(struct qpnp_pwm_chip *chip)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	struct qpnp_lut_config	lut_config = lpg_config->lut_config;
+	u8			value, mask;
+	int			rc = 0;
+
+	value = lut_config.hi_index;
+	mask = QPNP_HI_INDEX_MASK;
+
+	rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_HI_INDEX],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_HI_INDEX), 1, chip);
+	if (rc)
+		return rc;
+
+	value = lut_config.lo_index;
+	mask = QPNP_LO_INDEX_MASK;
+
+	rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_LO_INDEX],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_LO_INDEX), 1, chip);
+
+	return rc;
+}
+
+static int qpnp_lpg_change_lut(struct qpnp_pwm_chip *chip)
+{
+	int	rc;
+
+	rc = qpnp_lpg_configure_pattern(chip);
+	if (rc) {
+		pr_err("Failed to configure LUT pattern");
+		return rc;
+	}
+	rc = qpnp_lpg_configure_pwm(chip);
+	if (rc) {
+		pr_err("Failed to configure LUT pattern");
+		return rc;
+	}
+	rc = qpnp_configure_lpg_control(chip);
+	if (rc) {
+		pr_err("Failed to configure pause registers");
+		return rc;
+	}
+	rc = qpnp_lpg_configure_ramp_step_duration(chip);
+	if (rc) {
+		pr_err("Failed to configure duty time");
+		return rc;
+	}
+	rc = qpnp_lpg_configure_pause(chip);
+	if (rc) {
+		pr_err("Failed to configure pause registers");
+		return rc;
+	}
+	rc = qpnp_lpg_configure_index(chip);
+	if (rc) {
+		pr_err("Failed to configure index registers");
+		return rc;
+	}
+	return rc;
+}
+
+static int qpnp_dtest_config(struct qpnp_pwm_chip *chip, bool enable)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	u8			value;
+	u8			mask;
+	u16			addr;
+	int			rc = 0;
+
+	value = 0xA5;
+
+	addr = SPMI_LPG_REG_ADDR(lpg_config->base_addr, QPNP_LPG_SEC_ACCESS);
+
+	rc = regmap_write(chip->regmap, addr, value);
+
+	if (rc) {
+		pr_err("Couldn't set the access for test mode\n");
+		return rc;
+	}
+
+	addr = SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+			QPNP_LPG_DTEST + chip->dtest_line - 1);
+
+	if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE)
+		mask = QPNP_PWM_DTEST_OUTPUT_MASK;
+	else
+		mask = QPNP_LPG_DTEST_OUTPUT_MASK;
+
+	if (enable)
+		value = chip->dtest_output & mask;
+	else
+		value = 0;
+
+	pr_debug("Setting TEST mode for channel %d addr:%x value: %x\n",
+		chip->channel_id, addr, value);
+
+	rc = regmap_write(chip->regmap, addr, value);
+
+	return rc;
+}
+
+static int qpnp_lpg_configure_lut_state(struct qpnp_pwm_chip *chip,
+				enum qpnp_lut_state state)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	u8			value1, value2, mask1, mask2;
+	u8			*reg1, *reg2;
+	u16			addr, addr1;
+	int			rc;
+	bool			test_enable;
+
+	value1 = chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL];
+	reg1 = &chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL];
+	reg2 = &chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL];
+	mask2 = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
+		 QPNP_PWM_SRC_SELECT_MASK | QPNP_PWM_EN_RAMP_GEN_MASK;
+	if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
+		mask2 |= QPNP_EN_PWM_OUTPUT_MASK;
+
+	if (chip->sub_type == QPNP_LPG_CHAN_SUB_TYPE
+		&& chip->revision == QPNP_LPG_REVISION_0) {
+		if (state == QPNP_LUT_ENABLE) {
+			QPNP_ENABLE_LUT_V0(value1);
+			value2 = QPNP_ENABLE_LPG_MODE(chip);
+		} else {
+			QPNP_DISABLE_LUT_V0(value1);
+			value2 = QPNP_DISABLE_LPG_MODE(chip);
+		}
+		mask1 = QPNP_RAMP_START_MASK;
+		addr1 = SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+					QPNP_RAMP_CONTROL);
+	} else if ((chip->sub_type == QPNP_LPG_CHAN_SUB_TYPE
+			&& chip->revision == QPNP_LPG_REVISION_1)
+			|| chip->sub_type == QPNP_LPG_S_CHAN_SUB_TYPE) {
+		if (state == QPNP_LUT_ENABLE) {
+			QPNP_ENABLE_LUT_V1(value1,
+					lpg_config->lut_config.ramp_index);
+			value2 = QPNP_ENABLE_LPG_MODE(chip);
+		} else {
+			value2 = QPNP_DISABLE_LPG_MODE(chip);
+		}
+		mask1 = value1;
+		addr1 = lpg_config->lut_base_addr +
+			SPMI_LPG_REV1_RAMP_CONTROL_OFFSET;
+	} else {
+		pr_err("Unsupported LPG subtype 0x%02x, revision 0x%02x\n",
+			chip->sub_type, chip->revision);
+		return -EINVAL;
+	}
+
+	addr = SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+				QPNP_ENABLE_CONTROL);
+
+	if (chip->in_test_mode) {
+		test_enable = (state == QPNP_LUT_ENABLE) ? 1 : 0;
+		rc = qpnp_dtest_config(chip, test_enable);
+		if (rc)
+			pr_err("Failed to configure TEST mode\n");
+	}
+
+	rc = qpnp_lpg_save_and_write(value2, mask2, reg2,
+					addr, 1, chip);
+	if (rc)
+		return rc;
+
+	if (state == QPNP_LUT_ENABLE
+		|| (chip->sub_type == QPNP_LPG_CHAN_SUB_TYPE
+		&& chip->revision == QPNP_LPG_REVISION_0))
+		rc = qpnp_lpg_save_and_write(value1, mask1, reg1,
+					addr1, 1, chip);
+	return rc;
+}
+
+static inline int qpnp_enable_pwm_mode(struct qpnp_pwm_chip *chip)
+{
+	if (chip->pwm_config.supported_sizes == QPNP_PWM_SIZE_7_8_BIT)
+		return QPNP_ENABLE_PWM_MODE_GPLED_CHANNEL(chip);
+	return QPNP_ENABLE_PWM_MODE(chip);
+}
+
+static int qpnp_lpg_configure_pwm_state(struct qpnp_pwm_chip *chip,
+					enum qpnp_pwm_state state)
+{
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	u8			value, mask;
+	int			rc;
+	bool			test_enable;
+
+	if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE) {
+		if (state == QPNP_PWM_ENABLE)
+			value = QPNP_ENABLE_PWM_MODE_ONLY_SUB_TYPE;
+		else
+			value = QPNP_DISABLE_PWM_MODE_ONLY_SUB_TYPE;
+
+		mask = QPNP_PWM_MODE_ONLY_ENABLE_DISABLE_MASK_SUB_TYPE;
+	} else {
+		if (state == QPNP_PWM_ENABLE)
+			value = qpnp_enable_pwm_mode(chip);
+		else
+			value = QPNP_DISABLE_PWM_MODE(chip);
+
+		mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
+			QPNP_PWM_SRC_SELECT_MASK | QPNP_PWM_EN_RAMP_GEN_MASK;
+		if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
+			mask |= QPNP_EN_PWM_OUTPUT_MASK;
+	}
+
+	if (chip->in_test_mode) {
+		test_enable = (state == QPNP_PWM_ENABLE) ? 1 : 0;
+		rc = qpnp_dtest_config(chip, test_enable);
+		if (rc)
+			pr_err("Failed to configure TEST mode\n");
+	}
+
+	pr_debug("pwm_enable_control: %d\n", value);
+	rc = qpnp_lpg_save_and_write(value, mask,
+		&chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_ENABLE_CONTROL), 1, chip);
+	if (rc)
+		goto out;
+
+	/*
+	 * Due to LPG hardware bug, in the PWM mode, having enabled PWM,
+	 * We have to write PWM values one more time.
+	 */
+	if (state == QPNP_PWM_ENABLE)
+		return qpnp_lpg_save_pwm_value(chip);
+
+out:
+	return rc;
+}
+
+static int _pwm_config(struct qpnp_pwm_chip *chip,
+				enum time_level tm_lvl,
+				int duty_value, int period_value)
+{
+	int rc;
+	struct _qpnp_pwm_config *pwm_config = &chip->pwm_config;
+	struct pwm_period_config *period = &pwm_config->period;
+
+	pwm_config->pwm_duty = (tm_lvl == LVL_USEC) ? duty_value :
+			duty_value / NSEC_PER_USEC;
+	qpnp_lpg_calc_pwm_value(pwm_config, period_value, duty_value);
+	rc = qpnp_lpg_save_pwm_value(chip);
+	if (rc)
+		goto out;
+	rc = qpnp_lpg_configure_pwm(chip);
+	if (rc)
+		goto out;
+	rc = qpnp_configure_pwm_control(chip);
+	if (rc)
+		goto out;
+
+	if (!rc && chip->enabled) {
+		rc = qpnp_lpg_configure_pwm_state(chip, QPNP_PWM_ENABLE);
+		if (rc) {
+			pr_err("Error in configuring pwm state, rc=%d\n", rc);
+			return rc;
+		}
+
+		/* Enable the glitch removal after PWM is enabled */
+		rc = qpnp_lpg_glitch_removal(chip, true);
+		if (rc) {
+			pr_err("Error in enabling glitch control, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	pr_debug("duty/period=%u/%u %s: pwm_value=%d (of %d)\n",
+		 (unsigned int)duty_value, (unsigned int)period_value,
+		 (tm_lvl == LVL_USEC) ? "usec" : "nsec",
+		 pwm_config->pwm_value, 1 << period->pwm_size);
+
+out:
+	return rc;
+}
+
+static int _pwm_lut_config(struct qpnp_pwm_chip *chip, int period_us,
+		int duty_pct[], struct lut_params lut_params)
+{
+	struct qpnp_lpg_config		*lpg_config;
+	struct qpnp_lut_config		*lut_config;
+	struct pwm_period_config	*period;
+	struct _qpnp_pwm_config		*pwm_config;
+	int				start_idx = lut_params.start_idx;
+	int				len = lut_params.idx_len;
+	int				flags = lut_params.flags;
+	int				raw_lut, ramp_step_ms;
+	int				rc = 0;
+
+	pwm_config = &chip->pwm_config;
+	lpg_config = &chip->lpg_config;
+	lut_config = &lpg_config->lut_config;
+	period = &pwm_config->period;
+
+	if (flags & PM_PWM_LUT_NO_TABLE)
+		goto after_table_write;
+
+	raw_lut = 0;
+	if (flags & PM_PWM_LUT_USE_RAW_VALUE)
+		raw_lut = 1;
+
+	lut_config->list_len = len;
+	lut_config->lo_index = start_idx + 1;
+	lut_config->hi_index = start_idx + len;
+
+	rc = qpnp_lpg_change_table(chip, duty_pct, raw_lut);
+	if (rc) {
+		pr_err("qpnp_lpg_change_table: rc=%d\n", rc);
+		return -EINVAL;
+	}
+
+after_table_write:
+	ramp_step_ms = lut_params.ramp_step_ms;
+
+	if (ramp_step_ms > PM_PWM_LUT_RAMP_STEP_TIME_MAX)
+		ramp_step_ms = PM_PWM_LUT_RAMP_STEP_TIME_MAX;
+
+	QPNP_SET_PAUSE_CNT(lut_config->lut_pause_lo_cnt,
+			lut_params.lut_pause_lo, ramp_step_ms);
+	if (lut_config->lut_pause_lo_cnt > PM_PWM_MAX_PAUSE_CNT)
+		lut_config->lut_pause_lo_cnt = PM_PWM_MAX_PAUSE_CNT;
+
+	QPNP_SET_PAUSE_CNT(lut_config->lut_pause_hi_cnt,
+			lut_params.lut_pause_hi, ramp_step_ms);
+	if (lut_config->lut_pause_hi_cnt > PM_PWM_MAX_PAUSE_CNT)
+		lut_config->lut_pause_hi_cnt = PM_PWM_MAX_PAUSE_CNT;
+
+	lut_config->ramp_step_ms = ramp_step_ms;
+
+	lut_config->ramp_direction  = !!(flags & PM_PWM_LUT_RAMP_UP);
+	lut_config->pattern_repeat  = !!(flags & PM_PWM_LUT_LOOP);
+	lut_config->ramp_toggle	    = !!(flags & PM_PWM_LUT_REVERSE);
+	lut_config->enable_pause_hi = !!(flags & PM_PWM_LUT_PAUSE_HI_EN);
+	lut_config->enable_pause_lo = !!(flags & PM_PWM_LUT_PAUSE_LO_EN);
+
+	rc = qpnp_lpg_change_lut(chip);
+
+	if (!rc && chip->enabled)
+		rc = qpnp_lpg_configure_lut_state(chip, QPNP_LUT_ENABLE);
+
+	return rc;
+}
+
+static int _pwm_enable(struct qpnp_pwm_chip *chip)
+{
+	int rc = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lpg_lock, flags);
+
+	if (QPNP_IS_PWM_CONFIG_SELECTED(
+		chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL]) ||
+			chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED) {
+		rc = qpnp_lpg_configure_pwm_state(chip, QPNP_PWM_ENABLE);
+	} else if (!(chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED)) {
+		rc = qpnp_lpg_configure_lut_state(chip, QPNP_LUT_ENABLE);
+	}
+
+	if (!rc)
+		chip->enabled = true;
+
+	spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+	return rc;
+}
+
+/* APIs */
+/**
+ * qpnp_pwm_free - free a PWM device
+ * @pwm_chip: the PWM chip
+ * @pwm: the PWM device
+ */
+static void qpnp_pwm_free(struct pwm_chip *pwm_chip,
+		struct pwm_device *pwm)
+{
+	struct qpnp_pwm_chip	*chip = qpnp_pwm_from_pwm_chip(pwm_chip);
+	unsigned long		flags;
+
+	spin_lock_irqsave(&chip->lpg_lock, flags);
+
+	qpnp_lpg_configure_pwm_state(chip, QPNP_PWM_DISABLE);
+	if (!(chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED))
+		qpnp_lpg_configure_lut_state(chip, QPNP_LUT_DISABLE);
+
+	chip->enabled = false;
+	spin_unlock_irqrestore(&chip->lpg_lock, flags);
+}
+
+/**
+ * qpnp_pwm_config - change a PWM device configuration
+ * @pwm: the PWM device
+ * @period_ns: period in nanoseconds
+ * @duty_ns: duty cycle in nanoseconds
+ */
+static int qpnp_pwm_config(struct pwm_chip *pwm_chip,
+	struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+	int rc;
+	unsigned long flags;
+	struct qpnp_pwm_chip *chip = qpnp_pwm_from_pwm_chip(pwm_chip);
+	int prev_period_us = chip->pwm_config.pwm_period;
+
+	if ((unsigned int)period_ns < PM_PWM_PERIOD_MIN * NSEC_PER_USEC) {
+		pr_err("Invalid pwm handle or parameters\n");
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&chip->lpg_lock, flags);
+
+	if (prev_period_us > INT_MAX / NSEC_PER_USEC ||
+			prev_period_us * NSEC_PER_USEC != period_ns) {
+		qpnp_lpg_calc_period(LVL_NSEC, period_ns, chip);
+		qpnp_lpg_save_period(chip);
+		pwm->state.period = period_ns;
+		chip->pwm_config.pwm_period = period_ns / NSEC_PER_USEC;
+	}
+
+	rc = _pwm_config(chip, LVL_NSEC, duty_ns, period_ns);
+
+	spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+	if (rc)
+		pr_err("Failed to configure PWM mode\n");
+
+	return rc;
+}
+
+/**
+ * qpnp_pwm_enable - start a PWM output toggling
+ * @pwm_chip: the PWM chip
+ * @pwm: the PWM device
+ */
+static int qpnp_pwm_enable(struct pwm_chip *pwm_chip,
+		struct pwm_device *pwm)
+{
+	int rc;
+	struct qpnp_pwm_chip *chip = qpnp_pwm_from_pwm_chip(pwm_chip);
+
+	rc = _pwm_enable(chip);
+	if (rc)
+		pr_err("Failed to enable PWM channel: %d\n", chip->channel_id);
+
+	return rc;
+}
+
+/**
+ * qpnp_pwm_disable - stop a PWM output toggling
+ * @pwm_chip: the PWM chip
+ * @pwm: the PWM device
+ */
+static void qpnp_pwm_disable(struct pwm_chip *pwm_chip,
+		struct pwm_device *pwm)
+{
+
+	struct qpnp_pwm_chip	*chip = qpnp_pwm_from_pwm_chip(pwm_chip);
+	unsigned long		flags;
+	int rc = 0;
+
+	spin_lock_irqsave(&chip->lpg_lock, flags);
+
+	if (QPNP_IS_PWM_CONFIG_SELECTED(
+		chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL]) ||
+			chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED)
+		rc = qpnp_lpg_configure_pwm_state(chip,
+					QPNP_PWM_DISABLE);
+	else if (!(chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED))
+		rc = qpnp_lpg_configure_lut_state(chip,
+					QPNP_LUT_DISABLE);
+
+	if (!rc)
+		chip->enabled = false;
+
+	spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+	if (rc)
+		pr_err("Failed to disable PWM channel: %d\n",
+					chip->channel_id);
+}
+
+static int _pwm_change_mode(struct qpnp_pwm_chip *chip, enum pm_pwm_mode mode)
+{
+	int rc;
+
+	if (mode)
+		rc = qpnp_configure_lpg_control(chip);
+	else
+		rc = qpnp_configure_pwm_control(chip);
+
+	if (rc)
+		pr_err("Failed to change the mode\n");
+	return rc;
+}
+
+/**
+ * pwm_change_mode - Change the PWM mode configuration
+ * @pwm: the PWM device
+ * @mode: Mode selection value
+ */
+int pwm_change_mode(struct pwm_device *pwm, enum pm_pwm_mode mode)
+{
+	int rc;
+	unsigned long flags;
+	struct qpnp_pwm_chip *chip;
+
+	if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) {
+		pr_err("Invalid pwm handle or no pwm_chip\n");
+		return -EINVAL;
+	}
+
+	if (mode < PM_PWM_MODE_PWM || mode > PM_PWM_MODE_LPG) {
+		pr_err("Invalid mode value\n");
+		return -EINVAL;
+	}
+
+	chip = qpnp_pwm_from_pwm_dev(pwm);
+
+	spin_lock_irqsave(&chip->lpg_lock, flags);
+	rc = _pwm_change_mode(chip, mode);
+	spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+	return rc;
+}
+EXPORT_SYMBOL(pwm_change_mode);
+
+/**
+ * pwm_config_period - change PWM period
+ *
+ * @pwm: the PWM device
+ * @pwm_p: period in struct qpnp_lpg_period
+ */
+int pwm_config_period(struct pwm_device *pwm,
+			     struct pwm_period_config *period)
+{
+	struct _qpnp_pwm_config	*pwm_config;
+	struct qpnp_lpg_config	*lpg_config;
+	struct qpnp_pwm_chip	*chip;
+	unsigned long		flags;
+	int			rc = 0;
+
+	if (pwm == NULL || IS_ERR(pwm) || period == NULL)
+		return -EINVAL;
+	if (pwm->chip == NULL)
+		return -ENODEV;
+
+	chip = qpnp_pwm_from_pwm_dev(pwm);
+	pwm_config = &chip->pwm_config;
+	lpg_config = &chip->lpg_config;
+
+	spin_lock_irqsave(&chip->lpg_lock, flags);
+
+	pwm_config->period.pwm_size = period->pwm_size;
+	pwm_config->period.clk = period->clk;
+	pwm_config->period.pre_div = period->pre_div;
+	pwm_config->period.pre_div_exp = period->pre_div_exp;
+
+	qpnp_lpg_save_period(chip);
+
+	rc = regmap_write(chip->regmap,
+			SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+			QPNP_LPG_PWM_SIZE_CLK),
+			*&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]);
+
+	if (rc) {
+		pr_err("Write failed: QPNP_LPG_PWM_SIZE_CLK register, rc: %d\n",
+									rc);
+		goto out_unlock;
+	}
+
+	rc = regmap_write(chip->regmap,
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_LPG_PWM_FREQ_PREDIV_CLK),
+		*&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK]);
+	if (rc) {
+		pr_err("Failed to write to QPNP_LPG_PWM_FREQ_PREDIV_CLK\n");
+		pr_err("register, rc = %d\n", rc);
+	}
+
+out_unlock:
+	spin_unlock_irqrestore(&chip->lpg_lock, flags);
+	return rc;
+}
+EXPORT_SYMBOL(pwm_config_period);
+
+/**
+ * pwm_config_pwm_value - change a PWM device configuration
+ * @pwm: the PWM device
+ * @pwm_value: the duty cycle in raw PWM value (< 2^pwm_size)
+ */
+int pwm_config_pwm_value(struct pwm_device *pwm, int pwm_value)
+{
+	struct qpnp_lpg_config	*lpg_config;
+	struct _qpnp_pwm_config	*pwm_config;
+	struct qpnp_pwm_chip	*chip;
+	unsigned long		flags;
+	int			rc = 0;
+
+	if (pwm == NULL || IS_ERR(pwm)) {
+		pr_err("Invalid parameter passed\n");
+		return -EINVAL;
+	}
+
+	if (pwm->chip == NULL) {
+		pr_err("Invalid device handle\n");
+		return -ENODEV;
+	}
+
+	chip = qpnp_pwm_from_pwm_dev(pwm);
+	lpg_config = &chip->lpg_config;
+	pwm_config = &chip->pwm_config;
+
+	spin_lock_irqsave(&chip->lpg_lock, flags);
+
+	if (pwm_config->pwm_value == pwm_value)
+		goto out_unlock;
+
+	pwm_config->pwm_value = pwm_value;
+
+	rc = qpnp_lpg_save_pwm_value(chip);
+
+	if (rc)
+		pr_err("Could not update PWM value for channel %d rc=%d\n",
+						chip->channel_id, rc);
+
+out_unlock:
+	spin_unlock_irqrestore(&chip->lpg_lock, flags);
+	return rc;
+}
+EXPORT_SYMBOL(pwm_config_pwm_value);
+
+/**
+ * pwm_config_us - change a PWM device configuration
+ * @pwm: the PWM device
+ * @period_us: period in microseconds
+ * @duty_us: duty cycle in microseconds
+ */
+int pwm_config_us(struct pwm_device *pwm, int duty_us, int period_us)
+{
+	int rc;
+	unsigned long flags;
+	struct qpnp_pwm_chip *chip;
+
+	if (pwm == NULL || IS_ERR(pwm) ||
+		duty_us > period_us ||
+		(unsigned int)period_us > PM_PWM_PERIOD_MAX ||
+		(unsigned int)period_us < PM_PWM_PERIOD_MIN) {
+		pr_err("Invalid pwm handle or parameters\n");
+		return -EINVAL;
+	}
+
+	chip = qpnp_pwm_from_pwm_dev(pwm);
+
+	spin_lock_irqsave(&chip->lpg_lock, flags);
+
+	if (chip->pwm_config.pwm_period != period_us) {
+		qpnp_lpg_calc_period(LVL_USEC, period_us, chip);
+		qpnp_lpg_save_period(chip);
+		chip->pwm_config.pwm_period = period_us;
+		if ((unsigned int)period_us >
+		    (unsigned int)(-1) / NSEC_PER_USEC)
+			pwm->state.period = 0;
+		else
+			pwm->state.period
+				= (unsigned int)period_us * NSEC_PER_USEC;
+	}
+
+	rc = _pwm_config(chip, LVL_USEC, duty_us, period_us);
+
+	spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+	if (rc)
+		pr_err("Failed to configure PWM mode\n");
+
+	return rc;
+}
+EXPORT_SYMBOL(pwm_config_us);
+
+/**
+ * pwm_lut_config - change LPG LUT device configuration
+ * @pwm: the PWM device
+ * @period_us: period in micro second
+ * @duty_pct: array of duty cycles in percent, like 20, 50.
+ * @lut_params: Lookup table parameters
+ */
+int pwm_lut_config(struct pwm_device *pwm, int period_us,
+		int duty_pct[], struct lut_params lut_params)
+{
+	unsigned long flags;
+	struct qpnp_pwm_chip *chip;
+	int rc = 0;
+
+	if (pwm == NULL || IS_ERR(pwm) || !lut_params.idx_len) {
+		pr_err("Invalid pwm handle or idx_len=0\n");
+		return -EINVAL;
+	}
+
+	if (pwm->chip == NULL)
+		return -ENODEV;
+
+	if (duty_pct == NULL && !(lut_params.flags & PM_PWM_LUT_NO_TABLE)) {
+		pr_err("Invalid duty_pct with flag\n");
+		return -EINVAL;
+	}
+
+	chip = qpnp_pwm_from_pwm_dev(pwm);
+
+	if (chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED) {
+		pr_err("LUT mode isn't supported\n");
+		return -EINVAL;
+	}
+
+	if ((lut_params.start_idx + lut_params.idx_len) >
+				chip->lpg_config.lut_size) {
+		pr_err("Exceed LUT limit\n");
+		return -EINVAL;
+	}
+
+	if ((unsigned int)period_us > PM_PWM_PERIOD_MAX ||
+	    (unsigned int)period_us < PM_PWM_PERIOD_MIN) {
+		pr_err("Period out of range\n");
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&chip->lpg_lock, flags);
+
+	if (chip->pwm_config.pwm_period != period_us) {
+		qpnp_lpg_calc_period(LVL_USEC, period_us, chip);
+		qpnp_lpg_save_period(chip);
+		chip->pwm_config.pwm_period = period_us;
+	}
+
+	rc = _pwm_lut_config(chip, period_us, duty_pct, lut_params);
+
+	spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+	if (rc)
+		pr_err("Failed to configure LUT\n");
+
+	return rc;
+}
+EXPORT_SYMBOL(pwm_lut_config);
+
+static int qpnp_parse_pwm_dt_config(struct device_node *of_pwm_node,
+		struct device_node *of_parent, struct qpnp_pwm_chip *chip)
+{
+	int rc, period;
+
+	rc = of_property_read_u32(of_parent, "qcom,period", (u32 *)&period);
+	if (rc) {
+		pr_err("node is missing PWM Period prop");
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_pwm_node, "qcom,duty",
+				&chip->pwm_config.pwm_duty);
+	if (rc) {
+		pr_err("node is missing PWM Duty prop");
+		return rc;
+	}
+
+	if (period < chip->pwm_config.pwm_duty || period > PM_PWM_PERIOD_MAX ||
+		period < PM_PWM_PERIOD_MIN) {
+		pr_err("Invalid pwm period(%d) or duty(%d)\n", period,
+			chip->pwm_config.pwm_duty);
+		return -EINVAL;
+	}
+
+	qpnp_lpg_calc_period(LVL_USEC, period, chip);
+	qpnp_lpg_save_period(chip);
+	chip->pwm_config.pwm_period = period;
+
+	rc = _pwm_config(chip, LVL_USEC, chip->pwm_config.pwm_duty, period);
+
+	return rc;
+}
+
+static int qpnp_parse_lpg_dt_config(struct device_node *of_lpg_node,
+		struct device_node *of_parent, struct qpnp_pwm_chip *chip)
+{
+	int rc, period, list_size, start_idx, *duty_pct_list;
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	struct qpnp_lut_config	*lut_config = &lpg_config->lut_config;
+	struct lut_params	lut_params;
+
+	rc = of_property_read_u32(of_parent, "qcom,period", &period);
+	if (rc) {
+		pr_err("node is missing PWM Period prop\n");
+		return rc;
+	}
+
+	if (!of_get_property(of_lpg_node, "qcom,duty-percents", &list_size)) {
+		pr_err("node is missing duty-pct list\n");
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_lpg_node, "cell-index", &start_idx);
+	if (rc) {
+		pr_err("Missing start index\n");
+		return rc;
+	}
+
+	list_size /= sizeof(u32);
+
+	if (list_size + start_idx > lpg_config->lut_size) {
+		pr_err("duty pct list size overflows\n");
+		return -EINVAL;
+	}
+
+	duty_pct_list = kcalloc(list_size, sizeof(*duty_pct_list), GFP_KERNEL);
+	if (!duty_pct_list)
+		return -ENOMEM;
+
+	rc = of_property_read_u32_array(of_lpg_node, "qcom,duty-percents",
+						duty_pct_list, list_size);
+	if (rc) {
+		pr_err("invalid or missing property: qcom,duty-pcts-list\n");
+		goto out;
+	}
+
+	/* Read optional properties */
+	rc = of_property_read_u32(of_lpg_node, "qcom,ramp-step-duration",
+				  &lut_config->ramp_step_ms);
+	if (rc && rc != -EINVAL)
+		goto out;
+
+	rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-pause-hi",
+				  &lut_config->lut_pause_hi_cnt);
+	if (rc && rc != -EINVAL)
+		goto out;
+
+	rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-pause-lo",
+				  &lut_config->lut_pause_lo_cnt);
+	if (rc && rc != -EINVAL)
+		goto out;
+
+	rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-ramp-direction",
+				  (u32 *)&lut_config->ramp_direction);
+	if (rc && rc != -EINVAL)
+		goto out;
+
+	rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-pattern-repeat",
+				  (u32 *)&lut_config->pattern_repeat);
+	if (rc && rc != -EINVAL)
+		goto out;
+
+	rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-ramp-toggle",
+				  (u32 *)&lut_config->ramp_toggle);
+	if (rc && rc != -EINVAL)
+		goto out;
+
+	rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-enable-pause-hi",
+				  (u32 *)&lut_config->enable_pause_hi);
+	if (rc && rc != -EINVAL)
+		goto out;
+
+	rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-enable-pause-lo",
+				  (u32 *)&lut_config->enable_pause_lo);
+	if (rc && rc != -EINVAL)
+		goto out;
+	rc = 0;
+
+	qpnp_set_lut_params(&lut_params, lut_config, start_idx, list_size);
+
+	_pwm_lut_config(chip, period, duty_pct_list, lut_params);
+
+out:
+	kfree(duty_pct_list);
+	return rc;
+}
+
+static int qpnp_lpg_get_rev_subtype(struct qpnp_pwm_chip *chip)
+{
+	int rc;
+	uint val;
+
+	rc = regmap_read(chip->regmap,
+			 chip->lpg_config.base_addr + SPMI_LPG_SUB_TYPE_OFFSET,
+			 &val);
+
+	if (rc) {
+		pr_err("Couldn't read subtype rc: %d\n", rc);
+		goto out;
+	}
+	 chip->sub_type = (u8)val;
+
+	rc = regmap_read(chip->regmap,
+			 chip->lpg_config.base_addr + SPMI_LPG_REVISION2_OFFSET,
+			 &val);
+
+	if (rc) {
+		pr_err("Couldn't read revision2 rc: %d\n", rc);
+		goto out;
+	}
+	chip->revision = (u8)val;
+
+	if (chip->revision < QPNP_LPG_REVISION_0 ||
+		chip->revision > QPNP_LPG_REVISION_1) {
+		pr_err("Unknown LPG revision detected, rev:%d\n",
+						chip->revision);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (chip->sub_type != QPNP_PWM_MODE_ONLY_SUB_TYPE
+		&& chip->sub_type != QPNP_LPG_CHAN_SUB_TYPE
+		&& chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE) {
+		pr_err("Unknown LPG/PWM subtype detected, subtype:%d\n",
+						chip->sub_type);
+		rc = -EINVAL;
+	}
+out:
+	pr_debug("LPG rev 0x%02x subtype 0x%02x rc: %d\n", chip->revision,
+		chip->sub_type, rc);
+	return rc;
+}
+
+/* Fill in lpg device elements based on values found in device tree. */
+static int qpnp_parse_dt_config(struct platform_device *pdev,
+					struct qpnp_pwm_chip *chip)
+{
+	int			rc, enable, lut_entry_size, list_size, i;
+	const char		*label;
+	const __be32		*prop;
+	u32			size;
+	struct device_node	*node;
+	int found_pwm_subnode = 0;
+	int found_lpg_subnode = 0;
+	struct device_node	*of_node = pdev->dev.of_node;
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	struct qpnp_lut_config	*lut_config = &lpg_config->lut_config;
+	struct _qpnp_pwm_config	*pwm_config = &chip->pwm_config;
+	int			force_pwm_size = 0;
+	int			pwm_size_list[QPNP_PWM_SIZES_SUPPORTED];
+
+	rc = of_property_read_u32(of_node, "qcom,channel-id",
+				&chip->channel_id);
+	if (rc) {
+		dev_err(&pdev->dev, "%s: node is missing LPG channel id\n",
+								__func__);
+		return -EINVAL;
+	}
+
+	if (!of_get_property(of_node, "qcom,supported-sizes", &list_size)) {
+		pr_err("Missing qcom,supported-size list\n");
+		return -EINVAL;
+	}
+
+	list_size /= sizeof(u32);
+	if (list_size > QPNP_PWM_SIZES_SUPPORTED) {
+		pr_err(" qcom,supported-size list is too big\n");
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32_array(of_node, "qcom,supported-sizes",
+			pwm_size_list, list_size);
+
+	if (rc) {
+		pr_err("Invalid qcom,supported-size property\n");
+		return rc;
+	}
+
+	for (i = 0; i < list_size; i++) {
+		pwm_config->supported_sizes |=
+			(1 << (pwm_size_list[i] - QPNP_MIN_PWM_BIT_SIZE));
+	}
+
+	if (!(pwm_config->supported_sizes == QPNP_PWM_SIZE_6_9_BIT ||
+		pwm_config->supported_sizes == QPNP_PWM_SIZE_7_8_BIT ||
+		pwm_config->supported_sizes == QPNP_PWM_SIZE_6_7_9_BIT)) {
+		pr_err("PWM sizes list qcom,supported-size is not proper\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * For cetrain LPG channels PWM size can be forced. So that
+	 * for every requested pwm period closest pwm frequency is
+	 * selected in qpnp_lpg_calc_period() for the forced pwm size.
+	 */
+	rc = of_property_read_u32(of_node, "qcom,force-pwm-size",
+				&force_pwm_size);
+	if (pwm_config->supported_sizes == QPNP_PWM_SIZE_7_8_BIT) {
+		if (!(force_pwm_size == QPNP_PWM_SIZE_7_BIT ||
+				force_pwm_size == QPNP_PWM_SIZE_8_BIT))
+			force_pwm_size = 0;
+	} else if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE) {
+		if (!(force_pwm_size == QPNP_PWM_SIZE_6_BIT ||
+				force_pwm_size == QPNP_PWM_SIZE_9_BIT))
+			force_pwm_size = 0;
+	} else if (pwm_config->supported_sizes == QPNP_PWM_SIZE_6_7_9_BIT) {
+		if (!(force_pwm_size == QPNP_PWM_SIZE_6_BIT ||
+				force_pwm_size == QPNP_PWM_SIZE_7_BIT ||
+				force_pwm_size == QPNP_PWM_SIZE_9_BIT))
+			force_pwm_size = 0;
+	}
+
+	pwm_config->force_pwm_size = force_pwm_size;
+
+
+	prop = of_get_address_by_name(pdev->dev.of_node, QPNP_LPG_CHANNEL_BASE,
+			0, 0);
+	if (!prop) {
+		dev_err(&pdev->dev, "Couldnt find channel's base addr rc %d\n",
+				rc);
+		return rc;
+	}
+	lpg_config->base_addr = be32_to_cpu(*prop);
+
+	rc = qpnp_lpg_get_rev_subtype(chip);
+	if (rc)
+		return rc;
+
+	prop = of_get_address_by_name(pdev->dev.of_node, QPNP_LPG_LUT_BASE,
+			0, 0);
+	if (!prop) {
+		chip->flags |= QPNP_PWM_LUT_NOT_SUPPORTED;
+	} else {
+		lpg_config->lut_base_addr = be32_to_cpu(*prop);
+		rc = of_property_read_u32(of_node, "qcom,lpg-lut-size", &size);
+		if (rc < 0) {
+			dev_err(&pdev->dev, "Error reading qcom,lpg-lut-size, rc=%d\n",
+					rc);
+			return rc;
+		}
+
+		/*
+		 * Each entry of LUT is of 2 bytes for generic LUT and of 1 byte
+		 * for KPDBL/GLED LUT.
+		 */
+		lpg_config->lut_size = size >> 1;
+		lut_entry_size = sizeof(u16);
+
+		if (pwm_config->supported_sizes == QPNP_PWM_SIZE_7_8_BIT) {
+			lpg_config->lut_size = size;
+			lut_entry_size = sizeof(u8);
+		}
+
+		lut_config->duty_pct_list = kcalloc(lpg_config->lut_size,
+					lut_entry_size, GFP_KERNEL);
+		if (!lut_config->duty_pct_list)
+			return -ENOMEM;
+
+		rc = of_property_read_u32(of_node, "qcom,ramp-index",
+						&lut_config->ramp_index);
+		if (rc) {
+			pr_err("Missing LPG qcom,ramp-index property\n");
+			kfree(lut_config->duty_pct_list);
+			return rc;
+		}
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,dtest-line",
+		&chip->dtest_line);
+	if (rc) {
+		chip->in_test_mode = 0;
+	} else {
+		rc = of_property_read_u32(of_node, "qcom,dtest-output",
+			&chip->dtest_output);
+		if (rc) {
+			pr_err("Missing DTEST output configuration\n");
+			return rc;
+		}
+		chip->in_test_mode = 1;
+	}
+
+	if (chip->in_test_mode) {
+		if ((chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE) &&
+			(chip->dtest_line > QPNP_PWM_DTEST_LINE_MAX ||
+			chip->dtest_output > QPNP_PWM_DTEST_OUTPUT_MAX)) {
+			pr_err("DTEST line/output values are improper for PWM channel %d\n",
+				chip->channel_id);
+			return -EINVAL;
+		} else if (chip->dtest_line > QPNP_LPG_DTEST_LINE_MAX ||
+			chip->dtest_output > QPNP_LPG_DTEST_OUTPUT_MAX) {
+			pr_err("DTEST line/output values are improper for LPG channel %d\n",
+				chip->channel_id);
+			return -EINVAL;
+		}
+	}
+
+	for_each_child_of_node(of_node, node) {
+		rc = of_property_read_string(node, "label", &label);
+		if (rc) {
+			dev_err(&pdev->dev, "%s: Missing label property\n",
+								__func__);
+			goto out;
+		}
+		if (!strcmp(label, "pwm")) {
+			rc = qpnp_parse_pwm_dt_config(node, of_node, chip);
+			if (rc)
+				goto out;
+			found_pwm_subnode = 1;
+		} else if (!strcmp(label, "lpg") &&
+				!(chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED)) {
+			rc = qpnp_parse_lpg_dt_config(node, of_node, chip);
+			if (rc)
+				goto out;
+			found_lpg_subnode = 1;
+		} else {
+			dev_err(&pdev->dev,
+				"%s: Invalid value for lable prop",
+								__func__);
+		}
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,mode-select", &enable);
+	if (rc)
+		goto read_opt_props;
+
+	if ((enable == PM_PWM_MODE_PWM && found_pwm_subnode == 0) ||
+		(enable == PM_PWM_MODE_LPG && found_lpg_subnode == 0)) {
+		dev_err(&pdev->dev, "%s: Invalid mode select\n", __func__);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	_pwm_change_mode(chip, enable);
+	_pwm_enable(chip);
+
+read_opt_props:
+	/* Initialize optional config parameters from DT if provided */
+	of_property_read_string(node, "qcom,channel-owner",
+				&chip->channel_owner);
+
+	return 0;
+
+out:
+	kfree(lut_config->duty_pct_list);
+	return rc;
+}
+
+static struct pwm_ops qpnp_pwm_ops = {
+	.enable = qpnp_pwm_enable,
+	.disable = qpnp_pwm_disable,
+	.config = qpnp_pwm_config,
+	.free = qpnp_pwm_free,
+	.owner = THIS_MODULE,
+};
+
+static int qpnp_pwm_probe(struct platform_device *pdev)
+{
+	struct qpnp_pwm_chip	*pwm_chip;
+	int			rc;
+
+	pwm_chip = kzalloc(sizeof(*pwm_chip), GFP_KERNEL);
+	if (pwm_chip == NULL)
+		return -ENOMEM;
+
+	pwm_chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!pwm_chip->regmap) {
+		dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+		return -EINVAL;
+	}
+
+	spin_lock_init(&pwm_chip->lpg_lock);
+
+	pwm_chip->pdev = pdev;
+	dev_set_drvdata(&pdev->dev, pwm_chip);
+
+	rc = qpnp_parse_dt_config(pdev, pwm_chip);
+
+	if (rc) {
+		pr_err("Failed parsing DT parameters, rc=%d\n", rc);
+		goto failed_config;
+	}
+
+	pwm_chip->chip.dev = &pdev->dev;
+	pwm_chip->chip.ops = &qpnp_pwm_ops;
+	pwm_chip->chip.base = -1;
+	pwm_chip->chip.npwm = 1;
+
+	rc = pwmchip_add(&pwm_chip->chip);
+	if (rc < 0) {
+		pr_err("pwmchip_add() failed: %d\n", rc);
+		goto failed_insert;
+	}
+
+	if (pwm_chip->channel_owner)
+		pwm_chip->chip.pwms[0].label = pwm_chip->channel_owner;
+
+	pr_debug("PWM device channel:%d probed successfully\n",
+		pwm_chip->channel_id);
+	return 0;
+
+failed_insert:
+	kfree(pwm_chip->lpg_config.lut_config.duty_pct_list);
+failed_config:
+	dev_set_drvdata(&pdev->dev, NULL);
+	kfree(pwm_chip);
+	return rc;
+}
+
+static int qpnp_pwm_remove(struct platform_device *pdev)
+{
+	struct qpnp_pwm_chip *pwm_chip;
+	struct qpnp_lpg_config *lpg_config;
+
+	pwm_chip = dev_get_drvdata(&pdev->dev);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	if (pwm_chip) {
+		lpg_config = &pwm_chip->lpg_config;
+		pwmchip_remove(&pwm_chip->chip);
+		kfree(lpg_config->lut_config.duty_pct_list);
+		kfree(pwm_chip);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+	{ .compatible = QPNP_LPG_DRIVER_NAME, },
+	{}
+};
+
+static const struct platform_device_id qpnp_lpg_id[] = {
+	{ QPNP_LPG_DRIVER_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(spmi, qpnp_lpg_id);
+
+static struct platform_driver qpnp_lpg_driver = {
+	.driver		= {
+		.name		= QPNP_LPG_DRIVER_NAME,
+		.of_match_table	= spmi_match_table,
+		.owner		= THIS_MODULE,
+	},
+	.probe		= qpnp_pwm_probe,
+	.remove		= qpnp_pwm_remove,
+	.id_table	= qpnp_lpg_id,
+};
+
+/**
+ * qpnp_lpg_init() - register spmi driver for qpnp-lpg
+ */
+int __init qpnp_lpg_init(void)
+{
+	return platform_driver_register(&qpnp_lpg_driver);
+}
+
+static void __exit qpnp_lpg_exit(void)
+{
+	platform_driver_unregister(&qpnp_lpg_driver);
+}
+
+MODULE_DESCRIPTION("QPNP PMIC LPG driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" QPNP_LPG_DRIVER_NAME);
+
+subsys_initcall(qpnp_lpg_init);
+module_exit(qpnp_lpg_exit);
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 96a816d..534a1d7 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -988,6 +988,35 @@
 	  the higher ones by BHS. This driver allows for configuration of
 	  the rail between the LDO/BHS as well as the LDO voltage.
 
+config REGULATOR_QPNP_LABIBB
+	tristate "Qualcomm Technologies, Inc. QPNP LAB/IBB regulator support"
+	depends on SPMI
+	help
+	  This driver supports voltage regulators in Qualcomm Technologies, Inc.
+	  PMIC chips which comply with QPNP LAB/IBB regulators. QPNP LAB and IBB
+	  are SPMI based PMIC implementations. LAB regulator can be used as a
+	  regular positive boost regulator. IBB can be used as a regular
+	  negative boost regulator. LAB/IBB regulators can also be used
+	  together for LCD or AMOLED.
+
+config REGULATOR_QPNP_LCDB
+	tristate "Qualcomm Technologies, Inc. QPNP LCDB support"
+	depends on SPMI
+	help
+	  Supports the LCDB module in the Qualcomm Technologies, Inc.
+	  QPNP PMICs. Exposes regulators to control the positive and
+	  negative voltage bias for the LCD display panel. It also
+	  allows configurability for the various bias-voltage parameters.
+
+config REGULATOR_QPNP_OLEDB
+	tristate "Qualcomm Technologies, Inc. QPNP OLEDB regulator support"
+	depends on SPMI
+	help
+	  This driver supports the OLEDB (AVDD bias) signal for AMOLED panel in
+	  Qualcomm Technologies, Inc. QPNP PMICs. It exposes the OLED voltage
+	  configuration via the regulator framework. The configurable range of
+	  this bias is 5 V to 8.1 V.
+
 config REGULATOR_QPNP
 	tristate "Qualcomm Technologies, Inc. QPNP regulator support"
 	depends on SPMI
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 1a96c07..bf30b8d 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -123,6 +123,9 @@
 obj-$(CONFIG_REGULATOR_KRYO) += kryo-regulator.o
 obj-$(CONFIG_REGULATOR_MEM_ACC) += mem-acc-regulator.o
 obj-$(CONFIG_REGULATOR_MSM_GFX_LDO) += msm_gfx_ldo.o
+obj-$(CONFIG_REGULATOR_QPNP_LABIBB) += qpnp-labibb-regulator.o
+obj-$(CONFIG_REGULATOR_QPNP_LCDB) += qpnp-lcdb-regulator.o
+obj-$(CONFIG_REGULATOR_QPNP_OLEDB) += qpnp-oledb-regulator.o
 obj-$(CONFIG_REGULATOR_QPNP) += qpnp-regulator.o
 obj-$(CONFIG_REGULATOR_RPMH) += rpmh-regulator.o
 obj-$(CONFIG_REGULATOR_STUB) += stub-regulator.o
diff --git a/drivers/regulator/qpnp-labibb-regulator.c b/drivers/regulator/qpnp-labibb-regulator.c
new file mode 100644
index 0000000..cf8f000
--- /dev/null
+++ b/drivers/regulator/qpnp-labibb-regulator.c
@@ -0,0 +1,3877 @@
+/* Copyright (c) 2014-2017, 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)	"%s: " fmt, __func__
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/qpnp/qpnp-revid.h>
+
+#define QPNP_LABIBB_REGULATOR_DRIVER_NAME	"qcom,qpnp-labibb-regulator"
+
+#define REG_REVISION_2			0x01
+#define REG_PERPH_TYPE			0x04
+
+#define QPNP_LAB_TYPE			0x24
+#define QPNP_IBB_TYPE			0x20
+
+/* Common register value for LAB/IBB */
+#define REG_LAB_IBB_LCD_MODE		0x0
+#define REG_LAB_IBB_AMOLED_MODE		BIT(7)
+#define REG_LAB_IBB_SEC_ACCESS		0xD0
+#define REG_LAB_IBB_SEC_UNLOCK_CODE	0xA5
+
+/* LAB register offset definitions */
+#define REG_LAB_STATUS1			0x08
+#define REG_LAB_SWIRE_PGM_CTL		0x40
+#define REG_LAB_VOLTAGE			0x41
+#define REG_LAB_RING_SUPPRESSION_CTL	0x42
+#define REG_LAB_LCD_AMOLED_SEL		0x44
+#define REG_LAB_MODULE_RDY		0x45
+#define REG_LAB_ENABLE_CTL		0x46
+#define REG_LAB_PD_CTL			0x47
+#define REG_LAB_CLK_DIV			0x48
+#define REG_LAB_IBB_EN_RDY		0x49
+#define REG_LAB_CURRENT_LIMIT		0x4B
+#define REG_LAB_CURRENT_SENSE		0x4C
+#define REG_LAB_PS_CTL			0x50
+#define REG_LAB_RDSON_MNGMNT		0x53
+#define REG_LAB_PRECHARGE_CTL		0x5E
+#define REG_LAB_SOFT_START_CTL		0x5F
+#define REG_LAB_SPARE_CTL		0x60
+#define REG_LAB_PFM_CTL			0x62
+
+/* LAB registers for PM660A */
+#define REG_LAB_VOUT_DEFAULT		0x44
+#define REG_LAB_SW_HIGH_PSRR_CTL	0x70
+#define REG_LAB_LDO_PD_CTL		0x78
+#define REG_LAB_VPH_ENVELOP_CTL		0x7E
+
+/* LAB register bits definitions */
+
+/* REG_LAB_STATUS1 */
+#define LAB_STATUS1_VREG_OK_MASK	BIT(7)
+#define LAB_STATUS1_VREG_OK		BIT(7)
+
+/* REG_LAB_SWIRE_PGM_CTL */
+#define LAB_EN_SWIRE_PGM_VOUT		BIT(7)
+#define LAB_EN_SWIRE_PGM_PD		BIT(6)
+
+/* REG_LAB_VOLTAGE */
+#define LAB_VOLTAGE_OVERRIDE_EN		BIT(7)
+#define LAB_VOLTAGE_SET_MASK		GENMASK(3, 0)
+
+/* REG_LAB_RING_SUPPRESSION_CTL */
+#define LAB_RING_SUPPRESSION_CTL_EN	BIT(7)
+
+/* REG_LAB_MODULE_RDY */
+#define LAB_MODULE_RDY_EN		BIT(7)
+
+/* REG_LAB_ENABLE_CTL */
+#define LAB_ENABLE_CTL_EN		BIT(7)
+
+/* REG_LAB_PD_CTL */
+#define LAB_PD_CTL_STRONG_PULL		BIT(0)
+#define LAB_PD_CTL_STRENGTH_MASK	BIT(0)
+#define LAB_PD_CTL_DISABLE_PD		BIT(1)
+#define LAB_PD_CTL_EN_MASK		BIT(1)
+
+/* REG_LAB_IBB_EN_RDY */
+#define LAB_IBB_EN_RDY_EN		BIT(7)
+
+/* REG_LAB_CURRENT_LIMIT */
+#define LAB_CURRENT_LIMIT_MASK		GENMASK(2, 0)
+#define LAB_CURRENT_LIMIT_EN_BIT	BIT(7)
+#define LAB_OVERRIDE_CURRENT_MAX_BIT	BIT(3)
+
+/* REG_LAB_CURRENT_SENSE */
+#define LAB_CURRENT_SENSE_GAIN_MASK	GENMASK(1, 0)
+
+/* REG_LAB_PS_CTL */
+#define LAB_PS_THRESH_MASK		GENMASK(1, 0)
+#define LAB_PS_CTL_EN			BIT(7)
+
+/* REG_LAB_RDSON_MNGMNT */
+#define LAB_RDSON_MNGMNT_NFET_SLEW_EN	BIT(5)
+#define LAB_RDSON_MNGMNT_PFET_SLEW_EN	BIT(4)
+#define LAB_RDSON_MNGMNT_NFET_MASK	GENMASK(3, 2)
+#define LAB_RDSON_MNGMNT_NFET_SHIFT	2
+#define LAB_RDSON_MNGMNT_PFET_MASK	GENMASK(1, 0)
+#define LAB_RDSON_NFET_SW_SIZE_QUARTER	0x0
+#define LAB_RDSON_PFET_SW_SIZE_QUARTER	0x0
+
+/* REG_LAB_PRECHARGE_CTL */
+#define LAB_FAST_PRECHARGE_CTL_EN	BIT(2)
+#define LAB_MAX_PRECHARGE_TIME_MASK	GENMASK(1, 0)
+
+/* REG_LAB_SOFT_START_CTL */
+#define LAB_SOFT_START_CTL_MASK		GENMASK(1, 0)
+
+/* REG_LAB_SPARE_CTL */
+#define LAB_SPARE_TOUCH_WAKE_BIT	BIT(3)
+#define LAB_SPARE_DISABLE_SCP_BIT	BIT(0)
+
+/* REG_LAB_PFM_CTL */
+#define LAB_PFM_EN_BIT			BIT(7)
+
+/* REG_LAB_SW_HIGH_PSRR_CTL */
+#define LAB_EN_SW_HIGH_PSRR_MODE	BIT(7)
+#define LAB_SW_HIGH_PSRR_REQ		BIT(0)
+
+/* REG_LAB_VPH_ENVELOP_CTL */
+#define LAB_VREF_HIGH_PSRR_SEL_MASK	GENMASK(7, 6)
+#define LAB_SEL_HW_HIGH_PSRR_SRC_MASK	GENMASK(1, 0)
+#define LAB_SEL_HW_HIGH_PSRR_SRC_SHIFT	6
+
+/* IBB register offset definitions */
+#define REG_IBB_REVISION4		0x03
+#define REG_IBB_STATUS1			0x08
+#define REG_IBB_VOLTAGE			0x41
+#define REG_IBB_RING_SUPPRESSION_CTL	0x42
+#define REG_IBB_LCD_AMOLED_SEL		0x44
+#define REG_IBB_MODULE_RDY		0x45
+#define REG_IBB_ENABLE_CTL		0x46
+#define REG_IBB_PD_CTL			0x47
+#define REG_IBB_CLK_DIV			0x48
+#define REG_IBB_CURRENT_LIMIT		0x4B
+#define REG_IBB_PS_CTL			0x50
+#define REG_IBB_RDSON_MNGMNT		0x53
+#define REG_IBB_NONOVERLAP_TIME_1	0x56
+#define REG_IBB_NONOVERLAP_TIME_2	0x57
+#define REG_IBB_PWRUP_PWRDN_CTL_1	0x58
+#define REG_IBB_PWRUP_PWRDN_CTL_2	0x59
+#define REG_IBB_SOFT_START_CTL		0x5F
+#define REG_IBB_SWIRE_CTL		0x5A
+#define REG_IBB_OUTPUT_SLEW_CTL		0x5D
+#define REG_IBB_SPARE_CTL		0x60
+#define REG_IBB_NLIMIT_DAC		0x61
+
+/* IBB registers for PM660A */
+#define REG_IBB_DEFAULT_VOLTAGE		0x40
+#define REG_IBB_FLOAT_CTL		0x43
+#define REG_IBB_VREG_OK_CTL		0x55
+#define REG_IBB_VOUT_MIN_MAGNITUDE	0x5C
+#define REG_IBB_PFM_CTL			0x62
+#define REG_IBB_SMART_PS_CTL		0x65
+#define REG_IBB_ADAPT_DEAD_TIME		0x67
+
+/* IBB register bits definition */
+
+/* REG_IBB_STATUS1 */
+#define IBB_STATUS1_VREG_OK_MASK	BIT(7)
+#define IBB_STATUS1_VREG_OK		BIT(7)
+
+/* REG_IBB_VOLTAGE */
+#define IBB_VOLTAGE_OVERRIDE_EN		BIT(7)
+#define IBB_VOLTAGE_SET_MASK		GENMASK(5, 0)
+
+/* REG_IBB_CLK_DIV */
+#define IBB_CLK_DIV_OVERRIDE_EN		BIT(7)
+#define IBB_CLK_DIV_MASK		GENMASK(3, 0)
+
+/* REG_IBB_RING_SUPPRESSION_CTL */
+#define IBB_RING_SUPPRESSION_CTL_EN	BIT(7)
+
+/* REG_IBB_FLOAT_CTL */
+#define IBB_FLOAT_EN			BIT(0)
+#define IBB_SMART_FLOAT_EN		BIT(7)
+
+/* REG_IBB_MIN_MAGNITUDE */
+#define IBB_MIN_VOLTAGE_0P8_V		BIT(3)
+
+/* REG_IBB_MODULE_RDY */
+#define IBB_MODULE_RDY_EN		BIT(7)
+
+/* REG_IBB_ENABLE_CTL */
+#define IBB_ENABLE_CTL_MASK		(BIT(7) | BIT(6))
+#define IBB_ENABLE_CTL_SWIRE_RDY	BIT(6)
+#define IBB_ENABLE_CTL_MODULE_EN	BIT(7)
+
+/* REG_IBB_PD_CTL */
+#define IBB_PD_CTL_HALF_STRENGTH	BIT(0)
+#define IBB_PD_CTL_STRENGTH_MASK	BIT(0)
+#define IBB_PD_CTL_EN			BIT(7)
+#define IBB_SWIRE_PD_UPD		BIT(1)
+#define IBB_PD_CTL_EN_MASK		BIT(7)
+
+/* REG_IBB_CURRENT_LIMIT */
+#define IBB_CURRENT_LIMIT_MASK		GENMASK(4, 0)
+#define IBB_CURRENT_LIMIT_DEBOUNCE_SHIFT	5
+#define IBB_CURRENT_LIMIT_DEBOUNCE_MASK	GENMASK(6, 5)
+#define IBB_CURRENT_LIMIT_EN		BIT(7)
+#define IBB_ILIMIT_COUNT_CYC8		0
+#define IBB_CURRENT_MAX_500MA		0xA
+
+/* REG_IBB_PS_CTL */
+#define IBB_PS_CTL_EN			0x85
+
+/* REG_IBB_SMART_PS_CTL */
+#define IBB_SMART_PS_CTL_EN			BIT(7)
+#define IBB_NUM_SWIRE_PULSE_WAIT		0x5
+
+/* REG_IBB_OUTPUT_SLEW_CTL */
+#define IBB_SLEW_CTL_EN				BIT(7)
+#define IBB_SLEW_RATE_SPEED_FAST_EN		BIT(6)
+#define IBB_SLEW_RATE_TRANS_TIME_FAST_SHIFT	3
+#define IBB_SLEW_RATE_TRANS_TIME_FAST_MASK	GENMASK(5, 3)
+#define IBB_SLEW_RATE_TRANS_TIME_SLOW_MASK	GENMASK(2, 0)
+
+/* REG_IBB_VREG_OK_CTL */
+#define IBB_VREG_OK_EN_OVERLOAD_BLANK		BIT(7)
+#define IBB_VREG_OK_OVERLOAD_DEB_SHIFT		5
+#define IBB_VREG_OK_OVERLOAD_DEB_MASK		GENMASK(6, 5)
+
+/* REG_IBB_RDSON_MNGMNT */
+#define IBB_NFET_SLEW_EN		BIT(7)
+#define IBB_PFET_SLEW_EN		BIT(6)
+#define IBB_OVERRIDE_NFET_SW_SIZE	BIT(5)
+#define IBB_OVERRIDE_PFET_SW_SIZE	BIT(2)
+#define IBB_NFET_SW_SIZE_MASK		GENMASK(3, 2)
+#define IBB_PFET_SW_SIZE_MASK		GENMASK(1, 0)
+
+/* REG_IBB_NONOVERLAP_TIME_1 */
+#define IBB_OVERRIDE_NONOVERLAP		BIT(6)
+#define IBB_NONOVERLAP_NFET_MASK	GENMASK(2, 0)
+#define IBB_NFET_GATE_DELAY_2		0x3
+
+/* REG_IBB_NONOVERLAP_TIME_2 */
+#define IBB_N2P_MUX_SEL		BIT(0)
+
+/* REG_IBB_SOFT_START_CTL */
+#define IBB_SOFT_START_CHARGING_RESISTOR_16K	0x3
+
+/* REG_IBB_SPARE_CTL */
+#define IBB_BYPASS_PWRDN_DLY2_BIT	BIT(5)
+#define IBB_POFF_CTL_MASK		BIT(4)
+#define IBB_FASTER_PFET_OFF		BIT(4)
+#define IBB_FAST_STARTUP		BIT(3)
+
+/* REG_IBB_SWIRE_CTL */
+#define IBB_SWIRE_VOUT_UPD_EN		BIT(6)
+#define IBB_OUTPUT_VOLTAGE_AT_ONE_PULSE_MASK	GENMASK(5, 0)
+#define MAX_OUTPUT_EDGE_VOLTAGE_MV	6300
+#define MAX_OUTPUT_PULSE_VOLTAGE_MV	7700
+#define MIN_OUTPUT_PULSE_VOLTAGE_MV	1400
+#define OUTPUT_VOLTAGE_STEP_MV		100
+
+/* REG_IBB_NLIMIT_DAC */
+#define IBB_DEFAULT_NLIMIT_DAC		0x5
+
+/* REG_IBB_PFM_CTL */
+#define IBB_PFM_ENABLE			BIT(7)
+#define IBB_PFM_PEAK_CURRENT_BIT_SHIFT	1
+#define IBB_PFM_PEAK_CURRENT_MASK	GENMASK(3, 1)
+#define IBB_PFM_HYSTERESIS_BIT_SHIFT	4
+#define IBB_PFM_HYSTERESIS_MASK		GENMASK(5, 4)
+
+/* REG_IBB_PWRUP_PWRDN_CTL_1 */
+#define IBB_PWRUP_PWRDN_CTL_1_DLY1_BITS	2
+#define IBB_PWRUP_PWRDN_CTL_1_DLY1_MASK	GENMASK(5, 4)
+#define IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT	4
+#define IBB_PWRUP_PWRDN_CTL_1_EN_DLY2	BIT(3)
+#define IBB_PWRUP_PWRDN_CTL_1_DLY2_MASK	GENMASK(1, 0)
+#define IBB_PWRUP_PWRDN_CTL_1_LAB_VREG_OK	BIT(7)
+#define IBB_PWRUP_PWRDN_CTL_1_EN_DLY1	BIT(6)
+#define PWRUP_PWRDN_CTL_1_DISCHARGE_EN	BIT(2)
+
+/* REG_IBB_PWRUP_PWRDN_CTL_2 */
+#define IBB_DIS_DLY_MASK		GENMASK(1, 0)
+#define IBB_WAIT_MBG_OK			BIT(2)
+
+/* Constants */
+#define SWIRE_DEFAULT_2ND_CMD_DLY_MS		20
+#define SWIRE_DEFAULT_IBB_PS_ENABLE_DLY_MS	200
+#define IBB_HW_DEFAULT_SLEW_RATE		12000
+
+/**
+ * enum qpnp_labibb_mode - working mode of LAB/IBB regulators
+ * %QPNP_LABIBB_LCD_MODE:		configure LAB and IBB regulators
+ * together to provide power supply for LCD
+ * %QPNP_LABIBB_AMOLED_MODE:		configure LAB and IBB regulators
+ * together to provide power supply for AMOLED
+ * %QPNP_LABIBB_MAX_MODE		max number of configureable modes
+ * supported by qpnp_labibb_regulator
+ */
+enum qpnp_labibb_mode {
+	QPNP_LABIBB_LCD_MODE,
+	QPNP_LABIBB_AMOLED_MODE,
+	QPNP_LABIBB_MAX_MODE,
+};
+
+/**
+ * IBB_SW_CONTROL_EN: Specifies IBB is enabled through software.
+ * IBB_SW_CONTROL_DIS: Specifies IBB is disabled through software.
+ * IBB_HW_CONTROL: Specifies IBB is controlled through SWIRE (hardware).
+ */
+enum ibb_mode {
+	IBB_SW_CONTROL_EN,
+	IBB_SW_CONTROL_DIS,
+	IBB_HW_CONTROL,
+	IBB_HW_SW_CONTROL,
+};
+
+static const int ibb_dischg_res_table[] = {
+	300,
+	64,
+	32,
+	16,
+};
+
+static const int ibb_pwrup_dly_table[] = {
+	1000,
+	2000,
+	4000,
+	8000,
+};
+
+static const int ibb_pwrdn_dly_table[] = {
+	1000,
+	2000,
+	4000,
+	8000,
+};
+
+static const int lab_clk_div_table[] = {
+	3200,
+	2740,
+	2400,
+	2130,
+	1920,
+	1750,
+	1600,
+	1480,
+	1370,
+	1280,
+	1200,
+	1130,
+	1070,
+	1010,
+	960,
+	910,
+};
+
+static const int ibb_clk_div_table[] = {
+	3200,
+	2740,
+	2400,
+	2130,
+	1920,
+	1750,
+	1600,
+	1480,
+	1370,
+	1280,
+	1200,
+	1130,
+	1070,
+	1010,
+	960,
+	910,
+};
+
+static const int lab_current_limit_table[] = {
+	200,
+	400,
+	600,
+	800,
+	1000,
+	1200,
+	1400,
+	1600,
+};
+
+static const char * const lab_current_sense_table[] = {
+	"0.5x",
+	"1x",
+	"1.5x",
+	"2x"
+};
+
+static const int ibb_current_limit_table[] = {
+	0,
+	50,
+	100,
+	150,
+	200,
+	250,
+	300,
+	350,
+	400,
+	450,
+	500,
+	550,
+	600,
+	650,
+	700,
+	750,
+	800,
+	850,
+	900,
+	950,
+	1000,
+	1050,
+	1100,
+	1150,
+	1200,
+	1250,
+	1300,
+	1350,
+	1400,
+	1450,
+	1500,
+	1550,
+};
+
+static const int ibb_output_slew_ctl_table[] = {
+	100,
+	200,
+	500,
+	1000,
+	2000,
+	10000,
+	12000,
+	15000
+};
+
+static const int ibb_debounce_table[] = {
+	8,
+	16,
+	32,
+	64,
+};
+
+static const int ibb_overload_debounce_table[] = {
+	1,
+	2,
+	4,
+	8
+};
+
+static const int ibb_vreg_ok_deb_table[] = {
+	4,
+	8,
+	16,
+	32
+};
+
+static const int lab_ps_thresh_table_v1[] = {
+	20,
+	30,
+	40,
+	50,
+};
+
+static const int lab_ps_thresh_table_v2[] = {
+	50,
+	60,
+	70,
+	80,
+};
+
+static const int lab_soft_start_table[] = {
+	200,
+	400,
+	600,
+	800,
+};
+
+static const int lab_rdson_nfet_table[] = {
+	25,
+	50,
+	75,
+	100,
+};
+
+static const int lab_rdson_pfet_table[] = {
+	25,
+	50,
+	75,
+	100,
+};
+
+static const int lab_max_precharge_table[] = {
+	200,
+	300,
+	400,
+	500,
+};
+
+static const int ibb_pfm_peak_curr_table[] = {
+	150,
+	200,
+	250,
+	300,
+	350,
+	400,
+	450,
+	500
+};
+
+static const int ibb_pfm_hysteresis_table[] = {
+	0,
+	25,
+	50,
+	0
+};
+
+static const int lab_vref_high_psrr_table[] = {
+	350,
+	400,
+	450,
+	500
+};
+
+struct lab_regulator {
+	struct regulator_desc		rdesc;
+	struct regulator_dev		*rdev;
+	struct mutex			lab_mutex;
+
+	int				lab_vreg_ok_irq;
+	int				curr_volt;
+	int				min_volt;
+
+	int				step_size;
+	int				slew_rate;
+	int				soft_start;
+
+	int				vreg_enabled;
+};
+
+struct ibb_regulator {
+	struct regulator_desc		rdesc;
+	struct regulator_dev		*rdev;
+	struct mutex			ibb_mutex;
+
+	int				curr_volt;
+	int				min_volt;
+
+	int				step_size;
+	int				slew_rate;
+	int				soft_start;
+
+	u32				pwrup_dly;
+	u32				pwrdn_dly;
+
+	int				vreg_enabled;
+	int				num_swire_trans;
+};
+
+struct qpnp_labibb {
+	struct device			*dev;
+	struct platform_device		*pdev;
+	struct regmap			*regmap;
+	struct pmic_revid_data		*pmic_rev_id;
+	u16				lab_base;
+	u16				ibb_base;
+	u8				lab_dig_major;
+	u8				ibb_dig_major;
+	struct lab_regulator		lab_vreg;
+	struct ibb_regulator		ibb_vreg;
+	const struct ibb_ver_ops	*ibb_ver_ops;
+	const struct lab_ver_ops	*lab_ver_ops;
+	struct mutex			bus_mutex;
+	enum qpnp_labibb_mode		mode;
+	bool				standalone;
+	bool				ttw_en;
+	bool				in_ttw_mode;
+	bool				ibb_settings_saved;
+	bool				swire_control;
+	bool				pbs_control;
+	bool				ttw_force_lab_on;
+	bool				skip_2nd_swire_cmd;
+	bool				pfm_enable;
+	u32				swire_2nd_cmd_delay;
+	u32				swire_ibb_ps_enable_delay;
+};
+
+struct ibb_ver_ops {
+	int (*set_default_voltage)(struct qpnp_labibb *labibb,
+			bool use_default);
+	int (*set_voltage)(struct qpnp_labibb *labibb, int min_uV, int max_uV);
+	int (*sel_mode)(struct qpnp_labibb *labibb, bool is_ibb);
+	int (*get_mode)(struct qpnp_labibb *labibb);
+	int (*set_clk_div)(struct qpnp_labibb *labibb, u8 val);
+	int (*smart_ps_config)(struct qpnp_labibb *labibb, bool enable,
+				int num_swire_trans, int neg_curr_limit);
+	int (*soft_start_ctl)(struct qpnp_labibb *labibb,
+				 struct device_node *of_node);
+	int (*voltage_at_one_pulse)(struct qpnp_labibb *labibb, u32 volt);
+};
+
+struct lab_ver_ops {
+	const char *ver_str;
+	int (*set_default_voltage)(struct qpnp_labibb *labibb,
+					bool default_pres);
+	int (*ps_ctl)(struct qpnp_labibb *labibb,
+				u32 thresh, bool enable);
+};
+
+enum ibb_settings_index {
+	IBB_PD_CTL = 0,
+	IBB_CURRENT_LIMIT,
+	IBB_RDSON_MNGMNT,
+	IBB_PWRUP_PWRDN_CTL_1,
+	IBB_PWRUP_PWRDN_CTL_2,
+	IBB_NLIMIT_DAC,
+	IBB_PS_CTL,
+	IBB_SOFT_START_CTL,
+	IBB_SETTINGS_MAX,
+};
+
+enum lab_settings_index {
+	LAB_SOFT_START_CTL = 0,
+	LAB_PS_CTL,
+	LAB_RDSON_MNGMNT,
+	LAB_SETTINGS_MAX,
+};
+
+struct settings {
+	u16	address;
+	u8	value;
+	bool	sec_access;
+};
+
+#define SETTING(_id, _sec_access)		\
+	[_id] = {				\
+		.address = REG_##_id,		\
+		.sec_access = _sec_access,	\
+	}
+
+static struct settings ibb_settings[IBB_SETTINGS_MAX] = {
+	SETTING(IBB_PD_CTL, false),
+	SETTING(IBB_CURRENT_LIMIT, true),
+	SETTING(IBB_RDSON_MNGMNT, false),
+	SETTING(IBB_PWRUP_PWRDN_CTL_1, true),
+	SETTING(IBB_PWRUP_PWRDN_CTL_2, true),
+	SETTING(IBB_NLIMIT_DAC, false),
+	SETTING(IBB_PS_CTL, false),
+	SETTING(IBB_SOFT_START_CTL, false),
+};
+
+static struct settings lab_settings[LAB_SETTINGS_MAX] = {
+	SETTING(LAB_SOFT_START_CTL, false),
+	SETTING(LAB_PS_CTL, false),
+	SETTING(LAB_RDSON_MNGMNT, false),
+};
+
+static int
+qpnp_labibb_read(struct qpnp_labibb *labibb, u16 address,
+			u8 *val, int count)
+{
+	int rc = 0;
+	struct platform_device *pdev = labibb->pdev;
+
+	mutex_lock(&(labibb->bus_mutex));
+	rc = regmap_bulk_read(labibb->regmap, address, val, count);
+	if (rc < 0)
+		pr_err("SPMI read failed address=0x%02x sid=0x%02x rc=%d\n",
+			address, to_spmi_device(pdev->dev.parent)->usid, rc);
+
+	mutex_unlock(&(labibb->bus_mutex));
+	return rc;
+}
+
+static int
+qpnp_labibb_write(struct qpnp_labibb *labibb, u16 address,
+			u8 *val, int count)
+{
+	int rc = 0;
+	struct platform_device *pdev = labibb->pdev;
+
+	mutex_lock(&(labibb->bus_mutex));
+	if (address == 0) {
+		pr_err("address cannot be zero address=0x%02x sid=0x%02x rc=%d\n",
+			address, to_spmi_device(pdev->dev.parent)->usid, rc);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	rc = regmap_bulk_write(labibb->regmap, address, val, count);
+	if (rc < 0)
+		pr_err("write failed address=0x%02x sid=0x%02x rc=%d\n",
+			address, to_spmi_device(pdev->dev.parent)->usid, rc);
+
+error:
+	mutex_unlock(&(labibb->bus_mutex));
+	return rc;
+}
+
+static int
+qpnp_labibb_masked_write(struct qpnp_labibb *labibb, u16 address,
+						u8 mask, u8 val)
+{
+	int rc = 0;
+	struct platform_device *pdev = labibb->pdev;
+
+	mutex_lock(&(labibb->bus_mutex));
+	if (address == 0) {
+		pr_err("address cannot be zero address=0x%02x sid=0x%02x\n",
+			address, to_spmi_device(pdev->dev.parent)->usid);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	rc = regmap_update_bits(labibb->regmap, address, mask, val);
+	if (rc < 0)
+		pr_err("spmi write failed: addr=%03X, rc=%d\n", address, rc);
+
+error:
+	mutex_unlock(&(labibb->bus_mutex));
+	return rc;
+}
+
+static int qpnp_labibb_sec_write(struct qpnp_labibb *labibb, u16 base,
+					u8 offset, u8 val)
+{
+	int rc = 0;
+	u8 sec_val = REG_LAB_IBB_SEC_UNLOCK_CODE;
+	struct platform_device *pdev = labibb->pdev;
+
+	mutex_lock(&(labibb->bus_mutex));
+	if (base == 0) {
+		pr_err("base cannot be zero base=0x%02x sid=0x%02x\n",
+			base, to_spmi_device(pdev->dev.parent)->usid);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	rc = regmap_write(labibb->regmap, base + REG_LAB_IBB_SEC_ACCESS,
+				sec_val);
+	if (rc < 0) {
+		pr_err("register %x failed rc = %d\n",
+			base + REG_LAB_IBB_SEC_ACCESS, rc);
+		goto error;
+	}
+
+	rc = regmap_write(labibb->regmap, base + offset, val);
+	if (rc < 0)
+		pr_err("failed: addr=%03X, rc=%d\n",
+			base + offset, rc);
+
+error:
+	mutex_unlock(&(labibb->bus_mutex));
+	return rc;
+}
+
+static int qpnp_labibb_sec_masked_write(struct qpnp_labibb *labibb, u16 base,
+					u8 offset, u8 mask, u8 val)
+{
+	int rc = 0;
+	u8 sec_val = REG_LAB_IBB_SEC_UNLOCK_CODE;
+	struct platform_device *pdev = labibb->pdev;
+
+	mutex_lock(&(labibb->bus_mutex));
+	if (base == 0) {
+		pr_err("base cannot be zero base=0x%02x sid=0x%02x\n",
+			base, to_spmi_device(pdev->dev.parent)->usid);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	rc = regmap_write(labibb->regmap, base + REG_LAB_IBB_SEC_ACCESS,
+				sec_val);
+	if (rc < 0) {
+		pr_err("register %x failed rc = %d\n",
+			base + REG_LAB_IBB_SEC_ACCESS, rc);
+		goto error;
+	}
+
+	rc = regmap_update_bits(labibb->regmap, base + offset, mask, val);
+	if (rc < 0)
+		pr_err("spmi write failed: addr=%03X, rc=%d\n", base, rc);
+
+error:
+	mutex_unlock(&(labibb->bus_mutex));
+	return rc;
+}
+
+static int qpnp_ibb_smart_ps_config_v1(struct qpnp_labibb *labibb, bool enable,
+					int num_swire_trans, int neg_curr_limit)
+{
+	return 0;
+}
+
+static int qpnp_ibb_smart_ps_config_v2(struct qpnp_labibb *labibb, bool enable,
+					int num_swire_trans, int neg_curr_limit)
+{
+	u8 val;
+	int rc = 0;
+
+	if (enable) {
+		val = IBB_NUM_SWIRE_PULSE_WAIT;
+		rc = qpnp_labibb_write(labibb,
+			labibb->ibb_base + REG_IBB_PS_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("write register %x failed rc = %d\n",
+						REG_IBB_PS_CTL, rc);
+			return rc;
+		}
+	}
+
+	val = enable ? IBB_SMART_PS_CTL_EN : IBB_NUM_SWIRE_PULSE_WAIT;
+	if (num_swire_trans)
+		val |= num_swire_trans;
+	else
+		val |= IBB_NUM_SWIRE_PULSE_WAIT;
+
+	rc = qpnp_labibb_write(labibb,
+		labibb->ibb_base + REG_IBB_SMART_PS_CTL, &val, 1);
+	if (rc < 0) {
+		pr_err("write register %x failed rc = %d\n",
+					REG_IBB_SMART_PS_CTL, rc);
+		return rc;
+	}
+
+	val = enable ? (neg_curr_limit ? neg_curr_limit :
+		IBB_DEFAULT_NLIMIT_DAC) : IBB_DEFAULT_NLIMIT_DAC;
+
+	rc = qpnp_labibb_write(labibb,
+		labibb->ibb_base + REG_IBB_NLIMIT_DAC, &val, 1);
+	if (rc < 0)
+		pr_err("write register %x failed rc = %d\n",
+					REG_IBB_NLIMIT_DAC, rc);
+
+	return rc;
+}
+
+static int qpnp_labibb_sel_mode_v1(struct qpnp_labibb *labibb, bool is_ibb)
+{
+	int rc = 0;
+	u8 val;
+	u16 base;
+
+	val = (labibb->mode == QPNP_LABIBB_LCD_MODE) ? REG_LAB_IBB_LCD_MODE :
+				 REG_LAB_IBB_AMOLED_MODE;
+
+	base = is_ibb ? labibb->ibb_base : labibb->lab_base;
+
+	rc = qpnp_labibb_sec_write(labibb, base, REG_LAB_LCD_AMOLED_SEL,
+					val);
+	if (rc < 0)
+		pr_err("register %x failed rc = %d\n",
+			REG_LAB_LCD_AMOLED_SEL, rc);
+
+	return rc;
+}
+
+static int qpnp_labibb_sel_mode_v2(struct qpnp_labibb *labibb, bool is_ibb)
+{
+	return 0;
+}
+
+static int qpnp_ibb_get_mode_v1(struct qpnp_labibb *labibb)
+{
+	int rc = 0;
+	u8 val;
+
+	rc = qpnp_labibb_read(labibb, labibb->ibb_base + REG_IBB_LCD_AMOLED_SEL,
+				&val, 1);
+	if (rc < 0)
+		return rc;
+
+	if (val == REG_LAB_IBB_AMOLED_MODE)
+		labibb->mode = QPNP_LABIBB_AMOLED_MODE;
+	else
+		labibb->mode = QPNP_LABIBB_LCD_MODE;
+
+	return 0;
+}
+
+static int qpnp_ibb_get_mode_v2(struct qpnp_labibb *labibb)
+{
+	labibb->mode = QPNP_LABIBB_AMOLED_MODE;
+
+	return 0;
+}
+
+static int qpnp_ibb_set_clk_div_v1(struct qpnp_labibb *labibb, u8 val)
+{
+	int rc = 0;
+
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_CLK_DIV,
+				&val, 1);
+
+	return rc;
+}
+
+static int qpnp_ibb_set_clk_div_v2(struct qpnp_labibb *labibb, u8 val)
+{
+	int rc = 0;
+
+	val |= IBB_CLK_DIV_OVERRIDE_EN;
+	rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+				REG_IBB_CLK_DIV, IBB_CLK_DIV_MASK |
+				IBB_CLK_DIV_OVERRIDE_EN, val);
+
+	return rc;
+}
+
+static int qpnp_ibb_soft_start_ctl_v1(struct qpnp_labibb *labibb,
+					struct device_node *of_node)
+{
+	int rc = 0;
+	u8 val;
+	u32 tmp;
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-soft-start",
+					&(labibb->ibb_vreg.soft_start));
+	if (rc < 0) {
+		pr_err("qcom,qpnp-ibb-soft-start is missing, rc = %d\n",
+			rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-discharge-resistor",
+			&tmp);
+	if (!rc) {
+		for (val = 0; val < ARRAY_SIZE(ibb_dischg_res_table); val++) {
+			if (ibb_dischg_res_table[val] == tmp)
+				break;
+		}
+
+		if (val == ARRAY_SIZE(ibb_dischg_res_table)) {
+			pr_err("Invalid value in qcom,qpnp-ibb-discharge-resistor\n");
+			return -EINVAL;
+		}
+
+		rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+				REG_IBB_SOFT_START_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				REG_IBB_SOFT_START_CTL,	rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_ibb_soft_start_ctl_v2(struct qpnp_labibb *labibb,
+			 struct device_node *of_node)
+{
+	return 0;
+}
+
+static int qpnp_ibb_vreg_ok_ctl(struct qpnp_labibb *labibb,
+			struct device_node *of_node)
+{
+	u8 val = 0;
+	int rc = 0, i = 0;
+	u32 tmp;
+
+	if (labibb->pmic_rev_id->pmic_subtype != PM660L_SUBTYPE)
+		return rc;
+
+	val |= IBB_VREG_OK_EN_OVERLOAD_BLANK;
+
+	rc = of_property_read_u32(of_node,
+				"qcom,qpnp-ibb-overload-debounce", &tmp);
+	if (rc < 0) {
+		pr_err("failed to read qcom,qpnp-ibb-overload-debounce rc=%d\n",
+								rc);
+		return rc;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(ibb_overload_debounce_table); i++)
+		if (ibb_overload_debounce_table[i] == tmp)
+			break;
+
+	if (i == ARRAY_SIZE(ibb_overload_debounce_table)) {
+		pr_err("Invalid value in qcom,qpnp-ibb-overload-debounce\n");
+		return -EINVAL;
+	}
+	val |= i << IBB_VREG_OK_OVERLOAD_DEB_SHIFT;
+
+	rc = of_property_read_u32(of_node,
+				"qcom,qpnp-ibb-vreg-ok-debounce", &tmp);
+	if (rc < 0) {
+		pr_err("failed to read qcom,qpnp-ibb-vreg-ok-debounce rc=%d\n",
+								rc);
+		return rc;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(ibb_vreg_ok_deb_table); i++)
+		if (ibb_vreg_ok_deb_table[i] == tmp)
+			break;
+
+	if (i == ARRAY_SIZE(ibb_vreg_ok_deb_table)) {
+		pr_err("Invalid value in qcom,qpnp-ibb-vreg-ok-debounce\n");
+		return -EINVAL;
+	}
+	val |= i;
+
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+				REG_IBB_VREG_OK_CTL,
+				&val, 1);
+	if (rc < 0)
+		pr_err("write to register %x failed rc = %d\n",
+		 REG_IBB_VREG_OK_CTL, rc);
+
+	return rc;
+}
+
+static int qpnp_ibb_set_default_voltage_v1(struct qpnp_labibb *labibb,
+						 bool use_default)
+{
+	u8 val;
+	int rc = 0;
+
+	if (!use_default) {
+		if (labibb->ibb_vreg.curr_volt < labibb->ibb_vreg.min_volt) {
+			pr_err("qcom,qpnp-ibb-init-voltage %d is less than the the minimum voltage %d",
+			 labibb->ibb_vreg.curr_volt, labibb->ibb_vreg.min_volt);
+				return -EINVAL;
+		}
+
+		val = DIV_ROUND_UP(labibb->ibb_vreg.curr_volt -
+				labibb->ibb_vreg.min_volt,
+				labibb->ibb_vreg.step_size);
+		if (val > IBB_VOLTAGE_SET_MASK) {
+			pr_err("qcom,qpnp-lab-init-voltage %d is larger than the max supported voltage %ld",
+				labibb->ibb_vreg.curr_volt,
+				labibb->ibb_vreg.min_volt +
+				labibb->ibb_vreg.step_size *
+				IBB_VOLTAGE_SET_MASK);
+			return -EINVAL;
+		}
+
+		labibb->ibb_vreg.curr_volt = val * labibb->ibb_vreg.step_size +
+				labibb->ibb_vreg.min_volt;
+		val |= IBB_VOLTAGE_OVERRIDE_EN;
+	} else {
+		val = 0;
+	}
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+			REG_IBB_VOLTAGE, IBB_VOLTAGE_SET_MASK |
+			IBB_VOLTAGE_OVERRIDE_EN, val);
+	if (rc < 0)
+		pr_err("write to register %x failed rc = %d\n", REG_IBB_VOLTAGE,
+			rc);
+
+	return rc;
+}
+
+static int qpnp_ibb_set_default_voltage_v2(struct qpnp_labibb *labibb,
+						bool use_default)
+{
+	int rc = 0;
+	u8 val;
+
+	val = DIV_ROUND_UP(labibb->ibb_vreg.curr_volt,
+			labibb->ibb_vreg.step_size);
+	if (val > IBB_VOLTAGE_SET_MASK) {
+		pr_err("Invalid qcom,qpnp-ibb-init-voltage property %d",
+			labibb->ibb_vreg.curr_volt);
+		return -EINVAL;
+	}
+
+	labibb->ibb_vreg.curr_volt = val * labibb->ibb_vreg.step_size;
+
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+				REG_IBB_DEFAULT_VOLTAGE, &val, 1);
+	if (rc < 0)
+		pr_err("write to register %x failed rc = %d\n",
+			 REG_IBB_DEFAULT_VOLTAGE, rc);
+
+	return rc;
+}
+
+static int qpnp_ibb_set_voltage_v1(struct qpnp_labibb *labibb,
+				 int min_uV, int max_uV)
+{
+	int rc, new_uV;
+	u8 val;
+
+	if (min_uV < labibb->ibb_vreg.min_volt) {
+		pr_err("min_uV %d is less than min_volt %d", min_uV,
+			labibb->ibb_vreg.min_volt);
+		return -EINVAL;
+	}
+
+	val = DIV_ROUND_UP(min_uV - labibb->ibb_vreg.min_volt,
+				labibb->ibb_vreg.step_size);
+	new_uV = val * labibb->ibb_vreg.step_size + labibb->ibb_vreg.min_volt;
+
+	if (new_uV > max_uV) {
+		pr_err("unable to set voltage %d (min:%d max:%d)\n", new_uV,
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+				REG_IBB_VOLTAGE,
+				IBB_VOLTAGE_SET_MASK |
+				IBB_VOLTAGE_OVERRIDE_EN,
+				val | IBB_VOLTAGE_OVERRIDE_EN);
+
+	if (rc < 0) {
+		pr_err("write to register %x failed rc = %d\n", REG_IBB_VOLTAGE,
+			rc);
+		return rc;
+	}
+
+	if (new_uV > labibb->ibb_vreg.curr_volt) {
+		val = DIV_ROUND_UP(new_uV - labibb->ibb_vreg.curr_volt,
+				labibb->ibb_vreg.step_size);
+		udelay(val * labibb->ibb_vreg.slew_rate);
+	}
+	labibb->ibb_vreg.curr_volt = new_uV;
+
+	return 0;
+}
+
+static int qpnp_ibb_set_voltage_v2(struct qpnp_labibb *labibb,
+				int min_uV, int max_uV)
+{
+	int rc, new_uV;
+	u8 val;
+
+	val = DIV_ROUND_UP(min_uV, labibb->ibb_vreg.step_size);
+	new_uV = val * labibb->ibb_vreg.step_size;
+
+	if (new_uV > max_uV) {
+		pr_err("unable to set voltage %d (min:%d max:%d)\n", new_uV,
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+				REG_IBB_VOLTAGE, &val, 1);
+	if (rc < 0) {
+		pr_err("write to register %x failed rc = %d\n", REG_IBB_VOLTAGE,
+			rc);
+		return rc;
+	}
+
+	if (new_uV > labibb->ibb_vreg.curr_volt) {
+		val = DIV_ROUND_UP(new_uV - labibb->ibb_vreg.curr_volt,
+				labibb->ibb_vreg.step_size);
+		udelay(val * labibb->ibb_vreg.slew_rate);
+	}
+	labibb->ibb_vreg.curr_volt = new_uV;
+
+	return 0;
+}
+
+static int qpnp_ibb_output_voltage_at_one_pulse_v1(struct qpnp_labibb *labibb,
+						u32 volt)
+{
+	int rc = 0;
+	u8 val;
+
+	/*
+	 * Set the output voltage 100mV lower as the IBB HW module
+	 * counts one pulse less in SWIRE mode.
+	 */
+	val = DIV_ROUND_UP((volt - MIN_OUTPUT_PULSE_VOLTAGE_MV),
+				OUTPUT_VOLTAGE_STEP_MV) - 1;
+	rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+			REG_IBB_SWIRE_CTL,
+			IBB_OUTPUT_VOLTAGE_AT_ONE_PULSE_MASK,
+			val);
+	if (rc < 0)
+		pr_err("write register %x failed rc = %d\n",
+			REG_IBB_SWIRE_CTL, rc);
+
+	return rc;
+}
+
+static int qpnp_ibb_output_voltage_at_one_pulse_v2(struct qpnp_labibb *labibb,
+						u32 volt)
+{
+	int rc = 0;
+	u8 val;
+
+	val = DIV_ROUND_UP(volt, OUTPUT_VOLTAGE_STEP_MV);
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+			REG_IBB_SWIRE_CTL,
+			IBB_OUTPUT_VOLTAGE_AT_ONE_PULSE_MASK,
+			val);
+	if (rc < 0)
+		pr_err("qpnp_labiibb_write register %x failed rc = %d\n",
+			REG_IBB_SWIRE_CTL, rc);
+
+	return rc;
+}
+
+static const struct ibb_ver_ops ibb_ops_v1 = {
+	.set_default_voltage	= qpnp_ibb_set_default_voltage_v1,
+	.set_voltage		= qpnp_ibb_set_voltage_v1,
+	.sel_mode		= qpnp_labibb_sel_mode_v1,
+	.get_mode		= qpnp_ibb_get_mode_v1,
+	.set_clk_div		= qpnp_ibb_set_clk_div_v1,
+	.smart_ps_config	= qpnp_ibb_smart_ps_config_v1,
+	.soft_start_ctl		= qpnp_ibb_soft_start_ctl_v1,
+	.voltage_at_one_pulse	= qpnp_ibb_output_voltage_at_one_pulse_v1,
+};
+
+static const struct ibb_ver_ops ibb_ops_v2 = {
+	.set_default_voltage	= qpnp_ibb_set_default_voltage_v2,
+	.set_voltage		= qpnp_ibb_set_voltage_v2,
+	.sel_mode		= qpnp_labibb_sel_mode_v2,
+	.get_mode		= qpnp_ibb_get_mode_v2,
+	.set_clk_div		= qpnp_ibb_set_clk_div_v2,
+	.smart_ps_config	= qpnp_ibb_smart_ps_config_v2,
+	.soft_start_ctl		= qpnp_ibb_soft_start_ctl_v2,
+	.voltage_at_one_pulse	= qpnp_ibb_output_voltage_at_one_pulse_v2,
+};
+
+static int qpnp_lab_set_default_voltage_v1(struct qpnp_labibb *labibb,
+						 bool default_pres)
+{
+	u8 val;
+	int rc = 0;
+
+	if (!default_pres) {
+		if (labibb->lab_vreg.curr_volt < labibb->lab_vreg.min_volt) {
+			pr_err("qcom,qpnp-lab-init-voltage %d is less than the the minimum voltage %d",
+				labibb->lab_vreg.curr_volt,
+				labibb->lab_vreg.min_volt);
+			return -EINVAL;
+		}
+
+		val = DIV_ROUND_UP(labibb->lab_vreg.curr_volt -
+				labibb->lab_vreg.min_volt,
+				labibb->lab_vreg.step_size);
+		if (val > LAB_VOLTAGE_SET_MASK) {
+			pr_err("qcom,qpnp-lab-init-voltage %d is larger than the max supported voltage %ld",
+				labibb->lab_vreg.curr_volt,
+				labibb->lab_vreg.min_volt +
+				labibb->lab_vreg.step_size *
+				LAB_VOLTAGE_SET_MASK);
+			return -EINVAL;
+		}
+
+		labibb->lab_vreg.curr_volt = val * labibb->lab_vreg.step_size +
+				labibb->lab_vreg.min_volt;
+		val |= LAB_VOLTAGE_OVERRIDE_EN;
+
+	} else {
+		val = 0;
+	}
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_VOLTAGE, LAB_VOLTAGE_SET_MASK |
+				LAB_VOLTAGE_OVERRIDE_EN, val);
+
+	if (rc < 0)
+		pr_err("write to register %x failed rc = %d\n", REG_LAB_VOLTAGE,
+			rc);
+
+	return rc;
+}
+
+static int qpnp_lab_set_default_voltage_v2(struct qpnp_labibb *labibb,
+						 bool default_pres)
+{
+	int rc = 0;
+	u8 val;
+
+	val = DIV_ROUND_UP((labibb->lab_vreg.curr_volt
+		 - labibb->lab_vreg.min_volt), labibb->lab_vreg.step_size);
+
+	rc = qpnp_labibb_write(labibb, labibb->lab_base +
+				REG_LAB_VOUT_DEFAULT, &val, 1);
+	if (rc < 0)
+		pr_err("write to register %x failed rc = %d\n",
+			 REG_LAB_VOUT_DEFAULT, rc);
+
+	return rc;
+}
+
+static int qpnp_lab_ps_ctl_v1(struct qpnp_labibb *labibb,
+					u32 thresh, bool enable)
+{
+	int rc = 0;
+	u8 val;
+
+	if (enable) {
+		for (val = 0; val < ARRAY_SIZE(lab_ps_thresh_table_v1); val++)
+			if (lab_ps_thresh_table_v1[val] == thresh)
+				break;
+
+		if (val == ARRAY_SIZE(lab_ps_thresh_table_v1)) {
+			pr_err("Invalid value in qcom,qpnp-lab-ps-threshold\n");
+			return -EINVAL;
+		}
+
+		val |= LAB_PS_CTL_EN;
+	} else {
+		val = 0;
+	}
+
+	rc = qpnp_labibb_write(labibb, labibb->lab_base +
+			 REG_LAB_PS_CTL, &val, 1);
+
+	if (rc < 0)
+		pr_err("write register %x failed rc = %d\n",
+				REG_LAB_PS_CTL, rc);
+
+	return rc;
+}
+
+static int qpnp_lab_ps_ctl_v2(struct qpnp_labibb *labibb,
+				u32 thresh, bool enable)
+{
+	int rc = 0;
+	u8 val;
+
+	if (enable) {
+		for (val = 0; val < ARRAY_SIZE(lab_ps_thresh_table_v2); val++)
+			if (lab_ps_thresh_table_v2[val] == thresh)
+				break;
+
+		if (val == ARRAY_SIZE(lab_ps_thresh_table_v2)) {
+			pr_err("Invalid value in qcom,qpnp-lab-ps-threshold\n");
+			return -EINVAL;
+		}
+
+		val |= LAB_PS_CTL_EN;
+	} else {
+		val = 0;
+	}
+
+	rc = qpnp_labibb_write(labibb, labibb->lab_base +
+			 REG_LAB_PS_CTL, &val, 1);
+
+	if (rc < 0)
+		pr_err("write register %x failed rc = %d\n",
+				REG_LAB_PS_CTL, rc);
+
+	return rc;
+}
+
+static const struct lab_ver_ops lab_ops_v1 = {
+	.set_default_voltage	= qpnp_lab_set_default_voltage_v1,
+	.ps_ctl			= qpnp_lab_ps_ctl_v1,
+};
+
+static const struct lab_ver_ops lab_ops_v2 = {
+	.set_default_voltage	= qpnp_lab_set_default_voltage_v2,
+	.ps_ctl			= qpnp_lab_ps_ctl_v2,
+};
+
+static int qpnp_labibb_get_matching_idx(const char *val)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lab_current_sense_table); i++)
+		if (!strcmp(lab_current_sense_table[i], val))
+			return i;
+
+	return -EINVAL;
+}
+
+static int qpnp_ibb_set_mode(struct qpnp_labibb *labibb, enum ibb_mode mode)
+{
+	int rc;
+	u8 val;
+
+	if (mode == IBB_SW_CONTROL_EN)
+		val = IBB_ENABLE_CTL_MODULE_EN;
+	else if (mode == IBB_HW_CONTROL)
+		val = IBB_ENABLE_CTL_SWIRE_RDY;
+	else if (mode == IBB_HW_SW_CONTROL)
+		val = IBB_ENABLE_CTL_MODULE_EN | IBB_ENABLE_CTL_SWIRE_RDY;
+	else if (mode == IBB_SW_CONTROL_DIS)
+		val = 0;
+	else
+		return -EINVAL;
+
+	rc = qpnp_labibb_masked_write(labibb,
+		labibb->ibb_base + REG_IBB_ENABLE_CTL,
+		IBB_ENABLE_CTL_MASK, val);
+	if (rc < 0)
+		pr_err("Unable to configure IBB_ENABLE_CTL rc=%d\n", rc);
+
+	return rc;
+}
+
+static int qpnp_ibb_ps_config(struct qpnp_labibb *labibb, bool enable)
+{
+	u8 val;
+	int rc;
+
+	val = enable ? IBB_PS_CTL_EN : IBB_NUM_SWIRE_PULSE_WAIT;
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_PS_CTL,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("write register %x failed rc = %d\n",
+					REG_IBB_PS_CTL, rc);
+		return rc;
+	}
+
+	val = enable ? 0 : IBB_DEFAULT_NLIMIT_DAC;
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_NLIMIT_DAC,
+							&val, 1);
+	if (rc < 0)
+		pr_err("write register %x failed rc = %d\n",
+					REG_IBB_NLIMIT_DAC, rc);
+	return rc;
+}
+
+static int qpnp_lab_dt_init(struct qpnp_labibb *labibb,
+				struct device_node *of_node)
+{
+	int rc = 0;
+	u8 i, val, mask;
+	u32 tmp;
+
+	/*
+	 * Do not configure LCD_AMOLED_SEL for pmi8998 as it will be done by
+	 * GPIO selector.
+	 */
+	if (labibb->pmic_rev_id->pmic_subtype != PMI8998_SUBTYPE) {
+		rc = labibb->ibb_ver_ops->sel_mode(labibb, 0);
+		if (rc < 0)
+			return rc;
+	}
+
+	val = 0;
+	if (of_property_read_bool(of_node, "qcom,qpnp-lab-full-pull-down"))
+		val |= LAB_PD_CTL_STRONG_PULL;
+
+	if (!of_property_read_bool(of_node, "qcom,qpnp-lab-pull-down-enable"))
+		val |= LAB_PD_CTL_DISABLE_PD;
+
+	mask = LAB_PD_CTL_EN_MASK | LAB_PD_CTL_STRENGTH_MASK;
+	rc = qpnp_labibb_masked_write(labibb, labibb->lab_base + REG_LAB_PD_CTL,
+					mask, val);
+
+	if (rc < 0) {
+		pr_err("write to register %x failed rc = %d\n",
+				REG_LAB_PD_CTL, rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node,
+		"qcom,qpnp-lab-switching-clock-frequency", &tmp);
+	if (!rc) {
+		for (val = 0; val < ARRAY_SIZE(lab_clk_div_table); val++)
+			if (lab_clk_div_table[val] == tmp)
+				break;
+
+		if (val == ARRAY_SIZE(lab_clk_div_table)) {
+			pr_err("Invalid value in qpnp-lab-switching-clock-frequency\n");
+			return -EINVAL;
+		}
+
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+				REG_LAB_CLK_DIV, &val, 1);
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				REG_LAB_CLK_DIV, rc);
+			return rc;
+		}
+	}
+
+	if (of_property_read_bool(of_node,
+		"qcom,qpnp-lab-limit-max-current-enable")) {
+		val = LAB_CURRENT_LIMIT_EN_BIT;
+
+		rc = of_property_read_u32(of_node,
+			"qcom,qpnp-lab-limit-maximum-current", &tmp);
+
+		if (rc < 0) {
+			pr_err("get qcom,qpnp-lab-limit-maximum-current failed rc = %d\n",
+				rc);
+			return rc;
+		}
+
+		for (i = 0; i < ARRAY_SIZE(lab_current_limit_table); i++)
+			if (lab_current_limit_table[i] == tmp)
+				break;
+
+		if (i == ARRAY_SIZE(lab_current_limit_table)) {
+			pr_err("Invalid value in qcom,qpnp-lab-limit-maximum-current\n");
+			return -EINVAL;
+		}
+
+		val |= i;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+					REG_LAB_CURRENT_LIMIT, &val, 1);
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+					REG_LAB_CURRENT_LIMIT, rc);
+			return rc;
+		}
+	}
+
+	if (of_property_read_bool(of_node,
+		"qcom,qpnp-lab-ring-suppression-enable")) {
+		val = LAB_RING_SUPPRESSION_CTL_EN;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+					REG_LAB_RING_SUPPRESSION_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				REG_LAB_RING_SUPPRESSION_CTL, rc);
+			return rc;
+		}
+	}
+
+	if (of_property_read_bool(of_node, "qcom,qpnp-lab-ps-enable")) {
+
+		rc = of_property_read_u32(of_node,
+				 "qcom,qpnp-lab-ps-threshold", &tmp);
+
+		if (rc < 0) {
+			pr_err("get qcom,qpnp-lab-ps-threshold failed rc = %d\n",
+				rc);
+			return rc;
+		}
+		rc = labibb->lab_ver_ops->ps_ctl(labibb, tmp, true);
+		if (rc < 0)
+			return rc;
+	} else {
+		rc = labibb->lab_ver_ops->ps_ctl(labibb, tmp, false);
+		if (rc < 0)
+			return rc;
+	}
+
+	val = 0;
+	mask = 0;
+	rc = of_property_read_u32(of_node, "qcom,qpnp-lab-pfet-size", &tmp);
+	if (!rc) {
+		for (val = 0; val < ARRAY_SIZE(lab_rdson_pfet_table); val++)
+			if (tmp == lab_rdson_pfet_table[val])
+				break;
+
+		if (val == ARRAY_SIZE(lab_rdson_pfet_table)) {
+			pr_err("Invalid value in qcom,qpnp-lab-pfet-size\n");
+			return -EINVAL;
+		}
+		val |= LAB_RDSON_MNGMNT_PFET_SLEW_EN;
+		mask |= LAB_RDSON_MNGMNT_PFET_MASK |
+				LAB_RDSON_MNGMNT_PFET_SLEW_EN;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-lab-nfet-size",
+				 &tmp);
+	if (!rc) {
+		for (i = 0; i < ARRAY_SIZE(lab_rdson_nfet_table); i++)
+			if (tmp == lab_rdson_nfet_table[i])
+				break;
+
+		if (i == ARRAY_SIZE(lab_rdson_nfet_table)) {
+			pr_err("Invalid value in qcom,qpnp-lab-nfet-size\n");
+			return -EINVAL;
+		}
+
+		val |= i << LAB_RDSON_MNGMNT_NFET_SHIFT;
+		val |= LAB_RDSON_MNGMNT_NFET_SLEW_EN;
+		mask |= LAB_RDSON_MNGMNT_NFET_MASK |
+				LAB_RDSON_MNGMNT_NFET_SLEW_EN;
+	}
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_RDSON_MNGMNT, mask, val);
+	if (rc < 0) {
+		pr_err("write to register %x failed rc = %d\n",
+			REG_LAB_RDSON_MNGMNT, rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-lab-init-voltage",
+				&(labibb->lab_vreg.curr_volt));
+	if (rc < 0) {
+		pr_err("get qcom,qpnp-lab-init-voltage failed, rc = %d\n",
+				 rc);
+		return rc;
+	}
+
+	if (of_property_read_bool(of_node,
+			"qcom,qpnp-lab-use-default-voltage"))
+		rc = labibb->lab_ver_ops->set_default_voltage(labibb, true);
+	else
+		rc = labibb->lab_ver_ops->set_default_voltage(labibb, false);
+
+	if (rc < 0)
+		return rc;
+
+	if (of_property_read_bool(of_node,
+		"qcom,qpnp-lab-enable-sw-high-psrr")) {
+		val = LAB_EN_SW_HIGH_PSRR_MODE;
+
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+					REG_LAB_SW_HIGH_PSRR_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				REG_LAB_SW_HIGH_PSRR_CTL, rc);
+			return rc;
+		}
+	}
+
+	rc = of_property_read_u32(of_node,
+		"qcom,qpnp-lab-ldo-pulldown-enable", (u32 *)&val);
+	if (!rc) {
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+			REG_LAB_LDO_PD_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				REG_LAB_LDO_PD_CTL, rc);
+			return rc;
+		}
+	}
+
+	rc = of_property_read_u32(of_node,
+		"qcom,qpnp-lab-high-psrr-src-select", &tmp);
+	if (!rc) {
+		val = tmp;
+
+		rc = of_property_read_u32(of_node,
+			"qcom,qpnp-lab-vref-high-psrr-select", &tmp);
+		if (rc < 0) {
+			pr_err("get qcom,qpnp-lab-vref-high-psrr-select failed rc = %d\n",
+				rc);
+			return rc;
+		}
+
+		for (i = 0; i < ARRAY_SIZE(lab_vref_high_psrr_table); i++)
+			if (lab_vref_high_psrr_table[i] == tmp)
+				break;
+
+		if (i == ARRAY_SIZE(lab_vref_high_psrr_table)) {
+			pr_err("Invalid value in qpnp-lab-vref-high-psrr-selct\n");
+			return -EINVAL;
+		}
+		val |= (i << LAB_SEL_HW_HIGH_PSRR_SRC_SHIFT);
+
+		rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_VPH_ENVELOP_CTL,
+				LAB_VREF_HIGH_PSRR_SEL_MASK |
+				LAB_SEL_HW_HIGH_PSRR_SRC_MASK,
+				val);
+
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				REG_LAB_VPH_ENVELOP_CTL, rc);
+			return rc;
+		}
+	}
+
+	if (labibb->swire_control) {
+		rc = qpnp_ibb_set_mode(labibb, IBB_HW_CONTROL);
+		if (rc < 0) {
+			pr_err("Unable to set SWIRE_RDY rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+#define LAB_CURRENT_MAX_1600MA	0x7
+#define LAB_CURRENT_MAX_400MA	0x1
+static int qpnp_lab_pfm_disable(struct qpnp_labibb *labibb)
+{
+	int rc = 0;
+	u8 val, mask;
+
+	mutex_lock(&(labibb->lab_vreg.lab_mutex));
+	if (!labibb->pfm_enable) {
+		pr_debug("PFM already disabled\n");
+		goto out;
+	}
+
+	val = 0;
+	mask = LAB_PFM_EN_BIT;
+	rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_PFM_CTL, mask, val);
+	if (rc < 0) {
+		pr_err("Write register %x failed rc = %d\n",
+			REG_LAB_PFM_CTL, rc);
+		goto out;
+	}
+
+	val = LAB_CURRENT_MAX_1600MA;
+	mask = LAB_OVERRIDE_CURRENT_MAX_BIT | LAB_CURRENT_LIMIT_MASK;
+	rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_CURRENT_LIMIT, mask, val);
+	if (rc < 0) {
+		pr_err("Write register %x failed rc = %d\n",
+			REG_LAB_CURRENT_LIMIT, rc);
+		goto out;
+	}
+
+	labibb->pfm_enable = false;
+out:
+	mutex_unlock(&(labibb->lab_vreg.lab_mutex));
+	return rc;
+}
+
+static int qpnp_lab_pfm_enable(struct qpnp_labibb *labibb)
+{
+	int rc = 0;
+	u8 val, mask;
+
+	mutex_lock(&(labibb->lab_vreg.lab_mutex));
+	if (labibb->pfm_enable) {
+		pr_debug("PFM already enabled\n");
+		goto out;
+	}
+
+	/* Wait for ~100uS */
+	usleep_range(100, 105);
+
+	val = LAB_OVERRIDE_CURRENT_MAX_BIT | LAB_CURRENT_MAX_400MA;
+	mask = LAB_OVERRIDE_CURRENT_MAX_BIT | LAB_CURRENT_LIMIT_MASK;
+	rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_CURRENT_LIMIT, mask, val);
+	if (rc < 0) {
+		pr_err("Write register %x failed rc = %d\n",
+			REG_LAB_CURRENT_LIMIT, rc);
+		goto out;
+	}
+
+	/* Wait for ~100uS */
+	usleep_range(100, 105);
+
+	val = LAB_PFM_EN_BIT;
+	mask = LAB_PFM_EN_BIT;
+	rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_PFM_CTL, mask, val);
+	if (rc < 0) {
+		pr_err("Write register %x failed rc = %d\n",
+			REG_LAB_PFM_CTL, rc);
+		goto out;
+	}
+
+	labibb->pfm_enable = true;
+out:
+	mutex_unlock(&(labibb->lab_vreg.lab_mutex));
+	return rc;
+}
+
+static int qpnp_labibb_restore_settings(struct qpnp_labibb *labibb)
+{
+	int rc, i;
+
+	for (i = 0; i < ARRAY_SIZE(ibb_settings); i++) {
+		if (ibb_settings[i].sec_access)
+			rc = qpnp_labibb_sec_write(labibb, labibb->ibb_base,
+					ibb_settings[i].address,
+					ibb_settings[i].value);
+		else
+			rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+					ibb_settings[i].address,
+					&ibb_settings[i].value, 1);
+
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				ibb_settings[i].address, rc);
+			return rc;
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(lab_settings); i++) {
+		if (lab_settings[i].sec_access)
+			rc = qpnp_labibb_sec_write(labibb, labibb->lab_base,
+					lab_settings[i].address,
+					lab_settings[i].value);
+		else
+			rc = qpnp_labibb_write(labibb, labibb->lab_base +
+					lab_settings[i].address,
+					&lab_settings[i].value, 1);
+
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				lab_settings[i].address, rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_labibb_save_settings(struct qpnp_labibb *labibb)
+{
+	int rc, i;
+
+	for (i = 0; i < ARRAY_SIZE(ibb_settings); i++) {
+		rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+			 ibb_settings[i].address, &ibb_settings[i].value, 1);
+		if (rc < 0) {
+			pr_err("read register %x failed rc = %d\n",
+				ibb_settings[i].address, rc);
+			return rc;
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(lab_settings); i++) {
+		rc = qpnp_labibb_read(labibb, labibb->lab_base +
+			lab_settings[i].address, &lab_settings[i].value, 1);
+		if (rc < 0) {
+			pr_err("read register %x failed rc = %d\n",
+				lab_settings[i].address, rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_labibb_ttw_enter_ibb_common(struct qpnp_labibb *labibb)
+{
+	int rc = 0;
+	u8 val;
+
+	val = 0;
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_PD_CTL,
+				&val, 1);
+	if (rc < 0) {
+		pr_err("read register %x failed rc = %d\n",
+			REG_IBB_PD_CTL, rc);
+		return rc;
+	}
+
+	val = 0;
+	rc = qpnp_labibb_sec_write(labibb, labibb->ibb_base,
+				REG_IBB_PWRUP_PWRDN_CTL_1, val);
+	if (rc < 0) {
+		pr_err("write to register %x failed rc = %d\n",
+			REG_IBB_PWRUP_PWRDN_CTL_1, rc);
+		return rc;
+	}
+
+	val = IBB_WAIT_MBG_OK;
+	rc = qpnp_labibb_sec_masked_write(labibb, labibb->ibb_base,
+				REG_IBB_PWRUP_PWRDN_CTL_2,
+				IBB_DIS_DLY_MASK | IBB_WAIT_MBG_OK, val);
+	if (rc < 0) {
+		pr_err("write to register %x failed rc = %d\n",
+			REG_IBB_PWRUP_PWRDN_CTL_2, rc);
+		return rc;
+	}
+
+	val = IBB_NFET_SLEW_EN | IBB_PFET_SLEW_EN | IBB_OVERRIDE_NFET_SW_SIZE |
+		IBB_OVERRIDE_PFET_SW_SIZE;
+	rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+				REG_IBB_RDSON_MNGMNT, 0xFF, val);
+	if (rc < 0) {
+		pr_err("write to register %x failed rc = %d\n",
+			REG_IBB_RDSON_MNGMNT, rc);
+		return rc;
+	}
+
+	val = IBB_CURRENT_LIMIT_EN | IBB_CURRENT_MAX_500MA |
+		(IBB_ILIMIT_COUNT_CYC8 << IBB_CURRENT_LIMIT_DEBOUNCE_SHIFT);
+	rc = qpnp_labibb_sec_write(labibb, labibb->ibb_base,
+				REG_IBB_CURRENT_LIMIT, val);
+	if (rc < 0)
+		pr_err("write to register %x failed rc = %d\n",
+			REG_IBB_CURRENT_LIMIT, rc);
+
+	return rc;
+}
+
+static int qpnp_labibb_ttw_enter_ibb_pmi8996(struct qpnp_labibb *labibb)
+{
+	int rc;
+	u8 val;
+
+	val = IBB_BYPASS_PWRDN_DLY2_BIT | IBB_FAST_STARTUP;
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_SPARE_CTL,
+				&val, 1);
+	if (rc < 0)
+		pr_err("write to register %x failed rc = %d\n",
+			REG_IBB_SPARE_CTL, rc);
+
+	return rc;
+}
+
+static int qpnp_labibb_ttw_enter_ibb_pmi8950(struct qpnp_labibb *labibb)
+{
+	int rc;
+	u8 val;
+
+	rc = qpnp_ibb_ps_config(labibb, true);
+	if (rc < 0) {
+		pr_err("Failed to enable ibb_ps_config rc=%d\n", rc);
+		return rc;
+	}
+
+	val = IBB_SOFT_START_CHARGING_RESISTOR_16K;
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+				REG_IBB_SOFT_START_CTL, &val, 1);
+	if (rc < 0) {
+		pr_err("write to register %x failed rc = %d\n",
+			REG_IBB_SOFT_START_CTL, rc);
+		return rc;
+	}
+
+	val = IBB_MODULE_RDY_EN;
+	rc = qpnp_labibb_write(labibb, labibb->lab_base +
+				REG_IBB_MODULE_RDY, &val, 1);
+	if (rc < 0)
+		pr_err("write to register %x failed rc = %d\n",
+				REG_IBB_MODULE_RDY, rc);
+
+	return rc;
+}
+
+static int qpnp_labibb_regulator_ttw_mode_enter(struct qpnp_labibb *labibb)
+{
+	int rc = 0;
+	u8 val;
+
+	/* Save the IBB settings before they get modified for TTW mode */
+	if (!labibb->ibb_settings_saved) {
+		rc = qpnp_labibb_save_settings(labibb);
+		if (rc) {
+			pr_err("Error in storing IBB setttings, rc=%d\n", rc);
+			return rc;
+		}
+		labibb->ibb_settings_saved = true;
+	}
+
+	if (labibb->ttw_force_lab_on) {
+		val = LAB_MODULE_RDY_EN;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+					REG_LAB_MODULE_RDY, &val, 1);
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				REG_LAB_MODULE_RDY, rc);
+			return rc;
+		}
+
+		/* Prevents LAB being turned off by IBB */
+		val = LAB_ENABLE_CTL_EN;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+					REG_LAB_ENABLE_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				REG_LAB_ENABLE_CTL, rc);
+			return rc;
+		}
+
+		val = LAB_RDSON_MNGMNT_NFET_SLEW_EN |
+			LAB_RDSON_MNGMNT_PFET_SLEW_EN |
+			LAB_RDSON_NFET_SW_SIZE_QUARTER |
+			LAB_RDSON_PFET_SW_SIZE_QUARTER;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+				REG_LAB_RDSON_MNGMNT, &val, 1);
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				REG_LAB_RDSON_MNGMNT, rc);
+			return rc;
+		}
+
+		rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_PS_CTL, LAB_PS_CTL_EN, LAB_PS_CTL_EN);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+				REG_LAB_PS_CTL, rc);
+			return rc;
+		}
+	} else {
+		val = LAB_PD_CTL_DISABLE_PD;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+				REG_LAB_PD_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+				REG_LAB_PD_CTL, rc);
+			return rc;
+		}
+
+		val = LAB_SPARE_DISABLE_SCP_BIT;
+		if (labibb->pmic_rev_id->pmic_subtype != PMI8950_SUBTYPE)
+			val |= LAB_SPARE_TOUCH_WAKE_BIT;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+				REG_LAB_SPARE_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+				REG_LAB_SPARE_CTL, rc);
+			return rc;
+		}
+
+		val = 0;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+				REG_LAB_SOFT_START_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+				REG_LAB_SOFT_START_CTL, rc);
+			return rc;
+		}
+	}
+
+	rc = qpnp_labibb_ttw_enter_ibb_common(labibb);
+	if (rc) {
+		pr_err("Failed to apply TTW ibb common settings rc=%d\n", rc);
+		return rc;
+	}
+
+	switch (labibb->pmic_rev_id->pmic_subtype) {
+	case PMI8996_SUBTYPE:
+		rc = qpnp_labibb_ttw_enter_ibb_pmi8996(labibb);
+		break;
+	case PMI8950_SUBTYPE:
+		rc = qpnp_labibb_ttw_enter_ibb_pmi8950(labibb);
+		break;
+	}
+	if (rc < 0) {
+		pr_err("Failed to configure TTW-enter for IBB rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_ibb_set_mode(labibb, IBB_HW_CONTROL);
+	if (rc < 0) {
+		pr_err("Unable to set SWIRE_RDY rc = %d\n", rc);
+		return rc;
+	}
+	labibb->in_ttw_mode = true;
+	return 0;
+}
+
+static int qpnp_labibb_ttw_exit_ibb_common(struct qpnp_labibb *labibb)
+{
+	int rc;
+	u8 val;
+
+	val = IBB_FASTER_PFET_OFF;
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_SPARE_CTL,
+			&val, 1);
+	if (rc < 0)
+		pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+			REG_IBB_SPARE_CTL, rc);
+
+	return rc;
+}
+
+static int qpnp_labibb_regulator_ttw_mode_exit(struct qpnp_labibb *labibb)
+{
+	int rc = 0;
+	u8 val;
+
+	if (!labibb->ibb_settings_saved) {
+		pr_err("IBB settings are not saved!\n");
+		return -EINVAL;
+	}
+
+	/* Restore the IBB settings back to switch back to normal mode */
+	rc = qpnp_labibb_restore_settings(labibb);
+	if (rc < 0) {
+		pr_err("Error in restoring IBB setttings, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (labibb->ttw_force_lab_on) {
+		val = 0;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+					REG_LAB_ENABLE_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+				REG_LAB_ENABLE_CTL, rc);
+			return rc;
+		}
+	} else {
+		val = LAB_PD_CTL_STRONG_PULL;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+					REG_LAB_PD_CTL,	&val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+						REG_LAB_PD_CTL, rc);
+			return rc;
+		}
+
+		val = 0;
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+					REG_LAB_SPARE_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+					REG_LAB_SPARE_CTL, rc);
+			return rc;
+		}
+	}
+
+	switch (labibb->pmic_rev_id->pmic_subtype) {
+	case PMI8996_SUBTYPE:
+	case PMI8994_SUBTYPE:
+	case PMI8950_SUBTYPE:
+		rc = qpnp_labibb_ttw_exit_ibb_common(labibb);
+		break;
+	}
+	if (rc < 0) {
+		pr_err("Failed to configure TTW-exit for IBB rc=%d\n", rc);
+		return rc;
+	}
+
+	labibb->in_ttw_mode = false;
+	return rc;
+}
+
+static int qpnp_labibb_regulator_enable(struct qpnp_labibb *labibb)
+{
+	int rc;
+	u8 val;
+	int dly;
+	int retries;
+	bool enabled = false;
+
+	if (labibb->ttw_en && !labibb->ibb_vreg.vreg_enabled &&
+		labibb->in_ttw_mode) {
+		rc = qpnp_labibb_regulator_ttw_mode_exit(labibb);
+		if (rc) {
+			pr_err("Error in exiting TTW mode rc = %d\n", rc);
+			return rc;
+		}
+	}
+
+	rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_EN);
+	if (rc) {
+		pr_err("Unable to set IBB_MODULE_EN rc = %d\n", rc);
+		return rc;
+	}
+
+	/* total delay time */
+	dly = labibb->lab_vreg.soft_start + labibb->ibb_vreg.soft_start
+				+ labibb->ibb_vreg.pwrup_dly;
+	usleep_range(dly, dly + 100);
+
+	/* after this delay, lab should be enabled */
+	rc = qpnp_labibb_read(labibb, labibb->lab_base + REG_LAB_STATUS1,
+			&val, 1);
+	if (rc < 0) {
+		pr_err("read register %x failed rc = %d\n",
+			REG_LAB_STATUS1, rc);
+		goto err_out;
+	}
+
+	pr_debug("soft=%d %d up=%d dly=%d\n",
+		labibb->lab_vreg.soft_start, labibb->ibb_vreg.soft_start,
+				labibb->ibb_vreg.pwrup_dly, dly);
+
+	if (!(val & LAB_STATUS1_VREG_OK)) {
+		pr_err("failed for LAB %x\n", val);
+		goto err_out;
+	}
+
+	/* poll IBB_STATUS to make sure ibb had been enabled */
+	dly = labibb->ibb_vreg.soft_start + labibb->ibb_vreg.pwrup_dly;
+	retries = 10;
+	while (retries--) {
+		rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+					REG_IBB_STATUS1, &val, 1);
+		if (rc < 0) {
+			pr_err("read register %x failed rc = %d\n",
+				REG_IBB_STATUS1, rc);
+			goto err_out;
+		}
+
+		if (val & IBB_STATUS1_VREG_OK) {
+			enabled = true;
+			break;
+		}
+		usleep_range(dly, dly + 100);
+	}
+
+	if (!enabled) {
+		pr_err("failed for IBB %x\n", val);
+		goto err_out;
+	}
+
+	labibb->lab_vreg.vreg_enabled = 1;
+	labibb->ibb_vreg.vreg_enabled = 1;
+
+	return 0;
+err_out:
+	rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_DIS);
+	if (rc < 0) {
+		pr_err("Unable to set IBB_MODULE_EN rc = %d\n", rc);
+		return rc;
+	}
+	return -EINVAL;
+}
+
+static int qpnp_labibb_regulator_disable(struct qpnp_labibb *labibb)
+{
+	int rc;
+	u8 val;
+	int dly;
+	int retries;
+	bool disabled = false;
+
+	/*
+	 * When TTW mode is enabled and LABIBB regulators are disabled, it is
+	 * recommended not to disable IBB through IBB_ENABLE_CTL when switching
+	 * to SWIRE control on entering TTW mode. Hence, just enter TTW mode
+	 * and mark the regulators disabled. When we exit TTW mode, normal
+	 * mode settings will be restored anyways and regulators will be
+	 * enabled as before.
+	 */
+	if (labibb->ttw_en && !labibb->in_ttw_mode) {
+		rc = qpnp_labibb_regulator_ttw_mode_enter(labibb);
+		if (rc < 0) {
+			pr_err("Error in entering TTW mode rc = %d\n", rc);
+			return rc;
+		}
+		labibb->lab_vreg.vreg_enabled = 0;
+		labibb->ibb_vreg.vreg_enabled = 0;
+		return 0;
+	}
+
+	rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_DIS);
+	if (rc < 0) {
+		pr_err("Unable to set IBB_MODULE_EN rc = %d\n", rc);
+		return rc;
+	}
+
+	/* poll IBB_STATUS to make sure ibb had been disabled */
+	dly = labibb->ibb_vreg.pwrdn_dly;
+	retries = 2;
+	while (retries--) {
+		usleep_range(dly, dly + 100);
+		rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+				REG_IBB_STATUS1, &val, 1);
+		if (rc < 0) {
+			pr_err("read register %x failed rc = %d\n",
+				REG_IBB_STATUS1, rc);
+			return rc;
+		}
+
+		if (!(val & IBB_STATUS1_VREG_OK)) {
+			disabled = true;
+			break;
+		}
+	}
+
+	if (!disabled) {
+		pr_err("failed for IBB %x\n", val);
+		return -EINVAL;
+	}
+
+	if (labibb->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE &&
+		labibb->mode == QPNP_LABIBB_LCD_MODE) {
+		rc = qpnp_lab_pfm_disable(labibb);
+		if (rc < 0) {
+			pr_err("Error in disabling PFM, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	labibb->lab_vreg.vreg_enabled = 0;
+	labibb->ibb_vreg.vreg_enabled = 0;
+
+	return 0;
+}
+
+static int qpnp_lab_regulator_enable(struct regulator_dev *rdev)
+{
+	int rc;
+	u8 val;
+
+	struct qpnp_labibb *labibb  = rdev_get_drvdata(rdev);
+
+	if (labibb->skip_2nd_swire_cmd) {
+		rc = qpnp_ibb_ps_config(labibb, false);
+		if (rc < 0) {
+			pr_err("Failed to disable IBB PS rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (!labibb->lab_vreg.vreg_enabled && !labibb->swire_control) {
+
+		if (!labibb->standalone)
+			return qpnp_labibb_regulator_enable(labibb);
+
+		val = LAB_ENABLE_CTL_EN;
+		rc = qpnp_labibb_write(labibb,
+			labibb->lab_base + REG_LAB_ENABLE_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_lab_regulator_enable write register %x failed rc = %d\n",
+				REG_LAB_ENABLE_CTL, rc);
+			return rc;
+		}
+
+		udelay(labibb->lab_vreg.soft_start);
+
+		rc = qpnp_labibb_read(labibb, labibb->lab_base +
+					REG_LAB_STATUS1, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_lab_regulator_enable read register %x failed rc = %d\n",
+				REG_LAB_STATUS1, rc);
+			return rc;
+		}
+
+		if ((val & LAB_STATUS1_VREG_OK_MASK) != LAB_STATUS1_VREG_OK) {
+			pr_err("qpnp_lab_regulator_enable failed\n");
+			return -EINVAL;
+		}
+
+		labibb->lab_vreg.vreg_enabled = 1;
+	}
+
+	return 0;
+}
+
+static int qpnp_lab_regulator_disable(struct regulator_dev *rdev)
+{
+	int rc;
+	u8 val;
+	struct qpnp_labibb *labibb  = rdev_get_drvdata(rdev);
+
+	if (labibb->lab_vreg.vreg_enabled && !labibb->swire_control) {
+
+		if (!labibb->standalone)
+			return qpnp_labibb_regulator_disable(labibb);
+
+		val = 0;
+		rc = qpnp_labibb_write(labibb,
+			labibb->lab_base + REG_LAB_ENABLE_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_lab_regulator_enable write register %x failed rc = %d\n",
+				REG_LAB_ENABLE_CTL, rc);
+			return rc;
+		}
+
+		labibb->lab_vreg.vreg_enabled = 0;
+	}
+	return 0;
+}
+
+static int qpnp_lab_regulator_is_enabled(struct regulator_dev *rdev)
+{
+	struct qpnp_labibb *labibb  = rdev_get_drvdata(rdev);
+
+	if (labibb->swire_control)
+		return 0;
+
+	return labibb->lab_vreg.vreg_enabled;
+}
+
+static int qpnp_lab_regulator_set_voltage(struct regulator_dev *rdev,
+				int min_uV, int max_uV, unsigned int *selector)
+{
+	int rc, new_uV;
+	u8 val;
+	struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+	if (labibb->swire_control)
+		return 0;
+
+	if (min_uV < labibb->lab_vreg.min_volt) {
+		pr_err("min_uV %d is less than min_volt %d", min_uV,
+			labibb->lab_vreg.min_volt);
+		return -EINVAL;
+	}
+
+	val = DIV_ROUND_UP(min_uV - labibb->lab_vreg.min_volt,
+				labibb->lab_vreg.step_size);
+	new_uV = val * labibb->lab_vreg.step_size + labibb->lab_vreg.min_volt;
+
+	if (new_uV > max_uV) {
+		pr_err("unable to set voltage %d (min:%d max:%d)\n", new_uV,
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_VOLTAGE,
+				LAB_VOLTAGE_SET_MASK |
+				LAB_VOLTAGE_OVERRIDE_EN,
+				val | LAB_VOLTAGE_OVERRIDE_EN);
+
+	if (rc < 0) {
+		pr_err("write to register %x failed rc = %d\n", REG_LAB_VOLTAGE,
+			rc);
+		return rc;
+	}
+
+	if (new_uV > labibb->lab_vreg.curr_volt) {
+		val = DIV_ROUND_UP(new_uV - labibb->lab_vreg.curr_volt,
+				labibb->lab_vreg.step_size);
+		udelay(val * labibb->lab_vreg.slew_rate);
+	}
+	labibb->lab_vreg.curr_volt = new_uV;
+
+	return 0;
+}
+
+static int qpnp_skip_swire_command(struct qpnp_labibb *labibb)
+{
+	int rc = 0, retry = 50, dly;
+	u8 reg;
+
+	do {
+		/* poll for ibb vreg_ok */
+		rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+					REG_IBB_STATUS1, &reg, 1);
+		if (rc < 0) {
+			pr_err("Failed to read ibb_status1 reg rc=%d\n", rc);
+			return rc;
+		}
+		if ((reg & IBB_STATUS1_VREG_OK_MASK) == IBB_STATUS1_VREG_OK)
+			break;
+
+		/* poll delay */
+		usleep_range(500, 600);
+
+	} while (--retry);
+
+	if (!retry) {
+		pr_err("ibb vreg_ok failed to turn-on\n");
+		return -EBUSY;
+	}
+
+	/* move to SW control */
+	rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_EN);
+	if (rc < 0) {
+		pr_err("Failed switch to IBB_SW_CONTROL rc=%d\n", rc);
+		return rc;
+	}
+
+	/* delay to skip the second swire command */
+	dly = labibb->swire_2nd_cmd_delay * 1000;
+	while (dly / 20000) {
+		usleep_range(20000, 20010);
+		dly -= 20000;
+	}
+	if (dly)
+		usleep_range(dly, dly + 10);
+
+	rc = qpnp_ibb_set_mode(labibb, IBB_HW_SW_CONTROL);
+	if (rc < 0) {
+		pr_err("Failed switch to IBB_HW_SW_CONTROL rc=%d\n", rc);
+		return rc;
+	}
+
+	/* delay for SPMI to SWIRE transition */
+	usleep_range(1000, 1100);
+
+	/* Move back to SWIRE control */
+	rc = qpnp_ibb_set_mode(labibb, IBB_HW_CONTROL);
+	if (rc < 0)
+		pr_err("Failed switch to IBB_HW_CONTROL rc=%d\n", rc);
+
+	/* delay before enabling the PS mode */
+	msleep(labibb->swire_ibb_ps_enable_delay);
+	rc = qpnp_ibb_ps_config(labibb, true);
+	if (rc < 0)
+		pr_err("Unable to enable IBB PS rc=%d\n", rc);
+
+	return rc;
+}
+
+static irqreturn_t lab_vreg_ok_handler(int irq, void *_labibb)
+{
+	struct qpnp_labibb *labibb = _labibb;
+	int rc;
+
+	if (labibb->skip_2nd_swire_cmd && labibb->lab_dig_major < 2) {
+		rc = qpnp_skip_swire_command(labibb);
+		if (rc < 0)
+			pr_err("Failed in 'qpnp_skip_swire_command' rc=%d\n",
+				rc);
+	} else if (labibb->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE &&
+		labibb->mode == QPNP_LABIBB_LCD_MODE) {
+		rc = qpnp_lab_pfm_enable(labibb);
+		if (rc < 0)
+			pr_err("Failed to config PFM, rc=%d\n", rc);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int qpnp_lab_regulator_get_voltage(struct regulator_dev *rdev)
+{
+	struct qpnp_labibb *labibb  = rdev_get_drvdata(rdev);
+
+	if (labibb->swire_control)
+		return 0;
+
+	return labibb->lab_vreg.curr_volt;
+}
+
+static bool is_lab_vreg_ok_irq_available(struct qpnp_labibb *labibb)
+{
+	/*
+	 * LAB VREG_OK interrupt is used only to skip 2nd SWIRE command in
+	 * dig_major < 2 targets. For pmi8998, it is used to enable PFM in
+	 * LCD mode.
+	 */
+	if (labibb->skip_2nd_swire_cmd && labibb->lab_dig_major < 2)
+		return true;
+
+	if (labibb->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE &&
+		labibb->mode == QPNP_LABIBB_LCD_MODE)
+		return true;
+
+	return false;
+}
+
+static struct regulator_ops qpnp_lab_ops = {
+	.enable			= qpnp_lab_regulator_enable,
+	.disable		= qpnp_lab_regulator_disable,
+	.is_enabled		= qpnp_lab_regulator_is_enabled,
+	.set_voltage		= qpnp_lab_regulator_set_voltage,
+	.get_voltage		= qpnp_lab_regulator_get_voltage,
+};
+
+static int register_qpnp_lab_regulator(struct qpnp_labibb *labibb,
+					struct device_node *of_node)
+{
+	int rc = 0;
+	struct regulator_init_data *init_data;
+	struct regulator_desc *rdesc = &labibb->lab_vreg.rdesc;
+	struct regulator_config cfg = {};
+	u8 val, mask;
+	const char *current_sense_str;
+	bool config_current_sense = false;
+	u32 tmp;
+
+	if (!of_node) {
+		dev_err(labibb->dev, "qpnp lab regulator device tree node is missing\n");
+		return -EINVAL;
+	}
+
+	init_data = of_get_regulator_init_data(labibb->dev, of_node, rdesc);
+	if (!init_data) {
+		pr_err("unable to get regulator init data for qpnp lab regulator\n");
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-lab-min-voltage",
+					&(labibb->lab_vreg.min_volt));
+	if (rc < 0) {
+		pr_err("qcom,qpnp-lab-min-voltage is missing, rc = %d\n",
+			rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-lab-step-size",
+					&(labibb->lab_vreg.step_size));
+	if (rc < 0) {
+		pr_err("qcom,qpnp-lab-step-size is missing, rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-lab-slew-rate",
+					&(labibb->lab_vreg.slew_rate));
+	if (rc < 0) {
+		pr_err("qcom,qpnp-lab-slew-rate is missing, rc = %d\n",
+			rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-lab-soft-start",
+					&(labibb->lab_vreg.soft_start));
+	if (!rc) {
+		for (val = 0; val < ARRAY_SIZE(lab_soft_start_table); val++)
+			if (lab_soft_start_table[val] ==
+					labibb->lab_vreg.soft_start)
+				break;
+
+		if (val == ARRAY_SIZE(lab_soft_start_table))
+			val = ARRAY_SIZE(lab_soft_start_table) - 1;
+
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+				REG_LAB_SOFT_START_CTL, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+				REG_LAB_SOFT_START_CTL, rc);
+			return rc;
+		}
+
+		labibb->lab_vreg.soft_start = lab_soft_start_table
+				[val & LAB_SOFT_START_CTL_MASK];
+	}
+
+	val = 0;
+	mask = 0;
+	rc = of_property_read_u32(of_node,
+		"qcom,qpnp-lab-max-precharge-time", &tmp);
+	if (!rc) {
+		for (val = 0; val < ARRAY_SIZE(lab_max_precharge_table); val++)
+			if (lab_max_precharge_table[val] == tmp)
+				break;
+
+		if (val == ARRAY_SIZE(lab_max_precharge_table)) {
+			pr_err("Invalid value in qcom,qpnp-lab-max-precharge-time\n");
+			return -EINVAL;
+		}
+
+		mask = LAB_MAX_PRECHARGE_TIME_MASK;
+	}
+
+	if (of_property_read_bool(of_node,
+			"qcom,qpnp-lab-max-precharge-enable")) {
+		val |= LAB_FAST_PRECHARGE_CTL_EN;
+		mask |= LAB_FAST_PRECHARGE_CTL_EN;
+	}
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_PRECHARGE_CTL, mask, val);
+	if (rc < 0) {
+		pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
+			REG_LAB_PRECHARGE_CTL, rc);
+		return rc;
+	}
+
+	if (labibb->mode == QPNP_LABIBB_AMOLED_MODE &&
+		labibb->pmic_rev_id->pmic_subtype != PM660L_SUBTYPE) {
+		/*
+		 * default to 1.5 times current gain if
+		 * user doesn't specify the current-sense
+		 * dt parameter
+		 */
+		current_sense_str = "1.5x";
+		val = qpnp_labibb_get_matching_idx(current_sense_str);
+		config_current_sense = true;
+	}
+
+	if (of_find_property(of_node,
+		"qcom,qpnp-lab-current-sense", NULL)) {
+		config_current_sense = true;
+		rc = of_property_read_string(of_node,
+			"qcom,qpnp-lab-current-sense",
+			&current_sense_str);
+		if (!rc) {
+			val = qpnp_labibb_get_matching_idx(
+					current_sense_str);
+		} else {
+			pr_err("qcom,qpnp-lab-current-sense configured incorrectly rc = %d\n",
+				rc);
+			return rc;
+		}
+	}
+
+	if (config_current_sense) {
+		rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+			REG_LAB_CURRENT_SENSE,
+			LAB_CURRENT_SENSE_GAIN_MASK,
+			val);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+				REG_LAB_CURRENT_SENSE, rc);
+			return rc;
+		}
+	}
+
+	val = (labibb->standalone) ? 0 : LAB_IBB_EN_RDY_EN;
+	rc = qpnp_labibb_sec_write(labibb, labibb->lab_base,
+				REG_LAB_IBB_EN_RDY, val);
+
+	if (rc < 0) {
+		pr_err("qpnp_lab_sec_write register %x failed rc = %d\n",
+			REG_LAB_IBB_EN_RDY, rc);
+		return rc;
+	}
+
+	rc = qpnp_labibb_read(labibb, labibb->ibb_base + REG_IBB_ENABLE_CTL,
+				&val, 1);
+	if (rc < 0) {
+		pr_err("qpnp_labibb_read register %x failed rc = %d\n",
+			REG_IBB_ENABLE_CTL, rc);
+		return rc;
+	}
+
+	if (!(val & (IBB_ENABLE_CTL_SWIRE_RDY | IBB_ENABLE_CTL_MODULE_EN))) {
+		/* SWIRE_RDY and IBB_MODULE_EN not enabled */
+		rc = qpnp_lab_dt_init(labibb, of_node);
+		if (rc < 0) {
+			pr_err("qpnp-lab: wrong DT parameter specified: rc = %d\n",
+				rc);
+			return rc;
+		}
+	} else {
+		rc = labibb->ibb_ver_ops->get_mode(labibb);
+
+		rc = qpnp_labibb_read(labibb, labibb->lab_base +
+					REG_LAB_VOLTAGE, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_lab_read read register %x failed rc = %d\n",
+				REG_LAB_VOLTAGE, rc);
+			return rc;
+		}
+
+		labibb->lab_vreg.curr_volt =
+					(val &
+					LAB_VOLTAGE_SET_MASK) *
+					labibb->lab_vreg.step_size +
+					labibb->lab_vreg.min_volt;
+		if (labibb->mode == QPNP_LABIBB_LCD_MODE) {
+			rc = of_property_read_u32(of_node,
+				"qcom,qpnp-lab-init-lcd-voltage",
+				&(labibb->lab_vreg.curr_volt));
+			if (rc < 0) {
+				pr_err("get qcom,qpnp-lab-init-lcd-voltage failed, rc = %d\n",
+					rc);
+				return rc;
+			}
+		} else if (!(val & LAB_VOLTAGE_OVERRIDE_EN)) {
+			rc = of_property_read_u32(of_node,
+				"qcom,qpnp-lab-init-amoled-voltage",
+				&(labibb->lab_vreg.curr_volt));
+			if (rc < 0) {
+				pr_err("get qcom,qpnp-lab-init-amoled-voltage failed, rc = %d\n",
+					rc);
+				return rc;
+			}
+		}
+
+		labibb->lab_vreg.vreg_enabled = 1;
+	}
+
+	if (is_lab_vreg_ok_irq_available(labibb)) {
+		rc = devm_request_threaded_irq(labibb->dev,
+				labibb->lab_vreg.lab_vreg_ok_irq, NULL,
+				lab_vreg_ok_handler,
+				IRQF_ONESHOT | IRQF_TRIGGER_RISING,
+				"lab-vreg-ok", labibb);
+		if (rc) {
+			pr_err("Failed to register 'lab-vreg-ok' irq rc=%d\n",
+						rc);
+			return rc;
+		}
+	}
+
+	rc = qpnp_labibb_read(labibb, labibb->lab_base + REG_LAB_MODULE_RDY,
+				&val, 1);
+	if (rc < 0) {
+		pr_err("qpnp_lab_read read register %x failed rc = %d\n",
+			REG_LAB_MODULE_RDY, rc);
+		return rc;
+	}
+
+	if (!(val & LAB_MODULE_RDY_EN)) {
+		val = LAB_MODULE_RDY_EN;
+
+		rc = qpnp_labibb_write(labibb, labibb->lab_base +
+			REG_LAB_MODULE_RDY, &val, 1);
+
+		if (rc < 0) {
+			pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
+				REG_LAB_MODULE_RDY, rc);
+			return rc;
+		}
+	}
+
+	if (init_data->constraints.name) {
+		rdesc->owner		= THIS_MODULE;
+		rdesc->type		= REGULATOR_VOLTAGE;
+		rdesc->ops		= &qpnp_lab_ops;
+		rdesc->name		= init_data->constraints.name;
+
+		cfg.dev = labibb->dev;
+		cfg.init_data = init_data;
+		cfg.driver_data = labibb;
+		cfg.of_node = of_node;
+
+		if (of_get_property(labibb->dev->of_node, "parent-supply",
+				NULL))
+			init_data->supply_regulator = "parent";
+
+		init_data->constraints.valid_ops_mask
+				|= REGULATOR_CHANGE_VOLTAGE |
+					REGULATOR_CHANGE_STATUS;
+
+		labibb->lab_vreg.rdev = regulator_register(rdesc, &cfg);
+		if (IS_ERR(labibb->lab_vreg.rdev)) {
+			rc = PTR_ERR(labibb->lab_vreg.rdev);
+			labibb->lab_vreg.rdev = NULL;
+			pr_err("unable to get regulator init data for qpnp lab regulator, rc = %d\n",
+				rc);
+
+			return rc;
+		}
+	} else {
+		dev_err(labibb->dev, "qpnp lab regulator name missing\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int qpnp_ibb_pfm_mode_enable(struct qpnp_labibb *labibb,
+			struct device_node *of_node)
+{
+	int rc = 0;
+	u32 i, tmp = 0;
+	u8 val = IBB_PFM_ENABLE;
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-pfm-peak-curr",
+				&tmp);
+	if (rc < 0) {
+		pr_err("qcom,qpnp-ibb-pfm-peak-curr is missing, rc = %d\n",
+			rc);
+		return rc;
+	}
+	for (i = 0; i < ARRAY_SIZE(ibb_pfm_peak_curr_table); i++)
+		if (ibb_pfm_peak_curr_table[i] == tmp)
+			break;
+
+	if (i == ARRAY_SIZE(ibb_pfm_peak_curr_table)) {
+		pr_err("Invalid value in qcom,qpnp-ibb-pfm-peak-curr\n");
+		return -EINVAL;
+	}
+
+	val |= (i << IBB_PFM_PEAK_CURRENT_BIT_SHIFT);
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-pfm-hysteresis",
+				&tmp);
+	if (rc < 0) {
+		pr_err("qcom,qpnp-ibb-pfm-hysteresis is missing, rc = %d\n",
+			rc);
+		return rc;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(ibb_pfm_hysteresis_table); i++)
+		if (ibb_pfm_hysteresis_table[i] == tmp)
+			break;
+
+	if (i == ARRAY_SIZE(ibb_pfm_hysteresis_table)) {
+		pr_err("Invalid value in qcom,qpnp-ibb-pfm-hysteresis\n");
+		return -EINVAL;
+	}
+
+	val |= (i << IBB_PFM_HYSTERESIS_BIT_SHIFT);
+
+	rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+				REG_IBB_PFM_CTL, &val, 1);
+	if (rc < 0)
+		pr_err("qpnp_ibb_pfm_ctl write register %x failed rc = %d\n",
+					REG_IBB_PFM_CTL, rc);
+
+	return rc;
+}
+
+static int qpnp_labibb_pbs_mode_enable(struct qpnp_labibb *labibb,
+			struct device_node *of_node)
+{
+	int rc = 0;
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+				REG_IBB_SWIRE_CTL,
+				IBB_SWIRE_VOUT_UPD_EN, 0);
+	if (rc < 0) {
+		pr_err("qpnp_ibb_swire_ctl write register %x failed rc = %d\n",
+					REG_IBB_SWIRE_CTL, rc);
+		return rc;
+	}
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+				REG_IBB_PD_CTL, IBB_SWIRE_PD_UPD, 0);
+	if (rc < 0) {
+		pr_err("qpnp_ibb_pd_ctl write register %x failed rc = %d\n",
+					REG_IBB_PD_CTL, rc);
+		return rc;
+	}
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+				REG_LAB_SWIRE_PGM_CTL, LAB_EN_SWIRE_PGM_VOUT |
+				LAB_EN_SWIRE_PGM_PD, 0);
+	if (rc < 0)
+		pr_err("qpnp_lab_swire_pgm_ctl write register %x failed rc = %d\n",
+					REG_LAB_SWIRE_PGM_CTL, rc);
+
+	return rc;
+}
+
+static int qpnp_ibb_slew_rate_config(struct qpnp_labibb *labibb,
+			struct device_node *of_node)
+{
+	int rc = 0;
+	u32 i, tmp = 0;
+	u8 val = 0, mask = 0;
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-fast-slew-rate",
+						&tmp);
+	if (!rc) {
+		for (i = 0; i < ARRAY_SIZE(ibb_output_slew_ctl_table); i++)
+			if (ibb_output_slew_ctl_table[i] == tmp)
+				break;
+
+		if (i == ARRAY_SIZE(ibb_output_slew_ctl_table)) {
+			pr_err("Invalid value in qcom,qpnp-ibb-fast-slew-rate\n");
+			return -EINVAL;
+		}
+
+		labibb->ibb_vreg.slew_rate = tmp;
+		val |= (i << IBB_SLEW_RATE_TRANS_TIME_FAST_SHIFT) |
+				IBB_SLEW_RATE_SPEED_FAST_EN | IBB_SLEW_CTL_EN;
+
+		mask = IBB_SLEW_RATE_SPEED_FAST_EN |
+			IBB_SLEW_RATE_TRANS_TIME_FAST_MASK | IBB_SLEW_CTL_EN;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-slow-slew-rate",
+				&tmp);
+	if (!rc) {
+		for (i = 0; i < ARRAY_SIZE(ibb_output_slew_ctl_table); i++)
+			if (ibb_output_slew_ctl_table[i] == tmp)
+				break;
+
+		if (i == ARRAY_SIZE(ibb_output_slew_ctl_table)) {
+			pr_err("Invalid value in qcom,qpnp-ibb-slow-slew-rate\n");
+			return -EINVAL;
+		}
+
+		labibb->ibb_vreg.slew_rate = tmp;
+		val |= (i | IBB_SLEW_CTL_EN);
+
+		mask |= IBB_SLEW_RATE_SPEED_FAST_EN |
+			IBB_SLEW_RATE_TRANS_TIME_SLOW_MASK | IBB_SLEW_CTL_EN;
+	}
+
+	rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+				REG_IBB_OUTPUT_SLEW_CTL,
+				mask, val);
+	if (rc < 0)
+		pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+			REG_IBB_OUTPUT_SLEW_CTL, rc);
+
+	return rc;
+}
+
+static bool qpnp_ibb_poff_ctl_required(struct qpnp_labibb *labibb)
+{
+	if (labibb->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+		return false;
+
+	return true;
+}
+
+static int qpnp_ibb_dt_init(struct qpnp_labibb *labibb,
+				struct device_node *of_node)
+{
+	int rc = 0;
+	u32 i, tmp = 0;
+	u8 val, mask;
+
+	/*
+	 * Do not configure LCD_AMOLED_SEL for pmi8998 as it will be done by
+	 * GPIO selector. Override the labibb->mode with what was configured
+	 * by the bootloader.
+	 */
+	if (labibb->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE) {
+		rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+				REG_IBB_LCD_AMOLED_SEL, &val, 1);
+		if (rc) {
+			pr_err("qpnp_labibb_read register %x failed rc = %d\n",
+						REG_IBB_LCD_AMOLED_SEL, rc);
+			return rc;
+		}
+		if (val == REG_LAB_IBB_AMOLED_MODE)
+			labibb->mode = QPNP_LABIBB_AMOLED_MODE;
+		else
+			labibb->mode = QPNP_LABIBB_LCD_MODE;
+	} else {
+		rc = labibb->ibb_ver_ops->sel_mode(labibb, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_sec_write register %x failed rc = %d\n",
+				REG_IBB_LCD_AMOLED_SEL, rc);
+			return rc;
+		}
+	}
+
+	val = 0;
+	mask = 0;
+	rc = of_property_read_u32(of_node,
+		"qcom,qpnp-ibb-lab-pwrdn-delay", &tmp);
+	if (!rc) {
+		for (val = 0; val < ARRAY_SIZE(ibb_pwrdn_dly_table); val++)
+			if (ibb_pwrdn_dly_table[val] == tmp)
+				break;
+
+		if (val == ARRAY_SIZE(ibb_pwrdn_dly_table)) {
+			pr_err("Invalid value in qcom,qpnp-ibb-lab-pwrdn-delay\n");
+			return -EINVAL;
+		}
+
+		labibb->ibb_vreg.pwrdn_dly = tmp;
+		val |= IBB_PWRUP_PWRDN_CTL_1_EN_DLY2;
+		mask |= IBB_PWRUP_PWRDN_CTL_1_EN_DLY2;
+	}
+
+	rc = of_property_read_u32(of_node,
+			"qcom,qpnp-ibb-lab-pwrup-delay", &tmp);
+	if (!rc) {
+		for (i = 0; i < ARRAY_SIZE(ibb_pwrup_dly_table); i++)
+			if (ibb_pwrup_dly_table[i] == tmp)
+				break;
+
+		if (i == ARRAY_SIZE(ibb_pwrup_dly_table)) {
+			pr_err("Invalid value in qcom,qpnp-ibb-lab-pwrup-delay\n");
+			return -EINVAL;
+		}
+
+		labibb->ibb_vreg.pwrup_dly = tmp;
+
+		val |= (i << IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT);
+		val |= (IBB_PWRUP_PWRDN_CTL_1_EN_DLY1 |
+			IBB_PWRUP_PWRDN_CTL_1_LAB_VREG_OK);
+		mask |= (IBB_PWRUP_PWRDN_CTL_1_EN_DLY1 |
+			IBB_PWRUP_PWRDN_CTL_1_DLY1_MASK |
+			IBB_PWRUP_PWRDN_CTL_1_LAB_VREG_OK);
+	}
+
+	if (of_property_read_bool(of_node,
+				"qcom,qpnp-ibb-en-discharge")) {
+		val |= PWRUP_PWRDN_CTL_1_DISCHARGE_EN;
+		mask |= PWRUP_PWRDN_CTL_1_DISCHARGE_EN;
+	}
+
+	rc = qpnp_labibb_sec_masked_write(labibb, labibb->ibb_base,
+				REG_IBB_PWRUP_PWRDN_CTL_1, mask, val);
+	if (rc < 0) {
+		pr_err("qpnp_labibb_sec_write register %x failed rc = %d\n",
+			REG_IBB_PWRUP_PWRDN_CTL_1, rc);
+		return rc;
+	}
+
+	if (of_property_read_bool(of_node, "qcom,qpnp-ibb-slew-rate-config")) {
+
+		rc = qpnp_ibb_slew_rate_config(labibb, of_node);
+		if (rc < 0)
+			return rc;
+	}
+
+	val = 0;
+	if (!of_property_read_bool(of_node, "qcom,qpnp-ibb-full-pull-down"))
+		val = IBB_PD_CTL_HALF_STRENGTH;
+
+	if (of_property_read_bool(of_node, "qcom,qpnp-ibb-pull-down-enable"))
+		val |= IBB_PD_CTL_EN;
+
+	mask = IBB_PD_CTL_STRENGTH_MASK | IBB_PD_CTL_EN;
+	rc = qpnp_labibb_masked_write(labibb,
+			labibb->ibb_base + REG_IBB_PD_CTL, mask, val);
+
+	if (rc < 0) {
+		pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
+				REG_IBB_PD_CTL, rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node,
+		"qcom,qpnp-ibb-switching-clock-frequency", &tmp);
+	if (!rc) {
+		for (val = 0; val < ARRAY_SIZE(ibb_clk_div_table); val++)
+			if (ibb_clk_div_table[val] == tmp)
+				break;
+
+		if (val == ARRAY_SIZE(ibb_clk_div_table)) {
+			pr_err("Invalid value in qpnp-ibb-switching-clock-frequency\n");
+			return -EINVAL;
+		}
+		rc = labibb->ibb_ver_ops->set_clk_div(labibb, val);
+		if (rc < 0) {
+			pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
+				REG_IBB_CLK_DIV, rc);
+			return rc;
+		}
+	}
+
+	val = 0;
+	mask = 0;
+	rc = of_property_read_u32(of_node,
+		"qcom,qpnp-ibb-limit-maximum-current", &tmp);
+	if (!rc) {
+		for (val = 0; val < ARRAY_SIZE(ibb_current_limit_table); val++)
+			if (ibb_current_limit_table[val] == tmp)
+				break;
+
+		if (val == ARRAY_SIZE(ibb_current_limit_table)) {
+			pr_err("Invalid value in qcom,qpnp-ibb-limit-maximum-current\n");
+			return -EINVAL;
+		}
+
+		mask = IBB_CURRENT_LIMIT_MASK;
+	}
+
+	rc = of_property_read_u32(of_node,
+		"qcom,qpnp-ibb-debounce-cycle", &tmp);
+	if (!rc) {
+		for (i = 0; i < ARRAY_SIZE(ibb_debounce_table); i++)
+			if (ibb_debounce_table[i] == tmp)
+				break;
+
+		if (i == ARRAY_SIZE(ibb_debounce_table)) {
+			pr_err("Invalid value in qcom,qpnp-ibb-debounce-cycle\n");
+			return -EINVAL;
+		}
+
+		val |= (i << IBB_CURRENT_LIMIT_DEBOUNCE_SHIFT);
+		mask |= IBB_CURRENT_LIMIT_DEBOUNCE_MASK;
+	}
+
+	if (of_property_read_bool(of_node,
+		"qcom,qpnp-ibb-limit-max-current-enable")) {
+		val |= IBB_CURRENT_LIMIT_EN;
+		mask |= IBB_CURRENT_LIMIT_EN;
+	}
+
+	rc = qpnp_labibb_sec_masked_write(labibb, labibb->ibb_base,
+				REG_IBB_CURRENT_LIMIT, mask, val);
+	if (rc < 0) {
+		pr_err("qpnp_labibb_sec_write register %x failed rc = %d\n",
+				REG_IBB_CURRENT_LIMIT, rc);
+		return rc;
+	}
+
+	if (of_property_read_bool(of_node,
+		"qcom,qpnp-ibb-ring-suppression-enable")) {
+		val = IBB_RING_SUPPRESSION_CTL_EN;
+		rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+					REG_IBB_RING_SUPPRESSION_CTL,
+					&val,
+					1);
+		if (rc < 0) {
+			pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
+				REG_IBB_RING_SUPPRESSION_CTL, rc);
+			return rc;
+		}
+	}
+
+	if (of_property_read_bool(of_node, "qcom,qpnp-ibb-ps-enable")) {
+		rc = qpnp_ibb_ps_config(labibb, true);
+		if (rc < 0) {
+			pr_err("qpnp_ibb_dt_init PS enable failed rc=%d\n", rc);
+			return rc;
+		}
+	} else {
+		rc = qpnp_ibb_ps_config(labibb, false);
+		if (rc < 0) {
+			pr_err("qpnp_ibb_dt_init PS disable failed rc=%d\n",
+									rc);
+			return rc;
+		}
+	}
+
+	if (of_property_read_bool(of_node,
+				 "qcom,qpnp-ibb-smart-ps-enable")){
+		of_property_read_u32(of_node, "qcom,qpnp-ibb-num-swire-trans",
+					&labibb->ibb_vreg.num_swire_trans);
+
+		of_property_read_u32(of_node,
+				"qcom,qpnp-ibb-neg-curr-limit", &tmp);
+
+		rc = labibb->ibb_ver_ops->smart_ps_config(labibb, true,
+					labibb->ibb_vreg.num_swire_trans, tmp);
+		if (rc < 0) {
+			pr_err("qpnp_ibb_dt_init smart PS enable failed rc=%d\n",
+					 rc);
+			return rc;
+		}
+
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-init-voltage",
+					&(labibb->ibb_vreg.curr_volt));
+	if (rc < 0) {
+		pr_err("get qcom,qpnp-ibb-init-voltage failed, rc = %d\n", rc);
+		return rc;
+	}
+
+	if (of_property_read_bool(of_node,
+			"qcom,qpnp-ibb-use-default-voltage"))
+		rc = labibb->ibb_ver_ops->set_default_voltage(labibb, true);
+	else
+		rc = labibb->ibb_ver_ops->set_default_voltage(labibb, false);
+
+	if (rc < 0)
+		return rc;
+
+	if (of_property_read_bool(of_node, "qcom,qpnp-ibb-overload-blank")) {
+		rc = qpnp_ibb_vreg_ok_ctl(labibb, of_node);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static int qpnp_ibb_regulator_enable(struct regulator_dev *rdev)
+{
+	int rc, delay, retries = 10;
+	u8 val;
+	struct qpnp_labibb *labibb  = rdev_get_drvdata(rdev);
+
+	if (!labibb->ibb_vreg.vreg_enabled && !labibb->swire_control) {
+
+		if (!labibb->standalone)
+			return qpnp_labibb_regulator_enable(labibb);
+
+		rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_EN);
+		if (rc < 0) {
+			pr_err("Unable to set IBB_MODULE_EN rc = %d\n", rc);
+			return rc;
+		}
+
+		delay = labibb->ibb_vreg.soft_start;
+		while (retries--) {
+			/* Wait for a small period before reading IBB_STATUS1 */
+			usleep_range(delay, delay + 100);
+
+			rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+					REG_IBB_STATUS1, &val, 1);
+			if (rc < 0) {
+				pr_err("qpnp_ibb_regulator_enable read register %x failed rc = %d\n",
+					REG_IBB_STATUS1, rc);
+				return rc;
+			}
+
+			if (val & IBB_STATUS1_VREG_OK)
+				break;
+		}
+
+		if (!(val & IBB_STATUS1_VREG_OK)) {
+			pr_err("qpnp_ibb_regulator_enable failed\n");
+			return -EINVAL;
+		}
+
+		labibb->ibb_vreg.vreg_enabled = 1;
+	}
+	return 0;
+}
+
+static int qpnp_ibb_regulator_disable(struct regulator_dev *rdev)
+{
+	int rc;
+	struct qpnp_labibb *labibb  = rdev_get_drvdata(rdev);
+
+	if (labibb->ibb_vreg.vreg_enabled && !labibb->swire_control) {
+
+		if (!labibb->standalone)
+			return qpnp_labibb_regulator_disable(labibb);
+
+		rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_DIS);
+		if (rc < 0) {
+			pr_err("Unable to set IBB_MODULE_EN rc = %d\n", rc);
+			return rc;
+		}
+
+		labibb->ibb_vreg.vreg_enabled = 0;
+	}
+	return 0;
+}
+
+static int qpnp_ibb_regulator_is_enabled(struct regulator_dev *rdev)
+{
+	struct qpnp_labibb *labibb  = rdev_get_drvdata(rdev);
+
+	if (labibb->swire_control)
+		return 0;
+
+	return labibb->ibb_vreg.vreg_enabled;
+}
+
+static int qpnp_ibb_regulator_set_voltage(struct regulator_dev *rdev,
+				int min_uV, int max_uV, unsigned int *selector)
+{
+	int rc = 0;
+
+	struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+	if (labibb->swire_control)
+		return 0;
+
+	rc = labibb->ibb_ver_ops->set_voltage(labibb, min_uV, max_uV);
+	return rc;
+}
+
+
+static int qpnp_ibb_regulator_get_voltage(struct regulator_dev *rdev)
+{
+	struct qpnp_labibb *labibb  = rdev_get_drvdata(rdev);
+
+	if (labibb->swire_control)
+		return 0;
+
+	return labibb->ibb_vreg.curr_volt;
+}
+
+static struct regulator_ops qpnp_ibb_ops = {
+	.enable			= qpnp_ibb_regulator_enable,
+	.disable		= qpnp_ibb_regulator_disable,
+	.is_enabled		= qpnp_ibb_regulator_is_enabled,
+	.set_voltage		= qpnp_ibb_regulator_set_voltage,
+	.get_voltage		= qpnp_ibb_regulator_get_voltage,
+};
+
+static int register_qpnp_ibb_regulator(struct qpnp_labibb *labibb,
+					struct device_node *of_node)
+{
+	int rc = 0;
+	struct regulator_init_data *init_data;
+	struct regulator_desc *rdesc = &labibb->ibb_vreg.rdesc;
+	struct regulator_config cfg = {};
+	u8 val, ibb_enable_ctl, index;
+	u32 tmp;
+
+	if (!of_node) {
+		dev_err(labibb->dev, "qpnp ibb regulator device tree node is missing\n");
+		return -EINVAL;
+	}
+
+	init_data = of_get_regulator_init_data(labibb->dev, of_node, rdesc);
+	if (!init_data) {
+		pr_err("unable to get regulator init data for qpnp ibb regulator\n");
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-min-voltage",
+					&(labibb->ibb_vreg.min_volt));
+	if (rc < 0) {
+		pr_err("qcom,qpnp-ibb-min-voltage is missing, rc = %d\n",
+			rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-step-size",
+					&(labibb->ibb_vreg.step_size));
+	if (rc < 0) {
+		pr_err("qcom,qpnp-ibb-step-size is missing, rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-slew-rate",
+					&(labibb->ibb_vreg.slew_rate));
+	if (rc < 0)
+		labibb->ibb_vreg.slew_rate = IBB_HW_DEFAULT_SLEW_RATE;
+
+	rc = labibb->ibb_ver_ops->soft_start_ctl(labibb, of_node);
+	if (rc < 0) {
+		pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+			REG_IBB_SOFT_START_CTL, rc);
+		return rc;
+	}
+
+	if (of_find_property(of_node, "qcom,output-voltage-one-pulse", NULL)) {
+		if (!labibb->swire_control) {
+			pr_err("output-voltage-one-pulse valid for SWIRE only\n");
+			return -EINVAL;
+		}
+		rc = of_property_read_u32(of_node,
+				"qcom,output-voltage-one-pulse", &tmp);
+		if (rc < 0) {
+			pr_err("failed to read qcom,output-voltage-one-pulse rc=%d\n",
+									rc);
+			return rc;
+		}
+		if (tmp > MAX_OUTPUT_PULSE_VOLTAGE_MV ||
+				tmp < MIN_OUTPUT_PULSE_VOLTAGE_MV) {
+			pr_err("Invalid one-pulse voltage range %d\n", tmp);
+			return -EINVAL;
+		}
+		rc = labibb->ibb_ver_ops->voltage_at_one_pulse(labibb, tmp);
+		if (rc < 0)
+			return rc;
+	}
+
+	rc = qpnp_labibb_read(labibb, labibb->ibb_base + REG_IBB_ENABLE_CTL,
+				&ibb_enable_ctl, 1);
+	if (rc < 0) {
+		pr_err("qpnp_ibb_read register %x failed rc = %d\n",
+			REG_IBB_ENABLE_CTL, rc);
+		return rc;
+	}
+
+	/*
+	 * For pmi8998, override swire_control with what was configured
+	 * before by the bootloader.
+	 */
+	if (labibb->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE)
+		labibb->swire_control = ibb_enable_ctl &
+						IBB_ENABLE_CTL_SWIRE_RDY;
+
+	if (ibb_enable_ctl &
+		(IBB_ENABLE_CTL_SWIRE_RDY | IBB_ENABLE_CTL_MODULE_EN)) {
+
+		rc = labibb->ibb_ver_ops->get_mode(labibb);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_read register %x failed rc = %d\n",
+				REG_IBB_LCD_AMOLED_SEL, rc);
+			return rc;
+		}
+		rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+					REG_IBB_VOLTAGE, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_read read register %x failed rc = %d\n",
+				REG_IBB_VOLTAGE, rc);
+			return rc;
+		}
+
+		labibb->ibb_vreg.curr_volt =
+			(val & IBB_VOLTAGE_SET_MASK) *
+			labibb->ibb_vreg.step_size +
+			labibb->ibb_vreg.min_volt;
+
+		if (labibb->mode == QPNP_LABIBB_LCD_MODE) {
+			rc = of_property_read_u32(of_node,
+				"qcom,qpnp-ibb-init-lcd-voltage",
+				&(labibb->ibb_vreg.curr_volt));
+			if (rc < 0) {
+				pr_err("get qcom,qpnp-ibb-init-lcd-voltage failed, rc = %d\n",
+					rc);
+				return rc;
+			}
+		} else if (!(val & IBB_VOLTAGE_OVERRIDE_EN)) {
+			rc = of_property_read_u32(of_node,
+				"qcom,qpnp-ibb-init-amoled-voltage",
+				&(labibb->ibb_vreg.curr_volt));
+			if (rc < 0) {
+				pr_err("get qcom,qpnp-ibb-init-amoled-voltage failed, rc = %d\n",
+					rc);
+				return rc;
+			}
+
+		}
+
+		rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+				REG_IBB_PWRUP_PWRDN_CTL_1, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_config_init read register %x failed rc = %d\n",
+				REG_IBB_PWRUP_PWRDN_CTL_1, rc);
+			return rc;
+		}
+
+		index = (val & IBB_PWRUP_PWRDN_CTL_1_DLY1_MASK) >>
+				IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT;
+		labibb->ibb_vreg.pwrup_dly = ibb_pwrup_dly_table[index];
+		index = val & IBB_PWRUP_PWRDN_CTL_1_DLY2_MASK;
+		labibb->ibb_vreg.pwrdn_dly =  ibb_pwrdn_dly_table[index];
+
+		labibb->ibb_vreg.vreg_enabled = 1;
+	} else {
+		/* SWIRE_RDY and IBB_MODULE_EN not enabled */
+		rc = qpnp_ibb_dt_init(labibb, of_node);
+		if (rc < 0) {
+			pr_err("qpnp-ibb: wrong DT parameter specified: rc = %d\n",
+				rc);
+			return rc;
+		}
+	}
+
+	if (labibb->mode == QPNP_LABIBB_AMOLED_MODE &&
+			qpnp_ibb_poff_ctl_required(labibb)) {
+
+		val = IBB_OVERRIDE_NONOVERLAP | IBB_NFET_GATE_DELAY_2;
+		rc = qpnp_labibb_sec_masked_write(labibb, labibb->ibb_base,
+			REG_IBB_NONOVERLAP_TIME_1,
+			IBB_OVERRIDE_NONOVERLAP | IBB_NONOVERLAP_NFET_MASK,
+			val);
+
+		if (rc < 0) {
+			pr_err("qpnp_labibb_sec_masked_write register %x failed rc = %d\n",
+				REG_IBB_NONOVERLAP_TIME_1, rc);
+			return rc;
+		}
+
+		val = IBB_N2P_MUX_SEL;
+		rc = qpnp_labibb_sec_write(labibb, labibb->ibb_base,
+			REG_IBB_NONOVERLAP_TIME_2, val);
+
+		if (rc < 0) {
+			pr_err("qpnp_labibb_sec_write register %x failed rc = %d\n",
+				REG_IBB_NONOVERLAP_TIME_2, rc);
+			return rc;
+		}
+
+		val = IBB_FASTER_PFET_OFF;
+		rc = qpnp_labibb_masked_write(labibb,
+				labibb->ibb_base + REG_IBB_SPARE_CTL,
+				IBB_POFF_CTL_MASK, val);
+		if (rc < 0) {
+			pr_err("write to register %x failed rc = %d\n",
+				 REG_IBB_SPARE_CTL, rc);
+			return rc;
+		}
+	}
+
+	if (labibb->standalone) {
+		val = 0;
+		rc = qpnp_labibb_sec_write(labibb, labibb->ibb_base,
+				REG_IBB_PWRUP_PWRDN_CTL_1, val);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_sec_write register %x failed rc = %d\n",
+				REG_IBB_PWRUP_PWRDN_CTL_1, rc);
+			return rc;
+		}
+		labibb->ibb_vreg.pwrup_dly = 0;
+		labibb->ibb_vreg.pwrdn_dly = 0;
+	}
+
+	rc = qpnp_labibb_read(labibb, labibb->ibb_base + REG_IBB_MODULE_RDY,
+				&val, 1);
+	if (rc < 0) {
+		pr_err("qpnp_ibb_read read register %x failed rc = %d\n",
+			REG_IBB_MODULE_RDY, rc);
+		return rc;
+	}
+
+	if (!(val & IBB_MODULE_RDY_EN)) {
+		val = IBB_MODULE_RDY_EN;
+
+		rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+			REG_IBB_MODULE_RDY, &val, 1);
+
+		if (rc < 0) {
+			pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
+				REG_IBB_MODULE_RDY, rc);
+			return rc;
+		}
+	}
+
+	if (of_property_read_bool(of_node,
+			"qcom,qpnp-ibb-enable-pfm-mode")) {
+		rc = qpnp_ibb_pfm_mode_enable(labibb, of_node);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (labibb->pbs_control) {
+		rc = qpnp_labibb_pbs_mode_enable(labibb, of_node);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (init_data->constraints.name) {
+		rdesc->owner		= THIS_MODULE;
+		rdesc->type		= REGULATOR_VOLTAGE;
+		rdesc->ops		= &qpnp_ibb_ops;
+		rdesc->name		= init_data->constraints.name;
+
+		cfg.dev = labibb->dev;
+		cfg.init_data = init_data;
+		cfg.driver_data = labibb;
+		cfg.of_node = of_node;
+
+		if (of_get_property(labibb->dev->of_node, "parent-supply",
+				 NULL))
+			init_data->supply_regulator = "parent";
+
+		init_data->constraints.valid_ops_mask
+				|= REGULATOR_CHANGE_VOLTAGE |
+					REGULATOR_CHANGE_STATUS;
+
+		labibb->ibb_vreg.rdev = regulator_register(rdesc, &cfg);
+		if (IS_ERR(labibb->ibb_vreg.rdev)) {
+			rc = PTR_ERR(labibb->ibb_vreg.rdev);
+			labibb->ibb_vreg.rdev = NULL;
+			pr_err("unable to get regulator init data for qpnp ibb regulator, rc = %d\n",
+				rc);
+
+			return rc;
+		}
+	} else {
+		dev_err(labibb->dev, "qpnp ibb regulator name missing\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int qpnp_lab_register_irq(struct device_node *child,
+				struct qpnp_labibb *labibb)
+{
+	if (is_lab_vreg_ok_irq_available(labibb)) {
+		labibb->lab_vreg.lab_vreg_ok_irq =
+					of_irq_get_byname(child, "lab-vreg-ok");
+		if (labibb->lab_vreg.lab_vreg_ok_irq < 0) {
+			pr_err("Invalid lab-vreg-ok irq\n");
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_labibb_check_ttw_supported(struct qpnp_labibb *labibb)
+{
+	int rc = 0;
+	u8 val;
+
+	switch (labibb->pmic_rev_id->pmic_subtype) {
+	case PMI8996_SUBTYPE:
+		rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+					REG_IBB_REVISION4, &val, 1);
+		if (rc < 0) {
+			pr_err("qpnp_labibb_read register %x failed rc = %d\n",
+				REG_IBB_REVISION4, rc);
+			return rc;
+		}
+
+		/* PMI8996 has revision 1 */
+		if (val < 1) {
+			pr_err("TTW feature cannot be enabled for revision %d\n",
+									val);
+			labibb->ttw_en = false;
+		}
+		/* FORCE_LAB_ON in TTW is not required for PMI8996 */
+		labibb->ttw_force_lab_on = false;
+		break;
+	case PMI8950_SUBTYPE:
+		/* TTW supported for all revisions */
+		break;
+	default:
+		pr_info("TTW mode not supported for PMIC-subtype = %d\n",
+					labibb->pmic_rev_id->pmic_subtype);
+		labibb->ttw_en = false;
+		break;
+
+	}
+	return rc;
+}
+
+static int qpnp_labibb_regulator_probe(struct platform_device *pdev)
+{
+	struct qpnp_labibb *labibb;
+	unsigned int base;
+	struct device_node *child, *revid_dev_node;
+	const char *mode_name;
+	u8 type, revision;
+	int rc = 0;
+
+	labibb = devm_kzalloc(&pdev->dev, sizeof(*labibb), GFP_KERNEL);
+	if (labibb == NULL)
+		return -ENOMEM;
+
+	labibb->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!labibb->regmap) {
+		dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+		return -EINVAL;
+	}
+
+	labibb->dev = &(pdev->dev);
+	labibb->pdev = pdev;
+
+	mutex_init(&(labibb->lab_vreg.lab_mutex));
+	mutex_init(&(labibb->ibb_vreg.ibb_mutex));
+	mutex_init(&(labibb->bus_mutex));
+
+	revid_dev_node = of_parse_phandle(labibb->dev->of_node,
+					"qcom,pmic-revid", 0);
+	if (!revid_dev_node) {
+		pr_err("Missing qcom,pmic-revid property - driver failed\n");
+		return -EINVAL;
+	}
+
+	labibb->pmic_rev_id = get_revid_data(revid_dev_node);
+	if (IS_ERR(labibb->pmic_rev_id)) {
+		pr_debug("Unable to get revid data\n");
+		return -EPROBE_DEFER;
+	}
+
+	if (labibb->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+		labibb->ibb_ver_ops = &ibb_ops_v2;
+		labibb->lab_ver_ops = &lab_ops_v2;
+	} else {
+		labibb->ibb_ver_ops = &ibb_ops_v1;
+		labibb->lab_ver_ops = &lab_ops_v1;
+	}
+
+	if (labibb->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+		labibb->mode = QPNP_LABIBB_AMOLED_MODE;
+	} else {
+		rc = of_property_read_string(labibb->dev->of_node,
+				"qcom,qpnp-labibb-mode", &mode_name);
+		if (!rc) {
+			if (strcmp("lcd", mode_name) == 0) {
+				labibb->mode = QPNP_LABIBB_LCD_MODE;
+			} else if (strcmp("amoled", mode_name) == 0) {
+				labibb->mode = QPNP_LABIBB_AMOLED_MODE;
+			} else {
+				pr_err("Invalid device property in qcom,qpnp-labibb-mode: %s\n",
+					mode_name);
+				return -EINVAL;
+			}
+		} else {
+			pr_err("qpnp_labibb: qcom,qpnp-labibb-mode is missing.\n");
+			return rc;
+		}
+	}
+
+	labibb->standalone = of_property_read_bool(labibb->dev->of_node,
+				"qcom,labibb-standalone");
+
+	labibb->ttw_en = of_property_read_bool(labibb->dev->of_node,
+				"qcom,labibb-touch-to-wake-en");
+	if (labibb->ttw_en && labibb->mode != QPNP_LABIBB_LCD_MODE) {
+		pr_err("Invalid mode for TTW\n");
+		return -EINVAL;
+	}
+
+	labibb->ttw_force_lab_on = of_property_read_bool(
+		labibb->dev->of_node, "qcom,labibb-ttw-force-lab-on");
+
+	labibb->swire_control = of_property_read_bool(labibb->dev->of_node,
+							"qcom,swire-control");
+
+	labibb->pbs_control = of_property_read_bool(labibb->dev->of_node,
+							"qcom,pbs-control");
+	if (labibb->swire_control && labibb->mode != QPNP_LABIBB_AMOLED_MODE) {
+		pr_err("Invalid mode for SWIRE control\n");
+		return -EINVAL;
+	}
+
+	if (labibb->swire_control) {
+		labibb->skip_2nd_swire_cmd =
+				of_property_read_bool(labibb->dev->of_node,
+				"qcom,skip-2nd-swire-cmd");
+
+		rc = of_property_read_u32(labibb->dev->of_node,
+				"qcom,swire-2nd-cmd-delay",
+				&labibb->swire_2nd_cmd_delay);
+		if (rc < 0)
+			labibb->swire_2nd_cmd_delay =
+					SWIRE_DEFAULT_2ND_CMD_DLY_MS;
+
+		rc = of_property_read_u32(labibb->dev->of_node,
+				"qcom,swire-ibb-ps-enable-delay",
+				&labibb->swire_ibb_ps_enable_delay);
+		if (rc < 0)
+			labibb->swire_ibb_ps_enable_delay =
+					SWIRE_DEFAULT_IBB_PS_ENABLE_DLY_MS;
+	}
+
+	if (of_get_available_child_count(pdev->dev.of_node) == 0) {
+		pr_err("no child nodes\n");
+		return -ENXIO;
+	}
+
+	for_each_available_child_of_node(pdev->dev.of_node, child) {
+		rc = of_property_read_u32(child, "reg", &base);
+		if (rc < 0) {
+			dev_err(&pdev->dev,
+				"Couldn't find reg in node = %s rc = %d\n",
+				child->full_name, rc);
+			return rc;
+		}
+
+		rc = qpnp_labibb_read(labibb, base + REG_REVISION_2,
+					 &revision, 1);
+		if (rc < 0) {
+			pr_err("Reading REVISION_2 failed rc=%d\n", rc);
+			goto fail_registration;
+		}
+
+		rc = qpnp_labibb_read(labibb, base + REG_PERPH_TYPE,
+					&type, 1);
+		if (rc < 0) {
+			pr_err("Peripheral type read failed rc=%d\n", rc);
+			goto fail_registration;
+		}
+
+		switch (type) {
+		case QPNP_LAB_TYPE:
+			labibb->lab_base = base;
+			labibb->lab_dig_major = revision;
+			rc = qpnp_lab_register_irq(child, labibb);
+			if (rc) {
+				pr_err("Failed to register LAB IRQ rc=%d\n",
+							rc);
+				goto fail_registration;
+			}
+			rc = register_qpnp_lab_regulator(labibb, child);
+			if (rc < 0)
+				goto fail_registration;
+		break;
+
+		case QPNP_IBB_TYPE:
+			labibb->ibb_base = base;
+			labibb->ibb_dig_major = revision;
+			rc = register_qpnp_ibb_regulator(labibb, child);
+			if (rc < 0)
+				goto fail_registration;
+		break;
+
+		default:
+			pr_err("qpnp_labibb: unknown peripheral type %x\n",
+				type);
+			rc = -EINVAL;
+			goto fail_registration;
+		}
+	}
+
+	if (labibb->ttw_en) {
+		rc = qpnp_labibb_check_ttw_supported(labibb);
+		if (rc < 0) {
+			pr_err("pmic revision check failed for TTW rc=%d\n",
+									rc);
+			goto fail_registration;
+		}
+	}
+	dev_set_drvdata(&pdev->dev, labibb);
+	pr_info("LAB/IBB registered successfully, lab_vreg enable=%d ibb_vreg enable=%d swire_control=%d\n",
+						labibb->lab_vreg.vreg_enabled,
+						labibb->ibb_vreg.vreg_enabled,
+						labibb->swire_control);
+
+	return 0;
+
+fail_registration:
+	if (labibb->lab_vreg.rdev)
+		regulator_unregister(labibb->lab_vreg.rdev);
+	if (labibb->ibb_vreg.rdev)
+		regulator_unregister(labibb->ibb_vreg.rdev);
+
+	return rc;
+}
+
+static int qpnp_labibb_regulator_remove(struct platform_device *pdev)
+{
+	struct qpnp_labibb *labibb = dev_get_drvdata(&pdev->dev);
+
+	if (labibb) {
+		if (labibb->lab_vreg.rdev)
+			regulator_unregister(labibb->lab_vreg.rdev);
+		if (labibb->ibb_vreg.rdev)
+			regulator_unregister(labibb->ibb_vreg.rdev);
+	}
+	return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+	{ .compatible = QPNP_LABIBB_REGULATOR_DRIVER_NAME, },
+	{ },
+};
+
+static struct platform_driver qpnp_labibb_regulator_driver = {
+	.driver		= {
+		.name		= QPNP_LABIBB_REGULATOR_DRIVER_NAME,
+		.of_match_table	= spmi_match_table,
+	},
+	.probe		= qpnp_labibb_regulator_probe,
+	.remove		= qpnp_labibb_regulator_remove,
+};
+
+static int __init qpnp_labibb_regulator_init(void)
+{
+	return platform_driver_register(&qpnp_labibb_regulator_driver);
+}
+arch_initcall(qpnp_labibb_regulator_init);
+
+static void __exit qpnp_labibb_regulator_exit(void)
+{
+	platform_driver_unregister(&qpnp_labibb_regulator_driver);
+}
+module_exit(qpnp_labibb_regulator_exit);
+
+MODULE_DESCRIPTION("QPNP labibb driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/regulator/qpnp-lcdb-regulator.c b/drivers/regulator/qpnp-lcdb-regulator.c
new file mode 100644
index 0000000..a08ade6
--- /dev/null
+++ b/drivers/regulator/qpnp-lcdb-regulator.c
@@ -0,0 +1,1692 @@
+/*
+ * Copyright (c) 2016-2017, 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)	"LCDB: %s: " fmt, __func__
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+
+#define QPNP_LCDB_REGULATOR_DRIVER_NAME		"qcom,qpnp-lcdb-regulator"
+
+/* LCDB */
+#define LCDB_STS1_REG			0x08
+
+#define INT_RT_STATUS_REG		0x10
+#define VREG_OK_RT_STS_BIT		BIT(0)
+
+#define LCDB_AUTO_TOUCH_WAKE_CTL_REG	0x40
+#define EN_AUTO_TOUCH_WAKE_BIT		BIT(7)
+#define ATTW_TOFF_TIME_MASK		GENMASK(3, 2)
+#define ATTW_TON_TIME_MASK		GENMASK(1, 0)
+#define ATTW_TOFF_TIME_SHIFT		2
+#define ATTW_MIN_MS			4
+#define ATTW_MAX_MS			32
+
+#define LCDB_BST_OUTPUT_VOLTAGE_REG	0x41
+
+#define LCDB_MODULE_RDY_REG		0x45
+#define MODULE_RDY_BIT			BIT(7)
+
+#define LCDB_ENABLE_CTL1_REG		0x46
+#define MODULE_EN_BIT			BIT(7)
+#define HWEN_RDY_BIT			BIT(6)
+
+/* BST */
+#define LCDB_BST_PD_CTL_REG		0x47
+#define BOOST_DIS_PULLDOWN_BIT		BIT(1)
+#define BOOST_PD_STRENGTH_BIT		BIT(0)
+
+#define LCDB_BST_ILIM_CTL_REG		0x4B
+#define EN_BST_ILIM_BIT			BIT(7)
+#define SET_BST_ILIM_MASK		GENMASK(2, 0)
+#define MIN_BST_ILIM_MA			200
+#define MAX_BST_ILIM_MA			1600
+
+#define LCDB_PS_CTL_REG			0x50
+#define EN_PS_BIT			BIT(7)
+#define PS_THRESHOLD_MASK		GENMASK(1, 0)
+#define MIN_BST_PS_MA			50
+#define MAX_BST_PS_MA			80
+
+#define LCDB_RDSON_MGMNT_REG		0x53
+#define NFET_SW_SIZE_MASK		GENMASK(3, 2)
+#define NFET_SW_SIZE_SHIFT		2
+#define PFET_SW_SIZE_MASK		GENMASK(1, 0)
+
+#define LCDB_BST_VREG_OK_CTL_REG	0x55
+#define BST_VREG_OK_DEB_MASK		GENMASK(1, 0)
+
+#define LCDB_SOFT_START_CTL_REG		0x5F
+
+#define LCDB_MISC_CTL_REG		0x60
+#define AUTO_GM_EN_BIT			BIT(4)
+#define EN_TOUCH_WAKE_BIT		BIT(3)
+#define DIS_SCP_BIT			BIT(0)
+
+#define LCDB_PFM_CTL_REG		0x62
+#define EN_PFM_BIT			BIT(7)
+#define BYP_BST_SOFT_START_COMP_BIT	BIT(0)
+#define PFM_HYSTERESIS_SHIFT		4
+#define PFM_CURRENT_SHIFT		2
+
+#define LCDB_PWRUP_PWRDN_CTL_REG	0x66
+
+/* LDO */
+#define LCDB_LDO_OUTPUT_VOLTAGE_REG	0x71
+#define SET_OUTPUT_VOLTAGE_MASK		GENMASK(4, 0)
+
+#define LCDB_LDO_VREG_OK_CTL_REG	0x75
+#define VREG_OK_DEB_MASK		GENMASK(1, 0)
+
+#define LCDB_LDO_PD_CTL_REG		0x77
+#define LDO_DIS_PULLDOWN_BIT		BIT(1)
+#define LDO_PD_STRENGTH_BIT		BIT(0)
+
+#define LCDB_LDO_ILIM_CTL1_REG		0x7B
+#define EN_LDO_ILIM_BIT			BIT(7)
+#define SET_LDO_ILIM_MASK		GENMASK(2, 0)
+#define MIN_LDO_ILIM_MA			110
+#define MAX_LDO_ILIM_MA			460
+#define LDO_ILIM_STEP_MA		50
+
+#define LCDB_LDO_ILIM_CTL2_REG		0x7C
+
+#define LCDB_LDO_SOFT_START_CTL_REG	0x7F
+#define SOFT_START_MASK			GENMASK(1, 0)
+
+/* NCP */
+#define LCDB_NCP_OUTPUT_VOLTAGE_REG	0x81
+
+#define LCDB_NCP_VREG_OK_CTL_REG	0x85
+
+#define LCDB_NCP_PD_CTL_REG		0x87
+#define NCP_DIS_PULLDOWN_BIT		BIT(1)
+#define NCP_PD_STRENGTH_BIT		BIT(0)
+
+#define LCDB_NCP_ILIM_CTL1_REG		0x8B
+#define EN_NCP_ILIM_BIT			BIT(7)
+#define SET_NCP_ILIM_MASK		GENMASK(1, 0)
+#define MIN_NCP_ILIM_MA			260
+#define MAX_NCP_ILIM_MA			810
+
+#define LCDB_NCP_ILIM_CTL2_REG		0x8C
+
+#define LCDB_NCP_SOFT_START_CTL_REG	0x8F
+
+/* common for BST/NCP/LDO */
+#define MIN_DBC_US			2
+#define MAX_DBC_US			32
+
+#define MIN_SOFT_START_US		0
+#define MAX_SOFT_START_US		2000
+
+struct ldo_regulator {
+	struct regulator_desc		rdesc;
+	struct regulator_dev		*rdev;
+	struct device_node		*node;
+
+	/* LDO DT params */
+	int				pd;
+	int				pd_strength;
+	int				ilim_ma;
+	int				soft_start_us;
+	int				vreg_ok_dbc_us;
+	int				voltage_mv;
+};
+
+struct ncp_regulator {
+	struct regulator_desc		rdesc;
+	struct regulator_dev		*rdev;
+	struct device_node		*node;
+
+	/* NCP DT params */
+	int				pd;
+	int				pd_strength;
+	int				ilim_ma;
+	int				soft_start_us;
+	int				vreg_ok_dbc_us;
+	int				voltage_mv;
+};
+
+struct bst_params {
+	struct device_node		*node;
+
+	/* BST DT params */
+	int				pd;
+	int				pd_strength;
+	int				ilim_ma;
+	int				ps;
+	int				ps_threshold;
+	int				soft_start_us;
+	int				vreg_ok_dbc_us;
+	int				voltage_mv;
+};
+
+struct qpnp_lcdb {
+	struct device			*dev;
+	struct platform_device		*pdev;
+	struct regmap			*regmap;
+	u32				base;
+
+	/* TTW params */
+	bool				ttw_enable;
+	bool				ttw_mode_sw;
+
+	/* status parameters */
+	bool				lcdb_enabled;
+	bool				settings_saved;
+
+	struct mutex			lcdb_mutex;
+	struct mutex			read_write_mutex;
+	struct bst_params		bst;
+	struct ldo_regulator		ldo;
+	struct ncp_regulator		ncp;
+};
+
+struct settings {
+	u16	address;
+	u8	value;
+	bool	sec_access;
+};
+
+enum lcdb_module {
+	LDO,
+	NCP,
+	BST,
+};
+
+enum pfm_hysteresis {
+	PFM_HYST_15MV,
+	PFM_HYST_25MV,
+	PFM_HYST_35MV,
+	PFM_HYST_45MV,
+};
+
+enum pfm_peak_current {
+	PFM_PEAK_CURRENT_300MA,
+	PFM_PEAK_CURRENT_400MA,
+	PFM_PEAK_CURRENT_500MA,
+	PFM_PEAK_CURRENT_600MA,
+};
+
+enum rdson_fet_size {
+	RDSON_QUARTER,
+	RDSON_HALF,
+	RDSON_THREE_FOURTH,
+	RDSON_FULLSIZE,
+};
+
+enum lcdb_settings_index {
+	LCDB_BST_PD_CTL = 0,
+	LCDB_RDSON_MGMNT,
+	LCDB_MISC_CTL,
+	LCDB_SOFT_START_CTL,
+	LCDB_PFM_CTL,
+	LCDB_PWRUP_PWRDN_CTL,
+	LCDB_LDO_PD_CTL,
+	LCDB_LDO_SOFT_START_CTL,
+	LCDB_NCP_PD_CTL,
+	LCDB_NCP_SOFT_START_CTL,
+	LCDB_SETTING_MAX,
+};
+
+static u32 soft_start_us[] = {
+	0,
+	500,
+	1000,
+	2000,
+};
+
+static u32 dbc_us[] = {
+	2,
+	4,
+	16,
+	32,
+};
+
+static u32 ncp_ilim_ma[] = {
+	260,
+	460,
+	640,
+	810,
+};
+
+#define SETTING(_id, _sec_access)		\
+	[_id] = {				\
+		.address = _id##_REG,		\
+		.sec_access = _sec_access,	\
+	}					\
+
+static bool is_between(int value, int min, int max)
+{
+	if (value < min || value > max)
+		return false;
+	return true;
+}
+
+static int qpnp_lcdb_read(struct qpnp_lcdb *lcdb,
+			u16 addr, u8 *value, u8 count)
+{
+	int rc = 0;
+
+	mutex_lock(&lcdb->read_write_mutex);
+	rc = regmap_bulk_read(lcdb->regmap, addr, value, count);
+	if (rc < 0)
+		pr_err("Failed to read from addr=0x%02x rc=%d\n", addr, rc);
+	mutex_unlock(&lcdb->read_write_mutex);
+
+	return rc;
+}
+
+static int qpnp_lcdb_write(struct qpnp_lcdb *lcdb,
+			u16 addr, u8 *value, u8 count)
+{
+	int rc;
+
+	mutex_lock(&lcdb->read_write_mutex);
+	rc = regmap_bulk_write(lcdb->regmap, addr, value, count);
+	if (rc < 0)
+		pr_err("Failed to write to addr=0x%02x rc=%d\n", addr, rc);
+	mutex_unlock(&lcdb->read_write_mutex);
+
+	return rc;
+}
+
+#define SEC_ADDRESS_REG			0xD0
+#define SECURE_UNLOCK_VALUE		0xA5
+static int qpnp_lcdb_secure_write(struct qpnp_lcdb *lcdb,
+					u16 addr, u8 value)
+{
+	int rc;
+	u8 val = SECURE_UNLOCK_VALUE;
+
+	mutex_lock(&lcdb->read_write_mutex);
+	rc = regmap_write(lcdb->regmap, lcdb->base + SEC_ADDRESS_REG, val);
+	if (rc < 0) {
+		pr_err("Failed to unlock register rc=%d\n", rc);
+		goto fail_write;
+	}
+	rc = regmap_write(lcdb->regmap, addr, value);
+	if (rc < 0)
+		pr_err("Failed to write to addr=0x%02x rc=%d\n", addr, rc);
+
+fail_write:
+	mutex_unlock(&lcdb->read_write_mutex);
+	return rc;
+}
+
+static int qpnp_lcdb_masked_write(struct qpnp_lcdb *lcdb,
+				u16 addr, u8 mask, u8 value)
+{
+	int rc = 0;
+
+	mutex_lock(&lcdb->read_write_mutex);
+	rc = regmap_update_bits(lcdb->regmap, addr, mask, value);
+	if (rc < 0)
+		pr_err("Failed to write addr=0x%02x value=0x%02x rc=%d\n",
+			addr, value, rc);
+	mutex_unlock(&lcdb->read_write_mutex);
+
+	return rc;
+}
+
+static bool is_lcdb_enabled(struct qpnp_lcdb *lcdb)
+{
+	int rc;
+	u8 val = 0;
+
+	rc = qpnp_lcdb_read(lcdb, lcdb->base + LCDB_ENABLE_CTL1_REG, &val, 1);
+	if (rc < 0)
+		pr_err("Failed to read ENABLE_CTL1 rc=%d\n", rc);
+
+	return rc ? false : !!(val & MODULE_EN_BIT);
+}
+
+static int dump_status_registers(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0;
+	u8 sts[6] = {0};
+
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_STS1_REG, &sts[0], 6);
+	if (rc < 0) {
+		pr_err("Failed to write to STS registers rc=%d\n", rc);
+	} else {
+		rc = qpnp_lcdb_read(lcdb, lcdb->base + LCDB_STS1_REG, sts, 6);
+		if (rc < 0)
+			pr_err("Failed to read lcdb status rc=%d\n", rc);
+		else
+			pr_err("STS1=0x%02x STS2=0x%02x STS3=0x%02x STS4=0x%02x STS5=0x%02x, STS6=0x%02x\n",
+				sts[0], sts[1], sts[2], sts[3], sts[4], sts[5]);
+	}
+
+	return rc;
+}
+
+static struct settings lcdb_settings[] = {
+	SETTING(LCDB_BST_PD_CTL, false),
+	SETTING(LCDB_RDSON_MGMNT, false),
+	SETTING(LCDB_MISC_CTL, false),
+	SETTING(LCDB_SOFT_START_CTL, false),
+	SETTING(LCDB_PFM_CTL, false),
+	SETTING(LCDB_PWRUP_PWRDN_CTL, true),
+	SETTING(LCDB_LDO_PD_CTL, false),
+	SETTING(LCDB_LDO_SOFT_START_CTL, false),
+	SETTING(LCDB_NCP_PD_CTL, false),
+	SETTING(LCDB_NCP_SOFT_START_CTL, false),
+};
+
+static int qpnp_lcdb_save_settings(struct qpnp_lcdb *lcdb)
+{
+	int i, rc = 0;
+
+	for (i = 0; i < ARRAY_SIZE(lcdb_settings); i++) {
+		rc = qpnp_lcdb_read(lcdb, lcdb->base +
+				lcdb_settings[i].address,
+				&lcdb_settings[i].value, 1);
+		if (rc < 0) {
+			pr_err("Failed to read lcdb register address=%x\n",
+						lcdb_settings[i].address);
+			return rc;
+		}
+	}
+
+	return rc;
+}
+
+static int qpnp_lcdb_restore_settings(struct qpnp_lcdb *lcdb)
+{
+	int i, rc = 0;
+
+	for (i = 0; i < ARRAY_SIZE(lcdb_settings); i++) {
+		if (lcdb_settings[i].sec_access)
+			rc = qpnp_lcdb_secure_write(lcdb, lcdb->base +
+					lcdb_settings[i].address,
+					lcdb_settings[i].value);
+		else
+			rc = qpnp_lcdb_write(lcdb, lcdb->base +
+					lcdb_settings[i].address,
+					&lcdb_settings[i].value, 1);
+		if (rc < 0) {
+			pr_err("Failed to write register address=%x\n",
+						lcdb_settings[i].address);
+			return rc;
+		}
+	}
+
+	return rc;
+}
+
+static int qpnp_lcdb_ttw_enter(struct qpnp_lcdb *lcdb)
+{
+	int rc;
+	u8 val;
+
+	if (!lcdb->settings_saved) {
+		rc = qpnp_lcdb_save_settings(lcdb);
+		if (rc < 0) {
+			pr_err("Failed to save LCDB settings rc=%d\n", rc);
+			return rc;
+		}
+		lcdb->settings_saved = true;
+	}
+
+	val = BOOST_DIS_PULLDOWN_BIT;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_BST_PD_CTL_REG,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to set BST PD rc=%d\n", rc);
+		return rc;
+	}
+
+	val = (RDSON_HALF << NFET_SW_SIZE_SHIFT) | RDSON_HALF;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_RDSON_MGMNT_REG,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to set RDSON MGMT rc=%d\n", rc);
+		return rc;
+	}
+
+	val = AUTO_GM_EN_BIT | EN_TOUCH_WAKE_BIT | DIS_SCP_BIT;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_MISC_CTL_REG,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to set MISC CTL rc=%d\n", rc);
+		return rc;
+	}
+
+	val = 0;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_SOFT_START_CTL_REG,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to set LCDB_SOFT_START rc=%d\n", rc);
+		return rc;
+	}
+
+	val = EN_PFM_BIT | (PFM_HYST_25MV << PFM_HYSTERESIS_SHIFT) |
+		     (PFM_PEAK_CURRENT_400MA << PFM_CURRENT_SHIFT) |
+				BYP_BST_SOFT_START_COMP_BIT;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_PFM_CTL_REG,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to set PFM_CTL rc=%d\n", rc);
+		return rc;
+	}
+
+	val = 0;
+	rc = qpnp_lcdb_secure_write(lcdb, lcdb->base + LCDB_PWRUP_PWRDN_CTL_REG,
+							val);
+	if (rc < 0) {
+		pr_err("Failed to set PWRUP_PWRDN_CTL rc=%d\n", rc);
+		return rc;
+	}
+
+	val = LDO_DIS_PULLDOWN_BIT;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_LDO_PD_CTL_REG,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to set LDO_PD_CTL rc=%d\n", rc);
+		return rc;
+	}
+
+	val = 0;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_LDO_SOFT_START_CTL_REG,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to set LDO_SOFT_START rc=%d\n", rc);
+		return rc;
+	}
+
+	val = NCP_DIS_PULLDOWN_BIT;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_NCP_PD_CTL_REG,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to set NCP_PD_CTL rc=%d\n", rc);
+		return rc;
+	}
+
+	val = 0;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_NCP_SOFT_START_CTL_REG,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to set NCP_SOFT_START rc=%d\n", rc);
+		return rc;
+	}
+
+	if (lcdb->ttw_mode_sw) {
+		rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_AUTO_TOUCH_WAKE_CTL_REG,
+				EN_AUTO_TOUCH_WAKE_BIT,
+				EN_AUTO_TOUCH_WAKE_BIT);
+		if (rc < 0)
+			pr_err("Failed to enable auto(sw) TTW\n rc = %d\n", rc);
+	} else {
+		val = HWEN_RDY_BIT;
+		rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_ENABLE_CTL1_REG,
+							&val, 1);
+		if (rc < 0)
+			pr_err("Failed to hw_enable lcdb rc= %d\n", rc);
+	}
+
+	return rc;
+}
+
+static int qpnp_lcdb_ttw_exit(struct qpnp_lcdb *lcdb)
+{
+	int rc;
+
+	if (lcdb->settings_saved) {
+		rc = qpnp_lcdb_restore_settings(lcdb);
+		if (rc < 0) {
+			pr_err("Failed to restore lcdb settings rc=%d\n", rc);
+			return rc;
+		}
+		lcdb->settings_saved = false;
+	}
+
+	return 0;
+}
+
+static int qpnp_lcdb_enable(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0, timeout, delay;
+	u8 val = 0;
+
+	if (lcdb->lcdb_enabled)
+		return 0;
+
+	if (lcdb->ttw_enable) {
+		rc = qpnp_lcdb_ttw_exit(lcdb);
+		if (rc < 0) {
+			pr_err("Failed to exit TTW mode rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	val = MODULE_EN_BIT;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_ENABLE_CTL1_REG,
+							&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to enable lcdb rc= %d\n", rc);
+		goto fail_enable;
+	}
+
+	/* poll for vreg_ok */
+	timeout = 10;
+	delay = lcdb->bst.soft_start_us + lcdb->ldo.soft_start_us +
+					lcdb->ncp.soft_start_us;
+	delay += lcdb->bst.vreg_ok_dbc_us + lcdb->ldo.vreg_ok_dbc_us +
+					lcdb->ncp.vreg_ok_dbc_us;
+	while (timeout--) {
+		rc = qpnp_lcdb_read(lcdb, lcdb->base + INT_RT_STATUS_REG,
+								&val, 1);
+		if (rc < 0) {
+			pr_err("Failed to poll for vreg-ok status rc=%d\n", rc);
+			break;
+		}
+		if (val & VREG_OK_RT_STS_BIT)
+			break;
+
+		usleep_range(delay, delay + 100);
+	}
+
+	if (rc || !timeout) {
+		if (!timeout) {
+			pr_err("lcdb-vreg-ok status failed to change\n");
+			rc = -ETIMEDOUT;
+		}
+		goto fail_enable;
+	}
+
+	lcdb->lcdb_enabled = true;
+	pr_debug("lcdb enabled successfully!\n");
+
+	return 0;
+
+fail_enable:
+	dump_status_registers(lcdb);
+	pr_err("Failed to enable lcdb rc=%d\n", rc);
+	return rc;
+}
+
+static int qpnp_lcdb_disable(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0;
+	u8 val;
+
+	if (!lcdb->lcdb_enabled)
+		return 0;
+
+	if (lcdb->ttw_enable) {
+		rc = qpnp_lcdb_ttw_enter(lcdb);
+		if (rc < 0) {
+			pr_err("Failed to enable TTW mode rc=%d\n", rc);
+			return rc;
+		}
+		lcdb->lcdb_enabled = false;
+
+		return 0;
+	}
+
+	val = 0;
+	rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_ENABLE_CTL1_REG,
+							&val, 1);
+	if (rc < 0)
+		pr_err("Failed to disable lcdb rc= %d\n", rc);
+	else
+		lcdb->lcdb_enabled = false;
+
+	return rc;
+}
+
+#define MIN_BST_VOLTAGE_MV			4700
+#define MAX_BST_VOLTAGE_MV			6250
+#define MIN_VOLTAGE_MV				4000
+#define MAX_VOLTAGE_MV				6000
+#define VOLTAGE_MIN_STEP_100_MV			4000
+#define VOLTAGE_MIN_STEP_50_MV			4950
+#define VOLTAGE_STEP_100_MV			100
+#define VOLTAGE_STEP_50_MV			50
+#define VOLTAGE_STEP_50MV_OFFSET		0xA
+static int qpnp_lcdb_set_bst_voltage(struct qpnp_lcdb *lcdb,
+					int voltage_mv)
+{
+	int rc = 0;
+	u8 val = 0;
+
+	if (voltage_mv < MIN_BST_VOLTAGE_MV)
+		voltage_mv = MIN_BST_VOLTAGE_MV;
+	else if (voltage_mv > MAX_BST_VOLTAGE_MV)
+		voltage_mv = MAX_BST_VOLTAGE_MV;
+
+	val = DIV_ROUND_UP(voltage_mv - MIN_BST_VOLTAGE_MV,
+					VOLTAGE_STEP_50_MV);
+
+	rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_BST_OUTPUT_VOLTAGE_REG,
+				SET_OUTPUT_VOLTAGE_MASK, val);
+	if (rc < 0)
+		pr_err("Failed to set boost voltage %d mv rc=%d\n",
+			voltage_mv, rc);
+	else
+		pr_debug("Boost voltage set = %d mv (0x%02x = 0x%02x)\n",
+			voltage_mv, LCDB_BST_OUTPUT_VOLTAGE_REG, val);
+
+	return rc;
+}
+
+static int qpnp_lcdb_get_bst_voltage(struct qpnp_lcdb *lcdb,
+					int *voltage_mv)
+{
+	int rc;
+	u8 val = 0;
+
+	rc = qpnp_lcdb_read(lcdb, lcdb->base + LCDB_BST_OUTPUT_VOLTAGE_REG,
+						&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to reat BST voltage rc=%d\n", rc);
+		return rc;
+	}
+
+	val &= SET_OUTPUT_VOLTAGE_MASK;
+	*voltage_mv = (val * VOLTAGE_STEP_50_MV) + MIN_BST_VOLTAGE_MV;
+
+	return 0;
+}
+
+static int qpnp_lcdb_set_voltage(struct qpnp_lcdb *lcdb,
+					int voltage_mv, u8 type)
+{
+	int rc = 0;
+	u16 offset = LCDB_LDO_OUTPUT_VOLTAGE_REG;
+	u8 val = 0;
+
+	if (type == BST)
+		return qpnp_lcdb_set_bst_voltage(lcdb, voltage_mv);
+
+	if (type == NCP)
+		offset = LCDB_NCP_OUTPUT_VOLTAGE_REG;
+
+	if (!is_between(voltage_mv, MIN_VOLTAGE_MV, MAX_VOLTAGE_MV)) {
+		pr_err("Invalid voltage %dmv (min=%d max=%d)\n",
+			voltage_mv, MIN_VOLTAGE_MV, MAX_VOLTAGE_MV);
+		return -EINVAL;
+	}
+
+	/* Change the BST voltage to LDO + 100mV */
+	if (type == LDO) {
+		rc = qpnp_lcdb_set_bst_voltage(lcdb, voltage_mv + 100);
+		if (rc < 0) {
+			pr_err("Failed to set boost voltage rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	/* Below logic is only valid for LDO and NCP type */
+	if (voltage_mv < VOLTAGE_MIN_STEP_50_MV) {
+		val = DIV_ROUND_UP(voltage_mv - VOLTAGE_MIN_STEP_100_MV,
+						VOLTAGE_STEP_100_MV);
+	} else {
+		val = DIV_ROUND_UP(voltage_mv - VOLTAGE_MIN_STEP_50_MV,
+						VOLTAGE_STEP_50_MV);
+		val += VOLTAGE_STEP_50MV_OFFSET;
+	}
+
+	rc = qpnp_lcdb_masked_write(lcdb, lcdb->base + offset,
+				SET_OUTPUT_VOLTAGE_MASK, val);
+	if (rc < 0)
+		pr_err("Failed to set output voltage %d mv for %s rc=%d\n",
+			voltage_mv, (type == LDO) ? "LDO" : "NCP", rc);
+	else
+		pr_debug("%s voltage set = %d mv (0x%02x = 0x%02x)\n",
+			(type == LDO) ? "LDO" : "NCP", voltage_mv, offset, val);
+
+	return rc;
+}
+
+static int qpnp_lcdb_get_voltage(struct qpnp_lcdb *lcdb,
+					u32 *voltage_mv, u8 type)
+{
+	int rc = 0;
+	u16 offset = LCDB_LDO_OUTPUT_VOLTAGE_REG;
+	u8 val = 0;
+
+	if (type == BST)
+		return qpnp_lcdb_get_bst_voltage(lcdb, voltage_mv);
+
+	if (type == NCP)
+		offset = LCDB_NCP_OUTPUT_VOLTAGE_REG;
+
+	rc = qpnp_lcdb_read(lcdb, lcdb->base + offset, &val, 1);
+	if (rc < 0) {
+		pr_err("Failed to read %s volatge rc=%d\n",
+			(type == LDO) ? "LDO" : "NCP", rc);
+		return rc;
+	}
+
+	if (val < VOLTAGE_STEP_50MV_OFFSET) {
+		*voltage_mv = VOLTAGE_MIN_STEP_100_MV +
+				(val * VOLTAGE_STEP_100_MV);
+	} else {
+		*voltage_mv = VOLTAGE_MIN_STEP_50_MV +
+			((val - VOLTAGE_STEP_50MV_OFFSET) * VOLTAGE_STEP_50_MV);
+	}
+
+	if (!rc)
+		pr_debug("%s voltage read-back = %d mv (0x%02x = 0x%02x)\n",
+					(type == LDO) ? "LDO" : "NCP",
+					*voltage_mv, offset, val);
+
+	return rc;
+}
+
+static int qpnp_lcdb_set_soft_start(struct qpnp_lcdb *lcdb,
+					u32 ss_us, u8 type)
+{
+	int rc = 0, i = 0;
+	u16 offset = LCDB_LDO_SOFT_START_CTL_REG;
+	u8 val = 0;
+
+	if (type == NCP)
+		offset = LCDB_NCP_SOFT_START_CTL_REG;
+
+	if (!is_between(ss_us, MIN_SOFT_START_US, MAX_SOFT_START_US)) {
+		pr_err("Invalid soft_start_us %d (min=%d max=%d)\n",
+			ss_us, MIN_SOFT_START_US, MAX_SOFT_START_US);
+		return -EINVAL;
+	}
+
+	i = 0;
+	while (ss_us > soft_start_us[i])
+		i++;
+	val = ((i == 0) ? 0 : i - 1) & SOFT_START_MASK;
+
+	rc = qpnp_lcdb_masked_write(lcdb,
+			lcdb->base + offset, SOFT_START_MASK, val);
+	if (rc < 0)
+		pr_err("Failed to write %s soft-start time %d rc=%d",
+			(type == LDO) ? "LDO" : "NCP", soft_start_us[i], rc);
+
+	return rc;
+}
+
+static int qpnp_lcdb_ldo_regulator_enable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	struct qpnp_lcdb *lcdb  = rdev_get_drvdata(rdev);
+
+	mutex_lock(&lcdb->lcdb_mutex);
+	rc = qpnp_lcdb_enable(lcdb);
+	if (rc < 0)
+		pr_err("Failed to enable lcdb rc=%d\n", rc);
+	mutex_unlock(&lcdb->lcdb_mutex);
+
+	return rc;
+}
+
+static int qpnp_lcdb_ldo_regulator_disable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	struct qpnp_lcdb *lcdb  = rdev_get_drvdata(rdev);
+
+	mutex_lock(&lcdb->lcdb_mutex);
+	rc = qpnp_lcdb_disable(lcdb);
+	if (rc < 0)
+		pr_err("Failed to disable lcdb rc=%d\n", rc);
+	mutex_unlock(&lcdb->lcdb_mutex);
+
+	return rc;
+}
+
+static int qpnp_lcdb_ldo_regulator_is_enabled(struct regulator_dev *rdev)
+{
+	struct qpnp_lcdb *lcdb  = rdev_get_drvdata(rdev);
+
+	return lcdb->lcdb_enabled;
+}
+
+static int qpnp_lcdb_ldo_regulator_set_voltage(struct regulator_dev *rdev,
+				int min_uV, int max_uV, unsigned int *selector)
+{
+	int rc = 0;
+	struct qpnp_lcdb *lcdb  = rdev_get_drvdata(rdev);
+
+	rc = qpnp_lcdb_set_voltage(lcdb, min_uV / 1000, LDO);
+	if (rc < 0)
+		pr_err("Failed to set LDO voltage rc=%c\n", rc);
+
+	return rc;
+}
+
+static int qpnp_lcdb_ldo_regulator_get_voltage(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	u32 voltage_mv = 0;
+	struct qpnp_lcdb *lcdb  = rdev_get_drvdata(rdev);
+
+	rc = qpnp_lcdb_get_voltage(lcdb, &voltage_mv, LDO);
+	if (rc < 0) {
+		pr_err("Failed to get ldo voltage rc=%d\n", rc);
+		return rc;
+	}
+
+	return voltage_mv * 1000;
+}
+
+static struct regulator_ops qpnp_lcdb_ldo_ops = {
+	.enable			= qpnp_lcdb_ldo_regulator_enable,
+	.disable		= qpnp_lcdb_ldo_regulator_disable,
+	.is_enabled		= qpnp_lcdb_ldo_regulator_is_enabled,
+	.set_voltage		= qpnp_lcdb_ldo_regulator_set_voltage,
+	.get_voltage		= qpnp_lcdb_ldo_regulator_get_voltage,
+};
+
+static int qpnp_lcdb_ncp_regulator_enable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	struct qpnp_lcdb *lcdb  = rdev_get_drvdata(rdev);
+
+	mutex_lock(&lcdb->lcdb_mutex);
+	rc = qpnp_lcdb_enable(lcdb);
+	if (rc < 0)
+		pr_err("Failed to enable lcdb rc=%d\n", rc);
+	mutex_unlock(&lcdb->lcdb_mutex);
+
+	return rc;
+}
+
+static int qpnp_lcdb_ncp_regulator_disable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	struct qpnp_lcdb *lcdb  = rdev_get_drvdata(rdev);
+
+	mutex_lock(&lcdb->lcdb_mutex);
+	rc = qpnp_lcdb_disable(lcdb);
+	if (rc < 0)
+		pr_err("Failed to disable lcdb rc=%d\n", rc);
+	mutex_unlock(&lcdb->lcdb_mutex);
+
+	return rc;
+}
+
+static int qpnp_lcdb_ncp_regulator_is_enabled(struct regulator_dev *rdev)
+{
+	struct qpnp_lcdb *lcdb  = rdev_get_drvdata(rdev);
+
+	return lcdb->lcdb_enabled;
+}
+
+static int qpnp_lcdb_ncp_regulator_set_voltage(struct regulator_dev *rdev,
+				int min_uV, int max_uV, unsigned int *selector)
+{
+	int rc = 0;
+	struct qpnp_lcdb *lcdb  = rdev_get_drvdata(rdev);
+
+	rc = qpnp_lcdb_set_voltage(lcdb, min_uV / 1000, NCP);
+	if (rc < 0)
+		pr_err("Failed to set LDO voltage rc=%c\n", rc);
+
+	return rc;
+}
+
+static int qpnp_lcdb_ncp_regulator_get_voltage(struct regulator_dev *rdev)
+{
+	int rc;
+	u32 voltage_mv = 0;
+	struct qpnp_lcdb *lcdb  = rdev_get_drvdata(rdev);
+
+	rc = qpnp_lcdb_get_voltage(lcdb, &voltage_mv, NCP);
+	if (rc < 0) {
+		pr_err("Failed to get ncp voltage rc=%d\n", rc);
+		return rc;
+	}
+
+	return voltage_mv * 1000;
+}
+
+static struct regulator_ops qpnp_lcdb_ncp_ops = {
+	.enable			= qpnp_lcdb_ncp_regulator_enable,
+	.disable		= qpnp_lcdb_ncp_regulator_disable,
+	.is_enabled		= qpnp_lcdb_ncp_regulator_is_enabled,
+	.set_voltage		= qpnp_lcdb_ncp_regulator_set_voltage,
+	.get_voltage		= qpnp_lcdb_ncp_regulator_get_voltage,
+};
+
+static int qpnp_lcdb_regulator_register(struct qpnp_lcdb *lcdb, u8 type)
+{
+	int rc = 0;
+	struct regulator_init_data *init_data;
+	struct regulator_config cfg = {};
+	struct regulator_desc *rdesc;
+	struct regulator_dev *rdev;
+	struct device_node *node;
+
+	if (type == LDO) {
+		node			= lcdb->ldo.node;
+		rdesc			= &lcdb->ldo.rdesc;
+		rdesc->ops		= &qpnp_lcdb_ldo_ops;
+		rdev			= lcdb->ldo.rdev;
+	} else if (type == NCP) {
+		node			= lcdb->ncp.node;
+		rdesc			= &lcdb->ncp.rdesc;
+		rdesc->ops		= &qpnp_lcdb_ncp_ops;
+		rdev			= lcdb->ncp.rdev;
+	} else {
+		pr_err("Invalid regulator type %d\n", type);
+		return -EINVAL;
+	}
+
+	init_data = of_get_regulator_init_data(lcdb->dev, node, rdesc);
+	if (!init_data) {
+		pr_err("Failed to get regulator_init_data for %s\n",
+					(type == LDO) ? "LDO" : "NCP");
+		return -ENOMEM;
+	}
+
+	if (init_data->constraints.name) {
+		rdesc->owner		= THIS_MODULE;
+		rdesc->type		= REGULATOR_VOLTAGE;
+		rdesc->name		= init_data->constraints.name;
+
+		cfg.dev = lcdb->dev;
+		cfg.init_data = init_data;
+		cfg.driver_data = lcdb;
+		cfg.of_node = node;
+
+		if (of_get_property(lcdb->dev->of_node, "parent-supply", NULL))
+			init_data->supply_regulator = "parent";
+
+		init_data->constraints.valid_ops_mask
+				|= REGULATOR_CHANGE_VOLTAGE
+				| REGULATOR_CHANGE_STATUS;
+
+		rdev = devm_regulator_register(lcdb->dev, rdesc, &cfg);
+		if (IS_ERR(rdev)) {
+			rc = PTR_ERR(rdev);
+			rdev = NULL;
+			pr_err("Failed to register lcdb_%s regulator rc = %d\n",
+				(type == LDO) ? "LDO" : "NCP", rc);
+			return rc;
+		}
+	} else {
+		pr_err("%s_regulator name missing\n",
+				(type == LDO) ? "LDO" : "NCP");
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int qpnp_lcdb_parse_ttw(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0;
+	u32 temp;
+	u8 val = 0;
+	struct device_node *node = lcdb->dev->of_node;
+
+	if (of_property_read_bool(node, "qcom,ttw-mode-sw")) {
+		lcdb->ttw_mode_sw = true;
+		rc = of_property_read_u32(node, "qcom,attw-toff-ms", &temp);
+		if (!rc) {
+			if (!is_between(temp, ATTW_MIN_MS, ATTW_MAX_MS)) {
+				pr_err("Invalid TOFF val %d (min=%d max=%d)\n",
+					temp, ATTW_MIN_MS, ATTW_MAX_MS);
+					return -EINVAL;
+			}
+			val = ilog2(temp / 4) << ATTW_TOFF_TIME_SHIFT;
+		} else {
+			pr_err("qcom,attw-toff-ms not specified for TTW SW mode\n");
+			return rc;
+		}
+
+		rc = of_property_read_u32(node, "qcom,attw-ton-ms", &temp);
+		if (!rc) {
+			if (!is_between(temp, ATTW_MIN_MS, ATTW_MAX_MS)) {
+				pr_err("Invalid TON value %d (min=%d max=%d)\n",
+					temp, ATTW_MIN_MS, ATTW_MAX_MS);
+				return -EINVAL;
+			}
+			val |= ilog2(temp / 4);
+		} else {
+			pr_err("qcom,attw-ton-ms not specified for TTW SW mode\n");
+			return rc;
+		}
+		rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_AUTO_TOUCH_WAKE_CTL_REG,
+				ATTW_TON_TIME_MASK | ATTW_TOFF_TIME_MASK, val);
+		if (rc < 0) {
+			pr_err("Failed to write ATTW ON/OFF rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_lcdb_ldo_dt_init(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0;
+	struct device_node *node = lcdb->ldo.node;
+
+	/* LDO output voltage */
+	lcdb->ldo.voltage_mv = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,ldo-voltage-mv",
+					&lcdb->ldo.voltage_mv);
+	if (!rc && !is_between(lcdb->ldo.voltage_mv, MIN_VOLTAGE_MV,
+						MAX_VOLTAGE_MV)) {
+		pr_err("Invalid LDO voltage %dmv (min=%d max=%d)\n",
+			lcdb->ldo.voltage_mv, MIN_VOLTAGE_MV, MAX_VOLTAGE_MV);
+		return -EINVAL;
+	}
+
+	/* LDO PD configuration */
+	lcdb->ldo.pd = -EINVAL;
+	of_property_read_u32(node, "qcom,ldo-pd", &lcdb->ldo.pd);
+
+	lcdb->ldo.pd_strength = -EINVAL;
+	of_property_read_u32(node, "qcom,ldo-pd-strength",
+					&lcdb->ldo.pd_strength);
+
+	/* LDO ILIM configuration */
+	lcdb->ldo.ilim_ma = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,ldo-ilim-ma", &lcdb->ldo.ilim_ma);
+	if (!rc && !is_between(lcdb->ldo.ilim_ma, MIN_LDO_ILIM_MA,
+						MAX_LDO_ILIM_MA)) {
+		pr_err("Invalid ilim_ma %d (min=%d, max=%d)\n",
+			lcdb->ldo.ilim_ma, MIN_LDO_ILIM_MA,
+					MAX_LDO_ILIM_MA);
+		return -EINVAL;
+	}
+
+	/* LDO soft-start (SS) configuration */
+	lcdb->ldo.soft_start_us = -EINVAL;
+	of_property_read_u32(node, "qcom,ldo-soft-start-us",
+					&lcdb->ldo.soft_start_us);
+
+	return 0;
+}
+
+static int qpnp_lcdb_ncp_dt_init(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0;
+	struct device_node *node = lcdb->ncp.node;
+
+	/* NCP output voltage */
+	lcdb->ncp.voltage_mv = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,ncp-voltage-mv",
+					&lcdb->ncp.voltage_mv);
+	if (!rc && !is_between(lcdb->ncp.voltage_mv, MIN_VOLTAGE_MV,
+						MAX_VOLTAGE_MV)) {
+		pr_err("Invalid NCP voltage %dmv (min=%d max=%d)\n",
+			lcdb->ldo.voltage_mv, MIN_VOLTAGE_MV, MAX_VOLTAGE_MV);
+		return -EINVAL;
+	}
+
+	/* NCP PD configuration */
+	lcdb->ncp.pd = -EINVAL;
+	of_property_read_u32(node, "qcom,ncp-pd", &lcdb->ncp.pd);
+
+	lcdb->ncp.pd_strength = -EINVAL;
+	of_property_read_u32(node, "qcom,ncp-pd-strength",
+					&lcdb->ncp.pd_strength);
+
+	/* NCP ILIM configuration */
+	lcdb->ncp.ilim_ma = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,ncp-ilim-ma", &lcdb->ncp.ilim_ma);
+	if (!rc && !is_between(lcdb->ncp.ilim_ma, MIN_NCP_ILIM_MA,
+						MAX_NCP_ILIM_MA)) {
+		pr_err("Invalid ilim_ma %d (min=%d, max=%d)\n",
+			lcdb->ncp.ilim_ma, MIN_NCP_ILIM_MA, MAX_NCP_ILIM_MA);
+		return -EINVAL;
+	}
+
+	/* NCP soft-start (SS) configuration */
+	lcdb->ncp.soft_start_us = -EINVAL;
+	of_property_read_u32(node, "qcom,ncp-soft-start-us",
+					&lcdb->ncp.soft_start_us);
+
+	return 0;
+}
+
+static int qpnp_lcdb_bst_dt_init(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0;
+	struct device_node *node = lcdb->bst.node;
+
+	/* Boost PD  configuration */
+	lcdb->bst.pd = -EINVAL;
+	of_property_read_u32(node, "qcom,bst-pd", &lcdb->bst.pd);
+
+	lcdb->bst.pd_strength = -EINVAL;
+	of_property_read_u32(node, "qcom,bst-pd-strength",
+					&lcdb->bst.pd_strength);
+
+	/* Boost ILIM */
+	lcdb->bst.ilim_ma = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,bst-ilim-ma", &lcdb->bst.ilim_ma);
+	if (!rc && !is_between(lcdb->bst.ilim_ma, MIN_BST_ILIM_MA,
+						MAX_BST_ILIM_MA)) {
+		pr_err("Invalid ilim_ma %d (min=%d, max=%d)\n",
+			lcdb->bst.ilim_ma, MIN_BST_ILIM_MA, MAX_BST_ILIM_MA);
+			return -EINVAL;
+	}
+
+	/* Boost PS configuration */
+	lcdb->bst.ps = -EINVAL;
+	of_property_read_u32(node, "qcom,bst-ps", &lcdb->bst.ps);
+
+	lcdb->bst.ps_threshold = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,bst-ps-threshold-ma",
+					&lcdb->bst.ps_threshold);
+	if (!rc && !is_between(lcdb->bst.ps_threshold,
+				MIN_BST_PS_MA, MAX_BST_PS_MA)) {
+		pr_err("Invalid bst ps_threshold %d (min=%d, max=%d)\n",
+			lcdb->bst.ps_threshold, MIN_BST_PS_MA, MAX_BST_PS_MA);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int qpnp_lcdb_init_ldo(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0, ilim_ma;
+	u8 val = 0;
+
+	/* configure parameters only if LCDB is disabled */
+	if (!is_lcdb_enabled(lcdb)) {
+		if (lcdb->ldo.voltage_mv != -EINVAL) {
+			rc = qpnp_lcdb_set_voltage(lcdb,
+					lcdb->ldo.voltage_mv, LDO);
+			if (rc < 0) {
+				pr_err("Failed to set voltage rc=%d\n", rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->ldo.pd != -EINVAL) {
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_LDO_PD_CTL_REG, LDO_DIS_PULLDOWN_BIT,
+				lcdb->ldo.pd ? 0 : LDO_DIS_PULLDOWN_BIT);
+			if (rc < 0) {
+				pr_err("Failed to configure LDO PD rc=%d\n",
+								rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->ldo.pd_strength != -EINVAL) {
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_LDO_PD_CTL_REG, LDO_PD_STRENGTH_BIT,
+				lcdb->ldo.pd_strength ?
+				LDO_PD_STRENGTH_BIT : 0);
+			if (rc < 0) {
+				pr_err("Failed to configure LDO PD strength %s rc=%d",
+						lcdb->ldo.pd_strength ?
+						"(strong)" : "(weak)", rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->ldo.ilim_ma != -EINVAL) {
+			ilim_ma = lcdb->ldo.ilim_ma - MIN_LDO_ILIM_MA;
+			ilim_ma /= LDO_ILIM_STEP_MA;
+			val = (ilim_ma & SET_LDO_ILIM_MASK) | EN_LDO_ILIM_BIT;
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+					LCDB_LDO_ILIM_CTL1_REG,
+					SET_LDO_ILIM_MASK | EN_LDO_ILIM_BIT,
+					val);
+			if (rc < 0) {
+				pr_err("Failed to configure LDO ilim_ma (CTL1=%d) rc=%d",
+							val, rc);
+				return rc;
+			}
+
+			val = ilim_ma & SET_LDO_ILIM_MASK;
+			rc = qpnp_lcdb_masked_write(lcdb,
+					lcdb->base + LCDB_LDO_ILIM_CTL2_REG,
+					SET_LDO_ILIM_MASK, val);
+			if (rc < 0) {
+				pr_err("Failed to configure LDO ilim_ma (CTL2=%d) rc=%d",
+							val, rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->ldo.soft_start_us != -EINVAL) {
+			rc = qpnp_lcdb_set_soft_start(lcdb,
+					lcdb->ldo.soft_start_us, LDO);
+			if (rc < 0) {
+				pr_err("Failed to set LDO soft_start rc=%d\n",
+									rc);
+				return rc;
+			}
+		}
+	}
+
+	rc = qpnp_lcdb_get_voltage(lcdb, &lcdb->ldo.voltage_mv, LDO);
+	if (rc < 0) {
+		pr_err("Failed to get LDO volatge rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_lcdb_read(lcdb, lcdb->base +
+			LCDB_LDO_VREG_OK_CTL_REG, &val, 1);
+	if (rc < 0) {
+		pr_err("Failed to read ldo_vreg_ok rc=%d\n", rc);
+		return rc;
+	}
+	lcdb->ldo.vreg_ok_dbc_us = dbc_us[val & VREG_OK_DEB_MASK];
+
+	rc = qpnp_lcdb_read(lcdb, lcdb->base +
+			LCDB_LDO_SOFT_START_CTL_REG, &val, 1);
+	if (rc < 0) {
+		pr_err("Failed to read ldo_soft_start_ctl rc=%d\n", rc);
+		return rc;
+	}
+	lcdb->ldo.soft_start_us = soft_start_us[val & SOFT_START_MASK];
+
+	rc = qpnp_lcdb_regulator_register(lcdb, LDO);
+	if (rc < 0)
+		pr_err("Failed to register ldo rc=%d\n", rc);
+
+	return rc;
+}
+
+static int qpnp_lcdb_init_ncp(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0, i = 0;
+	u8 val = 0;
+
+	/* configure parameters only if LCDB is disabled */
+	if (!is_lcdb_enabled(lcdb)) {
+		if (lcdb->ncp.voltage_mv != -EINVAL) {
+			rc = qpnp_lcdb_set_voltage(lcdb,
+					lcdb->ncp.voltage_mv, NCP);
+			if (rc < 0) {
+				pr_err("Failed to set voltage rc=%d\n", rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->ncp.pd != -EINVAL) {
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_NCP_PD_CTL_REG, NCP_DIS_PULLDOWN_BIT,
+				lcdb->ncp.pd ? 0 : NCP_DIS_PULLDOWN_BIT);
+			if (rc < 0) {
+				pr_err("Failed to configure NCP PD rc=%d\n",
+									rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->ncp.pd_strength != -EINVAL) {
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_NCP_PD_CTL_REG, NCP_PD_STRENGTH_BIT,
+				lcdb->ncp.pd_strength ?
+				NCP_PD_STRENGTH_BIT : 0);
+			if (rc < 0) {
+				pr_err("Failed to configure NCP PD strength %s rc=%d",
+					lcdb->ncp.pd_strength ?
+					"(strong)" : "(weak)", rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->ncp.ilim_ma != -EINVAL) {
+			while (lcdb->ncp.ilim_ma > ncp_ilim_ma[i])
+				i++;
+			val = (i == 0) ? 0 : i - 1;
+			val = (lcdb->ncp.ilim_ma & SET_NCP_ILIM_MASK) |
+							EN_NCP_ILIM_BIT;
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+						LCDB_NCP_ILIM_CTL1_REG,
+				SET_NCP_ILIM_MASK | EN_NCP_ILIM_BIT, val);
+			if (rc < 0) {
+				pr_err("Failed to configure NCP ilim_ma (CTL1=%d) rc=%d",
+								val, rc);
+				return rc;
+			}
+			val = lcdb->ncp.ilim_ma & SET_NCP_ILIM_MASK;
+			rc = qpnp_lcdb_masked_write(lcdb,
+					lcdb->base + LCDB_NCP_ILIM_CTL2_REG,
+					SET_NCP_ILIM_MASK, val);
+			if (rc < 0) {
+				pr_err("Failed to configure NCP ilim_ma (CTL2=%d) rc=%d",
+							val, rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->ncp.soft_start_us != -EINVAL) {
+			rc = qpnp_lcdb_set_soft_start(lcdb,
+				lcdb->ncp.soft_start_us, NCP);
+			if (rc < 0) {
+				pr_err("Failed to set NCP soft_start rc=%d\n",
+								rc);
+				return rc;
+			}
+		}
+	}
+
+	rc = qpnp_lcdb_get_voltage(lcdb, &lcdb->ncp.voltage_mv, NCP);
+	if (rc < 0) {
+		pr_err("Failed to get NCP volatge rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_lcdb_read(lcdb, lcdb->base +
+			LCDB_NCP_VREG_OK_CTL_REG, &val, 1);
+	if (rc < 0) {
+		pr_err("Failed to read ncp_vreg_ok rc=%d\n", rc);
+		return rc;
+	}
+	lcdb->ncp.vreg_ok_dbc_us = dbc_us[val & VREG_OK_DEB_MASK];
+
+	rc = qpnp_lcdb_read(lcdb, lcdb->base +
+			LCDB_NCP_SOFT_START_CTL_REG, &val, 1);
+	if (rc < 0) {
+		pr_err("Failed to read ncp_soft_start_ctl rc=%d\n", rc);
+		return rc;
+	}
+	lcdb->ncp.soft_start_us = soft_start_us[val & SOFT_START_MASK];
+
+	rc = qpnp_lcdb_regulator_register(lcdb, NCP);
+	if (rc < 0)
+		pr_err("Failed to register NCP rc=%d\n", rc);
+
+	return rc;
+}
+
+static int qpnp_lcdb_init_bst(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0;
+	u8 val = 0;
+
+	/* configure parameters only if LCDB is disabled */
+	if (!is_lcdb_enabled(lcdb)) {
+		if (lcdb->bst.pd != -EINVAL) {
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_BST_PD_CTL_REG, BOOST_DIS_PULLDOWN_BIT,
+				lcdb->bst.pd ? 0 : BOOST_DIS_PULLDOWN_BIT);
+			if (rc < 0) {
+				pr_err("Failed to configure BST PD rc=%d\n",
+									rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->bst.pd_strength != -EINVAL) {
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_NCP_PD_CTL_REG, BOOST_PD_STRENGTH_BIT,
+				lcdb->bst.pd_strength ?
+				BOOST_PD_STRENGTH_BIT : 0);
+			if (rc < 0) {
+				pr_err("Failed to configure NCP PD strength %s rc=%d",
+					lcdb->bst.pd_strength ?
+					"(strong)" : "(weak)", rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->bst.ilim_ma != -EINVAL) {
+			val = (lcdb->bst.ilim_ma / MIN_BST_ILIM_MA) - 1;
+			val = (lcdb->bst.ilim_ma & SET_BST_ILIM_MASK) |
+							EN_BST_ILIM_BIT;
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_BST_ILIM_CTL_REG,
+				SET_BST_ILIM_MASK | EN_BST_ILIM_BIT, val);
+			if (rc < 0) {
+				pr_err("Failed to configure BST ilim_ma rc=%d",
+									rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->bst.ps != -EINVAL) {
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+					LCDB_PS_CTL_REG, EN_PS_BIT,
+					&lcdb->bst.ps ? EN_PS_BIT : 0);
+			if (rc < 0) {
+				pr_err("Failed to disable BST PS rc=%d", rc);
+				return rc;
+			}
+		}
+
+		if (lcdb->bst.ps_threshold != -EINVAL) {
+			val = (lcdb->bst.ps_threshold - MIN_BST_PS_MA) / 10;
+			val = (lcdb->bst.ps_threshold & PS_THRESHOLD_MASK) |
+								EN_PS_BIT;
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+						LCDB_PS_CTL_REG,
+						PS_THRESHOLD_MASK | EN_PS_BIT,
+						val);
+			if (rc < 0) {
+				pr_err("Failed to configure BST PS threshold rc=%d",
+								rc);
+				return rc;
+			}
+		}
+	}
+
+	rc = qpnp_lcdb_get_voltage(lcdb, &lcdb->bst.voltage_mv, BST);
+	if (rc < 0) {
+		pr_err("Failed to get BST volatge rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_lcdb_read(lcdb, lcdb->base +
+			LCDB_BST_VREG_OK_CTL_REG, &val, 1);
+	if (rc < 0) {
+		pr_err("Failed to read bst_vreg_ok rc=%d\n", rc);
+		return rc;
+	}
+	lcdb->bst.vreg_ok_dbc_us = dbc_us[val & VREG_OK_DEB_MASK];
+
+	rc = qpnp_lcdb_read(lcdb, lcdb->base +
+			LCDB_SOFT_START_CTL_REG, &val, 1);
+	if (rc < 0) {
+		pr_err("Failed to read ncp_soft_start_ctl rc=%d\n", rc);
+		return rc;
+	}
+	lcdb->bst.soft_start_us = (val & SOFT_START_MASK) * 200	+ 200;
+
+	return 0;
+}
+
+static int qpnp_lcdb_hw_init(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0;
+	u8 val = 0;
+
+	rc = qpnp_lcdb_init_bst(lcdb);
+	if (rc < 0) {
+		pr_err("Failed to initialize BOOST rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_lcdb_init_ldo(lcdb);
+	if (rc < 0) {
+		pr_err("Failed to initialize LDO rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_lcdb_init_ncp(lcdb);
+	if (rc < 0) {
+		pr_err("Failed to initialize NCP rc=%d\n", rc);
+		return rc;
+	}
+
+	if (!is_lcdb_enabled(lcdb)) {
+		rc = qpnp_lcdb_read(lcdb, lcdb->base +
+				LCDB_MODULE_RDY_REG, &val, 1);
+		if (rc < 0) {
+			pr_err("Failed to read MODULE_RDY rc=%d\n", rc);
+			return rc;
+		}
+		if (!(val & MODULE_RDY_BIT)) {
+			rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+				LCDB_MODULE_RDY_REG, MODULE_RDY_BIT,
+						MODULE_RDY_BIT);
+			if (rc < 0) {
+				pr_err("Failed to set MODULE RDY rc=%d\n", rc);
+				return rc;
+			}
+		}
+	} else {
+		/* module already enabled */
+		lcdb->lcdb_enabled = true;
+	}
+
+	return 0;
+}
+
+static int qpnp_lcdb_parse_dt(struct qpnp_lcdb *lcdb)
+{
+	int rc = 0;
+	const char *label;
+	struct device_node *temp, *node = lcdb->dev->of_node;
+
+	for_each_available_child_of_node(node, temp) {
+		rc = of_property_read_string(temp, "label", &label);
+		if (rc < 0) {
+			pr_err("Failed to read label rc=%d\n", rc);
+			return rc;
+		}
+
+		if (!strcmp(label, "ldo")) {
+			lcdb->ldo.node = temp;
+			rc = qpnp_lcdb_ldo_dt_init(lcdb);
+		} else if (!strcmp(label, "ncp")) {
+			lcdb->ncp.node = temp;
+			rc = qpnp_lcdb_ncp_dt_init(lcdb);
+		} else if (!strcmp(label, "bst")) {
+			lcdb->bst.node = temp;
+			rc = qpnp_lcdb_bst_dt_init(lcdb);
+		} else {
+			pr_err("Failed to identify label %s\n", label);
+			return -EINVAL;
+		}
+		if (rc < 0) {
+			pr_err("Failed to register %s module\n", label);
+			return rc;
+		}
+	}
+
+	if (of_property_read_bool(node, "qcom,ttw-enable")) {
+		rc = qpnp_lcdb_parse_ttw(lcdb);
+		if (rc < 0) {
+			pr_err("Failed to parse ttw-params rc=%d\n", rc);
+			return rc;
+		}
+		lcdb->ttw_enable = true;
+	}
+
+	return rc;
+}
+
+static int qpnp_lcdb_regulator_probe(struct platform_device *pdev)
+{
+	int rc;
+	struct device_node *node;
+	struct qpnp_lcdb *lcdb;
+
+	node = pdev->dev.of_node;
+	if (!node) {
+		pr_err("No nodes defined\n");
+		return -ENODEV;
+	}
+
+	lcdb = devm_kzalloc(&pdev->dev, sizeof(*lcdb), GFP_KERNEL);
+	if (!lcdb)
+		return -ENOMEM;
+
+	rc = of_property_read_u32(node, "reg", &lcdb->base);
+	if (rc < 0) {
+		pr_err("Failed to find reg node rc=%d\n", rc);
+		return rc;
+	}
+
+	lcdb->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!lcdb->regmap) {
+		pr_err("Failed to get the regmap handle rc=%d\n", rc);
+		return -EINVAL;
+	}
+
+	lcdb->dev = &pdev->dev;
+	lcdb->pdev = pdev;
+	mutex_init(&lcdb->lcdb_mutex);
+	mutex_init(&lcdb->read_write_mutex);
+
+	rc = qpnp_lcdb_parse_dt(lcdb);
+	if (rc < 0) {
+		pr_err("Failed to parse dt rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_lcdb_hw_init(lcdb);
+	if (rc < 0)
+		pr_err("Failed to initialize LCDB module rc=%d\n", rc);
+	else
+		pr_info("LCDB module successfully registered! lcdb_en=%d ldo_voltage=%dmV ncp_voltage=%dmV bst_voltage=%dmV\n",
+			lcdb->lcdb_enabled, lcdb->ldo.voltage_mv,
+			lcdb->ncp.voltage_mv, lcdb->bst.voltage_mv);
+
+	return rc;
+}
+
+static int qpnp_lcdb_regulator_remove(struct platform_device *pdev)
+{
+	struct qpnp_lcdb *lcdb = dev_get_drvdata(&pdev->dev);
+
+	mutex_destroy(&lcdb->lcdb_mutex);
+	mutex_destroy(&lcdb->read_write_mutex);
+
+	return 0;
+}
+
+static const struct of_device_id lcdb_match_table[] = {
+	{ .compatible = QPNP_LCDB_REGULATOR_DRIVER_NAME, },
+	{ },
+};
+
+static struct platform_driver qpnp_lcdb_regulator_driver = {
+	.driver		= {
+		.name		= QPNP_LCDB_REGULATOR_DRIVER_NAME,
+		.of_match_table	= lcdb_match_table,
+	},
+	.probe		= qpnp_lcdb_regulator_probe,
+	.remove		= qpnp_lcdb_regulator_remove,
+};
+
+static int __init qpnp_lcdb_regulator_init(void)
+{
+	return platform_driver_register(&qpnp_lcdb_regulator_driver);
+}
+arch_initcall(qpnp_lcdb_regulator_init);
+
+static void __exit qpnp_lcdb_regulator_exit(void)
+{
+	platform_driver_unregister(&qpnp_lcdb_regulator_driver);
+}
+module_exit(qpnp_lcdb_regulator_exit);
+
+MODULE_DESCRIPTION("QPNP LCDB regulator driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/regulator/qpnp-oledb-regulator.c b/drivers/regulator/qpnp-oledb-regulator.c
new file mode 100644
index 0000000..8d017fb
--- /dev/null
+++ b/drivers/regulator/qpnp-oledb-regulator.c
@@ -0,0 +1,1201 @@
+/* Copyright (c) 2016-2017, 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)	"OLEDB: %s: " fmt, __func__
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+
+#define QPNP_OLEDB_REGULATOR_DRIVER_NAME	"qcom,qpnp-oledb-regulator"
+#define OLEDB_VOUT_STEP_MV				100
+#define OLEDB_VOUT_MIN_MV				5000
+#define OLEDB_VOUT_MAX_MV				8100
+#define OLEDB_VOUT_HW_DEFAULT				6400
+
+#define OLEDB_MODULE_RDY				0x45
+#define OLEDB_MODULE_RDY_BIT				BIT(7)
+
+#define OLEDB_MODULE_ENABLE				0x46
+#define OLEDB_MODULE_ENABLE_BIT				BIT(7)
+
+#define OLEDB_EXT_PIN_CTL				0x47
+#define OLEDB_EXT_PIN_CTL_BIT				BIT(7)
+
+#define OLEDB_SWIRE_CONTROL				0x48
+#define OLEDB_EN_SWIRE_VOUT_UPD_BIT			BIT(6)
+#define OLEDB_EN_SWIRE_PD_UPD_BIT			BIT(7)
+
+#define OLEDB_VOUT_PGM					0x49
+#define OLEDB_VOUT_PGM_MASK				GENMASK(4, 0)
+
+#define OLEDB_VOUT_DEFAULT				0x4A
+#define OLEDB_VOUT_DEFAULT_MASK				GENMASK(4, 0)
+
+#define OLEDB_PD_CTL					0x4B
+
+#define OLEDB_ILIM_NFET					0x4E
+#define OLEDB_ILIMIT_NFET_MASK				GENMASK(2, 0)
+
+#define OLEDB_BIAS_GEN_WARMUP_DELAY			0x52
+#define OLEDB_BIAS_GEN_WARMUP_DELAY_MASK		GENMASK(1, 0)
+
+#define OLEDB_SHORT_PROTECT				0x59
+#define OLEDB_ENABLE_SC_DETECTION_BIT			BIT(7)
+#define OLEDB_DBNC_SHORT_DETECTION_MASK			GENMASK(1, 0)
+
+#define OLEDB_FAST_PRECHARGE				0x5A
+#define OLEDB_FAST_PRECHG_PPULSE_EN_BIT			BIT(7)
+#define OLEDB_DBNC_PRECHARGE_MASK			GENMASK(5, 4)
+#define OLEDB_DBNC_PRECHARGE_SHIFT			4
+#define OLEDB_PRECHARGE_PULSE_PERIOD_MASK		GENMASK(3, 2)
+#define OLEDB_PRECHARGE_PULSE_PERIOD_SHIFT		2
+#define OLEDB_PRECHARGE_PULSE_TON_MASK			GENMASK(1, 0)
+
+#define OLEDB_EN_PSM					0x5B
+#define OLEDB_PSM_ENABLE_BIT				BIT(7)
+
+#define OLEDB_PSM_CTL					0x5C
+#define OLEDB_PSM_HYSTERYSIS_CTL_BIT			BIT(3)
+#define OLEDB_PSM_HYSTERYSIS_CTL_BIT_SHIFT		3
+#define OLEDB_VREF_PSM_MASK				GENMASK(2, 0)
+
+#define OLEDB_PFM_CTL					0x5D
+#define OLEDB_PFM_ENABLE_BIT				BIT(7)
+#define OLEDB_PFM_HYSTERYSIS_CTRL_BIT_MASK		BIT(4)
+#define OLEDB_PFM_HYSTERYSIS_CTL_BIT_SHIFT		4
+#define OLEDB_PFM_CURR_LIMIT_MASK			GENMASK(3, 2)
+#define OLEDB_PFM_CURR_LIMIT_SHIFT			2
+#define OLEDB_PFM_OFF_TIME_NS_MASK			GENMASK(1, 0)
+
+#define OLEDB_NLIMIT					0x64
+#define OLEDB_ENABLE_NLIMIT_BIT				BIT(7)
+#define OLEDB_ENABLE_NLIMIT_BIT_SHIFT			7
+#define OLEDB_NLIMIT_PGM_MASK				GENMASK(1, 0)
+
+#define OLEDB_PSM_HYS_CTRL_MIN				13
+#define OLEDB_PSM_HYS_CTRL_MAX				26
+
+#define OLEDB_PFM_HYS_CTRL_MIN				13
+#define OLEDB_PFM_HYS_CTRL_MAX				26
+
+#define OLEDB_PFM_OFF_TIME_MIN				110
+#define OLEDB_PFM_OFF_TIME_MAX				480
+
+#define OLEDB_PRECHG_TIME_MIN				1
+#define OLEDB_PRECHG_TIME_MAX				8
+
+#define OLEDB_PRECHG_PULSE_PERIOD_MIN			3
+#define OLEDB_PRECHG_PULSE_PERIOD_MAX			12
+
+#define OLEDB_MIN_SC_DBNC_TIME_FSW			2
+#define OLEDB_MAX_SC_DBNC_TIME_FSW			16
+
+#define OLEDB_PRECHG_PULSE_ON_TIME_MIN			1200
+#define OLEDB_PRECHG_PULSE_ON_TIME_MAX			3000
+
+#define PSM_HYSTERYSIS_MV_TO_VAL(val_mv)		((val_mv/13) - 1)
+#define PFM_HYSTERYSIS_MV_TO_VAL(val_mv)		((val_mv/13) - 1)
+#define PFM_OFF_TIME_NS_TO_VAL(val_ns)			((val_ns/110) - 1)
+#define PRECHG_DEBOUNCE_TIME_MS_TO_VAL(val_ms)		((val_ms/2) - \
+								(val_ms/8))
+#define PRECHG_PULSE_PERIOD_US_TO_VAL(val_us)		((val_us/3) - 1)
+#define PRECHG_PULSE_ON_TIME_NS_TO_VAL(val_ns)		(val_ns/600 - 2)
+#define SHORT_CIRCUIT_DEBOUNCE_TIME_TO_VAL(val)		((val/4) - (val/16))
+
+struct qpnp_oledb_psm_ctl {
+	int psm_enable;
+	int   psm_hys_ctl;
+	int  psm_vref;
+};
+
+struct qpnp_oledb_pfm_ctl {
+	int pfm_enable;
+	int pfm_hys_ctl;
+	int pfm_curr_limit;
+	int pfm_off_time;
+};
+
+struct qpnp_oledb_fast_precharge_ctl {
+	int fast_prechg_ppulse_en;
+	int prechg_debounce_time;
+	int prechg_pulse_period;
+	int prechg_pulse_on_time;
+};
+
+struct qpnp_oledb {
+	struct platform_device			*pdev;
+	struct device				*dev;
+	struct regmap				*regmap;
+	struct regulator_desc			rdesc;
+	struct regulator_dev			*rdev;
+	struct qpnp_oledb_psm_ctl		psm_ctl;
+	struct qpnp_oledb_pfm_ctl		pfm_ctl;
+	struct qpnp_oledb_fast_precharge_ctl	fast_prechg_ctl;
+
+	u32					base;
+	u8					mod_enable;
+	u8					ext_pinctl_state;
+	int					current_voltage;
+	int					default_voltage;
+	int					vout_mv;
+	int					warmup_delay;
+	int					peak_curr_limit;
+	int					pd_ctl;
+	int					negative_curr_limit;
+	int					nlimit_enable;
+	int					sc_en;
+	int					sc_dbnc_time;
+	bool					swire_control;
+	bool					ext_pin_control;
+	bool					dynamic_ext_pinctl_config;
+	bool					pbs_control;
+};
+
+static const u16 oledb_warmup_dly_ns[] = {6700, 13300, 26700, 53400};
+static const u16 oledb_peak_curr_limit_ma[] = {115, 265, 415, 570,
+					      720, 870, 1020, 1170};
+static const u16 oledb_psm_vref_mv[] = {440, 510, 580, 650, 715,
+					 780, 850, 920};
+static const u16 oledb_pfm_curr_limit_ma[] = {130, 200, 270, 340};
+static const u16 oledb_nlimit_ma[] = {170, 300, 420, 550};
+
+static int qpnp_oledb_read(struct qpnp_oledb *oledb, u32 address,
+					u8 *val, int count)
+{
+	int rc = 0;
+	struct platform_device *pdev = oledb->pdev;
+
+	rc = regmap_bulk_read(oledb->regmap, address, val, count);
+	if (rc)
+		pr_err("Failed to read address=0x%02x sid=0x%02x rc=%d\n",
+			address, to_spmi_device(pdev->dev.parent)->usid, rc);
+
+	return rc;
+}
+
+static int qpnp_oledb_masked_write(struct qpnp_oledb *oledb,
+						u32 address, u8 mask, u8 val)
+{
+	int rc;
+
+	rc = regmap_update_bits(oledb->regmap, address, mask, val);
+	if (rc < 0)
+		pr_err("Failed to write address 0x%04X, rc = %d\n",
+					address, rc);
+	else
+		pr_debug("Wrote 0x%02X to addr 0x%04X\n",
+			val, address);
+
+	return rc;
+}
+
+static int qpnp_oledb_write(struct qpnp_oledb *oledb, u16 address, u8 *val,
+				int count)
+{
+	int rc = 0;
+	struct platform_device *pdev = oledb->pdev;
+
+	rc = regmap_bulk_write(oledb->regmap, address, val, count);
+	if (rc)
+		pr_err("Failed to write address=0x%02x sid=0x%02x rc=%d\n",
+			address, to_spmi_device(pdev->dev.parent)->usid, rc);
+	else
+		pr_debug("Wrote 0x%02X to addr 0x%04X\n",
+			*val, address);
+
+	return 0;
+}
+
+static int qpnp_oledb_regulator_enable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	u8 val = 0;
+
+	struct qpnp_oledb *oledb  = rdev_get_drvdata(rdev);
+
+	if (oledb->ext_pin_control) {
+		rc = qpnp_oledb_read(oledb, oledb->base + OLEDB_EXT_PIN_CTL,
+								 &val, 1);
+		if (rc < 0) {
+			pr_err("Failed to read EXT_PIN_CTL rc=%d\n", rc);
+			return rc;
+		}
+
+		/*
+		 * Enable ext-pin-ctl after display-supply is turned on.
+		 * This is to avoid glitches on the external pin.
+		 */
+		if (!(val & OLEDB_EXT_PIN_CTL_BIT) &&
+					oledb->dynamic_ext_pinctl_config) {
+			val = OLEDB_EXT_PIN_CTL_BIT;
+			rc = qpnp_oledb_write(oledb, oledb->base +
+					OLEDB_EXT_PIN_CTL, &val, 1);
+			if (rc < 0) {
+				pr_err("Failed to write EXT_PIN_CTL rc=%d\n",
+									rc);
+				return rc;
+			}
+		}
+		pr_debug("ext-pin-ctrl mode enabled\n");
+	} else {
+		val = OLEDB_MODULE_ENABLE_BIT;
+		rc = qpnp_oledb_write(oledb, oledb->base + OLEDB_MODULE_ENABLE,
+					&val, 1);
+		if (rc < 0) {
+			pr_err("Failed to write MODULE_ENABLE rc=%d\n", rc);
+			return rc;
+		}
+
+		ndelay(oledb->warmup_delay);
+		pr_debug("register-control mode, module enabled\n");
+	}
+
+	oledb->mod_enable = true;
+	if (oledb->pbs_control) {
+		rc = qpnp_oledb_masked_write(oledb, oledb->base +
+			OLEDB_SWIRE_CONTROL, OLEDB_EN_SWIRE_PD_UPD_BIT |
+					OLEDB_EN_SWIRE_VOUT_UPD_BIT, 0);
+		if (rc < 0)
+			pr_err("Failed to write SWIRE_CTL for pbs mode rc=%d\n",
+									 rc);
+	}
+
+	return rc;
+}
+
+static int qpnp_oledb_regulator_disable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+
+	struct qpnp_oledb *oledb  = rdev_get_drvdata(rdev);
+
+	/*
+	 * Disable ext-pin-ctl after display-supply is turned off. This is to
+	 * avoid glitches on the external pin.
+	 */
+	if (oledb->ext_pin_control) {
+		if (oledb->dynamic_ext_pinctl_config) {
+			rc = qpnp_oledb_masked_write(oledb, oledb->base +
+				 OLEDB_EXT_PIN_CTL, OLEDB_EXT_PIN_CTL_BIT, 0);
+			if (rc < 0) {
+				pr_err("Failed to write EXT_PIN_CTL rc=%d\n",
+									rc);
+				return rc;
+			}
+		}
+		pr_debug("ext-pin-ctrl mode disabled\n");
+	} else {
+		rc = qpnp_oledb_masked_write(oledb, oledb->base +
+					OLEDB_MODULE_ENABLE,
+					OLEDB_MODULE_ENABLE_BIT, 0);
+		if (rc < 0) {
+			pr_err("Failed to write MODULE_ENABLE rc=%d\n", rc);
+			return rc;
+		}
+		pr_debug("Register-control mode, module disabled\n");
+	}
+
+	oledb->mod_enable = false;
+
+	return rc;
+}
+
+static int qpnp_oledb_regulator_is_enabled(struct regulator_dev *rdev)
+{
+	struct qpnp_oledb *oledb  = rdev_get_drvdata(rdev);
+
+	return oledb->mod_enable;
+}
+
+static int qpnp_oledb_regulator_set_voltage(struct regulator_dev *rdev,
+				int min_uV, int max_uV, unsigned int *selector)
+{
+	u8 val;
+	int rc = 0;
+
+	struct qpnp_oledb *oledb = rdev_get_drvdata(rdev);
+
+	if (oledb->swire_control)
+		return 0;
+
+	val = DIV_ROUND_UP(min_uV - OLEDB_VOUT_MIN_MV, OLEDB_VOUT_STEP_MV);
+
+	rc = qpnp_oledb_write(oledb, oledb->base + OLEDB_VOUT_PGM,
+					&val, 1);
+	if (rc < 0) {
+		pr_err("Failed to write VOUT_PGM rc=%d\n", rc);
+		return rc;
+	}
+
+	oledb->current_voltage = min_uV;
+	pr_debug("register-control mode, current voltage %d\n",
+						oledb->current_voltage);
+
+	return 0;
+}
+
+static int qpnp_oledb_regulator_get_voltage(struct regulator_dev *rdev)
+{
+	struct qpnp_oledb *oledb  = rdev_get_drvdata(rdev);
+
+	if (oledb->swire_control)
+		return 0;
+
+	return oledb->current_voltage;
+}
+
+static struct regulator_ops qpnp_oledb_ops = {
+	.enable			= qpnp_oledb_regulator_enable,
+	.disable		= qpnp_oledb_regulator_disable,
+	.is_enabled		= qpnp_oledb_regulator_is_enabled,
+	.set_voltage		= qpnp_oledb_regulator_set_voltage,
+	.get_voltage		= qpnp_oledb_regulator_get_voltage,
+};
+
+static int qpnp_oledb_register_regulator(struct qpnp_oledb *oledb)
+{
+	int rc = 0;
+	struct platform_device *pdev = oledb->pdev;
+	struct regulator_init_data *init_data;
+	struct regulator_desc *rdesc = &oledb->rdesc;
+	struct regulator_config cfg = {};
+
+	init_data = of_get_regulator_init_data(&pdev->dev,
+						pdev->dev.of_node, rdesc);
+	if (!init_data) {
+		pr_err("Unable to get OLEDB regulator init data\n");
+		return -ENOMEM;
+	}
+
+	if (init_data->constraints.name) {
+		rdesc->owner		= THIS_MODULE;
+		rdesc->type		= REGULATOR_VOLTAGE;
+		rdesc->ops		= &qpnp_oledb_ops;
+		rdesc->name		= init_data->constraints.name;
+
+		cfg.dev = &pdev->dev;
+		cfg.init_data = init_data;
+		cfg.driver_data = oledb;
+		cfg.of_node = pdev->dev.of_node;
+
+		if (of_get_property(pdev->dev.of_node, "parent-supply",
+				 NULL))
+			init_data->supply_regulator = "parent";
+
+		init_data->constraints.valid_ops_mask
+				|= REGULATOR_CHANGE_VOLTAGE |
+					REGULATOR_CHANGE_STATUS;
+
+		oledb->rdev = devm_regulator_register(oledb->dev, rdesc, &cfg);
+		if (IS_ERR(oledb->rdev)) {
+			rc = PTR_ERR(oledb->rdev);
+			oledb->rdev = NULL;
+			pr_err("Unable to register OLEDB regulator, rc = %d\n",
+				rc);
+			return rc;
+		}
+	} else {
+		pr_err("OLEDB regulator name missing\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int qpnp_oledb_get_curr_voltage(struct qpnp_oledb *oledb,
+					u16 *current_voltage)
+{
+	int rc = 0;
+	u8 val;
+
+	if (!(oledb->mod_enable || oledb->ext_pinctl_state)) {
+		rc = qpnp_oledb_read(oledb, oledb->base + OLEDB_VOUT_DEFAULT,
+						&val, 1);
+		if (rc < 0) {
+			pr_err("Failed to read VOUT_DEFAULT rc=%d\n", rc);
+			return rc;
+		}
+	} else {
+		rc = qpnp_oledb_read(oledb, oledb->base +
+					OLEDB_VOUT_PGM, &val, 1);
+		if (rc < 0) {
+			pr_err("Failed to read VOUT_PGM rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	*current_voltage = (val * OLEDB_VOUT_STEP_MV) + OLEDB_VOUT_MIN_MV;
+
+	return rc;
+}
+
+static int qpnp_oledb_init_nlimit(struct qpnp_oledb *oledb)
+{
+	int rc = 0, i = 0;
+	u32 val, mask = 0;
+
+	if (oledb->nlimit_enable != -EINVAL) {
+		val = oledb->nlimit_enable <<
+					OLEDB_ENABLE_NLIMIT_BIT_SHIFT;
+		mask = OLEDB_ENABLE_NLIMIT_BIT;
+		if (oledb->negative_curr_limit != -EINVAL) {
+			for (i = 0; i < ARRAY_SIZE(oledb_nlimit_ma); i++) {
+				if (oledb->negative_curr_limit ==
+						oledb_nlimit_ma[i])
+					break;
+			}
+			val |= i;
+			mask |= OLEDB_NLIMIT_PGM_MASK;
+		}
+		rc = qpnp_oledb_masked_write(oledb, oledb->base +
+				OLEDB_NLIMIT, mask, val);
+		if (rc < 0)
+			pr_err("Failed to write NLIMT rc=%d\n", rc);
+	}
+
+	return rc;
+}
+
+static int qpnp_oledb_init_psm(struct qpnp_oledb *oledb)
+{
+	int rc = 0, i = 0;
+	u32 val = 0, mask = 0, temp = 0;
+	struct qpnp_oledb_psm_ctl *psm_ctl = &oledb->psm_ctl;
+
+	if (psm_ctl->psm_enable == -EINVAL)
+		return rc;
+
+	if (psm_ctl->psm_enable) {
+		val = OLEDB_PSM_ENABLE_BIT;
+		rc = qpnp_oledb_masked_write(oledb, oledb->base +
+				OLEDB_EN_PSM, OLEDB_PSM_ENABLE_BIT, val);
+		if (rc < 0) {
+			pr_err("Failed to write PSM_EN rc=%d\n", rc);
+			return rc;
+		}
+
+		val = 0;
+		if (psm_ctl->psm_vref != -EINVAL) {
+			for (i = 0; i < ARRAY_SIZE(oledb_psm_vref_mv); i++) {
+				if (psm_ctl->psm_vref ==
+						oledb_psm_vref_mv[i])
+					break;
+			}
+			val = i;
+			mask = OLEDB_VREF_PSM_MASK;
+		}
+
+		if (psm_ctl->psm_hys_ctl != -EINVAL) {
+			temp = PSM_HYSTERYSIS_MV_TO_VAL(psm_ctl->psm_hys_ctl);
+			val |= (temp << OLEDB_PSM_HYSTERYSIS_CTL_BIT_SHIFT);
+			mask |= OLEDB_PSM_HYSTERYSIS_CTL_BIT;
+		}
+		if (val) {
+			rc = qpnp_oledb_masked_write(oledb, oledb->base +
+					OLEDB_PSM_CTL, mask, val);
+			if (rc < 0)
+				pr_err("Failed to write PSM_CTL rc=%d\n", rc);
+		}
+	} else {
+		rc = qpnp_oledb_masked_write(oledb, oledb->base +
+				OLEDB_EN_PSM, OLEDB_PSM_ENABLE_BIT, 0);
+		if (rc < 0)
+			pr_err("Failed to write PSM_CTL rc=%d\n", rc);
+	}
+
+	return rc;
+}
+
+static int qpnp_oledb_init_pfm(struct qpnp_oledb *oledb)
+{
+	int rc = 0, i = 0;
+	u32 val = 0, temp = 0, mask = 0;
+	struct qpnp_oledb_pfm_ctl *pfm_ctl = &oledb->pfm_ctl;
+
+	if (pfm_ctl->pfm_enable == -EINVAL)
+		return rc;
+
+	if (pfm_ctl->pfm_enable) {
+		mask = val = OLEDB_PFM_ENABLE_BIT;
+		if (pfm_ctl->pfm_hys_ctl != -EINVAL) {
+			temp = PFM_HYSTERYSIS_MV_TO_VAL(pfm_ctl->pfm_hys_ctl);
+			val |= temp <<
+				OLEDB_PFM_HYSTERYSIS_CTL_BIT_SHIFT;
+			mask |= OLEDB_PFM_HYSTERYSIS_CTRL_BIT_MASK;
+		}
+
+		if (pfm_ctl->pfm_curr_limit != -EINVAL) {
+			for (i = 0; i < ARRAY_SIZE(oledb_pfm_curr_limit_ma);
+									i++) {
+				if (pfm_ctl->pfm_curr_limit ==
+						oledb_pfm_curr_limit_ma[i])
+					break;
+			}
+			val |= (i << OLEDB_PFM_CURR_LIMIT_SHIFT);
+			mask |= OLEDB_PFM_CURR_LIMIT_MASK;
+		}
+
+		if (pfm_ctl->pfm_off_time != -EINVAL) {
+			val |= PFM_OFF_TIME_NS_TO_VAL(pfm_ctl->pfm_off_time);
+			mask |= OLEDB_PFM_OFF_TIME_NS_MASK;
+		}
+
+		rc = qpnp_oledb_masked_write(oledb, oledb->base +
+				OLEDB_PFM_CTL, mask, val);
+		if (rc < 0)
+			pr_err("Failed to write PFM_CTL rc=%d\n", rc);
+	} else {
+		rc = qpnp_oledb_masked_write(oledb, oledb->base +
+				OLEDB_PFM_CTL, OLEDB_PFM_ENABLE_BIT, 0);
+		if (rc < 0)
+			pr_err("Failed to write PFM_CTL rc=%d\n", rc);
+	}
+
+	return rc;
+}
+
+static int qpnp_oledb_init_fast_precharge(struct qpnp_oledb *oledb)
+{
+	int rc = 0;
+	u32 val = 0, temp = 0, mask = 0;
+	struct qpnp_oledb_fast_precharge_ctl *prechg_ctl =
+					&oledb->fast_prechg_ctl;
+
+	if (prechg_ctl->fast_prechg_ppulse_en == -EINVAL)
+		return rc;
+
+	if (prechg_ctl->fast_prechg_ppulse_en) {
+		mask = val = OLEDB_FAST_PRECHG_PPULSE_EN_BIT;
+		if (prechg_ctl->prechg_debounce_time != -EINVAL) {
+			temp = PRECHG_DEBOUNCE_TIME_MS_TO_VAL(
+					prechg_ctl->prechg_debounce_time);
+			val |= temp << OLEDB_DBNC_PRECHARGE_SHIFT;
+			mask |= OLEDB_DBNC_PRECHARGE_MASK;
+		}
+
+		if (prechg_ctl->prechg_pulse_period != -EINVAL) {
+			temp = PRECHG_PULSE_PERIOD_US_TO_VAL(
+					prechg_ctl->prechg_pulse_period);
+			val |= temp << OLEDB_PRECHARGE_PULSE_PERIOD_SHIFT;
+			mask |= OLEDB_PRECHARGE_PULSE_PERIOD_MASK;
+		}
+
+		if (prechg_ctl->prechg_pulse_on_time != -EINVAL) {
+			val |= PRECHG_PULSE_ON_TIME_NS_TO_VAL(
+					prechg_ctl->prechg_pulse_on_time);
+			mask |= OLEDB_PRECHARGE_PULSE_TON_MASK;
+		}
+
+		rc = qpnp_oledb_masked_write(oledb, oledb->base +
+				OLEDB_FAST_PRECHARGE, mask, val);
+		if (rc < 0)
+			pr_err("Failed to write FAST_PRECHARGE rc=%d\n", rc);
+	} else {
+		rc = qpnp_oledb_masked_write(oledb, oledb->base +
+				OLEDB_FAST_PRECHARGE,
+				OLEDB_FAST_PRECHG_PPULSE_EN_BIT, 0);
+		if (rc < 0)
+			pr_err("Failed to write FAST_PRECHARGE rc=%d\n", rc);
+	}
+
+	return rc;
+}
+
+static int qpnp_oledb_hw_init(struct qpnp_oledb *oledb)
+{
+	int rc, i = 0;
+	u8 val = 0, mask = 0;
+	u16 current_voltage;
+
+	if (oledb->default_voltage != -EINVAL) {
+		val = (oledb->default_voltage - OLEDB_VOUT_MIN_MV) /
+					 OLEDB_VOUT_STEP_MV;
+		rc = qpnp_oledb_write(oledb, oledb->base +
+					OLEDB_VOUT_DEFAULT, &val, 1);
+		if (rc < 0) {
+			pr_err("Failed to write VOUT_DEFAULT rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	rc = qpnp_oledb_read(oledb, oledb->base + OLEDB_MODULE_ENABLE,
+				&oledb->mod_enable, 1);
+	if (rc < 0) {
+		pr_err("Failed to read MODULE_ENABLE rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_oledb_read(oledb, oledb->base + OLEDB_EXT_PIN_CTL,
+					&oledb->ext_pinctl_state, 1);
+	if (rc < 0) {
+		pr_err("Failed to read EXT_PIN_CTL rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_oledb_get_curr_voltage(oledb, &current_voltage);
+	if (rc < 0)
+		return rc;
+
+	/*
+	 * Go through if the module is not enabled either through
+	 * external pin control or SPMI interface.
+	 */
+	if (!((oledb->ext_pinctl_state & OLEDB_EXT_PIN_CTL_BIT)
+				|| oledb->mod_enable)) {
+		if (oledb->warmup_delay != -EINVAL) {
+			for (i = 0; i < ARRAY_SIZE(oledb_warmup_dly_ns); i++) {
+				if (oledb->warmup_delay ==
+							oledb_warmup_dly_ns[i])
+					break;
+			}
+			val = i;
+			rc = qpnp_oledb_masked_write(oledb,
+				oledb->base + OLEDB_BIAS_GEN_WARMUP_DELAY,
+				OLEDB_BIAS_GEN_WARMUP_DELAY_MASK, val);
+			if (rc < 0) {
+				pr_err("Failed to write WARMUP_DELAY rc=%d\n",
+									rc);
+				return rc;
+			}
+		} else {
+			rc = qpnp_oledb_read(oledb, oledb->base +
+					 OLEDB_BIAS_GEN_WARMUP_DELAY,
+					&val, 1);
+			if (rc < 0) {
+				pr_err("Failed to read WARMUP_DELAY rc=%d\n",
+									rc);
+				return rc;
+			}
+			oledb->warmup_delay = oledb_warmup_dly_ns[val];
+		}
+
+		if (oledb->peak_curr_limit != -EINVAL) {
+			for (i = 0; i < ARRAY_SIZE(oledb_peak_curr_limit_ma);
+									i++) {
+				if (oledb->peak_curr_limit ==
+						oledb_peak_curr_limit_ma[i])
+					break;
+			}
+			val = i;
+			rc = qpnp_oledb_write(oledb,
+					oledb->base + OLEDB_ILIM_NFET,
+					&val, 1);
+			if (rc < 0) {
+				pr_err("Failed to write ILIM_NEFT rc=%d\n", rc);
+				return rc;
+			}
+		}
+
+		if (oledb->pd_ctl != -EINVAL) {
+			val = oledb->pd_ctl;
+			rc = qpnp_oledb_write(oledb, oledb->base +
+					OLEDB_PD_CTL, &val, 1);
+			if (rc < 0) {
+				pr_err("Failed to write PD_CTL rc=%d\n", rc);
+				return rc;
+			}
+		}
+
+		if (oledb->sc_en != -EINVAL) {
+			val = oledb->sc_en ? OLEDB_ENABLE_SC_DETECTION_BIT : 0;
+			mask = OLEDB_ENABLE_SC_DETECTION_BIT;
+			if (oledb->sc_dbnc_time != -EINVAL) {
+				val |= SHORT_CIRCUIT_DEBOUNCE_TIME_TO_VAL(
+							oledb->sc_dbnc_time);
+				mask |= OLEDB_DBNC_PRECHARGE_MASK;
+			}
+
+			rc = qpnp_oledb_write(oledb, oledb->base +
+					OLEDB_SHORT_PROTECT, &val, 1);
+			if (rc < 0) {
+				pr_err("Failed to write SHORT_PROTECT rc=%d\n",
+									rc);
+				return rc;
+			}
+		}
+
+		rc = qpnp_oledb_init_nlimit(oledb);
+		if (rc < 0)
+			return rc;
+
+		rc = qpnp_oledb_init_psm(oledb);
+		if (rc < 0)
+			return rc;
+
+		rc = qpnp_oledb_init_pfm(oledb);
+		if (rc < 0)
+			return rc;
+
+		rc = qpnp_oledb_init_fast_precharge(oledb);
+		if (rc < 0)
+			return rc;
+
+		if (oledb->swire_control) {
+			val = OLEDB_EN_SWIRE_PD_UPD_BIT |
+				OLEDB_EN_SWIRE_VOUT_UPD_BIT;
+			rc = qpnp_oledb_masked_write(oledb, oledb->base +
+				OLEDB_SWIRE_CONTROL, OLEDB_EN_SWIRE_PD_UPD_BIT |
+				OLEDB_EN_SWIRE_VOUT_UPD_BIT, val);
+			if (rc < 0)
+				return rc;
+		}
+
+		rc = qpnp_oledb_read(oledb, oledb->base + OLEDB_MODULE_RDY,
+								&val, 1);
+		if (rc < 0) {
+			pr_err("Failed to read MODULE_RDY rc=%d\n", rc);
+			return rc;
+		}
+
+		if (!(val & OLEDB_MODULE_RDY_BIT)) {
+			val = OLEDB_MODULE_RDY_BIT;
+			rc = qpnp_oledb_write(oledb, oledb->base +
+				OLEDB_MODULE_RDY, &val, 1);
+			if (rc < 0) {
+				pr_err("Failed to write MODULE_RDY rc=%d\n",
+									rc);
+				return rc;
+			}
+		}
+
+		if (!oledb->dynamic_ext_pinctl_config) {
+			if (oledb->ext_pin_control) {
+				val = OLEDB_EXT_PIN_CTL_BIT;
+				rc = qpnp_oledb_write(oledb, oledb->base +
+						OLEDB_EXT_PIN_CTL, &val, 1);
+				if (rc < 0) {
+					pr_err("Failed to write EXT_PIN_CTL rc=%d\n",
+									rc);
+					return rc;
+				}
+			} else {
+				val = OLEDB_MODULE_ENABLE_BIT;
+				rc = qpnp_oledb_write(oledb, oledb->base +
+						 OLEDB_MODULE_ENABLE, &val, 1);
+				if (rc < 0) {
+					pr_err("Failed to write MODULE_ENABLE rc=%d\n",
+									rc);
+					return rc;
+				}
+
+				ndelay(oledb->warmup_delay);
+			}
+
+			oledb->mod_enable = true;
+			if (oledb->pbs_control) {
+				rc = qpnp_oledb_masked_write(oledb,
+				oledb->base + OLEDB_SWIRE_CONTROL,
+				OLEDB_EN_SWIRE_PD_UPD_BIT |
+				OLEDB_EN_SWIRE_VOUT_UPD_BIT, 0);
+				if (rc < 0) {
+					pr_err("Failed to write SWIRE_CTL rc=%d\n",
+									rc);
+					return rc;
+				}
+			}
+		}
+
+		oledb->current_voltage = current_voltage;
+	} else {
+		 /* module is enabled */
+		if (oledb->current_voltage == -EINVAL) {
+			oledb->current_voltage = current_voltage;
+		} else if (!oledb->swire_control) {
+			if (oledb->current_voltage < OLEDB_VOUT_MIN_MV) {
+				pr_err("current_voltage %d is less than min_volt %d\n",
+				    oledb->current_voltage, OLEDB_VOUT_MIN_MV);
+				return -EINVAL;
+			}
+			val = DIV_ROUND_UP(oledb->current_voltage -
+					OLEDB_VOUT_MIN_MV, OLEDB_VOUT_STEP_MV);
+			rc = qpnp_oledb_write(oledb, oledb->base +
+						OLEDB_VOUT_PGM, &val, 1);
+			if (rc < 0) {
+				pr_err("Failed to write VOUT_PGM rc=%d\n",
+									rc);
+				return rc;
+			}
+		}
+
+		oledb->mod_enable = true;
+	}
+
+	return rc;
+}
+
+static int qpnp_oledb_parse_nlimit(struct qpnp_oledb *oledb)
+{
+	int rc = 0;
+	struct device_node *of_node = oledb->dev->of_node;
+
+	oledb->nlimit_enable = -EINVAL;
+	rc = of_property_read_u32(of_node, "qcom,negative-curr-limit-enable",
+					&oledb->nlimit_enable);
+	if (!rc) {
+		oledb->negative_curr_limit = -EINVAL;
+		rc = of_property_read_u32(of_node,
+					 "qcom,negative-curr-limit-ma",
+					 &oledb->negative_curr_limit);
+		if (!rc) {
+			u16 min_curr_limit = oledb_nlimit_ma[0];
+			u16 max_curr_limit = oledb_nlimit_ma[ARRAY_SIZE(
+						oledb_nlimit_ma) - 1];
+			if (oledb->negative_curr_limit < min_curr_limit ||
+				oledb->negative_curr_limit > max_curr_limit) {
+				pr_err("Invalid value in qcom,negative-curr-limit-ma\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_oledb_parse_psm(struct qpnp_oledb *oledb)
+{
+	int rc = 0;
+	struct qpnp_oledb_psm_ctl *psm_ctl = &oledb->psm_ctl;
+	struct device_node *of_node = oledb->dev->of_node;
+
+	psm_ctl->psm_enable = -EINVAL;
+	rc = of_property_read_u32(of_node, "qcom,psm-enable",
+					&psm_ctl->psm_enable);
+	if (!rc) {
+		psm_ctl->psm_hys_ctl = -EINVAL;
+		rc = of_property_read_u32(of_node, "qcom,psm-hys-mv",
+						&psm_ctl->psm_hys_ctl);
+		if (!rc) {
+			if (psm_ctl->psm_hys_ctl < OLEDB_PSM_HYS_CTRL_MIN ||
+			      psm_ctl->psm_hys_ctl > OLEDB_PSM_HYS_CTRL_MAX) {
+				pr_err("Invalid value in qcom,psm-hys-mv\n");
+				return -EINVAL;
+			}
+		}
+
+		psm_ctl->psm_vref = -EINVAL;
+		rc = of_property_read_u32(of_node, "qcom,psm-vref-mv",
+							&psm_ctl->psm_vref);
+		if (!rc) {
+			u16 min_vref = oledb_psm_vref_mv[0];
+			u16 max_vref = oledb_psm_vref_mv[ARRAY_SIZE(
+						oledb_psm_vref_mv) - 1];
+			if (psm_ctl->psm_vref < min_vref ||
+					psm_ctl->psm_vref > max_vref) {
+				pr_err("Invalid value in qcom,psm-vref-mv\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_oledb_parse_pfm(struct qpnp_oledb *oledb)
+{
+	int rc = 0;
+	struct qpnp_oledb_pfm_ctl *pfm_ctl = &oledb->pfm_ctl;
+	struct device_node *of_node = oledb->dev->of_node;
+
+	pfm_ctl->pfm_enable = -EINVAL;
+	rc = of_property_read_u32(of_node, "qcom,pfm-enable",
+					&pfm_ctl->pfm_enable);
+	if (!rc) {
+		pfm_ctl->pfm_hys_ctl = -EINVAL;
+		rc = of_property_read_u32(of_node, "qcom,pfm-hys-mv",
+						&pfm_ctl->pfm_hys_ctl);
+		if (!rc) {
+			if (pfm_ctl->pfm_hys_ctl < OLEDB_PFM_HYS_CTRL_MIN ||
+			       pfm_ctl->pfm_hys_ctl > OLEDB_PFM_HYS_CTRL_MAX) {
+				pr_err("Invalid value in qcom,pfm-hys-mv\n");
+				return -EINVAL;
+			}
+		}
+
+		pfm_ctl->pfm_curr_limit = -EINVAL;
+		rc = of_property_read_u32(of_node,
+		      "qcom,pfm-curr-limit-ma", &pfm_ctl->pfm_curr_limit);
+		if (!rc) {
+			u16 min_limit = oledb_pfm_curr_limit_ma[0];
+			u16 max_limit = oledb_pfm_curr_limit_ma[ARRAY_SIZE(
+						oledb_pfm_curr_limit_ma) - 1];
+			if (pfm_ctl->pfm_curr_limit < min_limit ||
+					pfm_ctl->pfm_curr_limit > max_limit) {
+				pr_err("Invalid value in qcom,pfm-curr-limit-ma\n");
+				return -EINVAL;
+			}
+		}
+
+		pfm_ctl->pfm_off_time = -EINVAL;
+		rc = of_property_read_u32(of_node, "qcom,pfm-off-time-ns",
+							&pfm_ctl->pfm_off_time);
+		if (!rc) {
+			if (pfm_ctl->pfm_off_time < OLEDB_PFM_OFF_TIME_MIN ||
+			      pfm_ctl->pfm_off_time > OLEDB_PFM_OFF_TIME_MAX) {
+				pr_err("Invalid value in qcom,pfm-off-time-ns\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_oledb_parse_fast_precharge(struct qpnp_oledb *oledb)
+{
+	int rc = 0;
+	struct device_node *of_node = oledb->dev->of_node;
+	struct qpnp_oledb_fast_precharge_ctl *fast_prechg =
+					&oledb->fast_prechg_ctl;
+
+	fast_prechg->fast_prechg_ppulse_en = -EINVAL;
+	rc = of_property_read_u32(of_node, "qcom,fast-precharge-ppulse-enable",
+				&fast_prechg->fast_prechg_ppulse_en);
+	if (!rc) {
+		fast_prechg->prechg_debounce_time = -EINVAL;
+		rc = of_property_read_u32(of_node,
+					"qcom,precharge-debounce-time-ms",
+					&fast_prechg->prechg_debounce_time);
+		if (!rc) {
+			int dbnc_time = fast_prechg->prechg_debounce_time;
+
+			if (dbnc_time < OLEDB_PRECHG_TIME_MIN || dbnc_time >
+						      OLEDB_PRECHG_TIME_MAX) {
+				pr_err("Invalid value in qcom,precharge-debounce-time-ms\n");
+				return -EINVAL;
+			}
+		}
+
+		fast_prechg->prechg_pulse_period = -EINVAL;
+		rc = of_property_read_u32(of_node,
+					"qcom,precharge-pulse-period-us",
+					&fast_prechg->prechg_pulse_period);
+		if (!rc) {
+			int pulse_period = fast_prechg->prechg_pulse_period;
+
+			if (pulse_period < OLEDB_PRECHG_PULSE_PERIOD_MIN ||
+			       pulse_period > OLEDB_PRECHG_PULSE_PERIOD_MAX) {
+				pr_err("Invalid value in qcom,precharge-pulse-period-us\n");
+				return -EINVAL;
+			}
+		}
+
+		fast_prechg->prechg_pulse_on_time = -EINVAL;
+		rc = of_property_read_u32(of_node,
+					"qcom,precharge-pulse-on-time-ns",
+					&fast_prechg->prechg_pulse_on_time);
+		if (!rc) {
+			int pulse_on_time = fast_prechg->prechg_pulse_on_time;
+
+			if (pulse_on_time < OLEDB_PRECHG_PULSE_ON_TIME_MIN ||
+			      pulse_on_time > OLEDB_PRECHG_PULSE_ON_TIME_MAX) {
+				pr_err("Invalid value in qcom,precharge-pulse-on-time-ns\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_oledb_parse_dt(struct qpnp_oledb *oledb)
+{
+	int rc = 0;
+	struct device_node *of_node = oledb->dev->of_node;
+
+	oledb->swire_control =
+			of_property_read_bool(of_node, "qcom,swire-control");
+
+	oledb->ext_pin_control =
+			of_property_read_bool(of_node, "qcom,ext-pin-control");
+
+	if (oledb->ext_pin_control)
+		oledb->dynamic_ext_pinctl_config =
+				of_property_read_bool(of_node,
+					"qcom,dynamic-ext-pinctl-config");
+	oledb->pbs_control =
+			of_property_read_bool(of_node, "qcom,pbs-control");
+
+	oledb->current_voltage = -EINVAL;
+	rc = of_property_read_u32(of_node, "qcom,oledb-init-voltage-mv",
+						&oledb->current_voltage);
+	if (!rc && (oledb->current_voltage < OLEDB_VOUT_MIN_MV ||
+				oledb->current_voltage > OLEDB_VOUT_MAX_MV)) {
+		pr_err("Invalid value in qcom,oledb-init-voltage-mv\n");
+		return -EINVAL;
+	}
+
+	oledb->default_voltage = -EINVAL;
+	rc = of_property_read_u32(of_node, "qcom,oledb-default-voltage-mv",
+					&oledb->default_voltage);
+	if (!rc && (oledb->default_voltage < OLEDB_VOUT_MIN_MV ||
+				oledb->default_voltage > OLEDB_VOUT_MAX_MV)) {
+		pr_err("Invalid value in qcom,oledb-default-voltage-mv\n");
+		return -EINVAL;
+	}
+
+	oledb->warmup_delay = -EINVAL;
+	rc = of_property_read_u32(of_node, "qcom,bias-gen-warmup-delay-ns",
+					&oledb->warmup_delay);
+	if (!rc) {
+		u16 min_delay = oledb_warmup_dly_ns[0];
+		u16 max_delay = oledb_warmup_dly_ns[ARRAY_SIZE(
+						oledb_warmup_dly_ns) - 1];
+		if (oledb->warmup_delay < min_delay ||
+					oledb->warmup_delay > max_delay) {
+			pr_err("Invalid value in qcom,bias-gen-warmup-delay-ns\n");
+			return -EINVAL;
+		}
+	}
+
+	oledb->peak_curr_limit = -EINVAL;
+	rc = of_property_read_u32(of_node, "qcom,peak-curr-limit-ma",
+					&oledb->peak_curr_limit);
+	if (!rc) {
+		u16 min_limit = oledb_peak_curr_limit_ma[0];
+		u16 max_limit = oledb_peak_curr_limit_ma[ARRAY_SIZE(
+					oledb_peak_curr_limit_ma) - 1];
+		if (oledb->peak_curr_limit < min_limit ||
+				oledb->peak_curr_limit > max_limit) {
+			pr_err("Invalid value in qcom,peak-curr-limit-ma\n");
+			return -EINVAL;
+		}
+	}
+
+	oledb->pd_ctl = -EINVAL;
+	of_property_read_u32(of_node, "qcom,pull-down-enable", &oledb->pd_ctl);
+
+	oledb->sc_en = -EINVAL;
+	rc = of_property_read_u32(of_node, "qcom,enable-short-circuit",
+					&oledb->sc_en);
+	if (!rc) {
+		oledb->sc_dbnc_time = -EINVAL;
+		rc = of_property_read_u32(of_node,
+			"qcom,short-circuit-dbnc-time", &oledb->sc_dbnc_time);
+		if (!rc) {
+			if (oledb->sc_dbnc_time < OLEDB_MIN_SC_DBNC_TIME_FSW ||
+			    oledb->sc_dbnc_time > OLEDB_MAX_SC_DBNC_TIME_FSW) {
+				pr_err("Invalid value in qcom,short-circuit-dbnc-time\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	rc = qpnp_oledb_parse_nlimit(oledb);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_oledb_parse_psm(oledb);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_oledb_parse_pfm(oledb);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_oledb_parse_fast_precharge(oledb);
+
+	return rc;
+}
+
+static int qpnp_oledb_regulator_probe(struct platform_device *pdev)
+{
+	int rc = 0;
+	u32 val;
+	struct qpnp_oledb *oledb;
+	struct device_node *of_node = pdev->dev.of_node;
+
+	oledb = devm_kzalloc(&pdev->dev,
+			sizeof(struct qpnp_oledb), GFP_KERNEL);
+	if (!oledb)
+		return -ENOMEM;
+
+	oledb->pdev = pdev;
+	oledb->dev = &pdev->dev;
+	oledb->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	dev_set_drvdata(&pdev->dev, oledb);
+	if (!oledb->regmap) {
+		pr_err("Couldn't get parent's regmap\n");
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32(of_node, "reg", &val);
+	if (rc < 0) {
+		pr_err("Couldn't find reg in node, rc = %d\n", rc);
+		return rc;
+	}
+
+	oledb->base = val;
+	rc = qpnp_oledb_parse_dt(oledb);
+	if (rc < 0) {
+		pr_err("Failed to parse common OLEDB device tree\n");
+		return rc;
+	}
+
+	rc = qpnp_oledb_hw_init(oledb);
+	if (rc < 0) {
+		pr_err("Failed to initialize OLEDB, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_oledb_register_regulator(oledb);
+	if (!rc)
+		pr_info("OLEDB registered successfully, ext_pin_en=%d mod_en=%d cuurent_voltage=%d mV\n",
+			oledb->ext_pin_control, oledb->mod_enable,
+						oledb->current_voltage);
+
+	return rc;
+}
+
+static int qpnp_oledb_regulator_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+const struct of_device_id qpnp_oledb_regulator_match_table[] = {
+	{ .compatible = QPNP_OLEDB_REGULATOR_DRIVER_NAME,},
+	{ },
+};
+
+static struct platform_driver qpnp_oledb_regulator_driver = {
+	.driver		= {
+		.name		= QPNP_OLEDB_REGULATOR_DRIVER_NAME,
+		.of_match_table	= qpnp_oledb_regulator_match_table,
+	},
+	.probe		= qpnp_oledb_regulator_probe,
+	.remove		= qpnp_oledb_regulator_remove,
+};
+
+static int __init qpnp_oledb_regulator_init(void)
+{
+	return platform_driver_register(&qpnp_oledb_regulator_driver);
+}
+arch_initcall(qpnp_oledb_regulator_init);
+
+static void __exit qpnp_oledb_regulator_exit(void)
+{
+	platform_driver_unregister(&qpnp_oledb_regulator_driver);
+}
+module_exit(qpnp_oledb_regulator_exit);
+
+MODULE_DESCRIPTION("QPNP OLEDB driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("qpnp-oledb-regulator");
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index e859d14..6e5c443 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1589,6 +1589,15 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called rtc-pm8xxx.
 
+config RTC_DRV_QPNP
+	tristate "Qualcomm Technologies, Inc. QPNP PMIC RTC"
+	depends on SPMI
+	help
+	  This enables support for the RTC found on Qualcomm Technologies, Inc.
+	  QPNP PMIC chips.  This driver supports using the PMIC RTC peripheral
+	  to wake a mobile device up from suspend or to wake it up from power-
+	  off.
+
 config RTC_DRV_TEGRA
 	tristate "NVIDIA Tegra Internal RTC driver"
 	depends on ARCH_TEGRA || COMPILE_TEST
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 1ac694a..f282f73 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -120,6 +120,7 @@
 obj-$(CONFIG_RTC_DRV_PS3)	+= rtc-ps3.o
 obj-$(CONFIG_RTC_DRV_PUV3)	+= rtc-puv3.o
 obj-$(CONFIG_RTC_DRV_PXA)	+= rtc-pxa.o
+obj-$(CONFIG_RTC_DRV_QPNP)	+= qpnp-rtc.o
 obj-$(CONFIG_RTC_DRV_R9701)	+= rtc-r9701.o
 obj-$(CONFIG_RTC_DRV_RC5T583)	+= rtc-rc5t583.o
 obj-$(CONFIG_RTC_DRV_RK808)	+= rtc-rk808.o
diff --git a/drivers/rtc/qpnp-rtc.c b/drivers/rtc/qpnp-rtc.c
new file mode 100644
index 0000000..a2c004e
--- /dev/null
+++ b/drivers/rtc/qpnp-rtc.c
@@ -0,0 +1,717 @@
+/* Copyright (c) 2012-2015, 2017, 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/regmap.h>
+#include <linux/init.h>
+#include <linux/rtc.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/idr.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/alarmtimer.h>
+
+/* RTC/ALARM Register offsets */
+#define REG_OFFSET_ALARM_RW	0x40
+#define REG_OFFSET_ALARM_CTRL1	0x46
+#define REG_OFFSET_ALARM_CTRL2	0x48
+#define REG_OFFSET_RTC_WRITE	0x40
+#define REG_OFFSET_RTC_CTRL	0x46
+#define REG_OFFSET_RTC_READ	0x48
+#define REG_OFFSET_PERP_SUBTYPE	0x05
+
+/* RTC_CTRL register bit fields */
+#define BIT_RTC_ENABLE		BIT(7)
+#define BIT_RTC_ALARM_ENABLE	BIT(7)
+#define BIT_RTC_ABORT_ENABLE	BIT(0)
+#define BIT_RTC_ALARM_CLEAR	BIT(0)
+
+/* RTC/ALARM peripheral subtype values */
+#define RTC_PERPH_SUBTYPE       0x1
+#define ALARM_PERPH_SUBTYPE     0x3
+
+#define NUM_8_BIT_RTC_REGS	0x4
+
+#define TO_SECS(arr)		(arr[0] | (arr[1] << 8) | (arr[2] << 16) | \
+							(arr[3] << 24))
+
+/* Module parameter to control power-on-alarm */
+bool poweron_alarm;
+EXPORT_SYMBOL(poweron_alarm);
+module_param(poweron_alarm, bool, 0644);
+MODULE_PARM_DESC(poweron_alarm, "Enable/Disable power-on alarm");
+
+/* rtc driver internal structure */
+struct qpnp_rtc {
+	u8			rtc_ctrl_reg;
+	u8			alarm_ctrl_reg1;
+	u16			rtc_base;
+	u16			alarm_base;
+	u32			rtc_write_enable;
+	u32			rtc_alarm_powerup;
+	int			rtc_alarm_irq;
+	struct device		*rtc_dev;
+	struct rtc_device	*rtc;
+	struct platform_device	*pdev;
+	struct regmap		*regmap;
+	spinlock_t		alarm_ctrl_lock;
+};
+
+static int qpnp_read_wrapper(struct qpnp_rtc *rtc_dd, u8 *rtc_val,
+			u16 base, int count)
+{
+	int rc;
+
+	rc = regmap_bulk_read(rtc_dd->regmap, base, rtc_val, count);
+	if (rc) {
+		dev_err(rtc_dd->rtc_dev, "SPMI read failed\n");
+		return rc;
+	}
+	return 0;
+}
+
+static int qpnp_write_wrapper(struct qpnp_rtc *rtc_dd, u8 *rtc_val,
+			u16 base, int count)
+{
+	int rc;
+
+	rc = regmap_bulk_write(rtc_dd->regmap, base, rtc_val, count);
+	if (rc) {
+		dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");
+		return rc;
+	}
+
+	return 0;
+}
+
+static int
+qpnp_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	int rc;
+	unsigned long secs, irq_flags;
+	u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg;
+	u8 rtc_disabled = 0, rtc_ctrl_reg;
+	struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+
+	rtc_tm_to_time(tm, &secs);
+
+	value[0] = secs & 0xFF;
+	value[1] = (secs >> 8) & 0xFF;
+	value[2] = (secs >> 16) & 0xFF;
+	value[3] = (secs >> 24) & 0xFF;
+
+	dev_dbg(dev, "Seconds value to be written to RTC = %lu\n", secs);
+
+	spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+	ctrl_reg = rtc_dd->alarm_ctrl_reg1;
+
+	if (ctrl_reg & BIT_RTC_ALARM_ENABLE) {
+		alarm_enabled = 1;
+		ctrl_reg &= ~BIT_RTC_ALARM_ENABLE;
+		rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+			rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+		if (rc) {
+			dev_err(dev, "Write to ALARM ctrl reg failed\n");
+			goto rtc_rw_fail;
+		}
+	} else
+		spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+	/*
+	 * 32 bit seconds value is coverted to four 8 bit values
+	 *	|<------  32 bit time value in seconds  ------>|
+	 *      <- 8 bit ->|<- 8 bit ->|<- 8 bit ->|<- 8 bit ->|
+	 *       ----------------------------------------------
+	 *      | BYTE[3]  |  BYTE[2]  |  BYTE[1]  |  BYTE[0]  |
+	 *       ----------------------------------------------
+	 *
+	 * RTC has four 8 bit registers for writing time in seconds:
+	 *             WDATA[3], WDATA[2], WDATA[1], WDATA[0]
+	 *
+	 * Write to the RTC registers should be done in following order
+	 * Clear WDATA[0] register
+	 *
+	 * Write BYTE[1], BYTE[2] and BYTE[3] of time to
+	 * RTC WDATA[3], WDATA[2], WDATA[1] registers
+	 *
+	 * Write BYTE[0] of time to RTC WDATA[0] register
+	 *
+	 * Clearing BYTE[0] and writing in the end will prevent any
+	 * unintentional overflow from WDATA[0] to higher bytes during the
+	 * write operation
+	 */
+
+	/* Disable RTC H/w before writing on RTC register*/
+	rtc_ctrl_reg = rtc_dd->rtc_ctrl_reg;
+	if (rtc_ctrl_reg & BIT_RTC_ENABLE) {
+		rtc_disabled = 1;
+		rtc_ctrl_reg &= ~BIT_RTC_ENABLE;
+		rc = qpnp_write_wrapper(rtc_dd, &rtc_ctrl_reg,
+				rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);
+		if (rc) {
+			dev_err(dev, "Disabling of RTC control reg failed with error:%d\n",
+				rc);
+			goto rtc_rw_fail;
+		}
+		rtc_dd->rtc_ctrl_reg = rtc_ctrl_reg;
+	}
+
+	/* Clear WDATA[0] */
+	reg = 0x0;
+	rc = qpnp_write_wrapper(rtc_dd, &reg,
+				rtc_dd->rtc_base + REG_OFFSET_RTC_WRITE, 1);
+	if (rc) {
+		dev_err(dev, "Write to RTC reg failed\n");
+		goto rtc_rw_fail;
+	}
+
+	/* Write to WDATA[3], WDATA[2] and WDATA[1] */
+	rc = qpnp_write_wrapper(rtc_dd, &value[1],
+			rtc_dd->rtc_base + REG_OFFSET_RTC_WRITE + 1, 3);
+	if (rc) {
+		dev_err(dev, "Write to RTC reg failed\n");
+		goto rtc_rw_fail;
+	}
+
+	/* Write to WDATA[0] */
+	rc = qpnp_write_wrapper(rtc_dd, value,
+				rtc_dd->rtc_base + REG_OFFSET_RTC_WRITE, 1);
+	if (rc) {
+		dev_err(dev, "Write to RTC reg failed\n");
+		goto rtc_rw_fail;
+	}
+
+	/* Enable RTC H/w after writing on RTC register*/
+	if (rtc_disabled) {
+		rtc_ctrl_reg |= BIT_RTC_ENABLE;
+		rc = qpnp_write_wrapper(rtc_dd, &rtc_ctrl_reg,
+				rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);
+		if (rc) {
+			dev_err(dev, "Enabling of RTC control reg failed with error:%d\n",
+				rc);
+			goto rtc_rw_fail;
+		}
+		rtc_dd->rtc_ctrl_reg = rtc_ctrl_reg;
+	}
+
+	if (alarm_enabled) {
+		ctrl_reg |= BIT_RTC_ALARM_ENABLE;
+		rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+			rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+		if (rc) {
+			dev_err(dev, "Write to ALARM ctrl reg failed\n");
+			goto rtc_rw_fail;
+		}
+	}
+
+	rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+
+rtc_rw_fail:
+	if (alarm_enabled)
+		spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+	return rc;
+}
+
+static int
+qpnp_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	int rc;
+	u8 value[4], reg;
+	unsigned long secs;
+	struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+
+	rc = qpnp_read_wrapper(rtc_dd, value,
+				rtc_dd->rtc_base + REG_OFFSET_RTC_READ,
+				NUM_8_BIT_RTC_REGS);
+	if (rc) {
+		dev_err(dev, "Read from RTC reg failed\n");
+		return rc;
+	}
+
+	/*
+	 * Read the LSB again and check if there has been a carry over
+	 * If there is, redo the read operation
+	 */
+	rc = qpnp_read_wrapper(rtc_dd, &reg,
+				rtc_dd->rtc_base + REG_OFFSET_RTC_READ, 1);
+	if (rc) {
+		dev_err(dev, "Read from RTC reg failed\n");
+		return rc;
+	}
+
+	if (reg < value[0]) {
+		rc = qpnp_read_wrapper(rtc_dd, value,
+				rtc_dd->rtc_base + REG_OFFSET_RTC_READ,
+				NUM_8_BIT_RTC_REGS);
+		if (rc) {
+			dev_err(dev, "Read from RTC reg failed\n");
+			return rc;
+		}
+	}
+
+	secs = TO_SECS(value);
+
+	rtc_time_to_tm(secs, tm);
+
+	rc = rtc_valid_tm(tm);
+	if (rc) {
+		dev_err(dev, "Invalid time read from RTC\n");
+		return rc;
+	}
+
+	dev_dbg(dev, "secs = %lu, h:m:s == %d:%d:%d, d/m/y = %d/%d/%d\n",
+			secs, tm->tm_hour, tm->tm_min, tm->tm_sec,
+			tm->tm_mday, tm->tm_mon, tm->tm_year);
+
+	return 0;
+}
+
+static int
+qpnp_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	int rc;
+	u8 value[4], ctrl_reg;
+	unsigned long secs, secs_rtc, irq_flags;
+	struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+	struct rtc_time rtc_tm;
+
+	rtc_tm_to_time(&alarm->time, &secs);
+
+	/*
+	 * Read the current RTC time and verify if the alarm time is in the
+	 * past. If yes, return invalid
+	 */
+	rc = qpnp_rtc_read_time(dev, &rtc_tm);
+	if (rc) {
+		dev_err(dev, "Unable to read RTC time\n");
+		return -EINVAL;
+	}
+
+	rtc_tm_to_time(&rtc_tm, &secs_rtc);
+	if (secs < secs_rtc) {
+		dev_err(dev, "Trying to set alarm in the past\n");
+		return -EINVAL;
+	}
+
+	value[0] = secs & 0xFF;
+	value[1] = (secs >> 8) & 0xFF;
+	value[2] = (secs >> 16) & 0xFF;
+	value[3] = (secs >> 24) & 0xFF;
+
+	spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+	rc = qpnp_write_wrapper(rtc_dd, value,
+				rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+				NUM_8_BIT_RTC_REGS);
+	if (rc) {
+		dev_err(dev, "Write to ALARM reg failed\n");
+		goto rtc_rw_fail;
+	}
+
+	ctrl_reg = (alarm->enabled) ?
+			(rtc_dd->alarm_ctrl_reg1 | BIT_RTC_ALARM_ENABLE) :
+			(rtc_dd->alarm_ctrl_reg1 & ~BIT_RTC_ALARM_ENABLE);
+
+	rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+			rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+	if (rc) {
+		dev_err(dev, "Write to ALARM cntrol reg failed\n");
+		goto rtc_rw_fail;
+	}
+
+	rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+
+	dev_dbg(dev, "Alarm Set for h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+			alarm->time.tm_hour, alarm->time.tm_min,
+			alarm->time.tm_sec, alarm->time.tm_mday,
+			alarm->time.tm_mon, alarm->time.tm_year);
+rtc_rw_fail:
+	spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+	return rc;
+}
+
+static int
+qpnp_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	int rc;
+	u8 value[4];
+	unsigned long secs;
+	struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+
+	rc = qpnp_read_wrapper(rtc_dd, value,
+				rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+				NUM_8_BIT_RTC_REGS);
+	if (rc) {
+		dev_err(dev, "Read from ALARM reg failed\n");
+		return rc;
+	}
+
+	secs = TO_SECS(value);
+	rtc_time_to_tm(secs, &alarm->time);
+
+	rc = rtc_valid_tm(&alarm->time);
+	if (rc) {
+		dev_err(dev, "Invalid time read from RTC\n");
+		return rc;
+	}
+
+	dev_dbg(dev, "Alarm set for - h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+		alarm->time.tm_hour, alarm->time.tm_min,
+				alarm->time.tm_sec, alarm->time.tm_mday,
+				alarm->time.tm_mon, alarm->time.tm_year);
+
+	return 0;
+}
+
+
+static int
+qpnp_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	int rc;
+	unsigned long irq_flags;
+	struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+	u8 ctrl_reg;
+	u8 value[4] = {0};
+
+	spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+	ctrl_reg = rtc_dd->alarm_ctrl_reg1;
+	ctrl_reg = enabled ? (ctrl_reg | BIT_RTC_ALARM_ENABLE) :
+				(ctrl_reg & ~BIT_RTC_ALARM_ENABLE);
+
+	rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+			rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+	if (rc) {
+		dev_err(dev, "Write to ALARM control reg failed\n");
+		goto rtc_rw_fail;
+	}
+
+	rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+
+	/* Clear Alarm register */
+	if (!enabled) {
+		rc = qpnp_write_wrapper(rtc_dd, value,
+			rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+			NUM_8_BIT_RTC_REGS);
+		if (rc)
+			dev_err(dev, "Clear ALARM value reg failed\n");
+	}
+
+rtc_rw_fail:
+	spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+	return rc;
+}
+
+static const struct rtc_class_ops qpnp_rtc_ro_ops = {
+	.read_time = qpnp_rtc_read_time,
+	.set_alarm = qpnp_rtc_set_alarm,
+	.read_alarm = qpnp_rtc_read_alarm,
+	.alarm_irq_enable = qpnp_rtc_alarm_irq_enable,
+};
+
+static const struct rtc_class_ops qpnp_rtc_rw_ops = {
+	.read_time = qpnp_rtc_read_time,
+	.set_alarm = qpnp_rtc_set_alarm,
+	.read_alarm = qpnp_rtc_read_alarm,
+	.alarm_irq_enable = qpnp_rtc_alarm_irq_enable,
+	.set_time = qpnp_rtc_set_time,
+};
+
+static irqreturn_t qpnp_alarm_trigger(int irq, void *dev_id)
+{
+	struct qpnp_rtc *rtc_dd = dev_id;
+	u8 ctrl_reg;
+	int rc;
+	unsigned long irq_flags;
+
+	rtc_update_irq(rtc_dd->rtc, 1, RTC_IRQF | RTC_AF);
+
+	spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+	/* Clear the alarm enable bit */
+	ctrl_reg = rtc_dd->alarm_ctrl_reg1;
+	ctrl_reg &= ~BIT_RTC_ALARM_ENABLE;
+
+	rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+			rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+	if (rc) {
+		spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+		dev_err(rtc_dd->rtc_dev,
+				"Write to ALARM control reg failed\n");
+		goto rtc_alarm_handled;
+	}
+
+	rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+	spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+	/* Set ALARM_CLR bit */
+	ctrl_reg = 0x1;
+	rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+			rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL2, 1);
+	if (rc)
+		dev_err(rtc_dd->rtc_dev,
+				"Write to ALARM control reg failed\n");
+
+rtc_alarm_handled:
+	return IRQ_HANDLED;
+}
+
+static int qpnp_rtc_probe(struct platform_device *pdev)
+{
+	const struct rtc_class_ops *rtc_ops = &qpnp_rtc_ro_ops;
+	int rc;
+	u8 subtype;
+	struct qpnp_rtc *rtc_dd;
+	unsigned int base;
+	struct device_node *child;
+
+	rtc_dd = devm_kzalloc(&pdev->dev, sizeof(*rtc_dd), GFP_KERNEL);
+	if (rtc_dd == NULL)
+		return -ENOMEM;
+
+	rtc_dd->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!rtc_dd->regmap) {
+		dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+		return -EINVAL;
+	}
+
+	/* Get the rtc write property */
+	rc = of_property_read_u32(pdev->dev.of_node, "qcom,qpnp-rtc-write",
+						&rtc_dd->rtc_write_enable);
+	if (rc && rc != -EINVAL) {
+		dev_err(&pdev->dev,
+			"Error reading rtc_write_enable property %d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(pdev->dev.of_node,
+						"qcom,qpnp-rtc-alarm-pwrup",
+						&rtc_dd->rtc_alarm_powerup);
+	if (rc && rc != -EINVAL) {
+		dev_err(&pdev->dev,
+			"Error reading rtc_alarm_powerup property %d\n", rc);
+		return rc;
+	}
+
+	/* Initialise spinlock to protect RTC control register */
+	spin_lock_init(&rtc_dd->alarm_ctrl_lock);
+
+	rtc_dd->rtc_dev = &(pdev->dev);
+	rtc_dd->pdev = pdev;
+
+
+	if (of_get_available_child_count(pdev->dev.of_node) == 0) {
+		pr_err("no child nodes\n");
+		rc = -ENXIO;
+		goto fail_rtc_enable;
+	}
+
+	/* Get RTC/ALARM resources */
+	for_each_available_child_of_node(pdev->dev.of_node, child) {
+		rc = of_property_read_u32(child, "reg", &base);
+		if (rc < 0) {
+			dev_err(&pdev->dev,
+				"Couldn't find reg in node = %s rc = %d\n",
+				child->full_name, rc);
+			goto fail_rtc_enable;
+		}
+
+		rc = qpnp_read_wrapper(rtc_dd, &subtype,
+				base + REG_OFFSET_PERP_SUBTYPE, 1);
+		if (rc) {
+			dev_err(&pdev->dev,
+				"Peripheral subtype read failed\n");
+			goto fail_rtc_enable;
+		}
+
+		switch (subtype) {
+		case RTC_PERPH_SUBTYPE:
+			rtc_dd->rtc_base = base;
+			break;
+		case ALARM_PERPH_SUBTYPE:
+			rtc_dd->alarm_base = base;
+			rtc_dd->rtc_alarm_irq = of_irq_get(child, 0);
+			if (rtc_dd->rtc_alarm_irq < 0) {
+				dev_err(&pdev->dev, "ALARM IRQ absent\n");
+				rc = -ENXIO;
+				goto fail_rtc_enable;
+			}
+			break;
+		default:
+			dev_err(&pdev->dev, "Invalid peripheral subtype\n");
+			rc = -EINVAL;
+			goto fail_rtc_enable;
+		}
+	}
+
+	rc = qpnp_read_wrapper(rtc_dd, &rtc_dd->rtc_ctrl_reg,
+				rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);
+	if (rc) {
+		dev_err(&pdev->dev, "Read from RTC control reg failed\n");
+		goto fail_rtc_enable;
+	}
+
+	if (!(rtc_dd->rtc_ctrl_reg & BIT_RTC_ENABLE)) {
+		dev_err(&pdev->dev, "RTC h/w disabled, rtc not registered\n");
+		goto fail_rtc_enable;
+	}
+
+	rc = qpnp_read_wrapper(rtc_dd, &rtc_dd->alarm_ctrl_reg1,
+				rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+	if (rc) {
+		dev_err(&pdev->dev, "Read from  Alarm control reg failed\n");
+		goto fail_rtc_enable;
+	}
+	/* Enable abort enable feature */
+	rtc_dd->alarm_ctrl_reg1 |= BIT_RTC_ABORT_ENABLE;
+	rc = qpnp_write_wrapper(rtc_dd, &rtc_dd->alarm_ctrl_reg1,
+			rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+	if (rc) {
+		dev_err(&pdev->dev, "SPMI write failed!\n");
+		goto fail_rtc_enable;
+	}
+
+	if (rtc_dd->rtc_write_enable == true)
+		rtc_ops = &qpnp_rtc_rw_ops;
+
+	dev_set_drvdata(&pdev->dev, rtc_dd);
+
+	/* Register the RTC device */
+	rtc_dd->rtc = rtc_device_register("qpnp_rtc", &pdev->dev,
+					  rtc_ops, THIS_MODULE);
+	if (IS_ERR(rtc_dd->rtc)) {
+		dev_err(&pdev->dev, "%s: RTC registration failed (%ld)\n",
+					__func__, PTR_ERR(rtc_dd->rtc));
+		rc = PTR_ERR(rtc_dd->rtc);
+		goto fail_rtc_enable;
+	}
+
+	/* Init power_on_alarm after adding rtc device */
+	power_on_alarm_init();
+
+	/* Request the alarm IRQ */
+	rc = request_any_context_irq(rtc_dd->rtc_alarm_irq,
+				 qpnp_alarm_trigger, IRQF_TRIGGER_RISING,
+				 "qpnp_rtc_alarm", rtc_dd);
+	if (rc) {
+		dev_err(&pdev->dev, "Request IRQ failed (%d)\n", rc);
+		goto fail_req_irq;
+	}
+
+	device_init_wakeup(&pdev->dev, 1);
+	enable_irq_wake(rtc_dd->rtc_alarm_irq);
+
+	dev_dbg(&pdev->dev, "Probe success !!\n");
+
+	return 0;
+
+fail_req_irq:
+	rtc_device_unregister(rtc_dd->rtc);
+fail_rtc_enable:
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	return rc;
+}
+
+static int qpnp_rtc_remove(struct platform_device *pdev)
+{
+	struct qpnp_rtc *rtc_dd = dev_get_drvdata(&pdev->dev);
+
+	device_init_wakeup(&pdev->dev, 0);
+	free_irq(rtc_dd->rtc_alarm_irq, rtc_dd);
+	rtc_device_unregister(rtc_dd->rtc);
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	return 0;
+}
+
+static void qpnp_rtc_shutdown(struct platform_device *pdev)
+{
+	u8 value[4] = {0};
+	u8 reg;
+	int rc;
+	unsigned long irq_flags;
+	struct qpnp_rtc *rtc_dd;
+	bool rtc_alarm_powerup;
+
+	if (!pdev) {
+		pr_err("qpnp-rtc: spmi device not found\n");
+		return;
+	}
+	rtc_dd = dev_get_drvdata(&pdev->dev);
+	if (!rtc_dd) {
+		pr_err("qpnp-rtc: rtc driver data not found\n");
+		return;
+	}
+	rtc_alarm_powerup = rtc_dd->rtc_alarm_powerup;
+	if (!rtc_alarm_powerup && !poweron_alarm) {
+		spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+		dev_dbg(&pdev->dev, "Disabling alarm interrupts\n");
+
+		/* Disable RTC alarms */
+		reg = rtc_dd->alarm_ctrl_reg1;
+		reg &= ~BIT_RTC_ALARM_ENABLE;
+		rc = qpnp_write_wrapper(rtc_dd, &reg,
+			rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+		if (rc) {
+			dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");
+			goto fail_alarm_disable;
+		}
+
+		/* Clear Alarm register */
+		rc = qpnp_write_wrapper(rtc_dd, value,
+				rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+				NUM_8_BIT_RTC_REGS);
+		if (rc)
+			dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");
+
+fail_alarm_disable:
+		spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+	}
+}
+
+static const struct of_device_id spmi_match_table[] = {
+	{
+		.compatible = "qcom,qpnp-rtc",
+	},
+	{}
+};
+
+static struct platform_driver qpnp_rtc_driver = {
+	.probe		= qpnp_rtc_probe,
+	.remove		= qpnp_rtc_remove,
+	.shutdown	= qpnp_rtc_shutdown,
+	.driver		= {
+		.name		= "qcom,qpnp-rtc",
+		.owner		= THIS_MODULE,
+		.of_match_table	= spmi_match_table,
+	},
+};
+
+static int __init qpnp_rtc_init(void)
+{
+	return platform_driver_register(&qpnp_rtc_driver);
+}
+module_init(qpnp_rtc_init);
+
+static void __exit qpnp_rtc_exit(void)
+{
+	platform_driver_unregister(&qpnp_rtc_driver);
+}
+module_exit(qpnp_rtc_exit);
+
+MODULE_DESCRIPTION("SMPI PMIC RTC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index a13541b..68b3b24 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -419,6 +419,20 @@
 	  real time die temperature if an ADC is present or an estimate of the
 	  temperature based upon the over temperature stage value.
 
+config THERMAL_QPNP
+	tristate "Qualcomm Technologies, Inc. QPNP PMIC Temperature Alarm"
+	depends on OF && SPMI
+	help
+	  This enables a thermal Sysfs driver for Qualcomm Technologies, Inc.
+	  QPNP PMIC devices. It shows up in Sysfs as a thermal zone with
+	  multiple trip points. The temperature reported by the thermal zone
+	  reflects the real time die temperature if an ADC is present or an
+	  estimate of the temperature based upon the over temperature stage
+	  value if no ADC is available. If allowed via compile time
+	  configuration; enabling the thermal zone device via the mode file
+	  results in shifting PMIC over temperature shutdown control from
+	  hardware to software.
+
 config GENERIC_ADC_THERMAL
 	tristate "Generic ADC based thermal sensor"
 	depends on IIO
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index c92eb22..7d3b5312 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -27,6 +27,7 @@
 
 # platform thermal drivers
 obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM)	+= qcom-spmi-temp-alarm.o
+obj-$(CONFIG_THERMAL_QPNP)	+= qpnp-temp-alarm.o
 obj-$(CONFIG_SPEAR_THERMAL)	+= spear_thermal.o
 obj-$(CONFIG_ROCKCHIP_THERMAL)	+= rockchip_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
diff --git a/drivers/thermal/qpnp-temp-alarm.c b/drivers/thermal/qpnp-temp-alarm.c
new file mode 100644
index 0000000..e86a297
--- /dev/null
+++ b/drivers/thermal/qpnp-temp-alarm.c
@@ -0,0 +1,813 @@
+/*
+ * Copyright (c) 2011-2017, 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) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/thermal.h>
+#include <linux/qpnp/qpnp-adc.h>
+
+#define QPNP_TM_DRIVER_NAME "qcom,qpnp-temp-alarm"
+
+enum qpnp_tm_registers {
+	QPNP_TM_REG_TYPE		= 0x04,
+	QPNP_TM_REG_SUBTYPE		= 0x05,
+	QPNP_TM_REG_STATUS		= 0x08,
+	QPNP_TM_REG_SHUTDOWN_CTRL1	= 0x40,
+	QPNP_TM_REG_SHUTDOWN_CTRL2	= 0x42,
+	QPNP_TM_REG_ALARM_CTRL		= 0x46,
+};
+
+#define QPNP_TM_TYPE			0x09
+#define QPNP_TM_SUBTYPE_GEN1		0x08
+#define QPNP_TM_SUBTYPE_GEN2		0x09
+
+#define STATUS_STATE_MASK		0x70
+#define STATUS_STATE_SHIFT		4
+#define STATUS_STAGE_MASK		0x03
+
+#define SHUTDOWN_CTRL1_OVERRIDE_STAGE3	0x80
+#define SHUTDOWN_CTRL1_OVERRIDE_STAGE2	0x40
+#define SHUTDOWN_CTRL1_CLK_RATE_MASK	0x0C
+#define SHUTDOWN_CTRL1_CLK_RATE_SHIFT	2
+#define SHUTDOWN_CTRL1_THRESHOLD_MASK	0x03
+
+#define SHUTDOWN_CTRL2_CLEAR_STAGE3	0x80
+#define SHUTDOWN_CTRL2_CLEAR_STAGE2	0x40
+
+#define ALARM_CTRL_FORCE_ENABLE		0x80
+#define ALARM_CTRL_FOLLOW_HW_ENABLE	0x01
+
+#define TEMP_STAGE_STEP			20000	/* Stage step: 20.000 C */
+#define TEMP_STAGE_HYSTERESIS		2000
+
+#define TEMP_THRESH_MIN			105000	/* Threshold Min: 105 C */
+#define TEMP_THRESH_STEP		5000	/* Threshold step: 5 C */
+
+#define THRESH_MIN			0
+#define THRESH_MAX			3
+
+#define CLOCK_RATE_MIN			0
+#define CLOCK_RATE_MAX			3
+
+/* Trip points from most critical to least critical */
+#define TRIP_STAGE3			0
+#define TRIP_STAGE2			1
+#define TRIP_STAGE1			2
+#define TRIP_NUM			3
+
+enum qpnp_tm_adc_type {
+	QPNP_TM_ADC_NONE,	/* Estimates temp based on overload level. */
+	QPNP_TM_ADC_QPNP_ADC,
+};
+
+/*
+ * Temperature in millicelcius reported during stage 0 if no ADC is present and
+ * no value has been specified via device tree.
+ */
+#define DEFAULT_NO_ADC_TEMP		37000
+
+struct qpnp_tm_chip {
+	struct delayed_work		irq_work;
+	struct platform_device		*pdev;
+	struct regmap			*regmap;
+	struct thermal_zone_device	*tz_dev;
+	const char			*tm_name;
+	unsigned int			subtype;
+	enum qpnp_tm_adc_type		adc_type;
+	int				temperature;
+	enum thermal_device_mode	mode;
+	unsigned int			thresh;
+	unsigned int			clock_rate;
+	unsigned int			stage;
+	unsigned int			prev_stage;
+	int				irq;
+	enum qpnp_vadc_channels		adc_channel;
+	u16				base_addr;
+	bool				allow_software_override;
+	struct qpnp_vadc_chip		*vadc_dev;
+};
+
+/* Delay between TEMP_STAT IRQ going high and status value changing in ms. */
+#define STATUS_REGISTER_DELAY_MS       40
+
+enum pmic_thermal_override_mode {
+	SOFTWARE_OVERRIDE_DISABLED = 0,
+	SOFTWARE_OVERRIDE_ENABLED,
+};
+
+/* This array maps from GEN2 alarm state to GEN1 alarm stage */
+const unsigned int alarm_state_map[8] = {0, 1, 1, 2, 2, 3, 3, 3};
+
+static inline int qpnp_tm_read(struct qpnp_tm_chip *chip, u16 addr, u8 *buf,
+				int len)
+{
+	int rc;
+
+	rc = regmap_bulk_read(chip->regmap, chip->base_addr + addr, buf, len);
+
+	if (rc)
+		dev_err(&chip->pdev->dev,
+			"%s: regmap_bulk_readl failed. sid=%d, addr=%04X, len=%d, rc=%d\n",
+			__func__,
+			to_spmi_device(chip->pdev->dev.parent)->usid,
+			chip->base_addr + addr,
+			len, rc);
+
+	return rc;
+}
+
+static inline int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 *buf,
+				int len)
+{
+	int rc;
+
+	rc = regmap_bulk_write(chip->regmap, chip->base_addr + addr, buf, len);
+
+	if (rc)
+		dev_err(&chip->pdev->dev,
+			"%s: regmap_bulk_write failed. sid=%d, addr=%04X, len=%d, rc=%d\n",
+			__func__,
+			to_spmi_device(chip->pdev->dev.parent)->usid,
+			chip->base_addr + addr,
+			len, rc);
+
+	return rc;
+}
+
+
+static inline int qpnp_tm_shutdown_override(struct qpnp_tm_chip *chip,
+			    enum pmic_thermal_override_mode mode)
+{
+	int rc = 0;
+	u8 reg;
+
+	if (chip->allow_software_override) {
+		reg = chip->thresh & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+		reg |= (chip->clock_rate << SHUTDOWN_CTRL1_CLK_RATE_SHIFT)
+			& SHUTDOWN_CTRL1_CLK_RATE_MASK;
+
+		if (mode == SOFTWARE_OVERRIDE_ENABLED)
+			reg |= SHUTDOWN_CTRL1_OVERRIDE_STAGE2
+				| SHUTDOWN_CTRL1_OVERRIDE_STAGE3;
+
+		rc = qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, &reg, 1);
+	}
+
+	return rc;
+}
+
+static int qpnp_tm_update_temp(struct qpnp_tm_chip *chip)
+{
+	struct qpnp_vadc_result adc_result;
+	int rc;
+
+	rc = qpnp_vadc_read(chip->vadc_dev, chip->adc_channel, &adc_result);
+	if (!rc)
+		chip->temperature = adc_result.physical;
+	else
+		dev_err(&chip->pdev->dev,
+			"%s: qpnp_vadc_read(%d) failed, rc=%d\n",
+			__func__, chip->adc_channel, rc);
+
+	return rc;
+}
+
+static int qpnp_tm_get_temp_stage(struct qpnp_tm_chip *chip,
+			unsigned int *stage)
+{
+	int rc;
+	u8 reg;
+
+	rc = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, &reg, 1);
+	if (rc < 0)
+		return rc;
+
+	if (chip->subtype == QPNP_TM_SUBTYPE_GEN1)
+		*stage = reg & STATUS_STAGE_MASK;
+	else
+		*stage = (reg & STATUS_STATE_MASK) >> STATUS_STATE_SHIFT;
+
+	return 0;
+}
+
+/*
+ * This function initializes the internal temperature value based on only the
+ * current thermal stage and threshold.
+ */
+static int qpnp_tm_init_temp_no_adc(struct qpnp_tm_chip *chip)
+{
+	unsigned int stage;
+	int rc;
+
+	rc = qpnp_tm_get_temp_stage(chip, &chip->stage);
+	if (rc < 0)
+		return rc;
+
+	stage = chip->subtype == QPNP_TM_SUBTYPE_GEN1
+		? chip->stage : alarm_state_map[chip->stage];
+
+	if (stage)
+		chip->temperature = chip->thresh * TEMP_THRESH_STEP +
+			   (stage - 1) * TEMP_STAGE_STEP +
+			   TEMP_THRESH_MIN;
+
+	return 0;
+}
+
+/*
+ * This function updates the internal temperature value based on the
+ * current thermal stage and threshold as well as the previous stage
+ */
+static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
+{
+	unsigned int stage, stage_new, stage_old;
+	int rc;
+
+	rc = qpnp_tm_get_temp_stage(chip, &stage);
+	if (rc < 0)
+		return rc;
+
+	if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) {
+		stage_new = stage;
+		stage_old = chip->stage;
+	} else {
+		stage_new = alarm_state_map[stage];
+		stage_old = alarm_state_map[chip->stage];
+	}
+
+	if (stage_new > stage_old) {
+		/* increasing stage, use lower bound */
+		chip->temperature = (stage_new - 1) * TEMP_STAGE_STEP
+				+ chip->thresh * TEMP_THRESH_STEP
+				+ TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+	} else if (stage_new < stage_old) {
+		/* decreasing stage, use upper bound */
+		chip->temperature = stage_new * TEMP_STAGE_STEP
+				+ chip->thresh * TEMP_THRESH_STEP
+				- TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+	}
+
+	chip->stage = stage;
+
+	return 0;
+}
+
+static int qpnp_tz_get_temp_no_adc(struct thermal_zone_device *thermal,
+				     int *temperature)
+{
+	struct qpnp_tm_chip *chip = thermal->devdata;
+	int rc;
+
+	if (!temperature)
+		return -EINVAL;
+
+	rc = qpnp_tm_update_temp_no_adc(chip);
+	if (rc < 0)
+		return rc;
+
+	*temperature = chip->temperature;
+
+	return 0;
+}
+
+static int qpnp_tz_get_temp_qpnp_adc(struct thermal_zone_device *thermal,
+				      int *temperature)
+{
+	struct qpnp_tm_chip *chip = thermal->devdata;
+	int rc;
+
+	if (!temperature)
+		return -EINVAL;
+
+	rc = qpnp_tm_update_temp(chip);
+	if (rc < 0) {
+		dev_err(&chip->pdev->dev,
+			"%s: %s: adc read failed, rc = %d\n",
+			__func__, chip->tm_name, rc);
+		return rc;
+	}
+
+	*temperature = chip->temperature;
+
+	return 0;
+}
+
+static int qpnp_tz_get_mode(struct thermal_zone_device *thermal,
+			      enum thermal_device_mode *mode)
+{
+	struct qpnp_tm_chip *chip = thermal->devdata;
+
+	if (!mode)
+		return -EINVAL;
+
+	*mode = chip->mode;
+
+	return 0;
+}
+
+static int qpnp_tz_set_mode(struct thermal_zone_device *thermal,
+			      enum thermal_device_mode mode)
+{
+	struct qpnp_tm_chip *chip = thermal->devdata;
+	int rc = 0;
+
+	if (mode != chip->mode) {
+		if (mode == THERMAL_DEVICE_ENABLED)
+			rc = qpnp_tm_shutdown_override(chip,
+				SOFTWARE_OVERRIDE_ENABLED);
+		else
+			rc = qpnp_tm_shutdown_override(chip,
+				SOFTWARE_OVERRIDE_DISABLED);
+
+		chip->mode = mode;
+	}
+
+	return rc;
+}
+
+static int qpnp_tz_get_trip_type(struct thermal_zone_device *thermal,
+				   int trip, enum thermal_trip_type *type)
+{
+	if (trip < 0 || !type)
+		return -EINVAL;
+
+	switch (trip) {
+	case TRIP_STAGE3:
+		*type = THERMAL_TRIP_CRITICAL;
+		break;
+	case TRIP_STAGE2:
+		*type = THERMAL_TRIP_HOT;
+		break;
+	case TRIP_STAGE1:
+		*type = THERMAL_TRIP_HOT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int qpnp_tz_get_trip_temp(struct thermal_zone_device *thermal,
+				   int trip, int *temperature)
+{
+	struct qpnp_tm_chip *chip = thermal->devdata;
+	int thresh_temperature;
+
+	if (trip < 0 || !temperature)
+		return -EINVAL;
+
+	thresh_temperature = chip->thresh * TEMP_THRESH_STEP + TEMP_THRESH_MIN;
+
+	switch (trip) {
+	case TRIP_STAGE3:
+		thresh_temperature += 2 * TEMP_STAGE_STEP;
+		break;
+	case TRIP_STAGE2:
+		thresh_temperature += TEMP_STAGE_STEP;
+		break;
+	case TRIP_STAGE1:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	*temperature = thresh_temperature;
+
+	return 0;
+}
+
+static int qpnp_tz_get_crit_temp(struct thermal_zone_device *thermal,
+				   int *temperature)
+{
+	struct qpnp_tm_chip *chip = thermal->devdata;
+
+	if (!temperature)
+		return -EINVAL;
+
+	*temperature = chip->thresh * TEMP_THRESH_STEP + TEMP_THRESH_MIN +
+		2 * TEMP_STAGE_STEP;
+
+	return 0;
+}
+
+static struct thermal_zone_device_ops qpnp_thermal_zone_ops_no_adc = {
+	.get_temp = qpnp_tz_get_temp_no_adc,
+	.get_mode = qpnp_tz_get_mode,
+	.set_mode = qpnp_tz_set_mode,
+	.get_trip_type = qpnp_tz_get_trip_type,
+	.get_trip_temp = qpnp_tz_get_trip_temp,
+	.get_crit_temp = qpnp_tz_get_crit_temp,
+};
+
+static struct thermal_zone_device_ops qpnp_thermal_zone_ops_qpnp_adc = {
+	.get_temp = qpnp_tz_get_temp_qpnp_adc,
+	.get_mode = qpnp_tz_get_mode,
+	.set_mode = qpnp_tz_set_mode,
+	.get_trip_type = qpnp_tz_get_trip_type,
+	.get_trip_temp = qpnp_tz_get_trip_temp,
+	.get_crit_temp = qpnp_tz_get_crit_temp,
+};
+
+static void qpnp_tm_work(struct work_struct *work)
+{
+	struct delayed_work *dwork
+		= container_of(work, struct delayed_work, work);
+	struct qpnp_tm_chip *chip
+		= container_of(dwork, struct qpnp_tm_chip, irq_work);
+	unsigned int stage_new, stage_old;
+	int rc;
+
+	if (chip->adc_type == QPNP_TM_ADC_NONE) {
+		rc = qpnp_tm_update_temp_no_adc(chip);
+		if (rc < 0)
+			goto bail;
+	} else {
+		rc = qpnp_tm_get_temp_stage(chip, &chip->stage);
+		if (rc < 0)
+			goto bail;
+
+		rc = qpnp_tm_update_temp(chip);
+		if (rc < 0)
+			goto bail;
+	}
+
+	if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) {
+		stage_new = chip->stage;
+		stage_old = chip->prev_stage;
+	} else {
+		stage_new = alarm_state_map[chip->stage];
+		stage_old = alarm_state_map[chip->prev_stage];
+	}
+
+	chip->prev_stage = chip->stage;
+
+	if (stage_new != stage_old) {
+		if (chip->subtype == QPNP_TM_SUBTYPE_GEN1)
+			pr_crit("%s: PMIC Temp Alarm - stage=%u, threshold=%u, temperature=%d mC\n",
+				chip->tm_name, chip->stage, chip->thresh,
+				chip->temperature);
+		else
+			pr_crit("%s: PMIC Temp Alarm - stage=%u, state=%u, threshold=%u, temperature=%d mC\n",
+				chip->tm_name, stage_new, chip->stage,
+				chip->thresh, chip->temperature);
+
+		thermal_zone_device_update(chip->tz_dev,
+						THERMAL_EVENT_UNSPECIFIED);
+
+		/* Notify user space */
+		sysfs_notify(&chip->tz_dev->device.kobj, NULL, "type");
+	}
+
+bail:
+	return;
+}
+
+static irqreturn_t qpnp_tm_isr(int irq, void *data)
+{
+	struct qpnp_tm_chip *chip = data;
+
+	schedule_delayed_work(&chip->irq_work,
+			msecs_to_jiffies(STATUS_REGISTER_DELAY_MS) + 1);
+
+	return IRQ_HANDLED;
+}
+
+static int qpnp_tm_init_reg(struct qpnp_tm_chip *chip)
+{
+	int rc = 0;
+	u8 reg;
+
+	rc = qpnp_tm_read(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, &reg, 1);
+	if (rc < 0)
+		return rc;
+
+	if (chip->thresh < THRESH_MIN || chip->thresh > THRESH_MAX) {
+		/* Use hardware threshold value if configuration is invalid. */
+		chip->thresh = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+	}
+
+	if (chip->clock_rate < CLOCK_RATE_MIN
+	    || chip->clock_rate > CLOCK_RATE_MAX) {
+		/* Use hardware clock rate value if configuration is invalid. */
+		chip->clock_rate = (reg & SHUTDOWN_CTRL1_CLK_RATE_MASK)
+					>> SHUTDOWN_CTRL1_CLK_RATE_SHIFT;
+	}
+
+	/*
+	 * Set threshold and clock rate and also disable software override of
+	 * stage 2 and 3 shutdowns.
+	 */
+	reg = chip->thresh & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+	reg |= (chip->clock_rate << SHUTDOWN_CTRL1_CLK_RATE_SHIFT)
+		& SHUTDOWN_CTRL1_CLK_RATE_MASK;
+	rc = qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, &reg, 1);
+	if (rc < 0)
+		return rc;
+
+	/* Enable the thermal alarm PMIC module in always-on mode. */
+	reg = ALARM_CTRL_FORCE_ENABLE;
+	rc = qpnp_tm_write(chip, QPNP_TM_REG_ALARM_CTRL, &reg, 1);
+
+	return rc;
+}
+
+static int qpnp_tm_probe(struct platform_device *pdev)
+{
+	struct device_node *node;
+	unsigned int base;
+	struct qpnp_tm_chip *chip;
+	struct thermal_zone_device_ops *tz_ops;
+	char *tm_name;
+	u32 default_temperature;
+	int rc = 0;
+	u8 raw_type[2], type, subtype;
+
+	if (!pdev || !(&pdev->dev) || !pdev->dev.of_node) {
+		dev_err(&pdev->dev, "%s: device tree node not found\n",
+			__func__);
+		return -EINVAL;
+	}
+
+	node = pdev->dev.of_node;
+
+	chip = kzalloc(sizeof(struct qpnp_tm_chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!chip->regmap) {
+		dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+		return -EINVAL;
+	}
+
+	dev_set_drvdata(&pdev->dev, chip);
+
+	rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+	if (rc < 0) {
+		dev_err(&pdev->dev,
+			"Couldn't find reg in node = %s rc = %d\n",
+			pdev->dev.of_node->full_name, rc);
+		goto free_chip;
+	}
+	chip->base_addr	= base;
+	chip->pdev	= pdev;
+
+	chip->irq = platform_get_irq(pdev, 0);
+	if (chip->irq < 0) {
+		rc = chip->irq;
+		dev_err(&pdev->dev, "%s: node is missing irq, rc=%d\n",
+			__func__, rc);
+		goto free_chip;
+	}
+
+	chip->tm_name = of_get_property(node, "label", NULL);
+	if (chip->tm_name == NULL) {
+		dev_err(&pdev->dev, "%s: node is missing label\n", __func__);
+		rc = -EINVAL;
+		goto free_chip;
+	}
+
+	tm_name = kstrdup(chip->tm_name, GFP_KERNEL);
+	if (tm_name == NULL) {
+		rc = -ENOMEM;
+		goto free_chip;
+	}
+	chip->tm_name = tm_name;
+
+	INIT_DELAYED_WORK(&chip->irq_work, qpnp_tm_work);
+
+	/* These bindings are optional, so it is okay if they are not found. */
+	chip->thresh = THRESH_MAX + 1;
+	rc = of_property_read_u32(node, "qcom,threshold-set", &chip->thresh);
+	if (!rc && (chip->thresh < THRESH_MIN || chip->thresh > THRESH_MAX))
+		dev_err(&pdev->dev,
+			"%s: invalid qcom,threshold-set=%u specified\n",
+			__func__, chip->thresh);
+
+	chip->clock_rate = CLOCK_RATE_MAX + 1;
+	rc = of_property_read_u32(node, "qcom,clock-rate", &chip->clock_rate);
+	if (!rc && (chip->clock_rate < CLOCK_RATE_MIN
+		    || chip->clock_rate > CLOCK_RATE_MAX))
+		dev_err(&pdev->dev,
+			"%s: invalid qcom,clock-rate=%u specified\n", __func__,
+			chip->clock_rate);
+
+	chip->adc_type = QPNP_TM_ADC_NONE;
+	rc = of_property_read_u32(node, "qcom,channel-num", &chip->adc_channel);
+	if (!rc) {
+		if (chip->adc_channel < 0 || chip->adc_channel >= ADC_MAX_NUM) {
+			dev_err(&pdev->dev,
+				"%s: invalid qcom,channel-num=%d specified\n",
+				__func__, chip->adc_channel);
+		} else {
+			chip->adc_type = QPNP_TM_ADC_QPNP_ADC;
+			chip->vadc_dev = qpnp_get_vadc(&pdev->dev,
+							"temp_alarm");
+			if (IS_ERR(chip->vadc_dev)) {
+				rc = PTR_ERR(chip->vadc_dev);
+				if (rc != -EPROBE_DEFER)
+					pr_err("vadc property missing\n");
+				goto err_cancel_work;
+			}
+		}
+	}
+
+	if (chip->adc_type == QPNP_TM_ADC_QPNP_ADC)
+		tz_ops = &qpnp_thermal_zone_ops_qpnp_adc;
+	else
+		tz_ops = &qpnp_thermal_zone_ops_no_adc;
+
+	chip->allow_software_override
+		= of_property_read_bool(node, "qcom,allow-override");
+
+	default_temperature = DEFAULT_NO_ADC_TEMP;
+	rc = of_property_read_u32(node, "qcom,default-temp",
+					&default_temperature);
+	chip->temperature = default_temperature;
+
+	rc = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, raw_type, 2);
+	if (rc) {
+		dev_err(&pdev->dev,
+			"%s: could not read type register, rc=%d\n",
+			__func__, rc);
+		goto err_cancel_work;
+	}
+	type = raw_type[0];
+	subtype = raw_type[1];
+
+	if (type != QPNP_TM_TYPE || (subtype != QPNP_TM_SUBTYPE_GEN1
+				     && subtype != QPNP_TM_SUBTYPE_GEN2)) {
+		dev_err(&pdev->dev,
+			"%s: invalid type=%02X or subtype=%02X register value\n",
+			__func__, type, subtype);
+		rc = -ENODEV;
+		goto err_cancel_work;
+	}
+
+	chip->subtype = subtype;
+
+	rc = qpnp_tm_init_reg(chip);
+	if (rc) {
+		dev_err(&pdev->dev, "%s: qpnp_tm_init_reg() failed, rc=%d\n",
+			__func__, rc);
+		goto err_cancel_work;
+	}
+
+	if (chip->adc_type == QPNP_TM_ADC_NONE) {
+		rc = qpnp_tm_init_temp_no_adc(chip);
+		if (rc) {
+			dev_err(&pdev->dev,
+				"%s: qpnp_tm_init_temp_no_adc() failed, rc=%d\n",
+				__func__, rc);
+			goto err_cancel_work;
+		}
+	}
+
+	/* Start in HW control; switch to SW control when user changes mode. */
+	chip->mode = THERMAL_DEVICE_DISABLED;
+	rc = qpnp_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_DISABLED);
+	if (rc) {
+		dev_err(&pdev->dev,
+			"%s: qpnp_tm_shutdown_override() failed, rc=%d\n",
+			__func__, rc);
+		goto err_cancel_work;
+	}
+
+	chip->tz_dev = thermal_zone_device_register(tm_name, TRIP_NUM, 0, chip,
+			tz_ops, NULL, 0, 0);
+	if (chip->tz_dev == NULL) {
+		dev_err(&pdev->dev,
+			"%s: thermal_zone_device_register() failed.\n",
+			__func__);
+		rc = -ENODEV;
+		goto err_cancel_work;
+	}
+
+	rc = request_irq(chip->irq, qpnp_tm_isr, IRQF_TRIGGER_RISING, tm_name,
+			chip);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "%s: request_irq(%d) failed: %d\n",
+			__func__, chip->irq, rc);
+		goto err_free_tz;
+	}
+
+	return 0;
+
+err_free_tz:
+	thermal_zone_device_unregister(chip->tz_dev);
+err_cancel_work:
+	cancel_delayed_work_sync(&chip->irq_work);
+	kfree(chip->tm_name);
+free_chip:
+	dev_set_drvdata(&pdev->dev, NULL);
+	kfree(chip);
+	return rc;
+}
+
+static int qpnp_tm_remove(struct platform_device *pdev)
+{
+	struct qpnp_tm_chip *chip = dev_get_drvdata(&pdev->dev);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+	thermal_zone_device_unregister(chip->tz_dev);
+	kfree(chip->tm_name);
+	qpnp_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_DISABLED);
+	free_irq(chip->irq, chip);
+	cancel_delayed_work_sync(&chip->irq_work);
+	kfree(chip);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int qpnp_tm_suspend(struct device *dev)
+{
+	struct qpnp_tm_chip *chip = dev_get_drvdata(dev);
+
+	/* Clear override bits in suspend to allow hardware control */
+	qpnp_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_DISABLED);
+
+	return 0;
+}
+
+static int qpnp_tm_resume(struct device *dev)
+{
+	struct qpnp_tm_chip *chip = dev_get_drvdata(dev);
+
+	/* Override hardware actions so software can control */
+	if (chip->mode == THERMAL_DEVICE_ENABLED)
+		qpnp_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_ENABLED);
+
+	return 0;
+}
+
+static const struct dev_pm_ops qpnp_tm_pm_ops = {
+	.suspend = qpnp_tm_suspend,
+	.resume = qpnp_tm_resume,
+};
+
+#define QPNP_TM_PM_OPS	(&qpnp_tm_pm_ops)
+#else
+#define QPNP_TM_PM_OPS	NULL
+#endif
+
+static const struct of_device_id qpnp_tm_match_table[] = {
+	{ .compatible = QPNP_TM_DRIVER_NAME, },
+	{}
+};
+
+static const struct platform_device_id qpnp_tm_id[] = {
+	{ QPNP_TM_DRIVER_NAME, 0 },
+	{}
+};
+
+static struct platform_driver qpnp_tm_driver = {
+	.driver = {
+		.name		= QPNP_TM_DRIVER_NAME,
+		.of_match_table	= qpnp_tm_match_table,
+		.owner		= THIS_MODULE,
+		.pm		= QPNP_TM_PM_OPS,
+	},
+	.probe	  = qpnp_tm_probe,
+	.remove	  = qpnp_tm_remove,
+	.id_table = qpnp_tm_id,
+};
+
+int __init qpnp_tm_init(void)
+{
+	return platform_driver_register(&qpnp_tm_driver);
+}
+
+static void __exit qpnp_tm_exit(void)
+{
+	platform_driver_unregister(&qpnp_tm_driver);
+}
+
+module_init(qpnp_tm_init);
+module_exit(qpnp_tm_exit);
+
+MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver");
+MODULE_LICENSE("GPL v2");