summaryrefslogtreecommitdiff
path: root/drivers/leds
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds')
-rw-r--r--drivers/leds/Kconfig47
-rw-r--r--drivers/leds/Makefile5
-rw-r--r--drivers/leds/led-class.c22
-rw-r--r--drivers/leds/leds-qpnp-flash-common.c16
-rw-r--r--drivers/leds/leds-qpnp-flash-v2.c2479
-rw-r--r--drivers/leds/leds-qpnp-flash.c2711
-rw-r--r--drivers/leds/leds-qpnp-haptics.c2551
-rw-r--r--drivers/leds/leds-qpnp-wled.c2784
-rw-r--r--drivers/leds/leds-qpnp.c4450
-rw-r--r--drivers/leds/leds.h16
10 files changed, 15079 insertions, 2 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index b1ab8bdf8251..ab4d40857421 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -587,6 +587,43 @@ config LEDS_POWERNV
To compile this driver as a module, choose 'm' here: the module
will be called leds-powernv.
+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 && MFD_SPMI_PMIC
+ 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.
+
config LEDS_SYSCON
bool "LED support for LEDs on system controllers"
depends on LEDS_CLASS=y
@@ -605,6 +642,16 @@ config LEDS_VERSATILE
This option enabled support for the LEDs on the ARM Versatile
and RealView boards. Say Y to enabled these.
+config LEDS_QPNP_HAPTICS
+ tristate "Haptics support for QPNP PMIC"
+ depends on LEDS_CLASS && MFD_SPMI_PMIC
+ help
+ This option enables device driver support for the haptics peripheral
+ found on Qualcomm Technologies, Inc. QPNP PMICs. The haptic
+ peripheral is capable of driving both LRA and ERM vibrators. This
+ module provides haptic feedback for user actions such as a long press
+ on the touch screen.
+
comment "LED Triggers"
source "drivers/leds/trigger/Kconfig"
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index e9d53092765d..17cd850ad9ea 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -60,12 +60,17 @@ obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
+obj-$(CONFIG_LEDS_QPNP) += leds-qpnp.o
+obj-$(CONFIG_LEDS_QPNP_FLASH) += leds-qpnp-flash.o leds-qpnp-flash-common.o
+obj-$(CONFIG_LEDS_QPNP_FLASH_V2) += leds-qpnp-flash-v2.o leds-qpnp-flash-common.o
+obj-$(CONFIG_LEDS_QPNP_WLED) += leds-qpnp-wled.o
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o
obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
obj-$(CONFIG_LEDS_SEAD3) += leds-sead3.o
+obj-$(CONFIG_LEDS_QPNP_HAPTICS) += leds-qpnp-haptics.o
# LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 51a5b51ec467..cdb9018c3f02 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -53,9 +53,10 @@ static ssize_t brightness_store(struct device *dev,
if (ret)
goto unlock;
- if (state == LED_OFF)
+ if (state == LED_OFF && !(led_cdev->flags & LED_KEEP_TRIGGER))
led_trigger_remove(led_cdev);
led_set_brightness(led_cdev, state);
+ led_cdev->usr_brightness_req = state;
ret = size;
unlock:
@@ -71,7 +72,24 @@ static ssize_t max_brightness_show(struct device *dev,
return sprintf(buf, "%u\n", led_cdev->max_brightness);
}
-static DEVICE_ATTR_RO(max_brightness);
+
+static ssize_t max_brightness_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ unsigned long state;
+ ssize_t ret = -EINVAL;
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ led_cdev->max_brightness = state;
+ led_set_brightness(led_cdev, led_cdev->usr_brightness_req);
+
+ return size;
+}
+static DEVICE_ATTR_RW(max_brightness);
#ifdef CONFIG_LEDS_TRIGGERS
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
diff --git a/drivers/leds/leds-qpnp-flash-common.c b/drivers/leds/leds-qpnp-flash-common.c
new file mode 100644
index 000000000000..5aed9100bde4
--- /dev/null
+++ b/drivers/leds/leds-qpnp-flash-common.c
@@ -0,0 +1,16 @@
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/leds-qpnp-flash.h>
+
+int (*qpnp_flash_led_prepare)(struct led_trigger *trig, int options,
+ int *max_current);
diff --git a/drivers/leds/leds-qpnp-flash-v2.c b/drivers/leds/leds-qpnp-flash-v2.c
new file mode 100644
index 000000000000..86e70689ce2d
--- /dev/null
+++ b/drivers/leds/leds-qpnp-flash-v2.c
@@ -0,0 +1,2479 @@
+/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "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_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 type;
+ u8 ires_idx;
+ u8 default_ires_idx;
+ u8 hdrm_val;
+ u8 current_reg_val;
+ u8 strobe_ctrl;
+ u8 strobe_sel;
+ 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;
+};
+
+/*
+ * 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_init_settings(struct qpnp_flash_led *led)
+{
+ int rc, i, addr_offset;
+ u8 val = 0, mask, strobe_mask = 0, strobe_ctrl;
+
+ 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;
+
+ 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;
+ }
+
+ 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;
+
+ 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 = 0, rbatt_uohm = 0, ibat_now = 0, voltage_hdrm_mv = 0;
+ int rc = 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);
+ fnode->led_on = prgm_current_ma != 0;
+
+ 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_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 */
+ 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;
+}
+
+static int qpnp_flash_led_prepare_v2(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, 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_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;
+ bool hw_strobe = 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->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;
+ }
+
+ 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;
+
+ 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) {
+ 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_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;
+
+ 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_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 =
+ 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;
+
+ 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;
+ }
+
+ 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_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;
+ }
+
+ 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_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;
+
+ qpnp_flash_led_prepare = qpnp_flash_led_prepare_v2;
+ 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++;
+ }
+ }
+
+ /* 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 000000000000..cbb51c24dcf2
--- /dev/null
+++ b/drivers/leds/leds-qpnp-flash.c
@@ -0,0 +1,2711 @@
+/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/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 {
+ struct mutex debugfs_lock; /* Prevent thread concurrency */
+ size_t rpos;
+ size_t wpos;
+ size_t len;
+ struct qpnp_flash_led *led;
+ u32 buffer_cnt;
+ 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 dentry *dbgfs_root;
+ int num_leds;
+ 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);
+ mutex_init(&log->debugfs_lock);
+ log->led = led;
+
+ log->buffer_cnt = 1;
+ file->private_data = log;
+
+ 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_buffer *log = file->private_data;
+
+ if (log) {
+ file->private_data = NULL;
+ mutex_destroy(&log->debugfs_lock);
+ kfree(log);
+ }
+
+ return 0;
+}
+
+#define MIN_BUFFER_WRITE_LEN 20
+static int print_to_log(struct qpnp_flash_led_buffer *log,
+ const char *fmt, ...)
+{
+ va_list args;
+ int cnt;
+ char *log_buf;
+ size_t size = log->len - log->wpos;
+
+ if (size < MIN_BUFFER_WRITE_LEN)
+ return 0; /* not enough buffer left */
+
+ log_buf = &log->data[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_buffer *log = fp->private_data;
+ struct qpnp_flash_led *led;
+ uint val;
+ int rc = 0;
+ size_t len;
+ size_t ret;
+
+ if (!log) {
+ pr_err("error: file private data is NULL\n");
+ return -EFAULT;
+ }
+ led = log->led;
+
+ mutex_lock(&log->debugfs_lock);
+ if ((log->rpos >= log->wpos && log->buffer_cnt == 0) ||
+ ((log->len - log->wpos) < MIN_BUFFER_WRITE_LEN))
+ goto unlock_mutex;
+
+ 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);
+ goto unlock_mutex;
+ }
+ log->buffer_cnt--;
+
+ rc = print_to_log(log, "0x%05X ", INT_LATCHED_STS(led->base));
+ if (rc == 0)
+ goto unlock_mutex;
+
+ rc = print_to_log(log, "0x%02X ", val);
+ if (rc == 0)
+ goto unlock_mutex;
+
+ 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");
+ rc = -EFAULT;
+ goto unlock_mutex;
+ }
+
+ len -= ret;
+ *ppos += len;
+ log->rpos += len;
+
+ rc = len;
+
+unlock_mutex:
+ mutex_unlock(&log->debugfs_lock);
+ return rc;
+}
+
+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_buffer *log = fp->private_data;
+ struct qpnp_flash_led *led;
+ int rc = 0;
+ size_t len;
+ size_t ret;
+
+ if (!log) {
+ pr_err("error: file private data is NULL\n");
+ return -EFAULT;
+ }
+ led = log->led;
+
+ mutex_lock(&log->debugfs_lock);
+ if ((log->rpos >= log->wpos && log->buffer_cnt == 0) ||
+ ((log->len - log->wpos) < MIN_BUFFER_WRITE_LEN))
+ goto unlock_mutex;
+
+ log->buffer_cnt--;
+
+ rc = print_to_log(log, "0x%05X ", FLASH_LED_FAULT_STATUS(led->base));
+ if (rc == 0)
+ goto unlock_mutex;
+
+ rc = print_to_log(log, "0x%02X ", led->fault_reg);
+ if (rc == 0)
+ goto unlock_mutex;
+
+ 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");
+ rc = -EFAULT;
+ goto unlock_mutex;
+ }
+
+ len -= ret;
+ *ppos += len;
+ log->rpos += len;
+
+ rc = len;
+
+unlock_mutex:
+ mutex_unlock(&log->debugfs_lock);
+ return rc;
+}
+
+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_buffer *log = file->private_data;
+ struct qpnp_flash_led *led;
+ char *kbuf;
+
+ if (!log) {
+ pr_err("error: file private data is NULL\n");
+ return -EFAULT;
+ }
+ led = log->led;
+
+ mutex_lock(&log->debugfs_lock);
+ kbuf = kmalloc(count + 1, GFP_KERNEL);
+ if (!kbuf) {
+ ret = -ENOMEM;
+ goto unlock_mutex;
+ }
+
+ 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);
+unlock_mutex:
+ mutex_unlock(&log->debugfs_lock);
+ 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_buffer *log = file->private_data;
+ struct qpnp_flash_led *led;
+ char *kbuf;
+
+ if (!log) {
+ pr_err("error: file private data is NULL\n");
+ return -EFAULT;
+ }
+ led = log->led;
+
+ mutex_lock(&log->debugfs_lock);
+ kbuf = kmalloc(count + 1, GFP_KERNEL);
+ if (!kbuf) {
+ ret = -ENOMEM;
+ goto unlock_mutex;
+ }
+
+ 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);
+unlock_mutex:
+ mutex_unlock(&log->debugfs_lock);
+ 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;
+}
+
+static int qpnp_flash_led_prepare_v1(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 = 0;
+ 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);
+ qpnp_flash_led_prepare = qpnp_flash_led_prepare_v1;
+
+ led->pdata = devm_kzalloc(&pdev->dev, sizeof(*led->pdata), GFP_KERNEL);
+ if (!led->pdata)
+ return -ENOMEM;
+
+ rc = qpnp_flash_led_get_peripheral_type(led);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Failed to get peripheral type\n");
+ return rc;
+ }
+ led->peripheral_type = (u8) 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) {
+ j = -1;
+ 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_free_led_sysfs;
+ }
+
+ 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:
+ debugfs_remove_recursive(root);
+error_free_led_sysfs:
+ 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);
+ }
+ 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-haptics.c b/drivers/leds/leds-qpnp-haptics.c
new file mode 100644
index 000000000000..9c62ab6521e8
--- /dev/null
+++ b/drivers/leds/leds-qpnp-haptics.c
@@ -0,0 +1,2551 @@
+/* Copyright (c) 2014-2015, 2017, 2019, 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) "haptics: %s: " fmt, __func__
+
+#include <linux/atomic.h>
+#include <linux/delay.h>
+#include <linux/hrtimer.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/log2.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/qpnp-misc.h>
+#include <linux/qpnp/qpnp-revid.h>
+
+/* Register definitions */
+#define HAP_STATUS_1_REG(chip) (chip->base + 0x0A)
+#define HAP_BUSY_BIT BIT(1)
+#define SC_FLAG_BIT BIT(3)
+#define AUTO_RES_ERROR_BIT BIT(4)
+
+#define HAP_LRA_AUTO_RES_LO_REG(chip) (chip->base + 0x0B)
+#define HAP_LRA_AUTO_RES_HI_REG(chip) (chip->base + 0x0C)
+
+#define HAP_INT_RT_STS_REG(chip) (chip->base + 0x10)
+#define SC_INT_RT_STS_BIT BIT(0)
+#define PLAY_INT_RT_STS_BIT BIT(1)
+
+#define HAP_EN_CTL_REG(chip) (chip->base + 0x46)
+#define HAP_EN_BIT BIT(7)
+
+#define HAP_EN_CTL2_REG(chip) (chip->base + 0x48)
+#define BRAKE_EN_BIT BIT(0)
+
+#define HAP_AUTO_RES_CTRL_REG(chip) (chip->base + 0x4B)
+#define AUTO_RES_EN_BIT BIT(7)
+#define AUTO_RES_ERR_RECOVERY_BIT BIT(3)
+
+#define HAP_CFG1_REG(chip) (chip->base + 0x4C)
+#define HAP_ACT_TYPE_MASK BIT(0)
+#define HAP_LRA 0
+#define HAP_ERM 1
+
+#define HAP_CFG2_REG(chip) (chip->base + 0x4D)
+#define HAP_WAVE_SINE 0
+#define HAP_WAVE_SQUARE 1
+#define HAP_LRA_RES_TYPE_MASK BIT(0)
+
+#define HAP_SEL_REG(chip) (chip->base + 0x4E)
+#define HAP_WF_SOURCE_MASK GENMASK(5, 4)
+#define HAP_WF_SOURCE_SHIFT 4
+
+#define HAP_LRA_AUTO_RES_REG(chip) (chip->base + 0x4F)
+/* For pmi8998 */
+#define LRA_AUTO_RES_MODE_MASK GENMASK(6, 4)
+#define LRA_AUTO_RES_MODE_SHIFT 4
+#define LRA_HIGH_Z_MASK GENMASK(3, 2)
+#define LRA_HIGH_Z_SHIFT 2
+#define LRA_RES_CAL_MASK GENMASK(1, 0)
+#define HAP_RES_CAL_PERIOD_MIN 4
+#define HAP_RES_CAL_PERIOD_MAX 32
+/* For pm660 */
+#define PM660_AUTO_RES_MODE_BIT BIT(7)
+#define PM660_AUTO_RES_MODE_SHIFT 7
+#define PM660_CAL_DURATION_MASK GENMASK(6, 5)
+#define PM660_CAL_DURATION_SHIFT 5
+#define PM660_QWD_DRIVE_DURATION_BIT BIT(4)
+#define PM660_QWD_DRIVE_DURATION_SHIFT 4
+#define PM660_CAL_EOP_BIT BIT(3)
+#define PM660_CAL_EOP_SHIFT 3
+#define PM660_LRA_RES_CAL_MASK GENMASK(2, 0)
+#define HAP_PM660_RES_CAL_PERIOD_MAX 256
+
+#define HAP_VMAX_CFG_REG(chip) (chip->base + 0x51)
+#define HAP_VMAX_OVD_BIT BIT(6)
+#define HAP_VMAX_MASK GENMASK(5, 1)
+#define HAP_VMAX_SHIFT 1
+#define HAP_VMAX_MIN_MV 116
+#define HAP_VMAX_MAX_MV 3596
+
+#define HAP_ILIM_CFG_REG(chip) (chip->base + 0x52)
+#define HAP_ILIM_SEL_MASK BIT(0)
+#define HAP_ILIM_400_MA 0
+#define HAP_ILIM_800_MA 1
+
+#define HAP_SC_DEB_REG(chip) (chip->base + 0x53)
+#define HAP_SC_DEB_MASK GENMASK(2, 0)
+#define HAP_SC_DEB_CYCLES_MIN 0
+#define HAP_DEF_SC_DEB_CYCLES 8
+#define HAP_SC_DEB_CYCLES_MAX 32
+
+#define HAP_RATE_CFG1_REG(chip) (chip->base + 0x54)
+#define HAP_RATE_CFG1_MASK GENMASK(7, 0)
+
+#define HAP_RATE_CFG2_REG(chip) (chip->base + 0x55)
+#define HAP_RATE_CFG2_MASK GENMASK(3, 0)
+/* Shift needed to convert drive period upper bits [11:8] */
+#define HAP_RATE_CFG2_SHIFT 8
+
+#define HAP_INT_PWM_REG(chip) (chip->base + 0x56)
+#define INT_PWM_FREQ_SEL_MASK GENMASK(1, 0)
+#define INT_PWM_FREQ_253_KHZ 0
+#define INT_PWM_FREQ_505_KHZ 1
+#define INT_PWM_FREQ_739_KHZ 2
+#define INT_PWM_FREQ_1076_KHZ 3
+
+#define HAP_EXT_PWM_REG(chip) (chip->base + 0x57)
+#define EXT_PWM_FREQ_SEL_MASK GENMASK(1, 0)
+#define EXT_PWM_FREQ_25_KHZ 0
+#define EXT_PWM_FREQ_50_KHZ 1
+#define EXT_PWM_FREQ_75_KHZ 2
+#define EXT_PWM_FREQ_100_KHZ 3
+
+#define HAP_PWM_CAP_REG(chip) (chip->base + 0x58)
+
+#define HAP_SC_CLR_REG(chip) (chip->base + 0x59)
+#define SC_CLR_BIT BIT(0)
+
+#define HAP_BRAKE_REG(chip) (chip->base + 0x5C)
+#define HAP_BRAKE_PAT_MASK 0x3
+
+#define HAP_WF_REPEAT_REG(chip) (chip->base + 0x5E)
+#define WF_REPEAT_MASK GENMASK(6, 4)
+#define WF_REPEAT_SHIFT 4
+#define WF_REPEAT_MIN 1
+#define WF_REPEAT_MAX 128
+#define WF_S_REPEAT_MASK GENMASK(1, 0)
+#define WF_S_REPEAT_MIN 1
+#define WF_S_REPEAT_MAX 8
+
+#define HAP_WF_S1_REG(chip) (chip->base + 0x60)
+#define HAP_WF_SIGN_BIT BIT(7)
+#define HAP_WF_OVD_BIT BIT(6)
+#define HAP_WF_SAMP_MAX GENMASK(5, 1)
+#define HAP_WF_SAMPLE_LEN 8
+
+#define HAP_PLAY_REG(chip) (chip->base + 0x70)
+#define PLAY_BIT BIT(7)
+#define PAUSE_BIT BIT(0)
+
+#define HAP_SEC_ACCESS_REG(chip) (chip->base + 0xD0)
+
+#define HAP_TEST2_REG(chip) (chip->base + 0xE3)
+#define HAP_EXT_PWM_DTEST_MASK GENMASK(6, 4)
+#define HAP_EXT_PWM_DTEST_SHIFT 4
+#define PWM_MAX_DTEST_LINES 4
+#define HAP_EXT_PWM_PEAK_DATA 0x7F
+#define HAP_EXT_PWM_HALF_DUTY 50
+#define HAP_EXT_PWM_FULL_DUTY 100
+#define HAP_EXT_PWM_DATA_FACTOR 39
+
+/* Other definitions */
+#define HAP_BRAKE_PAT_LEN 4
+#define HAP_WAVE_SAMP_LEN 8
+#define NUM_WF_SET 4
+#define HAP_WAVE_SAMP_SET_LEN (HAP_WAVE_SAMP_LEN * NUM_WF_SET)
+#define HAP_RATE_CFG_STEP_US 5
+#define HAP_WAVE_PLAY_RATE_US_MIN 0
+#define HAP_DEF_WAVE_PLAY_RATE_US 5715
+#define HAP_WAVE_PLAY_RATE_US_MAX 20475
+#define HAP_MAX_PLAY_TIME_MS 15000
+
+enum hap_brake_pat {
+ NO_BRAKE = 0,
+ BRAKE_VMAX_4,
+ BRAKE_VMAX_2,
+ BRAKE_VMAX,
+};
+
+enum hap_auto_res_mode {
+ HAP_AUTO_RES_NONE,
+ HAP_AUTO_RES_ZXD,
+ HAP_AUTO_RES_QWD,
+ HAP_AUTO_RES_MAX_QWD,
+ HAP_AUTO_RES_ZXD_EOP,
+};
+
+enum hap_pm660_auto_res_mode {
+ HAP_PM660_AUTO_RES_ZXD,
+ HAP_PM660_AUTO_RES_QWD,
+};
+
+/* high Z option lines */
+enum hap_high_z {
+ HAP_LRA_HIGH_Z_NONE, /* opt0 for PM660 */
+ HAP_LRA_HIGH_Z_OPT1,
+ HAP_LRA_HIGH_Z_OPT2,
+ HAP_LRA_HIGH_Z_OPT3,
+};
+
+/* play modes */
+enum hap_mode {
+ HAP_DIRECT,
+ HAP_BUFFER,
+ HAP_AUDIO,
+ HAP_PWM,
+};
+
+/* wave/sample repeat */
+enum hap_rep_type {
+ HAP_WAVE_REPEAT = 1,
+ HAP_WAVE_SAMP_REPEAT,
+};
+
+/* status flags */
+enum hap_status {
+ AUTO_RESONANCE_ENABLED = BIT(0),
+};
+
+enum hap_play_control {
+ HAP_STOP,
+ HAP_PAUSE,
+ HAP_PLAY,
+};
+
+/* pwm channel parameters */
+struct pwm_param {
+ struct pwm_device *pwm_dev;
+ u32 duty_us;
+ u32 period_us;
+};
+
+/*
+ * hap_lra_ares_param - Haptic auto_resonance parameters
+ * @ lra_qwd_drive_duration - LRA QWD drive duration
+ * @ calibrate_at_eop - Calibrate at EOP
+ * @ lra_res_cal_period - LRA resonance calibration period
+ * @ auto_res_mode - auto resonace mode
+ * @ lra_high_z - high z option line
+ */
+struct hap_lra_ares_param {
+ int lra_qwd_drive_duration;
+ int calibrate_at_eop;
+ enum hap_high_z lra_high_z;
+ u16 lra_res_cal_period;
+ u8 auto_res_mode;
+};
+
+/*
+ * hap_chip - Haptics data structure
+ * @ pdev - platform device pointer
+ * @ regmap - regmap pointer
+ * @ bus_lock - spin lock for bus read/write
+ * @ play_lock - mutex lock for haptics play/enable control
+ * @ haptics_work - haptics worker
+ * @ stop_timer - hrtimer for stopping haptics
+ * @ auto_res_err_poll_timer - hrtimer for auto-resonance error
+ * @ base - base address
+ * @ play_irq - irq for play
+ * @ sc_irq - irq for short circuit
+ * @ pwm_data - pwm configuration
+ * @ ares_cfg - auto resonance configuration
+ * @ play_time_ms - play time set by the user in ms
+ * @ max_play_time_ms - max play time in ms
+ * @ vmax_mv - max voltage in mv
+ * @ ilim_ma - limiting current in ma
+ * @ sc_deb_cycles - short circuit debounce cycles
+ * @ wave_play_rate_us - play rate for waveform
+ * @ last_rate_cfg - Last rate config updated
+ * @ wave_rep_cnt - waveform repeat count
+ * @ wave_s_rep_cnt - waveform sample repeat count
+ * @ wf_samp_len - waveform sample length
+ * @ ext_pwm_freq_khz - external pwm frequency in KHz
+ * @ ext_pwm_dtest_line - DTEST line for external pwm
+ * @ status_flags - status
+ * @ play_mode - play mode
+ * @ act_type - actuator type
+ * @ wave_shape - waveform shape
+ * @ wave_samp_idx - wave sample id used to refer start of a sample set
+ * @ wave_samp - array of wave samples
+ * @ brake_pat - pattern for active breaking
+ * @ en_brake - brake state
+ * @ misc_clk_trim_error_reg - MISC clock trim error register if present
+ * @ clk_trim_error_code - MISC clock trim error code
+ * @ drive_period_code_max_limit - calculated drive period code with
+ percentage variation on the higher side.
+ * @ drive_period_code_min_limit - calculated drive period code with
+ percentage variation on the lower side
+ * @ drive_period_code_max_var_pct - maximum limit of percentage variation of
+ drive period code
+ * @ drive_period_code_min_var_pct - minimum limit of percentage variation of
+ drive period code
+ * @ last_sc_time - Last time short circuit was detected
+ * @ sc_count - counter to determine the duration of short circuit
+ condition
+ * @ perm_disable - Flag to disable module permanently
+ * @ state - current state of haptics
+ * @ module_en - module enable status of haptics
+ * @ lra_auto_mode - Auto mode selection
+ * @ play_irq_en - Play interrupt enable status
+ * @ auto_res_err_recovery_hw - Enable auto resonance error recovery by HW
+ */
+struct hap_chip {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct pmic_revid_data *revid;
+ struct led_classdev cdev;
+ spinlock_t bus_lock;
+ struct mutex play_lock;
+ struct mutex param_lock;
+ struct work_struct haptics_work;
+ struct hrtimer stop_timer;
+ struct hrtimer auto_res_err_poll_timer;
+ u16 base;
+ int play_irq;
+ int sc_irq;
+ struct pwm_param pwm_data;
+ struct hap_lra_ares_param ares_cfg;
+ struct regulator *vcc_pon;
+ u32 play_time_ms;
+ u32 max_play_time_ms;
+ u32 vmax_mv;
+ u8 ilim_ma;
+ u32 sc_deb_cycles;
+ u32 wave_play_rate_us;
+ u16 last_rate_cfg;
+ u32 wave_rep_cnt;
+ u32 wave_s_rep_cnt;
+ u32 wf_samp_len;
+ u32 ext_pwm_freq_khz;
+ u8 ext_pwm_dtest_line;
+ u32 status_flags;
+ enum hap_mode play_mode;
+ u8 act_type;
+ u8 wave_shape;
+ u8 wave_samp_idx;
+ u32 wave_samp[HAP_WAVE_SAMP_SET_LEN];
+ u32 brake_pat[HAP_BRAKE_PAT_LEN];
+ bool en_brake;
+ u32 misc_clk_trim_error_reg;
+ u8 clk_trim_error_code;
+ u16 drive_period_code_max_limit;
+ u16 drive_period_code_min_limit;
+ u8 drive_period_code_max_var_pct;
+ u8 drive_period_code_min_var_pct;
+ ktime_t last_sc_time;
+ u8 sc_count;
+ bool perm_disable;
+ atomic_t state;
+ bool module_en;
+ bool lra_auto_mode;
+ bool play_irq_en;
+ bool auto_res_err_recovery_hw;
+ bool vcc_pon_enabled;
+};
+
+static int qpnp_haptics_parse_buffer_dt(struct hap_chip *chip);
+static int qpnp_haptics_parse_pwm_dt(struct hap_chip *chip);
+
+static int qpnp_haptics_read_reg(struct hap_chip *chip, u16 addr, u8 *val,
+ int len)
+{
+ int rc;
+
+ rc = regmap_bulk_read(chip->regmap, addr, val, len);
+ if (rc < 0)
+ pr_err("Error reading address: 0x%x - rc %d\n", addr, rc);
+
+ return rc;
+}
+
+static inline bool is_secure(u16 addr)
+{
+ return ((addr & 0xFF) > 0xD0);
+}
+
+static int qpnp_haptics_write_reg(struct hap_chip *chip, u16 addr, u8 *val,
+ int len)
+{
+ unsigned long flags;
+ unsigned int unlock = 0xA5;
+ int rc = 0, i;
+
+ spin_lock_irqsave(&chip->bus_lock, flags);
+
+ if (is_secure(addr)) {
+ for (i = 0; i < len; i++) {
+ rc = regmap_write(chip->regmap,
+ HAP_SEC_ACCESS_REG(chip), unlock);
+ if (rc < 0) {
+ pr_err("Error writing unlock code - rc %d\n",
+ rc);
+ goto out;
+ }
+
+ rc = regmap_write(chip->regmap, addr + i, val[i]);
+ if (rc < 0) {
+ pr_err("Error writing address 0x%x - rc %d\n",
+ addr + i, rc);
+ goto out;
+ }
+ }
+ } else {
+ if (len > 1)
+ rc = regmap_bulk_write(chip->regmap, addr, val, len);
+ else
+ rc = regmap_write(chip->regmap, addr, *val);
+ }
+
+ if (rc < 0)
+ pr_err("Error writing address: 0x%x - rc %d\n", addr, rc);
+
+out:
+ spin_unlock_irqrestore(&chip->bus_lock, flags);
+ return rc;
+}
+
+static int qpnp_haptics_masked_write_reg(struct hap_chip *chip, u16 addr,
+ u8 mask, u8 val)
+{
+ unsigned long flags;
+ unsigned int unlock = 0xA5;
+ int rc;
+
+ spin_lock_irqsave(&chip->bus_lock, flags);
+ if (is_secure(addr)) {
+ rc = regmap_write(chip->regmap, HAP_SEC_ACCESS_REG(chip),
+ unlock);
+ if (rc < 0) {
+ pr_err("Error writing unlock code - rc %d\n", rc);
+ goto out;
+ }
+ }
+
+ rc = regmap_update_bits(chip->regmap, addr, mask, val);
+ if (rc < 0)
+ pr_err("Error writing address: 0x%x - rc %d\n", addr, rc);
+
+ if (!rc)
+ pr_debug("wrote to address 0x%x = 0x%x\n", addr, val);
+out:
+ spin_unlock_irqrestore(&chip->bus_lock, flags);
+ return rc;
+}
+
+static inline int get_buffer_mode_duration(struct hap_chip *chip)
+{
+ int sample_count, sample_duration;
+
+ sample_count = chip->wave_rep_cnt * chip->wave_s_rep_cnt *
+ chip->wf_samp_len;
+ sample_duration = sample_count * chip->wave_play_rate_us;
+ pr_debug("sample_count: %d sample_duration: %d\n", sample_count,
+ sample_duration);
+
+ return (sample_duration / 1000);
+}
+
+static bool is_sw_lra_auto_resonance_control(struct hap_chip *chip)
+{
+ if (chip->act_type != HAP_LRA)
+ return false;
+
+ if (chip->auto_res_err_recovery_hw)
+ return false;
+
+ /*
+ * For short pattern in auto mode, we use buffer mode and auto
+ * resonance is not needed.
+ */
+ if (chip->lra_auto_mode && chip->play_mode == HAP_BUFFER)
+ return false;
+
+ return true;
+}
+
+#define HAPTICS_BACK_EMF_DELAY_US 20000
+static int qpnp_haptics_auto_res_enable(struct hap_chip *chip, bool enable)
+{
+ int rc = 0;
+ u32 delay_us = HAPTICS_BACK_EMF_DELAY_US;
+ u8 val;
+ bool auto_res_mode_qwd;
+
+ if (chip->act_type != HAP_LRA)
+ return 0;
+
+ if (chip->revid->pmic_subtype == PM660_SUBTYPE)
+ auto_res_mode_qwd = (chip->ares_cfg.auto_res_mode ==
+ HAP_PM660_AUTO_RES_QWD);
+ else
+ auto_res_mode_qwd = (chip->ares_cfg.auto_res_mode ==
+ HAP_AUTO_RES_QWD);
+
+ /*
+ * Do not enable auto resonance if auto mode is enabled and auto
+ * resonance mode is QWD, meaning long pattern.
+ */
+ if (chip->lra_auto_mode && auto_res_mode_qwd && enable) {
+ pr_debug("auto_mode enabled, not enabling auto_res\n");
+ return 0;
+ }
+
+ /*
+ * For auto resonance detection to work properly, sufficient back-emf
+ * has to be generated. In general, back-emf takes some time to build
+ * up. When the auto resonance mode is chosen as QWD, high-z will be
+ * applied for every LRA cycle and hence there won't be enough back-emf
+ * at the start-up. Hence, the motor needs to vibrate for few LRA cycles
+ * after the PLAY bit is asserted. Enable the auto resonance after
+ * 'time_required_to_generate_back_emf_us' is completed.
+ */
+
+ if (auto_res_mode_qwd && enable)
+ usleep_range(delay_us, delay_us + 1);
+
+ val = enable ? AUTO_RES_EN_BIT : 0;
+
+ if (chip->revid->pmic_subtype == PM660_SUBTYPE)
+ rc = qpnp_haptics_masked_write_reg(chip,
+ HAP_AUTO_RES_CTRL_REG(chip),
+ AUTO_RES_EN_BIT, val);
+ else
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_TEST2_REG(chip),
+ AUTO_RES_EN_BIT, val);
+ if (rc < 0)
+ return rc;
+
+ if (enable)
+ chip->status_flags |= AUTO_RESONANCE_ENABLED;
+ else
+ chip->status_flags &= ~AUTO_RESONANCE_ENABLED;
+
+ pr_debug("auto_res %sabled\n", enable ? "en" : "dis");
+ return rc;
+}
+
+static int qpnp_haptics_update_rate_cfg(struct hap_chip *chip, u16 play_rate)
+{
+ int rc;
+ u8 val[2];
+
+ if (chip->last_rate_cfg == play_rate) {
+ pr_debug("Same rate_cfg %x\n", play_rate);
+ return 0;
+ }
+
+ val[0] = play_rate & HAP_RATE_CFG1_MASK;
+ val[1] = (play_rate >> HAP_RATE_CFG2_SHIFT) & HAP_RATE_CFG2_MASK;
+ rc = qpnp_haptics_write_reg(chip, HAP_RATE_CFG1_REG(chip), val, 2);
+ if (rc < 0)
+ return rc;
+
+ pr_debug("Play rate code 0x%x\n", play_rate);
+ chip->last_rate_cfg = play_rate;
+ return 0;
+}
+
+static void qpnp_haptics_update_lra_frequency(struct hap_chip *chip)
+{
+ u8 lra_auto_res[2], val;
+ u32 play_rate_code;
+ u16 rate_cfg;
+ int rc;
+
+ rc = qpnp_haptics_read_reg(chip, HAP_LRA_AUTO_RES_LO_REG(chip),
+ lra_auto_res, 2);
+ if (rc < 0) {
+ pr_err("Error in reading LRA_AUTO_RES_LO/HI, rc=%d\n", rc);
+ return;
+ }
+
+ play_rate_code =
+ (lra_auto_res[1] & 0xF0) << 4 | (lra_auto_res[0] & 0xFF);
+
+ pr_debug("lra_auto_res_lo = 0x%x lra_auto_res_hi = 0x%x play_rate_code = 0x%x\n",
+ lra_auto_res[0], lra_auto_res[1], play_rate_code);
+
+ rc = qpnp_haptics_read_reg(chip, HAP_STATUS_1_REG(chip), &val, 1);
+ if (rc < 0)
+ return;
+
+ /*
+ * If the drive period code read from AUTO_RES_LO and AUTO_RES_HI
+ * registers is more than the max limit percent variation or less
+ * than the min limit percent variation specified through DT, then
+ * auto-resonance is disabled.
+ */
+
+ if ((val & AUTO_RES_ERROR_BIT) ||
+ ((play_rate_code <= chip->drive_period_code_min_limit) ||
+ (play_rate_code >= chip->drive_period_code_max_limit))) {
+ if (val & AUTO_RES_ERROR_BIT)
+ pr_debug("Auto-resonance error %x\n", val);
+ else
+ pr_debug("play rate %x out of bounds [min: 0x%x, max: 0x%x]\n",
+ play_rate_code,
+ chip->drive_period_code_min_limit,
+ chip->drive_period_code_max_limit);
+ rc = qpnp_haptics_auto_res_enable(chip, false);
+ if (rc < 0)
+ pr_debug("Auto-resonance disable failed\n");
+ return;
+ }
+
+ /*
+ * bits[7:4] of AUTO_RES_HI should be written to bits[3:0] of RATE_CFG2
+ */
+ lra_auto_res[1] >>= 4;
+ rate_cfg = lra_auto_res[1] << 8 | lra_auto_res[0];
+ rc = qpnp_haptics_update_rate_cfg(chip, rate_cfg);
+ if (rc < 0)
+ pr_debug("Error in updating rate_cfg\n");
+}
+
+#define MAX_RETRIES 5
+#define HAP_CYCLES 4
+static bool is_haptics_idle(struct hap_chip *chip)
+{
+ unsigned long wait_time_us;
+ int rc, i;
+ u8 val;
+
+ rc = qpnp_haptics_read_reg(chip, HAP_STATUS_1_REG(chip), &val, 1);
+ if (rc < 0)
+ return false;
+
+ if (!(val & HAP_BUSY_BIT))
+ return true;
+
+ if (chip->play_time_ms <= 20)
+ wait_time_us = chip->play_time_ms * 1000;
+ else
+ wait_time_us = chip->wave_play_rate_us * HAP_CYCLES;
+
+ for (i = 0; i < MAX_RETRIES; i++) {
+ /* wait for play_rate cycles */
+ usleep_range(wait_time_us, wait_time_us + 1);
+
+ if (chip->play_mode == HAP_DIRECT ||
+ chip->play_mode == HAP_PWM)
+ return true;
+
+ rc = qpnp_haptics_read_reg(chip, HAP_STATUS_1_REG(chip), &val,
+ 1);
+ if (rc < 0)
+ return false;
+
+ if (!(val & HAP_BUSY_BIT))
+ return true;
+ }
+
+ if (i >= MAX_RETRIES && (val & HAP_BUSY_BIT)) {
+ pr_debug("Haptics Busy after %d retries\n", i);
+ return false;
+ }
+
+ return true;
+}
+
+static int qpnp_haptics_mod_enable(struct hap_chip *chip, bool enable)
+{
+ u8 val;
+ int rc;
+
+ if (chip->module_en == enable)
+ return 0;
+
+ if (!enable) {
+ if (!is_haptics_idle(chip))
+ pr_debug("Disabling module forcibly\n");
+ }
+
+ val = enable ? HAP_EN_BIT : 0;
+ rc = qpnp_haptics_write_reg(chip, HAP_EN_CTL_REG(chip), &val, 1);
+ if (rc < 0)
+ return rc;
+
+ chip->module_en = enable;
+ return 0;
+}
+
+static int qpnp_haptics_play_control(struct hap_chip *chip,
+ enum hap_play_control ctrl)
+{
+ u8 val;
+ int rc;
+
+ switch (ctrl) {
+ case HAP_STOP:
+ val = 0;
+ break;
+ case HAP_PAUSE:
+ val = PAUSE_BIT;
+ break;
+ case HAP_PLAY:
+ val = PLAY_BIT;
+ break;
+ default:
+ return 0;
+ }
+
+ rc = qpnp_haptics_write_reg(chip, HAP_PLAY_REG(chip), &val, 1);
+ if (rc < 0) {
+ pr_err("Error in writing to PLAY_REG, rc=%d\n", rc);
+ return rc;
+ }
+
+ pr_debug("haptics play ctrl: %d\n", ctrl);
+ return rc;
+}
+
+#define AUTO_RES_ERR_POLL_TIME_NS (20 * NSEC_PER_MSEC)
+static int qpnp_haptics_play(struct hap_chip *chip, bool enable)
+{
+ int rc = 0, time_ms = chip->play_time_ms;
+
+ if (chip->perm_disable && enable)
+ return 0;
+
+ mutex_lock(&chip->play_lock);
+
+ if (enable) {
+ if (chip->play_mode == HAP_PWM) {
+ rc = pwm_enable(chip->pwm_data.pwm_dev);
+ if (rc < 0) {
+ pr_err("Error in enabling PWM, rc=%d\n", rc);
+ goto out;
+ }
+ }
+
+ rc = qpnp_haptics_auto_res_enable(chip, false);
+ if (rc < 0) {
+ pr_err("Error in disabling auto_res, rc=%d\n", rc);
+ goto out;
+ }
+
+ rc = qpnp_haptics_mod_enable(chip, true);
+ if (rc < 0) {
+ pr_err("Error in enabling module, rc=%d\n", rc);
+ goto out;
+ }
+
+ rc = qpnp_haptics_play_control(chip, HAP_PLAY);
+ if (rc < 0) {
+ pr_err("Error in enabling play, rc=%d\n", rc);
+ goto out;
+ }
+
+ if (chip->play_mode == HAP_BUFFER)
+ time_ms = get_buffer_mode_duration(chip);
+ hrtimer_start(&chip->stop_timer,
+ ktime_set(time_ms / MSEC_PER_SEC,
+ (time_ms % MSEC_PER_SEC) * NSEC_PER_MSEC),
+ HRTIMER_MODE_REL);
+
+ rc = qpnp_haptics_auto_res_enable(chip, true);
+ if (rc < 0) {
+ pr_err("Error in enabling auto_res, rc=%d\n", rc);
+ goto out;
+ }
+
+ if (is_sw_lra_auto_resonance_control(chip))
+ hrtimer_start(&chip->auto_res_err_poll_timer,
+ ktime_set(0, AUTO_RES_ERR_POLL_TIME_NS),
+ HRTIMER_MODE_REL);
+ } else {
+ rc = qpnp_haptics_play_control(chip, HAP_STOP);
+ if (rc < 0) {
+ pr_err("Error in disabling play, rc=%d\n", rc);
+ goto out;
+ }
+
+ if (is_sw_lra_auto_resonance_control(chip)) {
+ if (chip->status_flags & AUTO_RESONANCE_ENABLED)
+ qpnp_haptics_update_lra_frequency(chip);
+ hrtimer_cancel(&chip->auto_res_err_poll_timer);
+ }
+
+ if (chip->play_mode == HAP_PWM)
+ pwm_disable(chip->pwm_data.pwm_dev);
+
+ if (chip->play_mode == HAP_BUFFER)
+ chip->wave_samp_idx = 0;
+ }
+
+out:
+ mutex_unlock(&chip->play_lock);
+ return rc;
+}
+
+static void qpnp_haptics_work(struct work_struct *work)
+{
+ struct hap_chip *chip = container_of(work, struct hap_chip,
+ haptics_work);
+ int rc;
+ bool enable;
+
+ enable = atomic_read(&chip->state);
+ pr_debug("state: %d\n", enable);
+
+ if (chip->vcc_pon && enable && !chip->vcc_pon_enabled) {
+ rc = regulator_enable(chip->vcc_pon);
+ if (rc < 0)
+ pr_err("%s: could not enable vcc_pon regulator rc=%d\n",
+ __func__, rc);
+ else
+ chip->vcc_pon_enabled = true;
+ }
+
+ rc = qpnp_haptics_play(chip, enable);
+ if (rc < 0)
+ pr_err("Error in %sing haptics, rc=%d\n",
+ enable ? "play" : "stopp", rc);
+
+ if (chip->vcc_pon && !enable && chip->vcc_pon_enabled) {
+ rc = regulator_disable(chip->vcc_pon);
+ if (rc)
+ pr_err("%s: could not disable vcc_pon regulator rc=%d\n",
+ __func__, rc);
+ else
+ chip->vcc_pon_enabled = false;
+ }
+}
+
+static enum hrtimer_restart hap_stop_timer(struct hrtimer *timer)
+{
+ struct hap_chip *chip = container_of(timer, struct hap_chip,
+ stop_timer);
+
+ atomic_set(&chip->state, 0);
+ schedule_work(&chip->haptics_work);
+
+ return HRTIMER_NORESTART;
+}
+
+static enum hrtimer_restart hap_auto_res_err_poll_timer(struct hrtimer *timer)
+{
+ struct hap_chip *chip = container_of(timer, struct hap_chip,
+ auto_res_err_poll_timer);
+
+ if (!(chip->status_flags & AUTO_RESONANCE_ENABLED))
+ return HRTIMER_NORESTART;
+
+ qpnp_haptics_update_lra_frequency(chip);
+ hrtimer_forward(&chip->auto_res_err_poll_timer, ktime_get(),
+ ktime_set(0, AUTO_RES_ERR_POLL_TIME_NS));
+
+ return HRTIMER_NORESTART;
+}
+
+static int qpnp_haptics_suspend(struct device *dev)
+{
+ struct hap_chip *chip = dev_get_drvdata(dev);
+ int rc;
+
+ rc = qpnp_haptics_play(chip, false);
+ if (rc < 0)
+ pr_err("Error in stopping haptics, rc=%d\n", rc);
+
+ rc = qpnp_haptics_mod_enable(chip, false);
+ if (rc < 0)
+ pr_err("Error in disabling module, rc=%d\n", rc);
+
+ return 0;
+}
+
+static int qpnp_haptics_wave_rep_config(struct hap_chip *chip,
+ enum hap_rep_type type)
+{
+ int rc;
+ u8 val = 0, mask = 0;
+
+ if (type & HAP_WAVE_REPEAT) {
+ if (chip->wave_rep_cnt < WF_REPEAT_MIN)
+ chip->wave_rep_cnt = WF_REPEAT_MIN;
+ else if (chip->wave_rep_cnt > WF_REPEAT_MAX)
+ chip->wave_rep_cnt = WF_REPEAT_MAX;
+ mask = WF_REPEAT_MASK;
+ val = ilog2(chip->wave_rep_cnt) << WF_REPEAT_SHIFT;
+ }
+
+ if (type & HAP_WAVE_SAMP_REPEAT) {
+ if (chip->wave_s_rep_cnt < WF_S_REPEAT_MIN)
+ chip->wave_s_rep_cnt = WF_S_REPEAT_MIN;
+ else if (chip->wave_s_rep_cnt > WF_S_REPEAT_MAX)
+ chip->wave_s_rep_cnt = WF_S_REPEAT_MAX;
+ mask |= WF_S_REPEAT_MASK;
+ val |= ilog2(chip->wave_s_rep_cnt);
+ }
+
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_WF_REPEAT_REG(chip),
+ mask, val);
+ return rc;
+}
+
+/* configuration api for buffer mode */
+static int qpnp_haptics_buffer_config(struct hap_chip *chip, u32 *wave_samp,
+ bool overdrive)
+{
+ u8 buf[HAP_WAVE_SAMP_LEN];
+ u32 *ptr;
+ int rc, i;
+
+ if (wave_samp) {
+ ptr = wave_samp;
+ } else {
+ if (chip->wave_samp_idx >= ARRAY_SIZE(chip->wave_samp)) {
+ pr_err("Incorrect wave_samp_idx %d\n",
+ chip->wave_samp_idx);
+ return -EINVAL;
+ }
+
+ ptr = &chip->wave_samp[chip->wave_samp_idx];
+ }
+
+ /* Don't set override bit in waveform sample for PM660 */
+ if (chip->revid->pmic_subtype == PM660_SUBTYPE)
+ overdrive = false;
+
+ /* Configure WAVE_SAMPLE1 to WAVE_SAMPLE8 register */
+ for (i = 0; i < HAP_WAVE_SAMP_LEN; i++) {
+ buf[i] = ptr[i];
+ if (buf[i])
+ buf[i] |= (overdrive ? HAP_WF_OVD_BIT : 0);
+ }
+
+ rc = qpnp_haptics_write_reg(chip, HAP_WF_S1_REG(chip), buf,
+ HAP_WAVE_SAMP_LEN);
+ return rc;
+}
+
+/* configuration api for pwm */
+static int qpnp_haptics_pwm_config(struct hap_chip *chip)
+{
+ u8 val = 0;
+ int rc;
+
+ if (chip->ext_pwm_freq_khz == 0)
+ return 0;
+
+ /* Configure the EXTERNAL_PWM register */
+ if (chip->ext_pwm_freq_khz <= EXT_PWM_FREQ_25_KHZ) {
+ chip->ext_pwm_freq_khz = EXT_PWM_FREQ_25_KHZ;
+ val = 0;
+ } else if (chip->ext_pwm_freq_khz <= EXT_PWM_FREQ_50_KHZ) {
+ chip->ext_pwm_freq_khz = EXT_PWM_FREQ_50_KHZ;
+ val = 1;
+ } else if (chip->ext_pwm_freq_khz <= EXT_PWM_FREQ_75_KHZ) {
+ chip->ext_pwm_freq_khz = EXT_PWM_FREQ_75_KHZ;
+ val = 2;
+ } else {
+ chip->ext_pwm_freq_khz = EXT_PWM_FREQ_100_KHZ;
+ val = 3;
+ }
+
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_EXT_PWM_REG(chip),
+ EXT_PWM_FREQ_SEL_MASK, val);
+ if (rc < 0)
+ return rc;
+
+ if (chip->ext_pwm_dtest_line < 0 ||
+ chip->ext_pwm_dtest_line > PWM_MAX_DTEST_LINES) {
+ pr_err("invalid dtest line\n");
+ return -EINVAL;
+ }
+
+ if (chip->ext_pwm_dtest_line > 0) {
+ /* disable auto res for PWM mode */
+ val = chip->ext_pwm_dtest_line << HAP_EXT_PWM_DTEST_SHIFT;
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_TEST2_REG(chip),
+ HAP_EXT_PWM_DTEST_MASK | AUTO_RES_EN_BIT, val);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = pwm_config(chip->pwm_data.pwm_dev,
+ chip->pwm_data.duty_us * NSEC_PER_USEC,
+ chip->pwm_data.period_us * NSEC_PER_USEC);
+ if (rc < 0) {
+ pr_err("pwm_config failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qpnp_haptics_lra_auto_res_config(struct hap_chip *chip,
+ struct hap_lra_ares_param *tmp_cfg)
+{
+ struct hap_lra_ares_param *ares_cfg;
+ int rc;
+ u8 val = 0, mask = 0;
+
+ /* disable auto resonance for ERM */
+ if (chip->act_type == HAP_ERM) {
+ val = 0x00;
+ rc = qpnp_haptics_write_reg(chip, HAP_LRA_AUTO_RES_REG(chip),
+ &val, 1);
+ return rc;
+ }
+
+ if (chip->auto_res_err_recovery_hw) {
+ rc = qpnp_haptics_masked_write_reg(chip,
+ HAP_AUTO_RES_CTRL_REG(chip),
+ AUTO_RES_ERR_RECOVERY_BIT, AUTO_RES_ERR_RECOVERY_BIT);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (tmp_cfg)
+ ares_cfg = tmp_cfg;
+ else
+ ares_cfg = &chip->ares_cfg;
+
+ if (ares_cfg->lra_res_cal_period < HAP_RES_CAL_PERIOD_MIN)
+ ares_cfg->lra_res_cal_period = HAP_RES_CAL_PERIOD_MIN;
+
+ if (chip->revid->pmic_subtype == PM660_SUBTYPE) {
+ if (ares_cfg->lra_res_cal_period >
+ HAP_PM660_RES_CAL_PERIOD_MAX)
+ ares_cfg->lra_res_cal_period =
+ HAP_PM660_RES_CAL_PERIOD_MAX;
+
+ if (ares_cfg->auto_res_mode == HAP_PM660_AUTO_RES_QWD)
+ ares_cfg->lra_res_cal_period = 0;
+
+ if (ares_cfg->lra_res_cal_period)
+ val = ilog2(ares_cfg->lra_res_cal_period /
+ HAP_RES_CAL_PERIOD_MIN) + 1;
+ } else {
+ if (ares_cfg->lra_res_cal_period > HAP_RES_CAL_PERIOD_MAX)
+ ares_cfg->lra_res_cal_period =
+ HAP_RES_CAL_PERIOD_MAX;
+
+ if (ares_cfg->lra_res_cal_period)
+ val = ilog2(ares_cfg->lra_res_cal_period /
+ HAP_RES_CAL_PERIOD_MIN);
+ }
+
+ if (chip->revid->pmic_subtype == PM660_SUBTYPE) {
+ val |= ares_cfg->auto_res_mode << PM660_AUTO_RES_MODE_SHIFT;
+ mask = PM660_AUTO_RES_MODE_BIT;
+ val |= ares_cfg->lra_high_z << PM660_CAL_DURATION_SHIFT;
+ mask |= PM660_CAL_DURATION_MASK;
+ if (ares_cfg->lra_qwd_drive_duration != -EINVAL) {
+ val |= ares_cfg->lra_qwd_drive_duration <<
+ PM660_QWD_DRIVE_DURATION_SHIFT;
+ mask |= PM660_QWD_DRIVE_DURATION_BIT;
+ }
+ if (ares_cfg->calibrate_at_eop != -EINVAL) {
+ val |= ares_cfg->calibrate_at_eop <<
+ PM660_CAL_EOP_SHIFT;
+ mask |= PM660_CAL_EOP_BIT;
+ }
+ mask |= PM660_LRA_RES_CAL_MASK;
+ } else {
+ val |= (ares_cfg->auto_res_mode << LRA_AUTO_RES_MODE_SHIFT);
+ val |= (ares_cfg->lra_high_z << LRA_HIGH_Z_SHIFT);
+ mask = LRA_AUTO_RES_MODE_MASK | LRA_HIGH_Z_MASK |
+ LRA_RES_CAL_MASK;
+ }
+
+ pr_debug("mode: %d hi_z period: %d cal_period: %d\n",
+ ares_cfg->auto_res_mode, ares_cfg->lra_high_z,
+ ares_cfg->lra_res_cal_period);
+
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_LRA_AUTO_RES_REG(chip),
+ mask, val);
+ return rc;
+}
+
+/* configuration api for play mode */
+static int qpnp_haptics_play_mode_config(struct hap_chip *chip)
+{
+ u8 val = 0;
+ int rc;
+
+ if (!is_haptics_idle(chip))
+ return -EBUSY;
+
+ val = chip->play_mode << HAP_WF_SOURCE_SHIFT;
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_SEL_REG(chip),
+ HAP_WF_SOURCE_MASK, val);
+ if (!rc) {
+ if (chip->play_mode == HAP_BUFFER && !chip->play_irq_en) {
+ enable_irq(chip->play_irq);
+ chip->play_irq_en = true;
+ } else if (chip->play_mode != HAP_BUFFER && chip->play_irq_en) {
+ disable_irq(chip->play_irq);
+ chip->play_irq_en = false;
+ }
+ }
+ return rc;
+}
+
+/* configuration api for max voltage */
+static int qpnp_haptics_vmax_config(struct hap_chip *chip, int vmax_mv,
+ bool overdrive)
+{
+ u8 val = 0;
+ int rc;
+
+ if (vmax_mv < 0)
+ return -EINVAL;
+
+ /* Allow setting override bit in VMAX_CFG only for PM660 */
+ if (chip->revid->pmic_subtype != PM660_SUBTYPE)
+ overdrive = false;
+
+ if (vmax_mv < HAP_VMAX_MIN_MV)
+ vmax_mv = HAP_VMAX_MIN_MV;
+ else if (vmax_mv > HAP_VMAX_MAX_MV)
+ vmax_mv = HAP_VMAX_MAX_MV;
+
+ val = DIV_ROUND_CLOSEST(vmax_mv, HAP_VMAX_MIN_MV);
+ val <<= HAP_VMAX_SHIFT;
+ if (overdrive)
+ val |= HAP_VMAX_OVD_BIT;
+
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_VMAX_CFG_REG(chip),
+ HAP_VMAX_MASK | HAP_VMAX_OVD_BIT, val);
+ return rc;
+}
+
+/* configuration api for ilim */
+static int qpnp_haptics_ilim_config(struct hap_chip *chip)
+{
+ int rc;
+
+ if (chip->ilim_ma < HAP_ILIM_400_MA)
+ chip->ilim_ma = HAP_ILIM_400_MA;
+ else if (chip->ilim_ma > HAP_ILIM_800_MA)
+ chip->ilim_ma = HAP_ILIM_800_MA;
+
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_ILIM_CFG_REG(chip),
+ HAP_ILIM_SEL_MASK, chip->ilim_ma);
+ return rc;
+}
+
+/* configuration api for short circuit debounce */
+static int qpnp_haptics_sc_deb_config(struct hap_chip *chip)
+{
+ u8 val = 0;
+ int rc;
+
+ if (chip->sc_deb_cycles < HAP_SC_DEB_CYCLES_MIN)
+ chip->sc_deb_cycles = HAP_SC_DEB_CYCLES_MIN;
+ else if (chip->sc_deb_cycles > HAP_SC_DEB_CYCLES_MAX)
+ chip->sc_deb_cycles = HAP_SC_DEB_CYCLES_MAX;
+
+ if (chip->sc_deb_cycles != HAP_SC_DEB_CYCLES_MIN)
+ val = ilog2(chip->sc_deb_cycles /
+ HAP_DEF_SC_DEB_CYCLES) + 1;
+ else
+ val = HAP_SC_DEB_CYCLES_MIN;
+
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_SC_DEB_REG(chip),
+ HAP_SC_DEB_MASK, val);
+
+ return rc;
+}
+
+static int qpnp_haptics_brake_config(struct hap_chip *chip, u32 *brake_pat)
+{
+ int rc, i;
+ u32 temp, *ptr;
+ u8 val;
+
+ /* Configure BRAKE register */
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_EN_CTL2_REG(chip),
+ BRAKE_EN_BIT, (u8)chip->en_brake);
+ if (rc < 0)
+ return rc;
+
+ /* If braking is not enabled, skip configuring brake pattern */
+ if (!chip->en_brake)
+ return 0;
+
+ if (!brake_pat)
+ ptr = chip->brake_pat;
+ else
+ ptr = brake_pat;
+
+ for (i = HAP_BRAKE_PAT_LEN - 1, val = 0; i >= 0; i--) {
+ ptr[i] &= HAP_BRAKE_PAT_MASK;
+ temp = i << 1;
+ val |= ptr[i] << temp;
+ }
+
+ rc = qpnp_haptics_write_reg(chip, HAP_BRAKE_REG(chip), &val, 1);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static int qpnp_haptics_auto_mode_config(struct hap_chip *chip, int time_ms)
+{
+ struct hap_lra_ares_param ares_cfg;
+ enum hap_mode old_play_mode;
+ u8 old_ares_mode;
+ u32 brake_pat[HAP_BRAKE_PAT_LEN] = {0};
+ u32 wave_samp[HAP_WAVE_SAMP_LEN] = {0};
+ int rc, vmax_mv;
+
+ if (!chip->lra_auto_mode)
+ return false;
+
+ /* For now, this is for LRA only */
+ if (chip->act_type == HAP_ERM)
+ return 0;
+
+ old_ares_mode = chip->ares_cfg.auto_res_mode;
+ old_play_mode = chip->play_mode;
+ pr_debug("auto_mode, time_ms: %d\n", time_ms);
+ if (time_ms <= 20) {
+ wave_samp[0] = HAP_WF_SAMP_MAX;
+ wave_samp[1] = HAP_WF_SAMP_MAX;
+ chip->wf_samp_len = 2;
+ if (time_ms > 15) {
+ wave_samp[2] = HAP_WF_SAMP_MAX;
+ chip->wf_samp_len = 3;
+ }
+
+ /* short pattern */
+ rc = qpnp_haptics_parse_buffer_dt(chip);
+ if (!rc) {
+ rc = qpnp_haptics_wave_rep_config(chip,
+ HAP_WAVE_REPEAT | HAP_WAVE_SAMP_REPEAT);
+ if (rc < 0) {
+ pr_err("Error in configuring wave_rep config %d\n",
+ rc);
+ return rc;
+ }
+
+ rc = qpnp_haptics_buffer_config(chip, wave_samp, true);
+ if (rc < 0) {
+ pr_err("Error in configuring buffer mode %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ ares_cfg.lra_high_z = HAP_LRA_HIGH_Z_OPT1;
+ ares_cfg.lra_res_cal_period = HAP_RES_CAL_PERIOD_MIN;
+ if (chip->revid->pmic_subtype == PM660_SUBTYPE) {
+ ares_cfg.auto_res_mode = HAP_PM660_AUTO_RES_QWD;
+ ares_cfg.lra_qwd_drive_duration = 0;
+ ares_cfg.calibrate_at_eop = 0;
+ } else {
+ ares_cfg.auto_res_mode = HAP_AUTO_RES_ZXD_EOP;
+ ares_cfg.lra_qwd_drive_duration = -EINVAL;
+ ares_cfg.calibrate_at_eop = -EINVAL;
+ }
+
+ vmax_mv = HAP_VMAX_MAX_MV;
+ rc = qpnp_haptics_vmax_config(chip, vmax_mv, true);
+ if (rc < 0)
+ return rc;
+
+ /* enable play_irq for buffer mode */
+ if (chip->play_irq >= 0 && !chip->play_irq_en) {
+ enable_irq(chip->play_irq);
+ chip->play_irq_en = true;
+ }
+
+ brake_pat[0] = BRAKE_VMAX;
+ chip->play_mode = HAP_BUFFER;
+ chip->wave_shape = HAP_WAVE_SQUARE;
+ } else {
+ /* long pattern */
+ ares_cfg.lra_high_z = HAP_LRA_HIGH_Z_OPT1;
+ if (chip->revid->pmic_subtype == PM660_SUBTYPE) {
+ ares_cfg.auto_res_mode = HAP_PM660_AUTO_RES_ZXD;
+ ares_cfg.lra_res_cal_period =
+ HAP_PM660_RES_CAL_PERIOD_MAX;
+ ares_cfg.lra_qwd_drive_duration = 0;
+ ares_cfg.calibrate_at_eop = 1;
+ } else {
+ ares_cfg.auto_res_mode = HAP_AUTO_RES_QWD;
+ ares_cfg.lra_res_cal_period = HAP_RES_CAL_PERIOD_MAX;
+ ares_cfg.lra_qwd_drive_duration = -EINVAL;
+ ares_cfg.calibrate_at_eop = -EINVAL;
+ }
+
+ vmax_mv = chip->vmax_mv;
+ rc = qpnp_haptics_vmax_config(chip, vmax_mv, false);
+ if (rc < 0)
+ return rc;
+
+ /* enable play_irq for direct mode */
+ if (chip->play_irq >= 0 && chip->play_irq_en) {
+ disable_irq(chip->play_irq);
+ chip->play_irq_en = false;
+ }
+
+ chip->play_mode = HAP_DIRECT;
+ chip->wave_shape = HAP_WAVE_SINE;
+ }
+
+ chip->ares_cfg.auto_res_mode = ares_cfg.auto_res_mode;
+ rc = qpnp_haptics_lra_auto_res_config(chip, &ares_cfg);
+ if (rc < 0) {
+ chip->ares_cfg.auto_res_mode = old_ares_mode;
+ return rc;
+ }
+
+ rc = qpnp_haptics_play_mode_config(chip);
+ if (rc < 0) {
+ chip->play_mode = old_play_mode;
+ return rc;
+ }
+
+ rc = qpnp_haptics_brake_config(chip, brake_pat);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_CFG2_REG(chip),
+ HAP_LRA_RES_TYPE_MASK, chip->wave_shape);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static irqreturn_t qpnp_haptics_play_irq_handler(int irq, void *data)
+{
+ struct hap_chip *chip = data;
+ int rc;
+
+ if (chip->play_mode != HAP_BUFFER)
+ goto irq_handled;
+
+ if (chip->wave_samp[chip->wave_samp_idx + HAP_WAVE_SAMP_LEN] > 0) {
+ chip->wave_samp_idx += HAP_WAVE_SAMP_LEN;
+ if (chip->wave_samp_idx >= ARRAY_SIZE(chip->wave_samp)) {
+ pr_debug("Samples over\n");
+ } else {
+ pr_debug("moving to next sample set %d\n",
+ chip->wave_samp_idx);
+
+ /* Moving to next set of wave sample */
+ rc = qpnp_haptics_buffer_config(chip, NULL, false);
+ if (rc < 0) {
+ pr_err("Error in configuring buffer, rc=%d\n",
+ rc);
+ goto irq_handled;
+ }
+ }
+ }
+
+irq_handled:
+ return IRQ_HANDLED;
+}
+
+#define SC_MAX_COUNT 5
+#define SC_COUNT_RST_DELAY_US 1000000
+static irqreturn_t qpnp_haptics_sc_irq_handler(int irq, void *data)
+{
+ struct hap_chip *chip = data;
+ int rc;
+ u8 val;
+ s64 sc_delta_time_us;
+ ktime_t temp;
+
+ rc = qpnp_haptics_read_reg(chip, HAP_STATUS_1_REG(chip), &val, 1);
+ if (rc < 0)
+ goto irq_handled;
+
+ if (!(val & SC_FLAG_BIT)) {
+ chip->sc_count = 0;
+ goto irq_handled;
+ }
+
+ pr_debug("SC irq fired\n");
+ temp = ktime_get();
+ sc_delta_time_us = ktime_us_delta(temp, chip->last_sc_time);
+ chip->last_sc_time = temp;
+
+ if (sc_delta_time_us > SC_COUNT_RST_DELAY_US)
+ chip->sc_count = 0;
+ else
+ chip->sc_count++;
+
+ val = SC_CLR_BIT;
+ rc = qpnp_haptics_write_reg(chip, HAP_SC_CLR_REG(chip), &val, 1);
+ if (rc < 0) {
+ pr_err("Error in writing to SC_CLR_REG, rc=%d\n", rc);
+ goto irq_handled;
+ }
+
+ /* Permanently disable module if SC condition persists */
+ if (chip->sc_count > SC_MAX_COUNT) {
+ pr_crit("SC persists, permanently disabling haptics\n");
+ rc = qpnp_haptics_mod_enable(chip, false);
+ if (rc < 0) {
+ pr_err("Error in disabling module, rc=%d\n", rc);
+ goto irq_handled;
+ }
+ chip->perm_disable = true;
+ }
+
+irq_handled:
+ return IRQ_HANDLED;
+}
+
+/* All sysfs show/store functions below */
+
+#define HAP_STR_SIZE 128
+static int parse_string(const char *in_buf, char *out_buf)
+{
+ int i;
+
+ if (snprintf(out_buf, HAP_STR_SIZE, "%s", in_buf) > HAP_STR_SIZE)
+ return -EINVAL;
+
+ for (i = 0; i < strlen(out_buf); i++) {
+ if (out_buf[i] == ' ' || out_buf[i] == '\n' ||
+ out_buf[i] == '\t') {
+ out_buf[i] = '\0';
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static ssize_t qpnp_haptics_show_state(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", chip->module_en);
+}
+
+static ssize_t qpnp_haptics_store_state(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+
+ /* At present, nothing to do with setting state */
+ return count;
+}
+
+static ssize_t qpnp_haptics_show_duration(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ ktime_t time_rem;
+ s64 time_us = 0;
+
+ if (hrtimer_active(&chip->stop_timer)) {
+ time_rem = hrtimer_get_remaining(&chip->stop_timer);
+ time_us = ktime_to_us(time_rem);
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%lld\n", div_s64(time_us, 1000));
+}
+
+static ssize_t qpnp_haptics_store_duration(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ u32 val;
+ int rc;
+
+ rc = kstrtouint(buf, 0, &val);
+ if (rc < 0)
+ return rc;
+
+ /* setting 0 on duration is NOP for now */
+ if (val <= 0)
+ return count;
+
+ if (val > chip->max_play_time_ms)
+ return -EINVAL;
+
+ mutex_lock(&chip->param_lock);
+ rc = qpnp_haptics_auto_mode_config(chip, val);
+ if (rc < 0) {
+ pr_err("Unable to do auto mode config\n");
+ mutex_unlock(&chip->param_lock);
+ return rc;
+ }
+
+ chip->play_time_ms = val;
+ mutex_unlock(&chip->param_lock);
+
+ return count;
+}
+
+static ssize_t qpnp_haptics_show_activate(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ /* For now nothing to show */
+ return snprintf(buf, PAGE_SIZE, "%d\n", 0);
+}
+
+static ssize_t qpnp_haptics_store_activate(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ u32 val;
+ int rc;
+
+ rc = kstrtouint(buf, 0, &val);
+ if (rc < 0)
+ return rc;
+
+ if (val != 0 && val != 1)
+ return count;
+
+ if (val) {
+ hrtimer_cancel(&chip->stop_timer);
+ if (is_sw_lra_auto_resonance_control(chip))
+ hrtimer_cancel(&chip->auto_res_err_poll_timer);
+ cancel_work_sync(&chip->haptics_work);
+
+ atomic_set(&chip->state, 1);
+ schedule_work(&chip->haptics_work);
+ } else {
+ rc = qpnp_haptics_mod_enable(chip, false);
+ if (rc < 0) {
+ pr_err("Error in disabling module, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return count;
+}
+
+static ssize_t qpnp_haptics_show_play_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ char *str;
+
+ if (chip->play_mode == HAP_BUFFER)
+ str = "buffer";
+ else if (chip->play_mode == HAP_DIRECT)
+ str = "direct";
+ else if (chip->play_mode == HAP_AUDIO)
+ str = "audio";
+ else if (chip->play_mode == HAP_PWM)
+ str = "pwm";
+ else
+ return -EINVAL;
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", str);
+}
+
+static ssize_t qpnp_haptics_store_play_mode(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ char str[HAP_STR_SIZE + 1];
+ int rc = 0, temp, old_mode;
+
+ rc = parse_string(buf, str);
+ if (rc < 0)
+ return rc;
+
+ if (strcmp(str, "buffer") == 0)
+ temp = HAP_BUFFER;
+ else if (strcmp(str, "direct") == 0)
+ temp = HAP_DIRECT;
+ else if (strcmp(str, "audio") == 0)
+ temp = HAP_AUDIO;
+ else if (strcmp(str, "pwm") == 0)
+ temp = HAP_PWM;
+ else
+ return -EINVAL;
+
+ if (temp == chip->play_mode)
+ return count;
+
+ if (temp == HAP_BUFFER) {
+ rc = qpnp_haptics_parse_buffer_dt(chip);
+ if (!rc) {
+ rc = qpnp_haptics_wave_rep_config(chip,
+ HAP_WAVE_REPEAT | HAP_WAVE_SAMP_REPEAT);
+ if (rc < 0) {
+ pr_err("Error in configuring wave_rep config %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = qpnp_haptics_buffer_config(chip, NULL, true);
+ } else if (temp == HAP_PWM) {
+ rc = qpnp_haptics_parse_pwm_dt(chip);
+ if (!rc)
+ rc = qpnp_haptics_pwm_config(chip);
+ }
+
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_haptics_mod_enable(chip, false);
+ if (rc < 0)
+ return rc;
+
+ old_mode = chip->play_mode;
+ chip->play_mode = temp;
+ rc = qpnp_haptics_play_mode_config(chip);
+ if (rc < 0) {
+ chip->play_mode = old_mode;
+ return rc;
+ }
+
+ if (chip->play_mode == HAP_AUDIO) {
+ rc = qpnp_haptics_mod_enable(chip, true);
+ if (rc < 0) {
+ chip->play_mode = old_mode;
+ return rc;
+ }
+ }
+
+ return count;
+}
+
+static ssize_t qpnp_haptics_show_wf_samp(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ char str[HAP_STR_SIZE + 1];
+ char *ptr = str;
+ int i, len = 0;
+
+ for (i = 0; i < ARRAY_SIZE(chip->wave_samp); i++) {
+ len = scnprintf(ptr, HAP_STR_SIZE, "%x ", chip->wave_samp[i]);
+ ptr += len;
+ }
+ ptr[len] = '\0';
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", str);
+}
+
+static ssize_t qpnp_haptics_store_wf_samp(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ u8 samp[HAP_WAVE_SAMP_SET_LEN] = {0};
+ int bytes_read, rc;
+ unsigned int data, pos = 0, i = 0;
+
+ while (pos < count && i < ARRAY_SIZE(samp) &&
+ sscanf(buf + pos, "%x%n", &data, &bytes_read) == 1) {
+ /* bit 0 is not used in WF_Sx */
+ samp[i++] = data & GENMASK(7, 1);
+ pos += bytes_read;
+ }
+
+ chip->wf_samp_len = i;
+ for (i = 0; i < ARRAY_SIZE(chip->wave_samp); i++)
+ chip->wave_samp[i] = samp[i];
+
+ rc = qpnp_haptics_buffer_config(chip, NULL, false);
+ if (rc < 0) {
+ pr_err("Error in configuring buffer mode %d\n", rc);
+ return rc;
+ }
+
+ return count;
+}
+
+static ssize_t qpnp_haptics_show_wf_rep_count(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", chip->wave_rep_cnt);
+}
+
+static ssize_t qpnp_haptics_store_wf_rep_count(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ int data, rc, old_wave_rep_cnt;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc < 0)
+ return rc;
+
+ old_wave_rep_cnt = chip->wave_rep_cnt;
+ chip->wave_rep_cnt = data;
+ rc = qpnp_haptics_wave_rep_config(chip, HAP_WAVE_REPEAT);
+ if (rc < 0) {
+ chip->wave_rep_cnt = old_wave_rep_cnt;
+ return rc;
+ }
+
+ return count;
+}
+
+static ssize_t qpnp_haptics_show_wf_s_rep_count(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", chip->wave_s_rep_cnt);
+}
+
+static ssize_t qpnp_haptics_store_wf_s_rep_count(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ int data, rc, old_wave_s_rep_cnt;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc < 0)
+ return rc;
+
+ old_wave_s_rep_cnt = chip->wave_s_rep_cnt;
+ chip->wave_s_rep_cnt = data;
+ rc = qpnp_haptics_wave_rep_config(chip, HAP_WAVE_SAMP_REPEAT);
+ if (rc < 0) {
+ chip->wave_s_rep_cnt = old_wave_s_rep_cnt;
+ return rc;
+ }
+
+ return count;
+}
+
+static ssize_t qpnp_haptics_show_vmax(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", chip->vmax_mv);
+}
+
+static ssize_t qpnp_haptics_store_vmax(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ int data, rc, old_vmax_mv;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc < 0)
+ return rc;
+
+ old_vmax_mv = chip->vmax_mv;
+ chip->vmax_mv = data;
+ rc = qpnp_haptics_vmax_config(chip, chip->vmax_mv, false);
+ if (rc < 0) {
+ chip->vmax_mv = old_vmax_mv;
+ return rc;
+ }
+
+ return count;
+}
+
+static ssize_t qpnp_haptics_show_lra_auto_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", chip->lra_auto_mode);
+}
+
+static ssize_t qpnp_haptics_store_lra_auto_mode(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
+ int rc, data;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc < 0)
+ return rc;
+
+ if (data != 0 && data != 1)
+ return count;
+
+ chip->lra_auto_mode = !!data;
+ return count;
+}
+
+static struct device_attribute qpnp_haptics_attrs[] = {
+ __ATTR(state, 0664, qpnp_haptics_show_state, qpnp_haptics_store_state),
+ __ATTR(duration, 0664, qpnp_haptics_show_duration,
+ qpnp_haptics_store_duration),
+ __ATTR(activate, 0664, qpnp_haptics_show_activate,
+ qpnp_haptics_store_activate),
+ __ATTR(play_mode, 0664, qpnp_haptics_show_play_mode,
+ qpnp_haptics_store_play_mode),
+ __ATTR(wf_samp, 0664, qpnp_haptics_show_wf_samp,
+ qpnp_haptics_store_wf_samp),
+ __ATTR(wf_rep_count, 0664, qpnp_haptics_show_wf_rep_count,
+ qpnp_haptics_store_wf_rep_count),
+ __ATTR(wf_s_rep_count, 0664, qpnp_haptics_show_wf_s_rep_count,
+ qpnp_haptics_store_wf_s_rep_count),
+ __ATTR(vmax_mv, 0664, qpnp_haptics_show_vmax, qpnp_haptics_store_vmax),
+ __ATTR(lra_auto_mode, 0664, qpnp_haptics_show_lra_auto_mode,
+ qpnp_haptics_store_lra_auto_mode),
+};
+
+/* Dummy functions for brightness */
+static
+enum led_brightness qpnp_haptics_brightness_get(struct led_classdev *cdev)
+{
+ return 0;
+}
+
+static void qpnp_haptics_brightness_set(struct led_classdev *cdev,
+ enum led_brightness level)
+{
+}
+
+static int qpnp_haptics_config(struct hap_chip *chip)
+{
+ u8 rc_clk_err_deci_pct;
+ u16 play_rate = 0;
+ int rc;
+
+ /* Configure the CFG1 register for actuator type */
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_CFG1_REG(chip),
+ HAP_ACT_TYPE_MASK, chip->act_type);
+ if (rc < 0)
+ return rc;
+
+ /* Configure auto resonance parameters */
+ rc = qpnp_haptics_lra_auto_res_config(chip, NULL);
+ if (rc < 0)
+ return rc;
+
+ /* Configure the PLAY MODE register */
+ rc = qpnp_haptics_play_mode_config(chip);
+ if (rc < 0)
+ return rc;
+
+ /* Configure the VMAX register */
+ rc = qpnp_haptics_vmax_config(chip, chip->vmax_mv, false);
+ if (rc < 0)
+ return rc;
+
+ /* Configure the ILIM register */
+ rc = qpnp_haptics_ilim_config(chip);
+ if (rc < 0)
+ return rc;
+
+ /* Configure the short circuit debounce register */
+ rc = qpnp_haptics_sc_deb_config(chip);
+ if (rc < 0)
+ return rc;
+
+ /* Configure the WAVE SHAPE register */
+ rc = qpnp_haptics_masked_write_reg(chip, HAP_CFG2_REG(chip),
+ HAP_LRA_RES_TYPE_MASK, chip->wave_shape);
+ if (rc < 0)
+ return rc;
+
+ play_rate = chip->wave_play_rate_us / HAP_RATE_CFG_STEP_US;
+
+ /*
+ * The frequency of 19.2 MHz RC clock is subject to variation. Currently
+ * some PMI chips have MISC_TRIM_ERROR_RC19P2_CLK register present in
+ * MISC peripheral. This register holds the trim error of RC clock.
+ */
+ if (chip->act_type == HAP_LRA && chip->misc_clk_trim_error_reg) {
+ /*
+ * Error is available in bits[3:0] and each LSB is 0.7%.
+ * Bit 7 is the sign bit for error code. If it is set, then a
+ * negative error correction needs to be made. Otherwise, a
+ * positive error correction needs to be made.
+ */
+ rc_clk_err_deci_pct = (chip->clk_trim_error_code & 0x0F) * 7;
+ if (chip->clk_trim_error_code & BIT(7))
+ play_rate = (play_rate *
+ (1000 - rc_clk_err_deci_pct)) / 1000;
+ else
+ play_rate = (play_rate *
+ (1000 + rc_clk_err_deci_pct)) / 1000;
+
+ pr_debug("TRIM register = 0x%x, play_rate=%d\n",
+ chip->clk_trim_error_code, play_rate);
+ }
+
+ /*
+ * Configure RATE_CFG1 and RATE_CFG2 registers.
+ * Note: For ERM these registers act as play rate and
+ * for LRA these represent resonance period
+ */
+ rc = qpnp_haptics_update_rate_cfg(chip, play_rate);
+ if (chip->act_type == HAP_LRA) {
+ chip->drive_period_code_max_limit = (play_rate *
+ (100 + chip->drive_period_code_max_var_pct)) / 100;
+ chip->drive_period_code_min_limit = (play_rate *
+ (100 - chip->drive_period_code_min_var_pct)) / 100;
+ pr_debug("Drive period code max limit %x min limit %x\n",
+ chip->drive_period_code_max_limit,
+ chip->drive_period_code_min_limit);
+ }
+
+ rc = qpnp_haptics_brake_config(chip, NULL);
+ if (rc < 0)
+ return rc;
+
+ if (chip->play_mode == HAP_BUFFER) {
+ rc = qpnp_haptics_wave_rep_config(chip,
+ HAP_WAVE_REPEAT | HAP_WAVE_SAMP_REPEAT);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_haptics_buffer_config(chip, NULL, false);
+ } else if (chip->play_mode == HAP_PWM) {
+ rc = qpnp_haptics_pwm_config(chip);
+ } else if (chip->play_mode == HAP_AUDIO) {
+ rc = qpnp_haptics_mod_enable(chip, true);
+ }
+
+ if (rc < 0)
+ return rc;
+
+ /* setup play irq */
+ if (chip->play_irq >= 0) {
+ rc = devm_request_threaded_irq(&chip->pdev->dev, chip->play_irq,
+ NULL, qpnp_haptics_play_irq_handler, IRQF_ONESHOT,
+ "haptics_play_irq", chip);
+ if (rc < 0) {
+ pr_err("Unable to request play(%d) IRQ(err:%d)\n",
+ chip->play_irq, rc);
+ return rc;
+ }
+
+ /* use play_irq only for buffer mode */
+ if (chip->play_mode != HAP_BUFFER) {
+ disable_irq(chip->play_irq);
+ chip->play_irq_en = false;
+ }
+ }
+
+ /* setup short circuit irq */
+ if (chip->sc_irq >= 0) {
+ rc = devm_request_threaded_irq(&chip->pdev->dev, chip->sc_irq,
+ NULL, qpnp_haptics_sc_irq_handler, IRQF_ONESHOT,
+ "haptics_sc_irq", chip);
+ if (rc < 0) {
+ pr_err("Unable to request sc(%d) IRQ(err:%d)\n",
+ chip->sc_irq, rc);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int qpnp_haptics_parse_buffer_dt(struct hap_chip *chip)
+{
+ struct device_node *node = chip->pdev->dev.of_node;
+ u32 temp;
+ int rc, i, wf_samp_len;
+
+ if (chip->wave_rep_cnt > 0 || chip->wave_s_rep_cnt > 0)
+ return 0;
+
+ chip->wave_rep_cnt = WF_REPEAT_MIN;
+ rc = of_property_read_u32(node, "qcom,wave-rep-cnt", &temp);
+ if (!rc) {
+ chip->wave_rep_cnt = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read rep cnt rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->wave_s_rep_cnt = WF_S_REPEAT_MIN;
+ rc = of_property_read_u32(node,
+ "qcom,wave-samp-rep-cnt", &temp);
+ if (!rc) {
+ chip->wave_s_rep_cnt = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read samp rep cnt rc=%d\n", rc);
+ return rc;
+ }
+
+ wf_samp_len = of_property_count_elems_of_size(node,
+ "qcom,wave-samples", sizeof(u32));
+ if (wf_samp_len > 0) {
+ if (wf_samp_len > HAP_WAVE_SAMP_SET_LEN) {
+ pr_err("Invalid length for wave samples\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32_array(node, "qcom,wave-samples",
+ chip->wave_samp, wf_samp_len);
+ if (rc < 0) {
+ pr_err("Error in reading qcom,wave-samples, rc=%d\n",
+ rc);
+ return rc;
+ }
+ } else {
+ /* Use default values */
+ for (i = 0; i < HAP_WAVE_SAMP_LEN; i++)
+ chip->wave_samp[i] = HAP_WF_SAMP_MAX;
+
+ wf_samp_len = HAP_WAVE_SAMP_LEN;
+ }
+ chip->wf_samp_len = wf_samp_len;
+
+ return 0;
+}
+
+static int qpnp_haptics_parse_pwm_dt(struct hap_chip *chip)
+{
+ struct device_node *node = chip->pdev->dev.of_node;
+ u32 temp;
+ int rc;
+
+ if (chip->pwm_data.period_us > 0 && chip->pwm_data.duty_us > 0)
+ return 0;
+
+ chip->pwm_data.pwm_dev = of_pwm_get(node, NULL);
+ if (IS_ERR(chip->pwm_data.pwm_dev)) {
+ rc = PTR_ERR(chip->pwm_data.pwm_dev);
+ pr_err("Cannot get PWM device rc=%d\n", rc);
+ chip->pwm_data.pwm_dev = NULL;
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,period-us", &temp);
+ if (!rc) {
+ chip->pwm_data.period_us = temp;
+ } else {
+ pr_err("Cannot read PWM period rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,duty-us", &temp);
+ if (!rc) {
+ chip->pwm_data.duty_us = temp;
+ } else {
+ pr_err("Cannot read PWM duty rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,ext-pwm-dtest-line", &temp);
+ if (!rc)
+ chip->ext_pwm_dtest_line = temp;
+
+ rc = of_property_read_u32(node, "qcom,ext-pwm-freq-khz", &temp);
+ if (!rc) {
+ chip->ext_pwm_freq_khz = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read ext pwm freq rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qpnp_haptics_parse_dt(struct hap_chip *chip)
+{
+ struct device_node *node = chip->pdev->dev.of_node;
+ struct device_node *revid_node, *misc_node;
+ const char *temp_str;
+ int rc, temp;
+ struct regulator *vcc_pon;
+
+ rc = of_property_read_u32(node, "reg", &temp);
+ if (rc < 0) {
+ pr_err("Couldn't find reg in node = %s rc = %d\n",
+ node->full_name, rc);
+ return rc;
+ }
+
+ if (temp <= 0) {
+ pr_err("Invalid base address %x\n", temp);
+ return -EINVAL;
+ }
+ chip->base = (u16)temp;
+
+ revid_node = of_parse_phandle(node, "qcom,pmic-revid", 0);
+ if (!revid_node) {
+ pr_err("Missing qcom,pmic-revid property\n");
+ return -EINVAL;
+ }
+
+ chip->revid = get_revid_data(revid_node);
+ of_node_put(revid_node);
+ if (IS_ERR_OR_NULL(chip->revid)) {
+ pr_err("Unable to get pmic_revid rc=%ld\n",
+ PTR_ERR(chip->revid));
+ /*
+ * the revid peripheral must be registered, any failure
+ * here only indicates that the rev-id module has not
+ * probed yet.
+ */
+ return -EPROBE_DEFER;
+ }
+
+ if (of_find_property(node, "qcom,pmic-misc", NULL)) {
+ misc_node = of_parse_phandle(node, "qcom,pmic-misc", 0);
+ if (!misc_node)
+ return -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,misc-clk-trim-error-reg",
+ &chip->misc_clk_trim_error_reg);
+ if (rc < 0 || !chip->misc_clk_trim_error_reg) {
+ pr_err("Invalid or missing misc-clk-trim-error-reg\n");
+ of_node_put(misc_node);
+ return rc;
+ }
+
+ rc = qpnp_misc_read_reg(misc_node,
+ chip->misc_clk_trim_error_reg,
+ &chip->clk_trim_error_code);
+ if (rc < 0) {
+ pr_err("Couldn't get clk_trim_error_code, rc=%d\n", rc);
+ of_node_put(misc_node);
+ return -EPROBE_DEFER;
+ }
+ of_node_put(misc_node);
+ }
+
+ chip->play_irq = platform_get_irq_byname(chip->pdev, "hap-play-irq");
+ if (chip->play_irq < 0) {
+ pr_err("Unable to get play irq\n");
+ return chip->play_irq;
+ }
+
+ chip->sc_irq = platform_get_irq_byname(chip->pdev, "hap-sc-irq");
+ if (chip->sc_irq < 0) {
+ pr_err("Unable to get sc irq\n");
+ return chip->sc_irq;
+ }
+
+ chip->act_type = HAP_LRA;
+ rc = of_property_read_u32(node, "qcom,actuator-type", &temp);
+ if (!rc) {
+ if (temp != HAP_LRA && temp != HAP_ERM) {
+ pr_err("Incorrect actuator type\n");
+ return -EINVAL;
+ }
+ chip->act_type = temp;
+ }
+
+ chip->lra_auto_mode = of_property_read_bool(node, "qcom,lra-auto-mode");
+
+ rc = of_property_read_string(node, "qcom,play-mode", &temp_str);
+ if (!rc) {
+ if (strcmp(temp_str, "direct") == 0)
+ chip->play_mode = HAP_DIRECT;
+ else if (strcmp(temp_str, "buffer") == 0)
+ chip->play_mode = HAP_BUFFER;
+ else if (strcmp(temp_str, "pwm") == 0)
+ chip->play_mode = HAP_PWM;
+ else if (strcmp(temp_str, "audio") == 0)
+ chip->play_mode = HAP_AUDIO;
+ else {
+ pr_err("Invalid play mode\n");
+ return -EINVAL;
+ }
+ } else {
+ if (rc == -EINVAL && chip->act_type == HAP_LRA) {
+ pr_info("Play mode not specified, using auto mode\n");
+ chip->lra_auto_mode = true;
+ } else {
+ pr_err("Unable to read play mode\n");
+ return rc;
+ }
+ }
+
+ chip->max_play_time_ms = HAP_MAX_PLAY_TIME_MS;
+ rc = of_property_read_u32(node, "qcom,max-play-time-ms", &temp);
+ if (!rc) {
+ chip->max_play_time_ms = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read max-play-time rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->vmax_mv = HAP_VMAX_MAX_MV;
+ rc = of_property_read_u32(node, "qcom,vmax-mv", &temp);
+ if (!rc) {
+ chip->vmax_mv = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read Vmax rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->ilim_ma = HAP_ILIM_400_MA;
+ rc = of_property_read_u32(node, "qcom,ilim-ma", &temp);
+ if (!rc) {
+ chip->ilim_ma = (u8)temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read ILIM rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->sc_deb_cycles = HAP_DEF_SC_DEB_CYCLES;
+ rc = of_property_read_u32(node, "qcom,sc-dbc-cycles", &temp);
+ if (!rc) {
+ chip->sc_deb_cycles = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read sc debounce rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->wave_shape = HAP_WAVE_SQUARE;
+ rc = of_property_read_string(node, "qcom,wave-shape", &temp_str);
+ if (!rc) {
+ if (strcmp(temp_str, "sine") == 0)
+ chip->wave_shape = HAP_WAVE_SINE;
+ else if (strcmp(temp_str, "square") == 0)
+ chip->wave_shape = HAP_WAVE_SQUARE;
+ else {
+ pr_err("Unsupported wave shape\n");
+ return -EINVAL;
+ }
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read wave shape rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->wave_play_rate_us = HAP_DEF_WAVE_PLAY_RATE_US;
+ rc = of_property_read_u32(node,
+ "qcom,wave-play-rate-us", &temp);
+ if (!rc) {
+ chip->wave_play_rate_us = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read play rate rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chip->wave_play_rate_us < HAP_WAVE_PLAY_RATE_US_MIN)
+ chip->wave_play_rate_us = HAP_WAVE_PLAY_RATE_US_MIN;
+ else if (chip->wave_play_rate_us > HAP_WAVE_PLAY_RATE_US_MAX)
+ chip->wave_play_rate_us = HAP_WAVE_PLAY_RATE_US_MAX;
+
+ chip->en_brake = of_property_read_bool(node, "qcom,en-brake");
+
+ rc = of_property_count_elems_of_size(node,
+ "qcom,brake-pattern", sizeof(u32));
+ if (rc > 0) {
+ if (rc != HAP_BRAKE_PAT_LEN) {
+ pr_err("Invalid length for brake pattern\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32_array(node, "qcom,brake-pattern",
+ chip->brake_pat, HAP_BRAKE_PAT_LEN);
+ if (rc < 0) {
+ pr_err("Error in reading qcom,brake-pattern, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* Read the following properties only for LRA */
+ if (chip->act_type == HAP_LRA) {
+ rc = of_property_read_string(node, "qcom,lra-auto-res-mode",
+ &temp_str);
+ if (!rc) {
+ if (chip->revid->pmic_subtype == PM660_SUBTYPE) {
+ chip->ares_cfg.auto_res_mode =
+ HAP_PM660_AUTO_RES_QWD;
+ if (strcmp(temp_str, "zxd") == 0)
+ chip->ares_cfg.auto_res_mode =
+ HAP_PM660_AUTO_RES_ZXD;
+ else if (strcmp(temp_str, "qwd") == 0)
+ chip->ares_cfg.auto_res_mode =
+ HAP_PM660_AUTO_RES_QWD;
+ } else {
+ chip->ares_cfg.auto_res_mode =
+ HAP_AUTO_RES_ZXD_EOP;
+ if (strcmp(temp_str, "none") == 0)
+ chip->ares_cfg.auto_res_mode =
+ HAP_AUTO_RES_NONE;
+ else if (strcmp(temp_str, "zxd") == 0)
+ chip->ares_cfg.auto_res_mode =
+ HAP_AUTO_RES_ZXD;
+ else if (strcmp(temp_str, "qwd") == 0)
+ chip->ares_cfg.auto_res_mode =
+ HAP_AUTO_RES_QWD;
+ else if (strcmp(temp_str, "max-qwd") == 0)
+ chip->ares_cfg.auto_res_mode =
+ HAP_AUTO_RES_MAX_QWD;
+ else
+ chip->ares_cfg.auto_res_mode =
+ HAP_AUTO_RES_ZXD_EOP;
+ }
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read auto res mode rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->ares_cfg.lra_high_z = HAP_LRA_HIGH_Z_OPT3;
+ rc = of_property_read_string(node, "qcom,lra-high-z",
+ &temp_str);
+ if (!rc) {
+ if (strcmp(temp_str, "none") == 0)
+ chip->ares_cfg.lra_high_z =
+ HAP_LRA_HIGH_Z_NONE;
+ else if (strcmp(temp_str, "opt1") == 0)
+ chip->ares_cfg.lra_high_z =
+ HAP_LRA_HIGH_Z_OPT1;
+ else if (strcmp(temp_str, "opt2") == 0)
+ chip->ares_cfg.lra_high_z =
+ HAP_LRA_HIGH_Z_OPT2;
+ else
+ chip->ares_cfg.lra_high_z =
+ HAP_LRA_HIGH_Z_OPT3;
+ if (chip->revid->pmic_subtype == PM660_SUBTYPE) {
+ if (strcmp(temp_str, "opt0") == 0)
+ chip->ares_cfg.lra_high_z =
+ HAP_LRA_HIGH_Z_NONE;
+ }
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read LRA high-z rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->ares_cfg.lra_res_cal_period = HAP_RES_CAL_PERIOD_MAX;
+ rc = of_property_read_u32(node,
+ "qcom,lra-res-cal-period", &temp);
+ if (!rc) {
+ chip->ares_cfg.lra_res_cal_period = temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read cal period rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->ares_cfg.lra_qwd_drive_duration = -EINVAL;
+ chip->ares_cfg.calibrate_at_eop = -EINVAL;
+ if (chip->revid->pmic_subtype == PM660_SUBTYPE) {
+ rc = of_property_read_u32(node,
+ "qcom,lra-qwd-drive-duration",
+ &chip->ares_cfg.lra_qwd_drive_duration);
+ if (rc && rc != -EINVAL) {
+ pr_err("Unable to read LRA QWD drive duration rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node,
+ "qcom,lra-calibrate-at-eop",
+ &chip->ares_cfg.calibrate_at_eop);
+ if (rc && rc != -EINVAL) {
+ pr_err("Unable to read Calibrate at EOP rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ chip->drive_period_code_max_var_pct = 25;
+ rc = of_property_read_u32(node,
+ "qcom,drive-period-code-max-variation-pct", &temp);
+ if (!rc) {
+ if (temp > 0 && temp < 100)
+ chip->drive_period_code_max_var_pct = (u8)temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read drive period code max var pct rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ chip->drive_period_code_min_var_pct = 25;
+ rc = of_property_read_u32(node,
+ "qcom,drive-period-code-min-variation-pct", &temp);
+ if (!rc) {
+ if (temp > 0 && temp < 100)
+ chip->drive_period_code_min_var_pct = (u8)temp;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read drive period code min var pct rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ chip->auto_res_err_recovery_hw =
+ of_property_read_bool(node,
+ "qcom,auto-res-err-recovery-hw");
+
+ if (chip->revid->pmic_subtype != PM660_SUBTYPE)
+ chip->auto_res_err_recovery_hw = false;
+ }
+
+ if (rc == -EINVAL)
+ rc = 0;
+
+ if (chip->play_mode == HAP_BUFFER)
+ rc = qpnp_haptics_parse_buffer_dt(chip);
+ else if (chip->play_mode == HAP_PWM)
+ rc = qpnp_haptics_parse_pwm_dt(chip);
+
+ if (of_find_property(node, "vcc_pon-supply", NULL)) {
+ vcc_pon = regulator_get(&chip->pdev->dev, "vcc_pon");
+ if (IS_ERR(vcc_pon)) {
+ rc = PTR_ERR(vcc_pon);
+ dev_err(&chip->pdev->dev,
+ "regulator get failed vcc_pon rc=%d\n", rc);
+ }
+ chip->vcc_pon = vcc_pon;
+ }
+
+ return rc;
+}
+
+static int qpnp_haptics_probe(struct platform_device *pdev)
+{
+ struct hap_chip *chip;
+ int rc, i;
+
+ 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 = qpnp_haptics_parse_dt(chip);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Error in parsing DT parameters, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ spin_lock_init(&chip->bus_lock);
+ mutex_init(&chip->play_lock);
+ mutex_init(&chip->param_lock);
+ INIT_WORK(&chip->haptics_work, qpnp_haptics_work);
+
+ rc = qpnp_haptics_config(chip);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Error in configuring haptics, rc=%d\n",
+ rc);
+ goto fail;
+ }
+
+ hrtimer_init(&chip->stop_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ chip->stop_timer.function = hap_stop_timer;
+ hrtimer_init(&chip->auto_res_err_poll_timer, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
+ chip->auto_res_err_poll_timer.function = hap_auto_res_err_poll_timer;
+ dev_set_drvdata(&pdev->dev, chip);
+
+ chip->cdev.name = "vibrator";
+ chip->cdev.brightness_get = qpnp_haptics_brightness_get;
+ chip->cdev.brightness_set = qpnp_haptics_brightness_set;
+ chip->cdev.max_brightness = 100;
+ rc = devm_led_classdev_register(&pdev->dev, &chip->cdev);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Error in registering led class device, rc=%d\n",
+ rc);
+ goto register_fail;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(qpnp_haptics_attrs); i++) {
+ rc = sysfs_create_file(&chip->cdev.dev->kobj,
+ &qpnp_haptics_attrs[i].attr);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Error in creating sysfs file, rc=%d\n",
+ rc);
+ goto sysfs_fail;
+ }
+ }
+
+ return 0;
+
+sysfs_fail:
+ for (--i; i >= 0; i--)
+ sysfs_remove_file(&chip->cdev.dev->kobj,
+ &qpnp_haptics_attrs[i].attr);
+register_fail:
+ cancel_work_sync(&chip->haptics_work);
+ hrtimer_cancel(&chip->auto_res_err_poll_timer);
+ hrtimer_cancel(&chip->stop_timer);
+fail:
+ mutex_destroy(&chip->play_lock);
+ mutex_destroy(&chip->param_lock);
+ if (chip->pwm_data.pwm_dev)
+ pwm_put(chip->pwm_data.pwm_dev);
+ dev_set_drvdata(&pdev->dev, NULL);
+ return rc;
+}
+
+static int qpnp_haptics_remove(struct platform_device *pdev)
+{
+ struct hap_chip *chip = dev_get_drvdata(&pdev->dev);
+
+ cancel_work_sync(&chip->haptics_work);
+ hrtimer_cancel(&chip->auto_res_err_poll_timer);
+ hrtimer_cancel(&chip->stop_timer);
+ mutex_destroy(&chip->play_lock);
+ mutex_destroy(&chip->param_lock);
+ if (chip->pwm_data.pwm_dev)
+ pwm_put(chip->pwm_data.pwm_dev);
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return 0;
+}
+
+static void qpnp_haptics_shutdown(struct platform_device *pdev)
+{
+ struct hap_chip *chip = dev_get_drvdata(&pdev->dev);
+
+ cancel_work_sync(&chip->haptics_work);
+
+ /* disable haptics */
+ qpnp_haptics_mod_enable(chip, false);
+}
+
+static const struct dev_pm_ops qpnp_haptics_pm_ops = {
+ .suspend = qpnp_haptics_suspend,
+};
+
+static const struct of_device_id hap_match_table[] = {
+ { .compatible = "qcom,qpnp-haptics" },
+ { },
+};
+
+static struct platform_driver qpnp_haptics_driver = {
+ .driver = {
+ .name = "qcom,qpnp-haptics",
+ .of_match_table = hap_match_table,
+ .pm = &qpnp_haptics_pm_ops,
+ },
+ .probe = qpnp_haptics_probe,
+ .remove = qpnp_haptics_remove,
+ .shutdown = qpnp_haptics_shutdown,
+};
+module_platform_driver(qpnp_haptics_driver);
+
+MODULE_DESCRIPTION("QPNP haptics driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-qpnp-wled.c b/drivers/leds/leds-qpnp-wled.c
new file mode 100644
index 000000000000..461693ef9d27
--- /dev/null
+++ b/drivers/leds/leds-qpnp-wled.c
@@ -0,0 +1,2784 @@
+/* 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_INT_RT_STS(b) (b + 0x10)
+#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_EN_PSM_REG(b) (b + 0x5A)
+#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_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_OVERWRITE_BIT BIT(7)
+#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_EN_PSM_BIT BIT(7)
+
+#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 GENMASK(3, 0)
+#define QPNP_WLED_SWITCH_FREQ_OVERWRITE BIT(7)
+#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
+#define QPNP_WLED_ILIM_FAULT_BIT BIT(0)
+#define QPNP_WLED_OVP_FAULT_BIT BIT(1)
+#define QPNP_WLED_SC_FAULT_BIT BIT(2)
+#define QPNP_WLED_OVP_FLT_RT_STS_BIT BIT(1)
+
+/* QPNP_WLED_SOFTSTART_RAMP_DLY */
+#define SOFTSTART_OVERWRITE_BIT BIT(7)
+#define SOFTSTART_RAMP_DELAY_MASK GENMASK(2, 0)
+
+/* 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 GENMASK(2, 0)
+#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 GENMASK(3, 0)
+#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 0x80
+#define QPNP_WLED_CABC_SHIFT 7
+#define QPNP_WLED_CURR_SINK_SHIFT 4
+#define QPNP_WLED_CURR_SINK_MASK GENMASK(7, 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 NUM_DDIC_CODES 256
+#define QPNP_WLED_MAX_STRINGS 4
+#define QPNP_PM660_WLED_MAX_STRINGS 3
+#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 QPNP_WLED_SOFT_START_DLY_US 10000
+
+#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
+ * @ wq - workqueue for setting brightness level
+ * @ 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
+ * @ auto_calibration_ovp_count - OVP fault irq count to run auto calibration
+ * @ max_strings - Number of strings supported in WLED peripheral
+ * @ prev_level - Previous brightness level
+ * @ brt_map_table - Brightness map table
+ * @ 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
+ * @ lcd_psm_ctrl - select if psm needs to be controlled 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
+ * @ prev_state - previous state of WLED
+ * @ stepper_en - Flag to enable stepper algorithm
+ * @ ovp_irq_disabled - OVP interrupt disable status
+ * @ auto_calib_enabled - Flag to enable auto calibration feature
+ * @ auto_calib_done - Flag to indicate auto calibration is done
+ * @ module_dis_perm - Flat to keep module permanently disabled
+ * @ start_ovp_fault_time - Time when the OVP fault first occurred
+ */
+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 workqueue_struct *wq;
+ 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;
+ u16 auto_calibration_ovp_count;
+ u16 max_strings;
+ u16 prev_level;
+ u16 *brt_map_table;
+ 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 lcd_psm_ctrl;
+ 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 stepper_en;
+ bool ovp_irq_disabled;
+ bool auto_calib_enabled;
+ bool auto_calib_done;
+ bool module_dis_perm;
+ ktime_t start_ovp_fault_time;
+};
+
+static int qpnp_wled_step_delay_us = 52000;
+module_param_named(
+ total_step_delay_us, qpnp_wled_step_delay_us, int, 0600
+);
+
+static int qpnp_wled_step_size_threshold = 3;
+module_param_named(
+ step_size_threshold, qpnp_wled_step_size_threshold, int, 0600
+);
+
+static int qpnp_wled_step_delay_gain = 2;
+module_param_named(
+ step_delay_gain, qpnp_wled_step_delay_gain, int, 0600
+);
+
+/* 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;
+ u16 low_limit = WLED_MAX_LEVEL_4095 * 4 / 1000;
+
+ /* WLED's lower limit of operation is 0.4% */
+ if (level > 0 && level < low_limit)
+ level = low_limit;
+
+ /* set brightness registers */
+ for (i = 0; i < wled->max_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;
+ }
+
+ pr_debug("level:%d\n", level);
+ return 0;
+}
+
+static int qpnp_wled_set_map_level(struct qpnp_wled *wled, int level)
+{
+ int rc, i;
+
+ if (level < wled->prev_level) {
+ for (i = wled->prev_level; i >= level; i--) {
+ rc = qpnp_wled_set_level(wled, wled->brt_map_table[i]);
+ if (rc < 0) {
+ pr_err("set brightness level failed, rc:%d\n",
+ rc);
+ return rc;
+ }
+ }
+ } else if (level > wled->prev_level) {
+ for (i = wled->prev_level; i <= level; i++) {
+ rc = qpnp_wled_set_level(wled, wled->brt_map_table[i]);
+ if (rc < 0) {
+ pr_err("set brightness level failed, rc:%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_wled_set_step_level(struct qpnp_wled *wled, int new_level)
+{
+ int rc, i, num_steps, delay_us;
+ u16 level, start_level, end_level, step_size;
+ bool level_inc = false;
+
+ level = wled->prev_level;
+ start_level = wled->brt_map_table[level];
+ end_level = wled->brt_map_table[new_level];
+ level_inc = (new_level > level);
+
+ num_steps = abs(start_level - end_level);
+ if (!num_steps)
+ return 0;
+
+ delay_us = qpnp_wled_step_delay_us / num_steps;
+ pr_debug("level goes from [%d %d] num_steps: %d, delay: %d\n",
+ start_level, end_level, num_steps, delay_us);
+
+ if (delay_us < 500) {
+ step_size = 1000 / delay_us;
+ num_steps = num_steps / step_size;
+ delay_us = 1000;
+ } else {
+ if (num_steps < qpnp_wled_step_size_threshold)
+ delay_us *= qpnp_wled_step_delay_gain;
+
+ step_size = 1;
+ }
+
+ i = start_level;
+ while (num_steps--) {
+ if (level_inc)
+ i += step_size;
+ else
+ i -= step_size;
+
+ rc = qpnp_wled_set_level(wled, i);
+ if (rc < 0)
+ return rc;
+
+ if (delay_us > 0) {
+ if (delay_us < 20000)
+ usleep_range(delay_us, delay_us + 1);
+ else
+ msleep(delay_us / USEC_PER_MSEC);
+ }
+ }
+
+ if (i != end_level) {
+ i = end_level;
+ rc = qpnp_wled_set_level(wled, i);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qpnp_wled_psm_config(struct qpnp_wled *wled, bool enable)
+{
+ int rc;
+
+ if (!wled->lcd_psm_ctrl)
+ return 0;
+
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_EN_PSM_REG(wled->ctrl_base),
+ QPNP_WLED_EN_PSM_BIT,
+ enable ? QPNP_WLED_EN_PSM_BIT : 0);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_PSM_CTRL_REG(wled->ctrl_base),
+ QPNP_WLED_PSM_OVERWRITE_BIT,
+ enable ? QPNP_WLED_PSM_OVERWRITE_BIT : 0);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static int qpnp_wled_module_en(struct qpnp_wled *wled,
+ u16 base_addr, bool state)
+{
+ int rc;
+
+ if (wled->module_dis_perm)
+ return 0;
+
+ 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;
+
+ /*
+ * Wait for at least 10ms before enabling OVP fault interrupt after
+ * enabling the module so that soft start is completed. Also, this
+ * delay can be used to control PSM during enable when required. Keep
+ * OVP interrupt disabled when the module is disabled.
+ */
+ if (state) {
+ usleep_range(QPNP_WLED_SOFT_START_DLY_US,
+ QPNP_WLED_SOFT_START_DLY_US + 1000);
+ rc = qpnp_wled_psm_config(wled, false);
+ if (rc < 0)
+ return rc;
+
+ if (wled->ovp_irq > 0 && wled->ovp_irq_disabled) {
+ enable_irq(wled->ovp_irq);
+ wled->ovp_irq_disabled = false;
+ }
+ } else {
+ if (wled->ovp_irq > 0 && !wled->ovp_irq_disabled) {
+ disable_irq(wled->ovp_irq);
+ wled->ovp_irq_disabled = true;
+ }
+
+ rc = qpnp_wled_psm_config(wled, true);
+ if (rc < 0)
+ return rc;
+ }
+
+ 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;
+ u8 reg;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < wled->max_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;
+
+ reg = data / QPNP_WLED_FS_CURR_STEP_UA;
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_FS_CURR_REG(wled->sink_base, i),
+ QPNP_WLED_FS_CURR_MASK, reg);
+ if (rc < 0)
+ 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, level_255, rc;
+
+ wled = container_of(work, struct qpnp_wled, work);
+
+ mutex_lock(&wled->lock);
+ level = wled->cdev.brightness;
+
+ if (wled->brt_map_table) {
+ /*
+ * Change the 12 bit level to 8 bit level and use the mapped
+ * values for 12 bit level from brightness map table.
+ */
+ level_255 = DIV_ROUND_CLOSEST(level, 16);
+ if (level_255 > 255)
+ level_255 = 255;
+
+ pr_debug("level: %d level_255: %d\n", level, level_255);
+ if (wled->stepper_en)
+ rc = qpnp_wled_set_step_level(wled, level_255);
+ else
+ rc = qpnp_wled_set_map_level(wled, level_255);
+ if (rc) {
+ dev_err(&wled->pdev->dev, "wled set level failed\n");
+ goto unlock_mutex;
+ }
+ wled->prev_level = level_255;
+ } else 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;
+ queue_work(wled->wq, &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_OVERWRITE_BIT;
+ 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;
+}
+
+#define AUTO_CALIB_BRIGHTNESS 200
+static int wled_auto_calibrate(struct qpnp_wled *wled)
+{
+ int rc = 0, i;
+ u8 reg = 0, sink_config = 0, sink_test = 0, sink_valid = 0, int_sts;
+
+ /* read configured sink configuration */
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_CURR_SINK_REG(wled->sink_base), &sink_config);
+ if (rc < 0) {
+ pr_err("Failed to read SINK configuration rc=%d\n", rc);
+ goto failed_calib;
+ }
+
+ /* disable the module before starting calibration */
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_MODULE_EN_REG(wled->ctrl_base),
+ QPNP_WLED_MODULE_EN_MASK, 0);
+ if (rc < 0) {
+ pr_err("Failed to disable WLED module rc=%d\n", rc);
+ goto failed_calib;
+ }
+
+ /* set low brightness across all sinks */
+ rc = qpnp_wled_set_level(wled, AUTO_CALIB_BRIGHTNESS);
+ if (rc < 0) {
+ pr_err("Failed to set brightness for calibration rc=%d\n", rc);
+ goto failed_calib;
+ }
+
+ if (wled->en_cabc) {
+ for (i = 0; i < wled->max_strings; i++) {
+ reg = 0;
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_CABC_REG(wled->sink_base, i),
+ QPNP_WLED_CABC_MASK, reg);
+ if (rc < 0)
+ goto failed_calib;
+ }
+ }
+
+ /* disable all sinks */
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_CURR_SINK_REG(wled->sink_base), 0);
+ if (rc < 0) {
+ pr_err("Failed to disable all sinks rc=%d\n", rc);
+ goto failed_calib;
+ }
+
+ /* iterate through the strings one by one */
+ for (i = 0; i < wled->max_strings; i++) {
+ sink_test = 1 << (QPNP_WLED_CURR_SINK_SHIFT + i);
+
+ /* Enable feedback control */
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_FDBK_OP_REG(wled->ctrl_base),
+ i + 1);
+ if (rc < 0) {
+ pr_err("Failed to enable feedback for SINK %d rc = %d\n",
+ i + 1, rc);
+ goto failed_calib;
+ }
+
+ /* enable the sink */
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_CURR_SINK_REG(wled->sink_base), sink_test);
+ if (rc < 0) {
+ pr_err("Failed to configure SINK %d rc=%d\n",
+ i + 1, rc);
+ goto failed_calib;
+ }
+
+ /* Enable the module */
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_MODULE_EN_REG(wled->ctrl_base),
+ QPNP_WLED_MODULE_EN_MASK, QPNP_WLED_MODULE_EN_MASK);
+ if (rc < 0) {
+ pr_err("Failed to enable WLED module rc=%d\n", rc);
+ goto failed_calib;
+ }
+
+ /* delay for WLED soft-start */
+ usleep_range(QPNP_WLED_SOFT_START_DLY_US,
+ QPNP_WLED_SOFT_START_DLY_US + 1000);
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_INT_RT_STS(wled->ctrl_base), &int_sts);
+ if (rc < 0) {
+ pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
+ goto failed_calib;
+ }
+
+ if (int_sts & QPNP_WLED_OVP_FAULT_BIT)
+ pr_debug("WLED OVP fault detected with SINK %d\n",
+ i + 1);
+ else
+ sink_valid |= sink_test;
+
+ /* Disable the module */
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_MODULE_EN_REG(wled->ctrl_base),
+ QPNP_WLED_MODULE_EN_MASK, 0);
+ if (rc < 0) {
+ pr_err("Failed to disable WLED module rc=%d\n", rc);
+ goto failed_calib;
+ }
+ }
+
+ if (sink_valid == sink_config) {
+ pr_debug("WLED auto-calibration complete, default sink-config=%x OK!\n",
+ sink_config);
+ } else {
+ pr_warn("Invalid WLED default sink config=%x changing it to=%x\n",
+ sink_config, sink_valid);
+ sink_config = sink_valid;
+ }
+
+ if (!sink_config) {
+ pr_warn("No valid WLED sinks found\n");
+ wled->module_dis_perm = true;
+ goto failed_calib;
+ }
+
+ /* write the new sink configuration */
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_CURR_SINK_REG(wled->sink_base), sink_config);
+ if (rc < 0) {
+ pr_err("Failed to reconfigure the default sink rc=%d\n", rc);
+ goto failed_calib;
+ }
+
+ /* MODULATOR_EN setting for valid sinks */
+ for (i = 0; i < wled->max_strings; i++) {
+ if (wled->en_cabc) {
+ reg = 1 << QPNP_WLED_CABC_SHIFT;
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_CABC_REG(wled->sink_base, i),
+ QPNP_WLED_CABC_MASK, reg);
+ if (rc < 0)
+ goto failed_calib;
+ }
+
+ if (sink_config & (1 << (QPNP_WLED_CURR_SINK_SHIFT + i)))
+ reg = (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT);
+ else
+ reg = 0x0; /* disable modulator_en for unused sink */
+
+ 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, i), reg);
+ if (rc < 0) {
+ pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
+ goto failed_calib;
+ }
+ }
+
+ /* restore the feedback setting */
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_FDBK_OP_REG(wled->ctrl_base),
+ wled->fdbk_op);
+ if (rc < 0) {
+ pr_err("Failed to restore feedback setting rc=%d\n", rc);
+ goto failed_calib;
+ }
+
+ /* restore brightness */
+ rc = qpnp_wled_set_level(wled, !wled->cdev.brightness ?
+ AUTO_CALIB_BRIGHTNESS : wled->cdev.brightness);
+ if (rc < 0) {
+ pr_err("Failed to set brightness after calibration rc=%d\n",
+ rc);
+ goto failed_calib;
+ }
+
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_MODULE_EN_REG(wled->ctrl_base),
+ QPNP_WLED_MODULE_EN_MASK,
+ QPNP_WLED_MODULE_EN_MASK);
+ if (rc < 0) {
+ pr_err("Failed to enable WLED module rc=%d\n", rc);
+ goto failed_calib;
+ }
+
+ /* delay for WLED soft-start */
+ usleep_range(QPNP_WLED_SOFT_START_DLY_US,
+ QPNP_WLED_SOFT_START_DLY_US + 1000);
+
+failed_calib:
+ return rc;
+}
+
+#define WLED_AUTO_CAL_OVP_COUNT 5
+#define WLED_AUTO_CAL_CNT_DLY_US 1000000 /* 1 second */
+static bool qpnp_wled_auto_cal_required(struct qpnp_wled *wled)
+{
+ s64 elapsed_time_us;
+
+ /*
+ * Check if the OVP fault was an occasional one
+ * or if its firing continuously, the latter qualifies
+ * for an auto-calibration check.
+ */
+ if (!wled->auto_calibration_ovp_count) {
+ wled->start_ovp_fault_time = ktime_get();
+ wled->auto_calibration_ovp_count++;
+ } else {
+ elapsed_time_us = ktime_us_delta(ktime_get(),
+ wled->start_ovp_fault_time);
+ if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
+ wled->auto_calibration_ovp_count = 0;
+ else
+ wled->auto_calibration_ovp_count++;
+
+ if (wled->auto_calibration_ovp_count >=
+ WLED_AUTO_CAL_OVP_COUNT) {
+ wled->auto_calibration_ovp_count = 0;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int qpnp_wled_auto_calibrate_at_init(struct qpnp_wled *wled)
+{
+ int rc;
+ u8 fault_status = 0, rt_status = 0;
+
+ if (!wled->auto_calib_enabled)
+ return 0;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_INT_RT_STS(wled->ctrl_base), &rt_status);
+ if (rc < 0)
+ pr_err("Failed to read RT status rc=%d\n", rc);
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_FAULT_STATUS(wled->ctrl_base), &fault_status);
+ if (rc < 0)
+ pr_err("Failed to read fault status rc=%d\n", rc);
+
+ if ((rt_status & QPNP_WLED_OVP_FLT_RT_STS_BIT) ||
+ (fault_status & QPNP_WLED_OVP_FAULT_BIT)) {
+ mutex_lock(&wled->lock);
+ rc = wled_auto_calibrate(wled);
+ if (rc < 0)
+ pr_err("Failed auto-calibration rc=%d\n", rc);
+ else
+ wled->auto_calib_done = true;
+ mutex_unlock(&wled->lock);
+ }
+
+ return rc;
+}
+
+/* ovp irq handler */
+static irqreturn_t qpnp_wled_ovp_irq_handler(int irq, void *_wled)
+{
+ struct qpnp_wled *wled = _wled;
+ int rc;
+ u8 fault_sts, int_sts;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_INT_RT_STS(wled->ctrl_base), &int_sts);
+ if (rc < 0) {
+ pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_FAULT_STATUS(wled->ctrl_base), &fault_sts);
+ if (rc < 0) {
+ pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+
+ if (fault_sts & (QPNP_WLED_OVP_FAULT_BIT | QPNP_WLED_ILIM_FAULT_BIT))
+ pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
+ int_sts, fault_sts);
+
+ if (fault_sts & QPNP_WLED_OVP_FAULT_BIT) {
+ if (wled->auto_calib_enabled && !wled->auto_calib_done) {
+ if (qpnp_wled_auto_cal_required(wled)) {
+ mutex_lock(&wled->lock);
+ if (wled->ovp_irq > 0 &&
+ !wled->ovp_irq_disabled) {
+ disable_irq_nosync(wled->ovp_irq);
+ wled->ovp_irq_disabled = true;
+ }
+
+ rc = wled_auto_calibrate(wled);
+ if (rc < 0)
+ pr_err("Failed auto-calibration rc=%d\n",
+ rc);
+ else
+ wled->auto_calib_done = true;
+
+ if (wled->ovp_irq > 0 &&
+ wled->ovp_irq_disabled) {
+ enable_irq(wled->ovp_irq);
+ wled->ovp_irq_disabled = false;
+ }
+ mutex_unlock(&wled->lock);
+ }
+ }
+ }
+
+ 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->disp_type_amoled) {
+ reg = 0;
+ mask |= QPNP_WLED_VLOOP_COMP_AUTO_GM_EN |
+ QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_MASK;
+ } else {
+ 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, sink_en = 0, mask;
+
+ /* 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;
+ mask = SOFTSTART_RAMP_DELAY_MASK;
+ if ((wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ && wled->disp_type_amoled) {
+ reg |= SOFTSTART_OVERWRITE_BIT;
+ mask |= SOFTSTART_OVERWRITE_BIT;
+ }
+
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_SOFTSTART_RAMP_DLY(wled->ctrl_base),
+ mask, 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 == 1600)
+ reg = QPNP_WLED_SWITCH_FREQ_1600_KHZ_CODE;
+ else
+ reg = QPNP_WLED_SWITCH_FREQ_800_KHZ_CODE;
+
+ /*
+ * Do not set the overwrite bit when switching frequency is selected
+ * for AMOLED. This register is in logic reset block which can cause
+ * the value to be overwritten during module enable/disable.
+ */
+ mask = QPNP_WLED_SWITCH_FREQ_MASK | QPNP_WLED_SWITCH_FREQ_OVERWRITE;
+ if (!wled->disp_type_amoled)
+ reg |= QPNP_WLED_SWITCH_FREQ_OVERWRITE;
+
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_SWITCH_FREQ_REG(wled->ctrl_base), mask, reg);
+ if (rc < 0)
+ 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->max_strings; i++) {
+ /* SYNC DELAY */
+ if (wled->sync_dly_us > QPNP_WLED_SYNC_DLY_MAX_US)
+ wled->sync_dly_us = QPNP_WLED_SYNC_DLY_MAX_US;
+
+ reg = wled->sync_dly_us / QPNP_WLED_SYNC_DLY_STEP_US;
+ mask = QPNP_WLED_SYNC_DLY_MASK;
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_SYNC_DLY_REG(wled->sink_base, i),
+ mask, reg);
+ if (rc < 0)
+ 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;
+
+ reg = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA;
+ mask = QPNP_WLED_FS_CURR_MASK;
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_FS_CURR_REG(wled->sink_base, i),
+ mask, reg);
+ if (rc < 0)
+ return rc;
+
+ /* CABC */
+ reg = wled->en_cabc ? (1 << QPNP_WLED_CABC_SHIFT) : 0;
+ mask = QPNP_WLED_CABC_MASK;
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_CABC_REG(wled->sink_base, i),
+ mask, reg);
+ if (rc < 0)
+ return rc;
+ }
+
+ /* Settings specific to valid sinks */
+ for (i = 0; i < wled->num_strings; i++) {
+ if (wled->strings[i] >= 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, 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, i), reg);
+ if (rc)
+ return rc;
+
+ /* SINK EN */
+ temp = wled->strings[i] + QPNP_WLED_CURR_SINK_SHIFT;
+ sink_en |= (1 << temp);
+ }
+ mask = QPNP_WLED_CURR_SINK_MASK;
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_CURR_SINK_REG(wled->sink_base),
+ mask, sink_en);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev,
+ "Failed to enable WLED sink config rc = %d\n", 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;
+ }
+
+ rc = qpnp_wled_auto_calibrate_at_init(wled);
+ if (rc < 0)
+ pr_err("Failed to auto-calibrate at init rc=%d\n", 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;
+ }
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), &reg);
+ /* disable the OVP irq only if the module is not enabled */
+ if (!rc && !(reg & QPNP_WLED_MODULE_EN_MASK)) {
+ disable_irq(wled->ovp_irq);
+ wled->ovp_irq_disabled = true;
+ }
+ }
+
+ 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, size;
+ 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;
+ }
+
+ if (of_find_property(pdev->dev.of_node, "qcom,wled-brightness-map",
+ NULL)) {
+ size = of_property_count_elems_of_size(pdev->dev.of_node,
+ "qcom,wled-brightness-map", sizeof(u16));
+ if (size != NUM_DDIC_CODES) {
+ pr_err("Invalid WLED brightness map size:%d\n", size);
+ return rc;
+ }
+
+ wled->brt_map_table = devm_kcalloc(&pdev->dev, NUM_DDIC_CODES,
+ sizeof(u16), GFP_KERNEL);
+ if (!wled->brt_map_table)
+ return -ENOMEM;
+
+ rc = of_property_read_u16_array(pdev->dev.of_node,
+ "qcom,wled-brightness-map", wled->brt_map_table,
+ NUM_DDIC_CODES);
+ if (rc < 0) {
+ pr_err("Error in reading WLED brightness map, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < NUM_DDIC_CODES; i++) {
+ if (wled->brt_map_table[i] > WLED_MAX_LEVEL_4095) {
+ pr_err("WLED brightness map not in range\n");
+ return -EDOM;
+ }
+
+ if ((i > 1) && wled->brt_map_table[i]
+ < wled->brt_map_table[i - 1]) {
+ pr_err("WLED brightness map not in ascending order?\n");
+ return -EDOM;
+ }
+ }
+ }
+
+ wled->stepper_en = of_property_read_bool(pdev->dev.of_node,
+ "qcom,wled-stepper-en");
+ 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 = 320;
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ wled->loop_comp_res_kohm = 300;
+
+ 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->pmic_subtype == PMI8998_SUBTYPE &&
+ 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 = wled->disp_type_amoled ? 1600 : 800;
+ 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");
+
+ if (wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ wled->max_strings = QPNP_PM660_WLED_MAX_STRINGS;
+ else
+ wled->max_strings = QPNP_WLED_MAX_STRINGS;
+
+ 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 = 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");
+
+ wled->lcd_psm_ctrl = of_property_read_bool(pdev->dev.of_node,
+ "qcom,lcd-psm-ctrl");
+
+ wled->auto_calib_enabled = of_property_read_bool(pdev->dev.of_node,
+ "qcom,auto-calibration-enable");
+ 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);
+ of_node_put(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);
+
+ wled->wq = alloc_ordered_workqueue("qpnp_wled_wq", WQ_HIGHPRI);
+ if (!wled->wq) {
+ pr_err("Unable to alloc workqueue for WLED\n");
+ return -ENOMEM;
+ }
+
+ prop = of_get_address_by_name(pdev->dev.of_node, QPNP_WLED_SINK_BASE,
+ NULL, NULL);
+ 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,
+ NULL, NULL);
+ 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);
+ mutex_init(&wled->lock);
+ rc = qpnp_wled_config(wled);
+ if (rc) {
+ dev_err(&pdev->dev, "wled config failed\n");
+ return rc;
+ }
+
+ 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);
+ destroy_workqueue(wled->wq);
+ 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);
+ destroy_workqueue(wled->wq);
+ 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 000000000000..deec2c4e246a
--- /dev/null
+++ b/drivers/leds/leds-qpnp.c
@@ -0,0 +1,4450 @@
+/* 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,
+};
+
+#define QPNP_ID_TO_RGB_IDX(id) (id - QPNP_ID_RGB_RED)
+
+/* 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;
+};
+
+/**
+ * struct rgb_sync - rgb led synchrnize structure
+ */
+struct rgb_sync {
+ struct led_classdev cdev;
+ struct platform_device *pdev;
+ struct qpnp_led_data *led_data[3];
+};
+
+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*/
+ rc = pwm_change_mode(led->mpp_cfg->pwm_cfg->pwm_dev,
+ PM_PWM_MODE_PWM);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Failed to set PWM mode, rc = %d\n",
+ rc);
+ return rc;
+ }
+ 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);
+ led->mpp_cfg->pwm_cfg->pwm_enabled = 1;
+ } 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);
+ led->mpp_cfg->pwm_cfg->pwm_enabled = 0;
+ }
+ 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 = 0, 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;
+ }
+ }
+
+ rc = 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;
+ }
+
+ rc = 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;
+ }
+
+ rc = 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;
+ }
+
+ 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,
+ "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) {
+ rc = pwm_change_mode(led->kpdbl_cfg->pwm_cfg->pwm_dev,
+ PM_PWM_MODE_PWM);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Failed to set PWM mode, rc = %d\n",
+ rc);
+ return rc;
+ }
+ 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;
+ }
+ led->kpdbl_cfg->pwm_cfg->pwm_enabled = 1;
+ 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;
+ }
+ led->kpdbl_cfg->pwm_cfg->pwm_enabled = 1;
+ } 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);
+ led->kpdbl_cfg->pwm_cfg->pwm_enabled = 0;
+ 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) {
+ rc = pwm_change_mode(led->rgb_cfg->pwm_cfg->pwm_dev,
+ PM_PWM_MODE_PWM);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Failed to set PWM mode, rc = %d\n",
+ rc);
+ return rc;
+ }
+ 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_enable(led->rgb_cfg->pwm_cfg->pwm_dev);
+ led->rgb_cfg->pwm_cfg->pwm_enabled = 1;
+ }
+ } else {
+ led->rgb_cfg->pwm_cfg->mode =
+ led->rgb_cfg->pwm_cfg->default_mode;
+ 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 = 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;
+ }
+ rc = pwm_change_mode(pwm_cfg->pwm_dev, PM_PWM_MODE_LPG);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Failed to set LPG mode\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;
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ if (ret) {
+ pwm_cfg->pwm_period_us = previous_pwm_us;
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+
+ 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;
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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;
+ }
+ if (pwm_cfg->pwm_enabled) {
+ pwm_disable(pwm_cfg->pwm_dev);
+ pwm_cfg->pwm_enabled = 0;
+ }
+ 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 inline void rgb_lock_leds(struct rgb_sync *rgb)
+{
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ if (rgb->led_data[i]) {
+ flush_work(&rgb->led_data[i]->work);
+ mutex_lock(&rgb->led_data[i]->lock);
+ }
+ }
+}
+
+static inline void rgb_unlock_leds(struct rgb_sync *rgb)
+{
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ if (rgb->led_data[i]) {
+ mutex_unlock(&rgb->led_data[i]->lock);
+ }
+ }
+}
+
+static void rgb_disable_leds(struct rgb_sync *rgb)
+{
+ int i;
+ struct qpnp_led_data *led;
+
+ //TODO Implement synchronized off
+ for (i = 0; i < 3; i++) {
+ led = rgb->led_data[i];
+ if (led && led->rgb_cfg->pwm_cfg->pwm_enabled) {
+ led->rgb_cfg->pwm_cfg->mode =
+ led->rgb_cfg->pwm_cfg->default_mode;
+ led->rgb_cfg->pwm_cfg->blinking = false;
+ pwm_disable(led->rgb_cfg->pwm_cfg->pwm_dev);
+ led->rgb_cfg->pwm_cfg->pwm_enabled = 0;
+ }
+ }
+}
+
+/**
+ * Should only be called when all RGB leds are off
+ */
+static int rgb_enable_leds(struct rgb_sync *rgb)
+{
+ struct qpnp_led_data *led;
+ struct pwm_device *pwm_dev[3];
+ int i, rc;
+
+ for (i = 0; i < 3; i++) {
+ led = rgb->led_data[i];
+ if (!led)
+ continue;
+
+ led->rgb_cfg->pwm_cfg->mode = LPG_MODE;
+ pwm_free(led->rgb_cfg->pwm_cfg->pwm_dev);
+ qpnp_pwm_init(led->rgb_cfg->pwm_cfg, led->pdev, led->cdev.name);
+ pwm_dev[i] = led->rgb_cfg->pwm_cfg->pwm_dev;
+ }
+
+ if (i == 0)
+ return 0;
+
+ rc = pwm_enable_synchronized(pwm_dev, i);
+ if (rc) {
+ dev_err(&rgb->pdev->dev, "Unable to enable pwms\n");
+ return rc;
+ }
+
+ for (i = 0; i < 3; i++) {
+ led = rgb->led_data[i];
+ if (!led)
+ continue;
+ led->rgb_cfg->pwm_cfg->blinking = true;
+ led->rgb_cfg->pwm_cfg->pwm_enabled = 1;
+ }
+
+ return rc;
+}
+
+static ssize_t rgb_blink_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct rgb_sync *rgb_sync;
+ struct qpnp_led_data *led;
+ unsigned long blinking;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t rc = -EINVAL, i;
+ u8 enable = 0;
+
+ rc = kstrtoul(buf, 10, &blinking);
+ if (rc)
+ return rc;
+ rgb_sync = container_of(led_cdev, struct rgb_sync, cdev);
+
+ rgb_lock_leds(rgb_sync);
+ for (i = 0; i < 3; i++) {
+ if (rgb_sync->led_data[i]) {
+ led = rgb_sync->led_data[i];
+ enable |= led->rgb_cfg->enable;
+ }
+ }
+
+ if (!led)
+ return count;
+
+ rc = qpnp_led_masked_write(led,
+ RGB_LED_EN_CTL(led->base),
+ enable, blinking ? enable : RGB_LED_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led enable reg\n");
+ rgb_unlock_leds(rgb_sync);
+ return rc;
+ }
+ rgb_disable_leds(rgb_sync);
+ if (blinking)
+ rgb_enable_leds(rgb_sync);
+ rgb_unlock_leds(rgb_sync);
+ 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 DEVICE_ATTR(rgb_blink, 0664, NULL, rgb_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 struct attribute *rgb_blink_attrs[] = {
+ &dev_attr_rgb_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 const struct attribute_group rgb_blink_attr_group = {
+ .attrs = rgb_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) {
+ if (led->kpdbl_cfg->pwm_cfg->pwm_dev)
+ pwm_put(led->kpdbl_cfg->pwm_cfg->pwm_dev);
+ 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) {
+ if (led->rgb_cfg->pwm_cfg->pwm_dev)
+ pwm_put(led->rgb_cfg->pwm_cfg->pwm_dev);
+ 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) {
+ if (led->mpp_cfg->pwm_cfg && led->mpp_cfg->pwm_cfg->pwm_dev)
+ pwm_put(led->mpp_cfg->pwm_cfg->pwm_dev);
+ 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;
+ struct rgb_sync *rgb_sync = NULL;
+
+ 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;
+
+ if (of_property_read_bool(node, "qcom,rgb-sync")) {
+ rgb_sync = devm_kzalloc(&pdev->dev,
+ sizeof(struct rgb_sync), GFP_KERNEL);
+ if (!rgb_sync) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ kfree(led_array);
+ return -ENOMEM;
+ }
+ rgb_sync->cdev.name = "rgb";
+ rgb_sync->pdev = pdev;
+ rc = led_classdev_register(&pdev->dev, &rgb_sync->cdev);
+ if (rc) {
+ dev_err(&pdev->dev, "unable to register rgb %d\n", rc);
+ goto fail_id_check;
+ }
+ rc = sysfs_create_group(&rgb_sync->cdev.dev->kobj,
+ &rgb_blink_attr_group);
+ if (rc) {
+ dev_err(&pdev->dev, "unable to create rgb sysfs %d\n", rc);
+ goto fail_id_check;
+ }
+ }
+
+ 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;
+
+ if (rgb_sync)
+ rgb_sync->led_data[QPNP_ID_TO_RGB_IDX(led->id)] = led;
+ } 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:
+ if (rgb_sync) {
+ led_classdev_unregister(&rgb_sync->cdev);
+ kfree(rgb_sync);
+ }
+ 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 4238fbc31d35..61de87e2ad80 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -44,6 +44,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);