Merge "leds: qpnp-flash-v2: Add support for QPNP flash v2 LED driver"
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 44097a3..81f93f2 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -756,6 +756,17 @@
 	  To compile this driver as a module, choose M here: the module
 	  will be called leds-nic78bx.
 
+config LEDS_QPNP_FLASH_V2
+        tristate "Support for QPNP V2 Flash LEDs"
+        depends on LEDS_CLASS && MFD_SPMI_PMIC
+	select LEDS_TRIGGERS
+        help
+          This driver supports the flash V2 LED functionality of Qualcomm
+          Technologies, Inc. QPNP PMICs.  This driver supports PMICs starting
+          from PMI8998, PM8150L and their derivatives.  It can configure the
+          flash LED target current for several independent channels.  It also
+          supports various over current and over temperature mitigation features.
+
 comment "LED Triggers"
 source "drivers/leds/trigger/Kconfig"
 
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 420b5d2..38b54a8 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -78,6 +78,7 @@
 obj-$(CONFIG_LEDS_LM3692X)		+= leds-lm3692x.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
 obj-$(CONFIG_LEDS_LM3601X)		+= leds-lm3601x.o
+obj-$(CONFIG_LEDS_QPNP_FLASH_V2)        += leds-qpnp-flash-v2.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_CR0014114)		+= leds-cr0014114.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..eb73621f
--- /dev/null
+++ b/drivers/leds/leds-qpnp-flash-v2.c
@@ -0,0 +1,2670 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ */
+
+#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_MULTI_STROBE_CTRL(base)	(base + 0x71)
+#define	FLASH_LED_REG_LPG_INPUT_CTRL(base)	(base + 0x72)
+#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_STROBE_MASK			GENMASK(1, 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	LED1N2_FLASH_ONCE_ONLY_BIT		BIT(0)
+#define	LED3_FLASH_ONCE_ONLY_BIT		BIT(1)
+#define	LPG_INPUT_SEL_BIT			BIT(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_VAL_TO_UV(val)		((val + 25) * 100000)
+#define	MITIGATION_THRSH_MA_TO_VAL(val_ma)	(val_ma / 100)
+#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_LMH_MITIGATION_SEL_DEFAULT	2
+#define	FLASH_LED_MITIGATION_SEL_MAX		2
+#define	FLASH_LED_CHGR_MITIGATION_SEL_SHIFT	4
+#define	FLASH_LED_CHGR_MITIGATION_THRSH_DEFAULT	0xA
+#define	FLASH_LED_CHGR_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_MAX_TOTAL_CURRENT_MA		3750
+#define	FLASH_LED_IRES5P0_MAX_CURR_MA		640
+#define	FLASH_LED_IRES7P5_MAX_CURR_MA		960
+#define	FLASH_LED_IRES10P0_MAX_CURR_MA		1280
+#define	FLASH_LED_IRES12P5_MAX_CURR_MA		1600
+#define	MAX_IRES_LEVELS				4
+
+/* notifier call chain for flash-led irqs */
+static ATOMIC_NOTIFIER_HEAD(irq_notifier_list);
+
+enum flash_charger_mitigation {
+	FLASH_DISABLE_CHARGER_MITIGATION,
+	FLASH_HW_CHARGER_MITIGATION_BY_ILED_THRSHLD,
+	FLASH_SW_CHARGER_MITIGATION,
+};
+
+enum flash_led_type {
+	FLASH_LED_TYPE_UNKNOWN,
+	FLASH_LED_TYPE_FLASH,
+	FLASH_LED_TYPE_TORCH,
+};
+
+enum {
+	LED1 = 0,
+	LED2,
+	LED3,
+};
+
+enum strobe_type {
+	SW_STROBE = 0,
+	HW_STROBE,
+	LPG_STROBE,
+};
+
+/*
+ * Configurations for each individual LED
+ */
+struct flash_node_data {
+	struct platform_device		*pdev;
+	struct led_classdev		cdev;
+	struct pinctrl			*strobe_pinctrl;
+	struct pinctrl_state		*hw_strobe_state_active;
+	struct pinctrl_state		*hw_strobe_state_suspend;
+	int				hw_strobe_gpio;
+	int				ires_ua;
+	int				default_ires_ua;
+	int				max_current;
+	int				current_ma;
+	int				prev_current_ma;
+	u8				duration;
+	u8				id;
+	u8				ires_idx;
+	u8				default_ires_idx;
+	u8				hdrm_val;
+	u8				current_reg_val;
+	u8				strobe_ctrl;
+	u8				strobe_sel;
+	enum flash_led_type		type;
+	bool				led_on;
+};
+
+
+struct flash_switch_data {
+	struct platform_device		*pdev;
+	struct regulator		*vreg;
+	struct pinctrl			*led_en_pinctrl;
+	struct pinctrl_state		*gpio_state_active;
+	struct pinctrl_state		*gpio_state_suspend;
+	struct led_classdev		cdev;
+	int				led_mask;
+	bool				regulator_on;
+	bool				enabled;
+	bool				symmetry_en;
+};
+
+/*
+ * 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;
+	int			hw_strobe_option;
+	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;
+	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;
+	int				total_current_ma;
+	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 max_ires_curr_ma_table[MAX_IRES_LEVELS] = {
+	FLASH_LED_IRES12P5_MAX_CURR_MA, FLASH_LED_IRES10P0_MAX_CURR_MA,
+	FLASH_LED_IRES7P5_MAX_CURR_MA, FLASH_LED_IRES5P0_MAX_CURR_MA
+};
+
+static inline int get_current_reg_code(int target_curr_ma, int ires_ua)
+{
+	if (!ires_ua || !target_curr_ma || (target_curr_ma < (ires_ua / 1000)))
+		return 0;
+
+	return DIV_ROUND_CLOSEST(target_curr_ma * 1000, ires_ua) - 1;
+}
+
+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_headroom_config(struct qpnp_flash_led *led)
+{
+	int rc, i, addr_offset;
+
+	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;
+	}
+
+	return rc;
+}
+
+static int qpnp_flash_led_strobe_config(struct qpnp_flash_led *led)
+{
+	int i, rc, addr_offset;
+	u8 val = 0, mask, strobe_mask = 0, strobe_ctrl;
+
+	for (i = 0; i < led->num_fnodes; i++) {
+		val |= 0x1 << led->fnode[i].id;
+
+		if (led->fnode[i].strobe_sel == HW_STROBE) {
+			if (led->fnode[i].id == LED3)
+				strobe_mask |= LED3_FLASH_ONCE_ONLY_BIT;
+			else
+				strobe_mask |= LED1N2_FLASH_ONCE_ONLY_BIT;
+		}
+
+		if (led->fnode[i].id == LED3 &&
+				led->fnode[i].strobe_sel == LPG_STROBE)
+			strobe_mask |= LED3_FLASH_ONCE_ONLY_BIT;
+		/*
+		 * As per the hardware recommendation, to use LED2/LED3 in HW
+		 * strobe mode, LED1 should be set to HW strobe mode as well.
+		 */
+		if (led->fnode[i].strobe_sel == HW_STROBE &&
+		      (led->fnode[i].id == LED2 || led->fnode[i].id == LED3)) {
+			mask = FLASH_HW_STROBE_MASK;
+			addr_offset = led->fnode[LED1].id;
+			/*
+			 * HW_STROBE: enable, TRIGGER: level,
+			 * POLARITY: active high
+			 */
+			strobe_ctrl = BIT(2) | BIT(0);
+			rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_STROBE_CTRL(
+				led->base + addr_offset),
+				mask, strobe_ctrl);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
+	rc = qpnp_flash_led_masked_write(led,
+		FLASH_LED_REG_MULTI_STROBE_CTRL(led->base),
+		strobe_mask, 0);
+	if (rc < 0)
+		return rc;
+
+	if (led->fnode[LED3].strobe_sel == LPG_STROBE) {
+		rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_LPG_INPUT_CTRL(led->base),
+			LPG_INPUT_SEL_BIT, LPG_INPUT_SEL_BIT);
+		if (rc < 0)
+			return rc;
+	}
+
+	return rc;
+}
+
+static qpnp_flash_led_thermal_config(struct qpnp_flash_led *led)
+{
+	int rc;
+	u8 val, mask;
+
+	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;
+	}
+
+	return rc;
+}
+
+static int qpnp_flash_led_init_settings(struct qpnp_flash_led *led)
+{
+	int rc;
+	u8 val = 0;
+
+	rc = qpnp_flash_led_headroom_config(led);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_strobe_config(led);
+	if (rc < 0)
+		return rc;
+
+	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;
+
+	rc = qpnp_flash_led_thermal_config(led);
+	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;
+
+	val = led->pdata->chgr_mitigation_sel
+				<< FLASH_LED_CHGR_MITIGATION_SEL_SHIFT;
+	rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_MITIGATION_SEL(led->base),
+			FLASH_LED_CHGR_MITIGATION_SEL_MASK,
+			val);
+	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 = get_current_reg_code(led->pdata->led1n2_iclamp_low_ma,
+						led->fnode[LED1].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 = get_current_reg_code(led->pdata->led1n2_iclamp_mid_ma,
+						led->fnode[LED1].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 = get_current_reg_code(led->pdata->led3_iclamp_low_ma,
+						led->fnode[LED3].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 = get_current_reg_code(led->pdata->led3_iclamp_mid_ma,
+						led->fnode[LED3].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;
+	}
+
+	if (led->pdata->hw_strobe_option > 0) {
+		rc = qpnp_flash_led_masked_write(led,
+				FLASH_LED_REG_STROBE_CFG(led->base),
+				FLASH_LED_STROBE_MASK,
+				led->pdata->hw_strobe_option);
+		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->strobe_pinctrl && fnode->hw_strobe_state_active &&
+					fnode->hw_strobe_state_suspend) {
+		rc = pinctrl_select_state(fnode->strobe_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 *max_current)
+{
+	int ocv_uv, ibat_now, voltage_hdrm_mv, rc;
+	int rbatt_uohm = 0;
+	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) {
+		*max_current = FLASH_LED_MAX_TOTAL_CURRENT_MA;
+		return 0;
+	}
+
+	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, max_current);
+	}
+
+	/*
+	 * 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);
+	*max_current = min(FLASH_LED_MAX_TOTAL_CURRENT_MA,
+			(int)(div64_s64(avail_flash_ua, MCONV)));
+	return 0;
+}
+
+static int qpnp_flash_led_calc_thermal_current_lim(struct qpnp_flash_led *led,
+						int *thermal_current_lim)
+{
+	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 0;
+}
+
+static int qpnp_flash_led_get_max_avail_current(struct qpnp_flash_led *led,
+						int *max_avail_current)
+{
+	int thermal_current_lim = 0, rc;
+
+	led->trigger_lmh = false;
+	rc = qpnp_flash_led_calc_max_current(led, max_avail_current);
+	if (rc < 0) {
+		pr_err("Couldn't calculate max_avail_current, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (led->pdata->thermal_derate_en) {
+		rc = qpnp_flash_led_calc_thermal_current_lim(led,
+			&thermal_current_lim);
+		if (rc < 0) {
+			pr_err("Couldn't calculate thermal_current_lim, rc=%d\n",
+				rc);
+			return rc;
+		}
+	}
+
+	if (thermal_current_lim)
+		*max_avail_current = min(*max_avail_current,
+					thermal_current_lim);
+
+	return 0;
+}
+
+static void qpnp_flash_led_aggregate_max_current(struct flash_node_data *fnode)
+{
+	struct qpnp_flash_led *led = dev_get_drvdata(&fnode->pdev->dev);
+
+	if (fnode->current_ma)
+		led->total_current_ma += fnode->current_ma
+						- fnode->prev_current_ma;
+	else
+		led->total_current_ma -= fnode->prev_current_ma;
+
+	fnode->prev_current_ma = fnode->current_ma;
+}
+
+static void qpnp_flash_led_node_set(struct flash_node_data *fnode, int value)
+{
+	int i = 0;
+	int prgm_current_ma = value;
+	int min_ma = fnode->ires_ua / 1000;
+	struct qpnp_flash_led *led = dev_get_drvdata(&fnode->pdev->dev);
+
+	if (value <= 0)
+		prgm_current_ma = 0;
+	else if (value < min_ma)
+		prgm_current_ma = min_ma;
+
+	fnode->ires_idx = fnode->default_ires_idx;
+	fnode->ires_ua = fnode->default_ires_ua;
+
+	prgm_current_ma = min(prgm_current_ma, fnode->max_current);
+	if (prgm_current_ma > max_ires_curr_ma_table[fnode->ires_idx]) {
+		/* find the matching ires */
+		for (i = MAX_IRES_LEVELS - 1; i >= 0; i--) {
+			if (prgm_current_ma <= max_ires_curr_ma_table[i]) {
+				fnode->ires_idx = i;
+				fnode->ires_ua = FLASH_LED_IRES_MIN_UA +
+				      (FLASH_LED_IRES_BASE - fnode->ires_idx) *
+				      FLASH_LED_IRES_DIVISOR;
+				break;
+			}
+		}
+	}
+	fnode->current_ma = prgm_current_ma;
+	fnode->cdev.brightness = prgm_current_ma;
+	fnode->current_reg_val = get_current_reg_code(prgm_current_ma,
+					fnode->ires_ua);
+	if (prgm_current_ma)
+		fnode->led_on = true;
+
+	if (led->pdata->chgr_mitigation_sel == FLASH_SW_CHARGER_MITIGATION) {
+		qpnp_flash_led_aggregate_max_current(fnode);
+		led->trigger_chgr = false;
+		if (led->total_current_ma >= 1000)
+			led->trigger_chgr = true;
+	}
+}
+
+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].strobe_sel == HW_STROBE) {
+			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;
+			}
+		}
+	}
+
+	if (snode->led_en_pinctrl) {
+		pr_debug("Selecting suspend state for %s\n", snode->cdev.name);
+		rc = pinctrl_select_state(snode->led_en_pinctrl,
+				snode->gpio_state_suspend);
+		if (rc < 0) {
+			pr_err("failed to select pinctrl suspend state rc=%d\n",
+				rc);
+			return rc;
+		}
+	}
+
+	snode->enabled = false;
+	return 0;
+}
+
+static int qpnp_flash_led_symmetry_config(struct flash_switch_data *snode)
+{
+	struct qpnp_flash_led *led = dev_get_drvdata(&snode->pdev->dev);
+	int i, total_curr_ma = 0, num_leds = 0, prgm_current_ma;
+	enum flash_led_type type = FLASH_LED_TYPE_UNKNOWN;
+
+	for (i = 0; i < led->num_fnodes; i++) {
+		if (snode->led_mask & BIT(led->fnode[i].id)) {
+			if (led->fnode[i].type == FLASH_LED_TYPE_FLASH &&
+				led->fnode[i].led_on)
+				type = FLASH_LED_TYPE_FLASH;
+
+			if (led->fnode[i].type == FLASH_LED_TYPE_TORCH &&
+				led->fnode[i].led_on)
+				type = FLASH_LED_TYPE_TORCH;
+		}
+	}
+
+	if (type == FLASH_LED_TYPE_UNKNOWN) {
+		pr_err("Incorrect type possibly because of no active LEDs\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < led->num_fnodes; i++) {
+		if ((snode->led_mask & BIT(led->fnode[i].id)) &&
+			(led->fnode[i].type == type)) {
+			total_curr_ma += led->fnode[i].current_ma;
+			num_leds++;
+		}
+	}
+
+	if (num_leds > 0 && total_curr_ma > 0) {
+		prgm_current_ma = total_curr_ma / num_leds;
+	} else {
+		pr_err("Incorrect configuration, num_leds: %d total_curr_ma: %d\n",
+			num_leds, total_curr_ma);
+		return -EINVAL;
+	}
+
+	if (prgm_current_ma == 0) {
+		pr_warn("prgm_curr_ma cannot be 0\n");
+		return 0;
+	}
+
+	pr_debug("num_leds: %d total: %d prgm_curr_ma: %d\n", num_leds,
+		total_curr_ma, prgm_current_ma);
+
+	for (i = 0; i < led->num_fnodes; i++) {
+		if (snode->led_mask & BIT(led->fnode[i].id) &&
+			led->fnode[i].current_ma != prgm_current_ma &&
+			led->fnode[i].type == type) {
+			qpnp_flash_led_node_set(&led->fnode[i],
+				prgm_current_ma);
+			pr_debug("%s LED %d current: %d code: %d ires_ua: %d\n",
+				(type == FLASH_LED_TYPE_FLASH) ?
+					"flash" : "torch",
+				led->fnode[i].id, prgm_current_ma,
+				led->fnode[i].current_reg_val,
+				led->fnode[i].ires_ua);
+		}
+	}
+
+	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 active leds for this switch node */
+	if (snode->symmetry_en) {
+		rc = qpnp_flash_led_symmetry_config(snode);
+		if (rc < 0) {
+			pr_err("Failed to configure current symmetrically, rc=%d\n",
+				rc);
+			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))
+			val |= led->fnode[i].ires_idx << (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;
+
+	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].strobe_sel == SW_STROBE)
+			mask = FLASH_LED_HW_SW_STROBE_SEL_BIT;
+		else
+			mask = FLASH_HW_STROBE_MASK;
+		rc = qpnp_flash_led_masked_write(led,
+			FLASH_LED_REG_STROBE_CTRL(led->base + addr_offset),
+			mask, led->fnode[i].strobe_ctrl);
+		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].strobe_sel == HW_STROBE) {
+			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 (snode->led_en_pinctrl) {
+		pr_debug("Selecting active state for %s\n", snode->cdev.name);
+		rc = pinctrl_select_state(snode->led_en_pinctrl,
+				snode->gpio_state_active);
+		if (rc < 0) {
+			pr_err("failed to select pinctrl active state 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;
+		}
+		/* Wait for LMH mitigation to take effect */
+		udelay(500);
+	}
+
+	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_AVAIL_CURRENT) {
+		rc = qpnp_flash_led_get_max_avail_current(led, max_current);
+		if (rc < 0) {
+			pr_err("query max current failed, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	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, max_current = 0;
+	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, &max_current);
+	if (rc < 0)
+		pr_err("query max current failed, rc=%d\n", rc);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", max_current);
+}
+
+/* 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 inline u8 get_safety_timer_code(u32 duration_ms)
+{
+	if (!duration_ms)
+		return 0;
+
+	return (duration_ms / 10) - 1;
+}
+
+static inline u8 get_vph_droop_thresh_code(u32 val_mv)
+{
+	if (!val_mv)
+		return 0;
+
+	return (val_mv / 100) - 25;
+}
+
+static int qpnp_flash_led_parse_hw_strobe_dt(struct flash_node_data *fnode)
+{
+	struct device_node *node = fnode->cdev.dev->of_node;
+
+	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 if (fnode->strobe_pinctrl) {
+		fnode->hw_strobe_gpio = -1;
+		fnode->hw_strobe_state_active =
+			pinctrl_lookup_state(fnode->strobe_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->strobe_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;
+		}
+	}
+
+	return 0;
+}
+
+static int qpnp_flash_led_parse_strobe_sel_dt(struct qpnp_flash_led *led,
+						struct flash_node_data *fnode,
+						struct device_node *node)
+{
+	int rc;
+	u32 val;
+	u8 hw_strobe = 0, edge_trigger = 0, active_high = 0;
+
+	fnode->strobe_sel = SW_STROBE;
+	rc = of_property_read_u32(node, "qcom,strobe-sel", &val);
+	if (rc < 0) {
+		if (rc != -EINVAL) {
+			pr_err("Unable to read qcom,strobe-sel property\n");
+			return rc;
+		}
+	} else {
+		if (val < SW_STROBE || val > LPG_STROBE) {
+			pr_err("Incorrect strobe selection specified %d\n",
+				val);
+			return -EINVAL;
+		}
+		fnode->strobe_sel = (u8)val;
+	}
+
+	/*
+	 * LPG strobe is allowed only for LED3 and HW strobe option should be
+	 * option 2 or 3.
+	 */
+	if (fnode->strobe_sel == LPG_STROBE) {
+		if (led->pdata->hw_strobe_option ==
+				FLASH_LED_HW_STROBE_OPTION_1) {
+			pr_err("Incorrect strobe option for LPG strobe\n");
+			return -EINVAL;
+		}
+		if (fnode->id != LED3) {
+			pr_err("Incorrect LED chosen for LPG strobe\n");
+			return -EINVAL;
+		}
+	}
+
+	if (fnode->strobe_sel == HW_STROBE) {
+		edge_trigger = of_property_read_bool(node,
+						"qcom,hw-strobe-edge-trigger");
+		active_high = !of_property_read_bool(node,
+						"qcom,hw-strobe-active-low");
+		hw_strobe = 1;
+	} else if (fnode->strobe_sel == LPG_STROBE) {
+		/* LPG strobe requires level trigger and active high */
+		edge_trigger = 0;
+		active_high =  1;
+		hw_strobe = 1;
+	}
+
+	fnode->strobe_ctrl = (hw_strobe << 2) | (edge_trigger << 1) |
+				active_high;
+
+	return 0;
+}
+
+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, min_ma;
+	u32 val;
+
+	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->default_ires_ua = fnode->ires_ua = FLASH_LED_IRES_DEFAULT_UA;
+	fnode->default_ires_idx = fnode->ires_idx = FLASH_LED_IRES_DEFAULT_VAL;
+	rc = of_property_read_u32(node, "qcom,ires-ua", &val);
+	if (!rc) {
+		fnode->default_ires_ua = fnode->ires_ua = val;
+		fnode->default_ires_idx = fnode->ires_idx =
+			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;
+	}
+
+	min_ma = fnode->ires_ua / 1000;
+	rc = of_property_read_u32(node, "qcom,max-current", &val);
+	if (!rc) {
+		if (val < min_ma)
+			val = min_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 < min_ma || val > fnode->max_current)
+			pr_warn("Invalid operational current specified, capping it\n");
+		if (val < min_ma)
+			val = min_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 = get_safety_timer_code(val);
+		if (fnode->duration)
+			fnode->duration |= 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;
+	}
+
+	rc = qpnp_flash_led_parse_strobe_sel_dt(led, fnode, node);
+	if (rc < 0)
+		return rc;
+
+	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->strobe_pinctrl = devm_pinctrl_get(fnode->cdev.dev);
+	if (IS_ERR_OR_NULL(fnode->strobe_pinctrl)) {
+		pr_debug("No pinctrl defined for %s, err=%ld\n",
+			fnode->cdev.name, PTR_ERR(fnode->strobe_pinctrl));
+		fnode->strobe_pinctrl = NULL;
+	}
+
+	if (fnode->strobe_sel == HW_STROBE)
+		return qpnp_flash_led_parse_hw_strobe_dt(fnode);
+
+	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;
+	}
+
+	snode->symmetry_en = of_property_read_bool(node, "qcom,symmetry-en");
+
+	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;
+
+	snode->led_en_pinctrl = devm_pinctrl_get(snode->cdev.dev);
+	if (IS_ERR_OR_NULL(snode->led_en_pinctrl)) {
+		pr_debug("No pinctrl defined for %s, err=%ld\n",
+			snode->cdev.name, PTR_ERR(snode->led_en_pinctrl));
+		snode->led_en_pinctrl = NULL;
+	}
+
+	if (snode->led_en_pinctrl) {
+		snode->gpio_state_active =
+			pinctrl_lookup_state(snode->led_en_pinctrl,
+						"led_enable");
+		if (IS_ERR_OR_NULL(snode->gpio_state_active)) {
+			pr_err("Cannot lookup LED active state\n");
+			devm_pinctrl_put(snode->led_en_pinctrl);
+			snode->led_en_pinctrl = NULL;
+			return PTR_ERR(snode->gpio_state_active);
+		}
+
+		snode->gpio_state_suspend =
+			pinctrl_lookup_state(snode->led_en_pinctrl,
+						"led_disable");
+		if (IS_ERR_OR_NULL(snode->gpio_state_suspend)) {
+			pr_err("Cannot lookup LED disable state\n");
+			devm_pinctrl_put(snode->led_en_pinctrl);
+			snode->led_en_pinctrl = NULL;
+			return PTR_ERR(snode->gpio_state_suspend);
+		}
+	}
+
+	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_thermal_config_dt(struct qpnp_flash_led *led,
+						struct device_node *node)
+{
+	int rc;
+	u32 val;
+
+	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;
+	}
+
+	return rc;
+}
+
+static int qpnp_flash_led_parse_vph_droop_config_dt(struct qpnp_flash_led *led,
+						struct device_node *node)
+{
+	int rc;
+	u32 val;
+
+	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 =
+			get_vph_droop_thresh_code(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;
+	return rc;
+}
+
+static int qpnp_flash_led_parse_iclamp_config_dt(struct qpnp_flash_led *led,
+						struct device_node *node)
+{
+	int rc;
+	u32 val;
+
+	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;
+	}
+
+	return rc;
+}
+
+static int qpnp_flash_led_parse_lmh_config_dt(struct qpnp_flash_led *led,
+						struct device_node *node)
+{
+	int rc;
+	u32 val;
+
+	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_LMH_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;
+	}
+
+	return rc;
+}
+
+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;
+
+	rc = of_property_read_u32(node, "reg", &val);
+	if (rc < 0) {
+		pr_err("Couldn't find reg in node %s, rc = %d\n",
+			node->full_name, rc);
+		return rc;
+	}
+
+	led->base = val;
+	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;
+	}
+	of_node_put(revid_node);
+
+	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;
+
+	rc = qpnp_flash_led_parse_thermal_config_dt(led, node);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_parse_vph_droop_config_dt(led, node);
+	if (rc < 0)
+		return rc;
+
+	rc = qpnp_flash_led_parse_iclamp_config_dt(led, node);
+	if (rc < 0)
+		return rc;
+
+	led->pdata->hw_strobe_option = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,hw-strobe-option", &val);
+	if (!rc) {
+		led->pdata->hw_strobe_option = val;
+	} else if (rc != -EINVAL) {
+		pr_err("Unable to parse hw strobe option, 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;
+	}
+
+	rc = qpnp_flash_led_parse_lmh_config_dt(led, node);
+	if (rc < 0)
+		return rc;
+
+	led->pdata->chgr_mitigation_sel = FLASH_SW_CHARGER_MITIGATION;
+	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->iled_thrsh_val = FLASH_LED_CHGR_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_CHGR_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_register_interrupts(struct qpnp_flash_led *led)
+{
+	int rc;
+
+	/* 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);
+			return rc;
+		}
+	}
+
+	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);
+			return rc;
+		}
+	}
+
+	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);
+			return rc;
+		}
+	}
+
+	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;
+	int rc, i = 0, j = 0;
+
+	node = pdev->dev.of_node;
+	if (!node) {
+		pr_err("No flash LED nodes defined\n");
+		return -ENODEV;
+	}
+
+	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->pdev = pdev;
+	led->pdata = devm_kzalloc(&pdev->dev,
+			sizeof(struct flash_led_platform_data), GFP_KERNEL);
+	if (!led->pdata)
+		return -ENOMEM;
+
+	spin_lock_init(&led->lock);
+
+	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;
+			}
+			i++;
+		}
+
+		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;
+			}
+			j++;
+		}
+	}
+
+	rc = qpnp_flash_led_register_interrupts(led);
+	if (rc < 0)
+		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;
+			}
+		}
+	}
+
+	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.h b/drivers/leds/leds.h
index 7d38e6b..dbb9a36 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -21,6 +21,22 @@ static inline int led_get_brightness(struct led_classdev *led_cdev)
 	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/include/linux/leds-qpnp-flash-v2.h b/include/linux/leds-qpnp-flash-v2.h
new file mode 100644
index 0000000..b961ab1
--- /dev/null
+++ b/include/linux/leds-qpnp-flash-v2.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef __LEDS_QPNP_FLASH_V2_H
+#define __LEDS_QPNP_FLASH_V2_H
+
+#include <linux/leds.h>
+#include <linux/notifier.h>
+
+enum flash_led_irq_type {
+	LED_FAULT_IRQ = BIT(0),
+	MITIGATION_IRQ = BIT(1),
+	FLASH_TIMER_EXP_IRQ = BIT(2),
+	ALL_RAMP_DOWN_DONE_IRQ = BIT(3),
+	ALL_RAMP_UP_DONE_IRQ = BIT(4),
+	LED3_RAMP_UP_DONE_IRQ = BIT(5),
+	LED2_RAMP_UP_DONE_IRQ = BIT(6),
+	LED1_RAMP_UP_DONE_IRQ = BIT(7),
+	INVALID_IRQ = BIT(8),
+};
+
+int qpnp_flash_led_register_irq_notifier(struct notifier_block *nb);
+int qpnp_flash_led_unregister_irq_notifier(struct notifier_block *nb);
+
+#endif
diff --git a/include/linux/leds-qpnp-flash.h b/include/linux/leds-qpnp-flash.h
new file mode 100644
index 0000000..d820d2c
--- /dev/null
+++ b/include/linux/leds-qpnp-flash.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef __LEDS_QPNP_FLASH_H
+#define __LEDS_QPNP_FLASH_H
+
+#include <linux/leds.h>
+
+#define ENABLE_REGULATOR		BIT(0)
+#define DISABLE_REGULATOR		BIT(1)
+#define QUERY_MAX_AVAIL_CURRENT		BIT(2)
+#define QUERY_MAX_CURRENT		BIT(3)
+
+#define FLASH_LED_PREPARE_OPTIONS_MASK	GENMASK(3, 0)
+
+int qpnp_flash_led_prepare(struct led_trigger *trig, int options,
+					int *max_current);
+#ifdef CONFIG_BACKLIGHT_QCOM_SPMI_WLED
+int wled_flash_led_prepare(struct led_trigger *trig, int options,
+					int *max_current);
+#else
+static inline int wled_flash_led_prepare(struct led_trigger *trig, int options,
+					int *max_current)
+{
+	return -EINVAL;
+}
+#endif
+
+#endif