diff options
Diffstat (limited to 'drivers/leds')
| -rw-r--r-- | drivers/leds/Kconfig | 47 | ||||
| -rw-r--r-- | drivers/leds/Makefile | 5 | ||||
| -rw-r--r-- | drivers/leds/led-class.c | 22 | ||||
| -rw-r--r-- | drivers/leds/leds-qpnp-flash-common.c | 16 | ||||
| -rw-r--r-- | drivers/leds/leds-qpnp-flash-v2.c | 2479 | ||||
| -rw-r--r-- | drivers/leds/leds-qpnp-flash.c | 2711 | ||||
| -rw-r--r-- | drivers/leds/leds-qpnp-haptics.c | 2551 | ||||
| -rw-r--r-- | drivers/leds/leds-qpnp-wled.c | 2784 | ||||
| -rw-r--r-- | drivers/leds/leds-qpnp.c | 4450 | ||||
| -rw-r--r-- | drivers/leds/leds.h | 16 |
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], ®); + 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), ®); + 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), ®); + 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), ®); + 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), + ®); + 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), ®); + 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), ®); + 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), ®); + 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), + ®); + 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), + ®); + 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), ®); + 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), + ®); + 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), ®); + 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), ®); + /* 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), ®); + 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), ®); + 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, ®ulator_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); |
