diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index d32258c5f6f4..418c32440945 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -552,6 +552,21 @@ config LEDS_PWM help This option enables support for pwm driven LEDs +config LEDS_QTI_FLASH + tristate "Support for QTI Flash LEDs" + depends on LEDS_CLASS_FLASH + depends on MFD_SPMI_PMIC + select LEDS_TRIGGERS + help + This driver supports flash LED peripheral that is present on + some Qualcomm Technologies, Inc. PMICs (e.g. PM8350C). It can + configure the flash LED target current for several independent + channels. It also supports various over current and over + temperature mitigation features. + + To compile this driver as a module, choose M here: the + module will be called leds-qti-flash. + config LEDS_REGULATOR tristate "REGULATOR driven LED support" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 440a6f825870..183970807937 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o obj-$(CONFIG_LEDS_PWM) += leds-pwm.o +obj-$(CONFIG_LEDS_QTI_FLASH) += leds-qti-flash.o obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c new file mode 100644 index 000000000000..646a2aac058f --- /dev/null +++ b/drivers/leds/leds-qti-flash.c @@ -0,0 +1,1761 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + */ +#define pr_fmt(fmt) "qti-flash: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "leds.h" + +#define FLASH_LED_REVISION1 0x00 + +#define FLASH_LED_PERIPH_SUBTYPE 0x05 + +#define FLASH_LED_STATUS1 0x06 + +#define FLASH_LED_STATUS2 0x07 +#define FLASH_LED_OTST1_STATUS BIT(5) +#define FLASH_LED_OTST2_STATUS BIT(4) +#define FLASH_LED_VPH_PWR_LOW BIT(0) + +#define FLASH_INT_RT_STS 0x10 +#define FLASH_LED_FAULT_RT_STS BIT(0) +#define FLASH_LED_ALL_RAMP_DN_DONE_RT_STS BIT(3) +#define FLASH_LED_ALL_RAMP_UP_DONE_RT_STS BIT(4) + +#define FLASH_LED_SAFETY_TIMER(id) (0x3E + id) +#define FLASH_LED_SAFETY_TIMER_EN_MASK BIT(7) +#define FLASH_LED_SAFETY_TIMER_EN BIT(7) +#define SAFETY_TIMER_MAX_TIMEOUT_MS 1280 +#define SAFETY_TIMER_MIN_TIMEOUT_MS 10 +#define SAFETY_TIMER_STEP_SIZE 10 +#define SAFETY_TIMER_DEFAULT_TIMEOUT_MS 200 + +#define FLASH_LED_ITARGET(id) (0x42 + id) +#define FLASH_LED_ITARGET_MASK GENMASK(6, 0) + +#define FLASH_ENABLE_CONTROL 0x46 +#define FLASH_MODULE_ENABLE BIT(7) +#define FLASH_MODULE_DISABLE 0x0 + +#define FLASH_LED_IRESOLUTION 0x49 +#define FLASH_LED_IRESOLUTION_MASK(id) BIT(id) + +#define FLASH_LED_STROBE_CTRL(id) (0x4A + id) +#define FLASH_LED_STROBE_CFG_MASK GENMASK(6, 4) +#define FLASH_LED_STROBE_CFG_SHIFT 4 +#define FLASH_LED_HW_SW_STROBE_SEL BIT(2) +#define FLASH_LED_STROBE_SEL_SHIFT 2 + +#define FLASH_EN_LED_CTRL 0x4E +#define FLASH_LED_ENABLE(id) BIT(id) +#define FLASH_LED_DISABLE 0 + +#define FLASH_LED_MITIGATION_SEL 0x63 +#define FLASH_LED_PREEMPTIVE_LMH_MASK GENMASK(1, 0) +#define FLASH_LED_LMH_MITIGATION_SW 0x2 + +#define FLASH_LED_THERMAL_OTST2_CFG1 0x78 +#define FLASH_LED_THERMAL_OTST1_CFG1 0x7A +#define FLASH_LED_THERMAL_THRSH_MASK GENMASK(2, 0) +#define FLASH_LED_V1_OTST1_THRSH_MIN 0x13 +#define FLASH_LED_V2_OTST1_THRSH_MIN 0x10 +#define FLASH_LED_OTST2_THRSH_MIN 0x30 + +#define MAX_IRES_LEVELS 2 +#define IRES_12P5_MAX_CURR_MA 1500 +#define IRES_5P0_MAX_CURR_MA 640 +#define TORCH_MAX_CURR_MA 500 +#define IRES_12P5_UA 12500 +#define IRES_5P0_UA 5000 +#define IRES_DEFAULT_UA IRES_12P5_UA +#define MAX_FLASH_CURRENT_MA 2000 +#define IBATT_OCP_THRESH_DEFAULT_UA 4500000 +#define FLASH_THERMAL_LEVELS 2 +#define OTST1_IDX 0 +#define OTST2_IDX 1 +#define OTST1_CURR_LIM_MA 200 +#define OTST2_CURR_LIM_MA 500 +#define VLED_MAX_DEFAULT_UV 3500000 + +enum flash_led_type { + FLASH_LED_TYPE_UNKNOWN, + FLASH_LED_TYPE_FLASH, + FLASH_LED_TYPE_TORCH, +}; + +enum flash_led_revision { + FLASH_LED_REVISION_2P0 = 1, +}; + +enum flash_led_subtype { + FLASH_LED_4_CHAN = 0x7, +}; + +enum strobe_type { + SW_STROBE = 0, + HW_STROBE, +}; + +struct flash_node_data { + struct qti_flash_led *led; + struct led_classdev_flash fdev; + u32 ires_ua; + u32 default_ires_ua; + u32 current_ma; + u32 max_current; + u8 duration; + u8 id; + u8 updated_ires_idx; + u8 ires_idx; + u8 strobe_config; + u8 strobe_sel; + enum flash_led_type type; + bool configured; + bool enabled; +}; + +struct flash_switch_data { + struct qti_flash_led *led; + struct led_classdev cdev; + struct hrtimer on_timer; + struct hrtimer off_timer; + u64 on_time_ms; + u64 off_time_ms; + u32 led_mask; + bool enabled; + bool symmetry_en; +}; + +/** + * struct qti_flash_led: Main Flash LED data structure + * @pdev : Pointer for platform device + * @regmap : Pointer for regmap structure + * @fnode : Pointer for array of child LED devices + * @snode : Pointer for array of child switch devices + * @batt_psy : Pointer for battery power supply + * @lock : Spinlock to be used for critical section + * @num_fnodes : Number of flash/torch nodes defined in device tree + * @num_snodes : Number of switch nodes defined in device tree + * @hw_strobe_gpio : Pointer for array of GPIOs for HW strobing + * @all_ramp_up_done_irq : IRQ number for all ramp up interrupt + * @all_ramp_down_done_irq : IRQ number for all ramp down interrupt + * @led_fault_irq : IRQ number for LED fault interrupt + * @max_current : Maximum current available for flash + * @thermal_derate_current : Thermal derating current limits + * @base : Base address of the flash LED module + * @revision : Revision of the flash LED module + * @subtype : Peripheral subtype of the flash LED module + * @max_channels : Maximum number of channels supported by flash module + * @chan_en_map : Bit map of individual channel enable + * @module_en : Flag used to enable/disable flash LED module + * @trigger_lmh : Flag to enable lmh mitigation + */ +struct qti_flash_led { + struct platform_device *pdev; + struct regmap *regmap; + struct flash_node_data *fnode; + struct flash_switch_data *snode; + struct power_supply *batt_psy; + spinlock_t lock; + u32 num_fnodes; + u32 num_snodes; + int *hw_strobe_gpio; + int all_ramp_up_done_irq; + int all_ramp_down_done_irq; + int led_fault_irq; + int max_current; + int thermal_derate_current[FLASH_THERMAL_LEVELS]; + u16 base; + u8 revision; + u8 subtype; + u8 max_channels; + u8 chan_en_map; + bool module_en; + bool trigger_lmh; +}; + +struct flash_current_headroom { + u16 current_ma; + u16 headroom_mv; +}; + +static const struct flash_current_headroom pm8350c_map[4] = { + {750, 200}, {1000, 250}, {1250, 300}, {1500, 400}, +}; + +static const u32 flash_led_max_ires_values[MAX_IRES_LEVELS] = { + IRES_5P0_MAX_CURR_MA, IRES_12P5_MAX_CURR_MA +}; + +static int timeout_to_code(u32 timeout) +{ + if (!timeout || timeout > SAFETY_TIMER_MAX_TIMEOUT_MS) + return -EINVAL; + + return DIV_ROUND_CLOSEST(timeout, SAFETY_TIMER_STEP_SIZE) - 1; +} + +static int get_ires_idx(u32 ires_ua) +{ + if (ires_ua == IRES_5P0_UA) + return 0; + else if (ires_ua == IRES_12P5_UA) + return 1; + else + return -EINVAL; +} + +static int current_to_code(u32 target_curr_ma, u32 ires_ua) +{ + if (!ires_ua || !target_curr_ma || + (target_curr_ma < DIV_ROUND_CLOSEST(ires_ua, 1000))) + return 0; + + return DIV_ROUND_CLOSEST(target_curr_ma * 1000, ires_ua) - 1; +} + +static int qti_flash_led_read(struct qti_flash_led *led, u16 offset, + u8 *data, u8 len) +{ + int rc; + + rc = regmap_bulk_read(led->regmap, (led->base + offset), data, len); + if (rc < 0) + pr_err("Failed to read from 0x%04X rc = %d\n", + (led->base + offset), rc); + else + pr_debug("Read %*ph from addr %#x\n", len, data, + (led->base + offset)); + + return rc; +} + +static int qti_flash_led_write(struct qti_flash_led *led, u16 offset, + u8 *data, u8 len) +{ + int rc; + + rc = regmap_bulk_write(led->regmap, (led->base + offset), data, + len); + if (rc < 0) + pr_err("Failed to write to 0x%04X rc = %d\n", + (led->base + offset), rc); + else + pr_debug("Wrote %*ph to addr %#x\n", len, data, + (led->base + offset)); + + return rc; +} + +static int qti_flash_led_masked_write(struct qti_flash_led *led, + u16 offset, u8 mask, u8 data) +{ + int rc; + + rc = regmap_update_bits(led->regmap, (led->base + offset), + mask, data); + if (rc < 0) + pr_err("Failed to update bits from 0x%04X, rc = %d\n", + (led->base + offset), rc); + else + pr_debug("Wrote %#x mask %#x to addr %#x\n", data, mask, + (led->base + offset)); + + return rc; +} + +static int qti_flash_led_module_control(struct qti_flash_led *led, + bool enable) +{ + int rc = 0; + u8 val; + + if (enable) { + if (!led->module_en && led->chan_en_map) { + val = FLASH_MODULE_ENABLE; + rc = qti_flash_led_write(led, FLASH_ENABLE_CONTROL, + &val, 1); + if (rc < 0) + return rc; + + led->module_en = true; + } + } else { + if (led->module_en && !led->chan_en_map) { + val = FLASH_MODULE_DISABLE; + rc = qti_flash_led_write(led, FLASH_ENABLE_CONTROL, + &val, 1); + if (rc < 0) + return rc; + + led->module_en = false; + } + } + + return rc; +} + +static int qti_flash_led_strobe(struct qti_flash_led *led, + struct flash_switch_data *snode, + u8 mask, u8 value) +{ + int rc, i; + bool enable = mask & value; + + spin_lock(&led->lock); + + if (enable) { + for (i = 0; i < led->max_channels; i++) + if ((mask & BIT(i)) && (value & BIT(i))) + led->chan_en_map |= BIT(i); + + rc = qti_flash_led_module_control(led, enable); + if (rc < 0) + goto error; + + if (snode && snode->off_time_ms) { + pr_debug("Off timer started with delay %d ms\n", + snode->off_time_ms); + hrtimer_start(&snode->off_timer, + ms_to_ktime(snode->off_time_ms), + HRTIMER_MODE_REL); + } + + rc = qti_flash_led_masked_write(led, FLASH_EN_LED_CTRL, + mask, value); + if (rc < 0) + goto error; + } else { + for (i = 0; i < led->max_channels; i++) + if ((led->chan_en_map & BIT(i)) && + (mask & BIT(i)) && !(value & BIT(i))) + led->chan_en_map &= ~(BIT(i)); + + rc = qti_flash_led_masked_write(led, FLASH_EN_LED_CTRL, + mask, value); + if (rc < 0) + goto error; + + rc = qti_flash_led_module_control(led, enable); + if (rc < 0) + goto error; + } + +error: + spin_unlock(&led->lock); + + return rc; +} + +static int qti_flash_led_enable(struct flash_node_data *fnode) +{ + struct qti_flash_led *led = fnode->led; + int rc; + u8 val, addr_offset; + + addr_offset = fnode->id; + + spin_lock(&led->lock); + val = (fnode->updated_ires_idx ? 0 : 1) << fnode->id; + rc = qti_flash_led_masked_write(led, FLASH_LED_IRESOLUTION, + FLASH_LED_IRESOLUTION_MASK(fnode->id), val); + if (rc < 0) + goto out; + + rc = qti_flash_led_masked_write(led, + FLASH_LED_ITARGET(addr_offset), FLASH_LED_ITARGET_MASK, + current_to_code(fnode->current_ma, fnode->ires_ua)); + if (rc < 0) + goto out; + + /* + * For dynamic brightness control of Torch LEDs, + * just configure the target current. + */ + if (fnode->type == FLASH_LED_TYPE_TORCH && fnode->enabled) { + spin_unlock(&led->lock); + return 0; + } + + if (fnode->type == FLASH_LED_TYPE_FLASH) { + val = fnode->duration | FLASH_LED_SAFETY_TIMER_EN; + rc = qti_flash_led_write(led, + FLASH_LED_SAFETY_TIMER(addr_offset), &val, 1); + if (rc < 0) + goto out; + } + + fnode->configured = true; + + if ((fnode->strobe_sel == HW_STROBE) && + gpio_is_valid(led->hw_strobe_gpio[fnode->id])) + gpio_set_value(led->hw_strobe_gpio[fnode->id], 1); + +out: + spin_unlock(&led->lock); + return rc; +} + +static int qti_flash_led_disable(struct flash_node_data *fnode) +{ + struct qti_flash_led *led = fnode->led; + int rc; + + if (!fnode->configured) + return -EINVAL; + + spin_lock(&led->lock); + if ((fnode->strobe_sel == HW_STROBE) && + gpio_is_valid(led->hw_strobe_gpio[fnode->id])) + gpio_set_value(led->hw_strobe_gpio[fnode->id], 0); + + rc = qti_flash_led_masked_write(led, + FLASH_LED_ITARGET(fnode->id), FLASH_LED_ITARGET_MASK, 0); + if (rc < 0) + goto out; + + rc = qti_flash_led_masked_write(led, + FLASH_LED_SAFETY_TIMER(fnode->id), + FLASH_LED_SAFETY_TIMER_EN_MASK, 0); + if (rc < 0) + goto out; + + fnode->configured = false; + fnode->current_ma = 0; + +out: + spin_unlock(&led->lock); + return rc; +} + +static enum led_brightness qti_flash_led_brightness_get( + struct led_classdev *led_cdev) +{ + return led_cdev->brightness; +} + +static void qti_flash_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct qti_flash_led *led = NULL; + struct flash_node_data *fnode = NULL; + struct led_classdev_flash *fdev = NULL; + int rc; + u32 current_ma = brightness; + u32 min_current_ma; + + fdev = container_of(led_cdev, struct led_classdev_flash, led_cdev); + fnode = container_of(fdev, struct flash_node_data, fdev); + led = fnode->led; + + if (!brightness) { + rc = qti_flash_led_strobe(fnode->led, NULL, + FLASH_LED_ENABLE(fnode->id), 0); + if (rc < 0) { + pr_err("Failed to destrobe LED, rc=%d\n", rc); + return; + } + + rc = qti_flash_led_disable(fnode); + if (rc < 0) + pr_err("Failed to disable LED\n"); + + return; + } + + min_current_ma = DIV_ROUND_CLOSEST(fnode->ires_ua, 1000); + if (current_ma < min_current_ma) + current_ma = min_current_ma; + + fnode->updated_ires_idx = fnode->ires_idx; + fnode->ires_ua = fnode->default_ires_ua; + + current_ma = min(current_ma, fnode->max_current); + if (current_ma > flash_led_max_ires_values[fnode->ires_idx]) { + if (current_ma > IRES_5P0_MAX_CURR_MA) + fnode->ires_ua = IRES_12P5_UA; + else + fnode->ires_ua = IRES_5P0_UA; + fnode->ires_idx = get_ires_idx(fnode->ires_ua); + } + + fnode->current_ma = current_ma; + led_cdev->brightness = current_ma; + + rc = qti_flash_led_enable(fnode); + if (rc < 0) + pr_err("Failed to set brightness %d to LED\n", brightness); +} + +static int qti_flash_led_symmetry_config( + struct flash_switch_data *snode) +{ + struct qti_flash_led *led = snode->led; + int i, total_curr_ma = 0, symmetric_leds = 0, per_led_curr_ma; + enum flash_led_type type = FLASH_LED_TYPE_UNKNOWN; + + /* Determine which LED type has triggered switch ON */ + for (i = 0; i < led->num_fnodes; i++) { + if ((snode->led_mask & BIT(led->fnode[i].id)) && + (led->fnode[i].configured)) + type = led->fnode[i].type; + } + + if (type == FLASH_LED_TYPE_UNKNOWN) { + /* No channels are configured */ + return 0; + } + + for (i = 0; i < led->num_fnodes; i++) { + if ((snode->led_mask & BIT(led->fnode[i].id)) && + (led->fnode[i].type == type)) { + total_curr_ma += led->fnode[i].current_ma; + symmetric_leds++; + } + } + + if (symmetric_leds > 0 && total_curr_ma > 0) { + per_led_curr_ma = total_curr_ma / symmetric_leds; + } else { + pr_err("Incorrect configuration, symmetric_leds: %d total_curr_ma: %d\n", + symmetric_leds, total_curr_ma); + return -EINVAL; + } + + if (per_led_curr_ma == 0) { + pr_warn("per_led_curr_ma cannot be 0\n"); + return 0; + } + + pr_debug("symmetric_leds: %d total: %d per_led_curr_ma: %d\n", + symmetric_leds, total_curr_ma, per_led_curr_ma); + + for (i = 0; i < led->num_fnodes; i++) { + if (snode->led_mask & BIT(led->fnode[i].id) && + led->fnode[i].type == type) { + qti_flash_led_brightness_set( + &led->fnode[i].fdev.led_cdev, per_led_curr_ma); + } + } + + return 0; +} + +static int qti_flash_switch_enable(struct flash_switch_data *snode) +{ + struct qti_flash_led *led = snode->led; + int rc = 0, i; + u8 led_en = 0; + + /* If symmetry enabled switch, then turn ON all its LEDs */ + if (snode->symmetry_en) { + rc = qti_flash_led_symmetry_config(snode); + if (rc < 0) { + pr_err("Failed to configure switch symmetrically, rc=%d\n", + rc); + return rc; + } + } + + for (i = 0; i < led->num_fnodes; i++) { + /* + * Do not turn ON flash/torch device if + * i. the device is not under this switch or + * ii. brightness is not configured for device under this switch + */ + if (!(snode->led_mask & BIT(led->fnode[i].id)) || + !led->fnode[i].configured) + continue; + + led_en |= (1 << led->fnode[i].id); + } + + return qti_flash_led_strobe(led, snode, snode->led_mask, led_en); +} + +static int qti_flash_switch_disable(struct flash_switch_data *snode) +{ + struct qti_flash_led *led = snode->led; + int rc = 0, i; + u8 led_dis = 0; + + for (i = 0; i < led->num_fnodes; i++) { + if (!(snode->led_mask & BIT(led->fnode[i].id)) || + !led->fnode[i].configured) + continue; + + led_dis |= BIT(led->fnode[i].id); + } + + rc = qti_flash_led_strobe(led, NULL, led_dis, ~led_dis); + if (rc < 0) { + pr_err("Failed to destrobe LEDs under with switch, rc=%d\n", + rc); + return rc; + } + + for (i = 0; i < led->num_fnodes; i++) { + /* + * Do not turn OFF flash/torch device if + * i. the device is not under this switch or + * ii. brightness is not configured for device under this switch + */ + if (!(snode->led_mask & BIT(led->fnode[i].id)) || + !led->fnode[i].configured) + continue; + + rc = qti_flash_led_disable(&led->fnode[i]); + if (rc < 0) { + pr_err("Failed to disable LED%d\n", + &led->fnode[i].id); + break; + } + } + + snode->on_time_ms = 0; + snode->off_time_ms = 0; + + return rc; +} + +static void qti_flash_led_switch_brightness_set( + struct led_classdev *led_cdev, enum led_brightness value) +{ + struct flash_switch_data *snode = NULL; + int rc = 0; + bool state = value > 0; + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + + if (snode->enabled == state) { + pr_debug("Switch is already %s!\n", + state ? "enabled" : "disabled"); + return; + } + + if (state) { + if (snode->on_time_ms) { + pr_debug("On timer started with delay %d ms\n", + snode->on_time_ms); + hrtimer_start(&snode->on_timer, + ms_to_ktime(snode->on_time_ms), + HRTIMER_MODE_REL); + snode->enabled = state; + return; + } + + rc = qti_flash_switch_enable(snode); + } else { + rc = qti_flash_switch_disable(snode); + } + + if (rc < 0) + pr_err("Failed to %s switch, rc=%d\n", + state ? "enable" : "disable", rc); + else + snode->enabled = state; +} + +static 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; +} + +static enum hrtimer_restart off_timer_function(struct hrtimer *timer) +{ + struct flash_switch_data *snode = container_of(timer, + struct flash_switch_data, off_timer); + int rc = 0; + + rc = qti_flash_switch_disable(snode); + if (rc < 0) + pr_err("Failed to disable flash LED switch %s, rc=%d\n", + snode->cdev.name, rc); + + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart on_timer_function(struct hrtimer *timer) +{ + struct flash_switch_data *snode = container_of(timer, + struct flash_switch_data, on_timer); + int rc = 0; + + rc = qti_flash_switch_enable(snode); + if (rc < 0) { + snode->enabled = false; + pr_err("Failed to enable flash LED switch %s, rc=%d\n", + snode->cdev.name, rc); + } + + return HRTIMER_NORESTART; +} + +int qti_flash_led_set_param(struct led_trigger *trig, + struct flash_led_param param) +{ + struct led_classdev *led_cdev = trigger_to_lcdev(trig); + struct flash_switch_data *snode; + + if (!led_cdev) { + pr_err("Invalid led_cdev in trigger %s\n", trig->name); + return -EINVAL; + } + + if (!param.on_time_ms && !param.off_time_ms) { + pr_err("Invalid param, on_time/off_time cannot be 0\n"); + return -EINVAL; + } + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + snode->on_time_ms = param.on_time_ms; + snode->off_time_ms = param.off_time_ms; + + return 0; +} +EXPORT_SYMBOL(qti_flash_led_set_param); + +#define UCONV 1000000LL +#define MCONV 1000LL +#define VIN_FLASH_MIN_UV 3300000LL +#define BOB_EFFICIENCY 900LL +#define VFLASH_DIP_MARGIN_UV 50000 +#define VOLTAGE_HDRM_DEFAULT_MV 400 +#define VDIP_THRESH_DEFAULT_UV 2800000LL + +static int qti_flash_led_get_voltage_headroom( + struct qti_flash_led *led) +{ + static const struct flash_current_headroom *hdrm_map; + int i, j, voltage_hdrm_mv = 0, voltage_hdrm_max = 0; + u32 map_size = 0; + + if (led->subtype == FLASH_LED_4_CHAN) { + hdrm_map = pm8350c_map; + map_size = ARRAY_SIZE(pm8350c_map); + } + + for (i = 0; i < led->num_fnodes; i++) { + if (!led->fnode[i].configured) + continue; + + voltage_hdrm_mv = VOLTAGE_HDRM_DEFAULT_MV; + + for (j = 0; j < map_size; j++) { + if (led->fnode[i].current_ma <= hdrm_map[j].current_ma) + voltage_hdrm_mv = hdrm_map[j].headroom_mv; + } + + voltage_hdrm_max = max(voltage_hdrm_max, voltage_hdrm_mv); + } + + if (!voltage_hdrm_max) + voltage_hdrm_max = VOLTAGE_HDRM_DEFAULT_MV; + + return voltage_hdrm_max; +} + +static int qti_flash_led_calc_max_avail_current( + struct qti_flash_led *led, + int *max_current_ma) +{ + int rc; + int rbatt_uohm, ocv_uv, ibatt_now_ua, voltage_hdrm_mv; + int64_t ibatt_safe_ua, i_flash_ua, i_avail_ua, vflash_vdip, + vph_flash_uv, vin_flash_uv, p_flash_fw; + union power_supply_propval prop = {}; + + rc = qti_battery_charger_get_prop("battery", BATTERY_RESISTANCE, + &rbatt_uohm); + if (rc < 0) { + pr_err("Failed to get battery resistance, rc=%d\n", + rc); + return rc; + } + + if (!rbatt_uohm) { + *max_current_ma = MAX_FLASH_CURRENT_MA; + return 0; + } + + if (!led->batt_psy) + led->batt_psy = power_supply_get_by_name("battery"); + + if (!led->batt_psy) { + pr_err("Failed to get battery power supply, rc=%d\n", rc); + return -EINVAL; + } + + rc = power_supply_get_property(led->batt_psy, + POWER_SUPPLY_PROP_VOLTAGE_OCV, &prop); + if (rc < 0) { + pr_err("Failed to get battery OCV, rc=%d\n", rc); + return rc; + } + ocv_uv = prop.intval; + + rc = power_supply_get_property(led->batt_psy, + POWER_SUPPLY_PROP_CURRENT_NOW, &prop); + if (rc < 0) { + pr_err("Failed to get battery current, rc=%d\n", rc); + return rc; + } + + /* Battery power supply returns -ve value for discharging */ + ibatt_now_ua = -(prop.intval); + + voltage_hdrm_mv = qti_flash_led_get_voltage_headroom(led); + vflash_vdip = VDIP_THRESH_DEFAULT_UV; + + if (!led->trigger_lmh) { + rc = qti_flash_led_masked_write(led, FLASH_LED_MITIGATION_SEL, + FLASH_LED_PREEMPTIVE_LMH_MASK, + FLASH_LED_LMH_MITIGATION_SW); + if (rc < 0) { + pr_err("Failed to enable LMH mitigation, rc=%d\n", rc); + return rc; + } + + /* Wait for lmh mitigation to take effect */ + udelay(100); + + led->trigger_lmh = true; + return qti_flash_led_calc_max_avail_current(led, + max_current_ma); + } + + ibatt_safe_ua = DIV_ROUND_CLOSEST((ocv_uv - + (vflash_vdip + VFLASH_DIP_MARGIN_UV)) * UCONV, + rbatt_uohm); + + if (ibatt_safe_ua < IBATT_OCP_THRESH_DEFAULT_UA) { + i_flash_ua = ibatt_safe_ua - ibatt_now_ua; + vph_flash_uv = vflash_vdip + VFLASH_DIP_MARGIN_UV; + } else { + i_flash_ua = IBATT_OCP_THRESH_DEFAULT_UA - ibatt_now_ua; + vph_flash_uv = ocv_uv - DIV_ROUND_CLOSEST((int64_t)rbatt_uohm + * IBATT_OCP_THRESH_DEFAULT_UA, UCONV); + } + + vin_flash_uv = max(VLED_MAX_DEFAULT_UV + + (voltage_hdrm_mv * MCONV), VIN_FLASH_MIN_UV); + + p_flash_fw = BOB_EFFICIENCY * vph_flash_uv * i_flash_ua; + i_avail_ua = DIV_ROUND_CLOSEST(p_flash_fw, (vin_flash_uv * MCONV)); + + *max_current_ma = min(MAX_FLASH_CURRENT_MA, + (int)(DIV_ROUND_CLOSEST(i_avail_ua, MCONV))); + + pr_debug("rbatt_uohm=%d ocv_uv=%d ibatt_now_ua=%d i_avail_ua=%lld\n", + rbatt_uohm, ocv_uv, ibatt_now_ua, i_avail_ua); + + return 0; +} + +static int qti_flash_led_calc_thermal_current( + struct qti_flash_led *led, + int *thermal_current_limit) +{ + int rc; + u8 otst_status, thrsh_min = 0; + + if (led->subtype == FLASH_LED_4_CHAN) { + thrsh_min = FLASH_LED_V1_OTST1_THRSH_MIN; + + if (led->revision == FLASH_LED_REVISION_2P0) + thrsh_min = FLASH_LED_V2_OTST1_THRSH_MIN; + } + + rc = qti_flash_led_masked_write(led, + FLASH_LED_THERMAL_OTST1_CFG1, + FLASH_LED_THERMAL_THRSH_MASK, + thrsh_min); + if (rc < 0) + return rc; + + rc = qti_flash_led_masked_write(led, + FLASH_LED_THERMAL_OTST2_CFG1, + FLASH_LED_THERMAL_THRSH_MASK, + FLASH_LED_OTST2_THRSH_MIN); + if (rc < 0) + return rc; + + /* Check THERMAL OTST status */ + rc = qti_flash_led_read(led, FLASH_LED_STATUS2, &otst_status, 1); + if (rc < 0) + return rc; + + if (otst_status & FLASH_LED_OTST1_STATUS) + *thermal_current_limit = led->thermal_derate_current[OTST1_IDX]; + else if (otst_status & FLASH_LED_OTST2_STATUS) + *thermal_current_limit = led->thermal_derate_current[OTST2_IDX]; + + pr_debug("thermal_current_limit=%d\n", *thermal_current_limit); + + return 0; +} + +static int qti_flash_led_get_max_avail_current( + struct qti_flash_led *led, int *max_current_ma) +{ + int thermal_current_limit = 0, rc; + + led->trigger_lmh = false; + rc = qti_flash_led_calc_max_avail_current(led, max_current_ma); + if (rc < 0) { + pr_err("Failed to calculate max avail current, rc=%d\n", rc); + return rc; + } + + rc = qti_flash_led_calc_thermal_current(led, + &thermal_current_limit); + if (rc < 0) { + pr_err("Failed to calculate thermal current limit, rc=%d\n", + rc); + return rc; + } + + if (thermal_current_limit) + *max_current_ma = min(*max_current_ma, thermal_current_limit); + + led->max_current = *max_current_ma; + + pr_debug("max_current_ma=%d\n", *max_current_ma); + + return 0; +} + +int qti_flash_led_prepare(struct led_trigger *trig, int options, + int *max_current) +{ + struct led_classdev *led_cdev; + struct flash_switch_data *snode; + int rc = 0; + + if (!trig) { + pr_err("Invalid led_trigger\n"); + return -EINVAL; + } + + led_cdev = trigger_to_lcdev(trig); + if (!led_cdev) { + pr_err("Invalid led_cdev in trigger %s\n", trig->name); + return -ENODEV; + } + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + + if (options & QUERY_MAX_AVAIL_CURRENT) { + if (!max_current) { + pr_err("Invalid max_current pointer\n"); + return -EINVAL; + } + + rc = qti_flash_led_get_max_avail_current(snode->led, + max_current); + if (rc < 0) { + pr_err("Failed to query max avail current, rc=%d\n", + rc); + return rc; + } + } + + return 0; +} +EXPORT_SYMBOL(qti_flash_led_prepare); + +static ssize_t qti_flash_led_max_current_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct flash_switch_data *snode; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + int rc; + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + rc = qti_flash_led_get_max_avail_current(snode->led, + &snode->led->max_current); + if (rc < 0) { + pr_err("Failed to get max current, rc=%d\n", rc); + return -EINVAL; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", snode->led->max_current); +} + +static ssize_t qti_flash_on_time_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int rc; + struct flash_switch_data *snode; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + u64 val; + + rc = kstrtou64(buf, 0, &val); + if (rc < 0) + return rc; + + if (!val) + return -EINVAL; + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + snode->on_time_ms = val; + + return count; +} + +static ssize_t qti_flash_on_time_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct flash_switch_data *snode; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + + return scnprintf(buf, PAGE_SIZE, "%lu\n", snode->on_time_ms); +} + +static ssize_t qti_flash_off_time_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int rc; + struct flash_switch_data *snode; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + u64 val; + + rc = kstrtou64(buf, 0, &val); + if (rc < 0) + return rc; + + val = min_t(u64, val, SAFETY_TIMER_MAX_TIMEOUT_MS); + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + snode->off_time_ms = val; + + return count; +} + +static ssize_t qti_flash_off_time_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct flash_switch_data *snode; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + + return scnprintf(buf, PAGE_SIZE, "%lu\n", snode->off_time_ms); +} + +static struct device_attribute qti_flash_led_attrs[] = { + __ATTR(max_current, 0400, qti_flash_led_max_current_show, NULL), + __ATTR(on_time, 0600, qti_flash_on_time_show, + qti_flash_on_time_store), + __ATTR(off_time, 0600, qti_flash_off_time_show, + qti_flash_off_time_store), +}; + +static int qti_flash_brightness_set_blocking( + struct led_classdev *led_cdev, enum led_brightness value) +{ + qti_flash_led_brightness_set(led_cdev, value); + + return 0; +} + +static int qti_flash_brightness_set( + struct led_classdev_flash *fdev, u32 brightness) +{ + qti_flash_led_brightness_set(&fdev->led_cdev, brightness); + + return 0; +} + +static int qti_flash_brightness_get( + struct led_classdev_flash *fdev, u32 *brightness) +{ + *brightness = qti_flash_led_brightness_get(&fdev->led_cdev); + + return 0; +} + +static int qti_flash_strobe_set(struct led_classdev_flash *fdev, + bool state) +{ + struct flash_node_data *fnode; + int rc; + u8 mask, value; + + fnode = container_of(fdev, struct flash_node_data, fdev); + + if (fnode->enabled == state) + return 0; + + if (state && !fnode->configured) + return -EINVAL; + + if (!fnode->duration) { + pr_debug("Safety time duration is zero, strobe not set\n"); + return -EINVAL; + } + + mask = FLASH_LED_ENABLE(fnode->id); + value = state ? FLASH_LED_ENABLE(fnode->id) : 0; + + rc = qti_flash_led_strobe(fnode->led, NULL, mask, value); + if (rc < 0) { + pr_err("Failed to %s LED, rc=%d\n", + state ? "strobe" : "desrobe", rc); + return rc; + } + fnode->enabled = state; + + if (!state) { + rc = qti_flash_led_disable(fnode); + if (rc < 0) + pr_err("Failed to disable LED %u\n", fnode->id); + } + + return rc; +} + +static int qti_flash_strobe_get(struct led_classdev_flash *fdev, + bool *state) +{ + struct flash_node_data *fnode = container_of(fdev, + struct flash_node_data, fdev); + + *state = fnode->enabled; + + return 0; +} + +static int qti_flash_timeout_set(struct led_classdev_flash *fdev, + u32 timeout) +{ + struct qti_flash_led *led; + struct flash_node_data *fnode; + int rc = 0; + u8 val; + + fnode = container_of(fdev, struct flash_node_data, fdev); + led = fnode->led; + + if (!timeout) { + fnode->duration = 0; + return 0; + } + + timeout = timeout / 1000; + + rc = timeout_to_code(timeout); + if (rc < 0) + return rc; + fnode->duration = rc; + val = fnode->duration | FLASH_LED_SAFETY_TIMER_EN; + rc = qti_flash_led_write(led, + FLASH_LED_SAFETY_TIMER(fnode->id), &val, 1); + + return rc; +} + +static const struct led_flash_ops flash_ops = { + .flash_brightness_set = qti_flash_brightness_set, + .flash_brightness_get = qti_flash_brightness_get, + .strobe_set = qti_flash_strobe_set, + .strobe_get = qti_flash_strobe_get, + .timeout_set = qti_flash_timeout_set, +}; + +static int qti_flash_led_setup(struct qti_flash_led *led) +{ + int rc = 0, i, addr_offset; + u8 val, mask; + + rc = qti_flash_led_read(led, FLASH_LED_REVISION1, &val, 1); + if (rc < 0) + return rc; + + led->revision = val; + + rc = qti_flash_led_read(led, FLASH_LED_PERIPH_SUBTYPE, &val, 1); + if (rc < 0) + return rc; + + led->subtype = val; + + for (i = 0; i < led->num_fnodes; i++) { + addr_offset = led->fnode[i].id; + rc = qti_flash_led_masked_write(led, + FLASH_LED_SAFETY_TIMER(addr_offset), + FLASH_LED_SAFETY_TIMER_EN_MASK, 0); + if (rc < 0) + return rc; + + val = (led->fnode[i].strobe_config << + FLASH_LED_STROBE_CFG_SHIFT) | + (led->fnode[i].strobe_sel << + FLASH_LED_STROBE_SEL_SHIFT); + mask = FLASH_LED_STROBE_CFG_MASK | FLASH_LED_HW_SW_STROBE_SEL; + rc = qti_flash_led_masked_write(led, + FLASH_LED_STROBE_CTRL(addr_offset), mask, val); + if (rc < 0) + return rc; + } + + led->max_current = MAX_FLASH_CURRENT_MA; + + led->thermal_derate_current[OTST1_IDX] = OTST1_CURR_LIM_MA; + led->thermal_derate_current[OTST2_IDX] = OTST2_CURR_LIM_MA; + + return rc; +} + +static irqreturn_t qti_flash_led_irq_handler(int irq, void *_led) +{ + struct qti_flash_led *led = _led; + int rc; + u8 irq_status, led_status1, led_status2; + + rc = qti_flash_led_read(led, FLASH_INT_RT_STS, &irq_status, 1); + if (rc < 0) + goto exit; + + if (irq == led->led_fault_irq) { + if (irq_status & FLASH_LED_FAULT_RT_STS) + pr_debug("Led fault open/short/vreg_not_ready detected\n"); + } else if (irq == led->all_ramp_down_done_irq) { + if (irq_status & FLASH_LED_ALL_RAMP_DN_DONE_RT_STS) + pr_debug("All LED channels ramp down done detected\n"); + } else if (irq == led->all_ramp_up_done_irq) { + if (irq_status & FLASH_LED_ALL_RAMP_UP_DONE_RT_STS) + pr_debug("All LED channels ramp up done detected\n"); + } + + rc = qti_flash_led_read(led, FLASH_LED_STATUS1, &led_status1, 1); + if (rc < 0) + goto exit; + + if (led_status1) + pr_debug("LED channel open/short fault detected\n"); + + rc = qti_flash_led_read(led, FLASH_LED_STATUS2, &led_status2, 1); + if (rc < 0) + goto exit; + + if (led_status2 & FLASH_LED_VPH_PWR_LOW) + pr_debug("LED vph_droop fault detected!\n"); + + pr_debug("LED irq handled, irq_status=%02x led_status1=%02x led_status2=%02x\n", + irq_status, led_status1, led_status2); + +exit: + return IRQ_HANDLED; +} + +static int qti_flash_led_register_interrupts(struct qti_flash_led *led) +{ + int rc; + + rc = devm_request_threaded_irq(&led->pdev->dev, + led->all_ramp_up_done_irq, NULL, qti_flash_led_irq_handler, + IRQF_ONESHOT, "flash_all_ramp_up", led); + if (rc < 0) { + pr_err("Failed to request all_ramp_up_done(%d) IRQ(err:%d)\n", + led->all_ramp_up_done_irq, rc); + return rc; + } + + rc = devm_request_threaded_irq(&led->pdev->dev, + led->all_ramp_down_done_irq, NULL, qti_flash_led_irq_handler, + IRQF_ONESHOT, "flash_all_ramp_down", led); + if (rc < 0) { + pr_err("Failed to request all_ramp_down_done(%d) IRQ(err:%d)\n", + led->all_ramp_down_done_irq, + rc); + return rc; + } + + rc = devm_request_threaded_irq(&led->pdev->dev, + led->led_fault_irq, NULL, qti_flash_led_irq_handler, + IRQF_ONESHOT, "flash_fault", led); + if (rc < 0) { + pr_err("Failed to request led_fault(%d) IRQ(err:%d)\n", + led->led_fault_irq, rc); + return rc; + } + + return 0; +} + +static int register_switch_device(struct qti_flash_led *led, + struct flash_switch_data *snode, struct device_node *node) +{ + int rc, i; + + 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 = of_property_read_string(node, "qcom,default-led-trigger", + &snode->cdev.default_trigger); + if (rc < 0) { + pr_err("Failed 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("Failed to read led mask rc=%d\n", rc); + return rc; + } + if ((snode->led_mask > ((1 << led->max_channels) - 1))) { + pr_err("Error, Invalid value for led-mask mask=0x%x\n", + snode->led_mask); + return -EINVAL; + } + + snode->symmetry_en = of_property_read_bool(node, "qcom,symmetry-en"); + + snode->on_time_ms = 0; + snode->off_time_ms = 0; + hrtimer_init(&snode->on_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_init(&snode->off_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + snode->on_timer.function = on_timer_function; + snode->off_timer.function = off_timer_function; + + snode->led = led; + snode->cdev.brightness_set = qti_flash_led_switch_brightness_set; + snode->cdev.brightness_get = qti_flash_led_brightness_get; + + rc = devm_led_classdev_register(&led->pdev->dev, &snode->cdev); + if (rc < 0) { + pr_err("Failed to register led switch device:%s\n", + snode->cdev.name); + return rc; + } + + for (i = 0; i < ARRAY_SIZE(qti_flash_led_attrs); i++) { + rc = sysfs_create_file(&snode->cdev.dev->kobj, + &qti_flash_led_attrs[i].attr); + if (rc < 0) { + pr_err("Failed to create sysfs attrs, rc=%d\n", rc); + goto sysfs_fail; + } + } + + return 0; + +sysfs_fail: + while (i >= 0) + sysfs_remove_file(&snode->cdev.dev->kobj, + &qti_flash_led_attrs[i--].attr); + return rc; +} + +static int register_flash_device(struct qti_flash_led *led, + struct flash_node_data *fnode, struct device_node *node) +{ + struct led_flash_setting *setting; + const char *temp_string; + int rc; + u32 val, default_curr_ma, duration; + + rc = of_property_read_string(node, "qcom,led-name", + &fnode->fdev.led_cdev.name); + if (rc < 0) { + pr_err("Failed to read flash LED names\n"); + return rc; + } + + rc = of_property_read_string(node, "label", &temp_string); + if (rc < 0) { + pr_err("Failed to read flash LED label\n"); + return 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("Incorrect flash LED type %s\n", temp_string); + return rc; + } + + rc = of_property_read_u32(node, "qcom,id", &val); + if (rc < 0) { + pr_err("Failed to read flash LED ID\n"); + return rc; + } + fnode->id = (u8)val; + + rc = of_property_read_string(node, "qcom,default-led-trigger", + &fnode->fdev.led_cdev.default_trigger); + if (rc < 0) { + pr_err("Failed to read trigger name\n"); + return rc; + } + + rc = of_property_read_u32(node, "qcom,ires-ua", &val); + if (rc < 0) { + pr_err("Failed to read current resolution, rc=%d\n", rc); + return rc; + } else if (!rc) { + rc = get_ires_idx(val); + if (rc < 0) { + pr_err("Incorrect ires-ua configured, ires-ua=%u\n", + val); + return rc; + } + fnode->default_ires_ua = fnode->ires_ua = val; + fnode->updated_ires_idx = fnode->ires_idx = rc; + } + + rc = of_property_read_u32(node, "qcom,max-current-ma", &val); + if (rc < 0) { + pr_err("Failed to read max current, rc=%d\n", rc); + return rc; + } + + if (fnode->type == FLASH_LED_TYPE_FLASH && + (val > IRES_12P5_MAX_CURR_MA)) { + pr_err("Incorrect max-current-ma for flash %u\n", + val); + return -EINVAL; + } + + if (fnode->type == FLASH_LED_TYPE_TORCH && + (val > TORCH_MAX_CURR_MA)) { + pr_err("Incorrect max-current-ma for torch %u\n", + val); + return -EINVAL; + } + + fnode->max_current = val; + fnode->fdev.led_cdev.max_brightness = val; + + duration = SAFETY_TIMER_DEFAULT_TIMEOUT_MS; + rc = of_property_read_u32(node, "qcom,duration-ms", &val); + if (!rc && (val >= SAFETY_TIMER_MIN_TIMEOUT_MS && + val <= SAFETY_TIMER_MAX_TIMEOUT_MS)) + duration = val; + + rc = timeout_to_code(duration); + if (rc < 0) { + pr_err("Incorrect timeout configured %u\n", duration); + return rc; + } + fnode->duration = rc; + + fnode->strobe_sel = SW_STROBE; + rc = of_property_read_u32(node, "qcom,strobe-sel", &val); + if (!rc) + fnode->strobe_sel = (u8)val; + + if (fnode->strobe_sel == HW_STROBE) { + rc = of_property_read_u32(node, "qcom,strobe-config", &val); + if (!rc) { + fnode->strobe_config = (u8)val; + } else { + pr_err("Failed to read qcom,strobe-config property\n"); + return rc; + } + + } + + fnode->led = led; + fnode->fdev.led_cdev.brightness_set = qti_flash_led_brightness_set; + fnode->fdev.led_cdev.brightness_get = qti_flash_led_brightness_get; + fnode->enabled = false; + fnode->configured = false; + fnode->fdev.ops = &flash_ops; + + if (fnode->type == FLASH_LED_TYPE_FLASH) { + fnode->fdev.led_cdev.flags = LED_DEV_CAP_FLASH; + fnode->fdev.led_cdev.brightness_set_blocking = + qti_flash_brightness_set_blocking; + } + + default_curr_ma = DIV_ROUND_CLOSEST(fnode->ires_ua, 1000); + setting = &fnode->fdev.brightness; + setting->min = 0; + setting->max = fnode->max_current; + setting->step = 1; + setting->val = default_curr_ma; + + setting = &fnode->fdev.timeout; + setting->min = 0; + setting->max = SAFETY_TIMER_MAX_TIMEOUT_MS * 1000; + setting->step = SAFETY_TIMER_STEP_SIZE * 1000; + setting->val = SAFETY_TIMER_DEFAULT_TIMEOUT_MS * 1000; + + rc = led_classdev_flash_register(&led->pdev->dev, &fnode->fdev); + if (rc < 0) { + pr_err("Failed to register flash led device:%s\n", + fnode->fdev.led_cdev.name); + return rc; + } + + return 0; +} + +static int qti_flash_led_register_device(struct qti_flash_led *led, + struct device_node *node) +{ + struct device_node *temp; + char buffer[20]; + const char *label; + int rc, i = 0, j = 0; + u32 val; + + rc = of_property_read_u32(node, "reg", &val); + if (rc < 0) { + pr_err("Failed to find reg in node %s, rc = %d\n", + node->full_name, rc); + return rc; + } + led->base = val; + + led->hw_strobe_gpio = devm_kcalloc(&led->pdev->dev, + led->max_channels, sizeof(u32), GFP_KERNEL); + if (!led->hw_strobe_gpio) + return -ENOMEM; + + for (i = 0; i < led->max_channels; i++) { + + led->hw_strobe_gpio[i] = -EINVAL; + + rc = of_get_named_gpio(node, "hw-strobe-gpios", i); + if (rc < 0) { + pr_debug("Failed to get hw strobe gpio, rc = %d\n", rc); + continue; + } + + if (!gpio_is_valid(rc)) { + pr_err("Error, Invalid gpio specified\n"); + return -EINVAL; + } + led->hw_strobe_gpio[i] = rc; + + scnprintf(buffer, sizeof(buffer), "hw_strobe_gpio%d", i); + rc = devm_gpio_request_one(&led->pdev->dev, + led->hw_strobe_gpio[i], GPIOF_DIR_OUT, + buffer); + if (rc < 0) { + pr_err("Failed to acquire gpio rc = %d\n", rc); + return rc; + } + + gpio_direction_output(led->hw_strobe_gpio[i], 0); + + } + + led->all_ramp_up_done_irq = of_irq_get_byname(node, + "all-ramp-up-done-irq"); + if (led->all_ramp_up_done_irq < 0) + pr_debug("all-ramp-up-done-irq not used\n"); + + led->all_ramp_down_done_irq = of_irq_get_byname(node, + "all-ramp-down-done-irq"); + if (led->all_ramp_down_done_irq < 0) + pr_debug("all-ramp-down-done-irq not used\n"); + + led->led_fault_irq = of_irq_get_byname(node, + "led-fault-irq"); + if (led->led_fault_irq < 0) + pr_debug("led-fault-irq not used\n"); + + for_each_available_child_of_node(node, temp) { + rc = of_property_read_string(temp, "label", &label); + if (rc < 0) { + pr_err("Failed to parse label, rc=%d\n", rc); + of_node_put(temp); + return rc; + } + + if (!strcmp("flash", label) || !strcmp("torch", label)) { + led->num_fnodes++; + } else if (!strcmp("switch", label)) { + led->num_snodes++; + } else { + pr_err("Invalid label for led node label=%s\n", + label); + of_node_put(temp); + return -EINVAL; + } + } + + if (!led->num_fnodes) { + pr_err("No flash/torch devices defined\n"); + return -ECHILD; + } + + if (!led->num_snodes) { + pr_err("No switch devices defined\n"); + return -ECHILD; + } + + led->fnode = devm_kcalloc(&led->pdev->dev, led->num_fnodes, + sizeof(*led->fnode), GFP_KERNEL); + led->snode = devm_kcalloc(&led->pdev->dev, led->num_snodes, + sizeof(*led->snode), GFP_KERNEL); + if ((!led->fnode) || (!led->snode)) + return -ENOMEM; + + i = 0; + for_each_available_child_of_node(node, temp) { + rc = of_property_read_string(temp, "label", &label); + if (rc < 0) { + pr_err("Failed to parse label, rc=%d\n", rc); + of_node_put(temp); + return rc; + } + + if (!strcmp("flash", label) || !strcmp("torch", label)) { + rc = register_flash_device(led, &led->fnode[i], temp); + if (rc < 0) { + pr_err("Failed to register flash device %s rc=%d\n", + led->fnode[i].fdev.led_cdev.name, rc); + of_node_put(temp); + goto unreg_led; + } + led->fnode[i++].fdev.led_cdev.dev->of_node = temp; + } else { + rc = register_switch_device(led, &led->snode[j], temp); + if (rc < 0) { + pr_err("Failed to register switch device %s rc=%d\n", + led->snode[j].cdev.name, rc); + i--; + of_node_put(temp); + goto unreg_led; + } + led->snode[j++].cdev.dev->of_node = temp; + } + } + + return 0; + +unreg_led: + while (i >= 0) + led_classdev_flash_unregister(&led->fnode[i--].fdev); + + return rc; +} + +static int qti_flash_led_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct qti_flash_led *led; + int 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) { + pr_err("Failed to get parent's regmap\n"); + return -EINVAL; + } + + led->max_channels = (u8)(uintptr_t)of_device_get_match_data(&pdev->dev); + if (!led->max_channels) { + pr_err("Failed to get max supported led channels\n"); + return -EINVAL; + } + + led->pdev = pdev; + spin_lock_init(&led->lock); + + rc = qti_flash_led_register_device(led, node); + if (rc < 0) { + pr_err("Failed to parse and register LED devices rc=%d\n", rc); + return rc; + } + + rc = qti_flash_led_setup(led); + if (rc < 0) { + pr_err("Failed to initialize flash LED, rc=%d\n", rc); + return rc; + } + + rc = qti_flash_led_register_interrupts(led); + if (rc < 0) { + pr_err("Failed to register LED interrupts rc=%d\n", rc); + return rc; + } + + dev_set_drvdata(&pdev->dev, led); + + return 0; +} + +static int qti_flash_led_remove(struct platform_device *pdev) +{ + struct qti_flash_led *led = dev_get_drvdata(&pdev->dev); + int i, j; + + if (led->batt_psy) + power_supply_put(led->batt_psy); + + for (i = 0; (i < led->num_snodes); i++) { + for (j = 0; j < ARRAY_SIZE(qti_flash_led_attrs); j++) + sysfs_remove_file(&led->snode[i].cdev.dev->kobj, + &qti_flash_led_attrs[j].attr); + + led_classdev_unregister(&led->snode[i].cdev); + } + + for (i = 0; (i < led->num_fnodes); i++) + led_classdev_flash_unregister(&led->fnode[i].fdev); + + return 0; +} + +static const struct of_device_id qti_flash_led_match_table[] = { + { .compatible = "qcom,pm8350c-flash-led", .data = (void *)4, }, + { }, +}; + +static struct platform_driver qti_flash_led_driver = { + .driver = { + .name = "leds-qti-flash", + .of_match_table = qti_flash_led_match_table, + }, + .probe = qti_flash_led_probe, + .remove = qti_flash_led_remove, +}; + +module_platform_driver(qti_flash_led_driver); + +MODULE_DESCRIPTION("QTI Flash LED driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/leds-qti-flash.h b/include/linux/leds-qti-flash.h new file mode 100644 index 000000000000..337ba0369f91 --- /dev/null +++ b/include/linux/leds-qti-flash.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __LEDS_QTI_FLASH_H +#define __LEDS_QTI_FLASH_H + +#include + +#define ENABLE_REGULATOR BIT(0) +#define DISABLE_REGULATOR BIT(1) +#define QUERY_MAX_AVAIL_CURRENT BIT(2) + +/** + * struct flash_led_param: QTI flash LED parameter data + * @on_time_ms : Time to wait before strobing the switch + * @off_time_ms : Time to wait to turn off LED after strobing switch + */ +struct flash_led_param { + u64 on_time_ms; + u64 off_time_ms; +}; + +#if IS_ENABLED(CONFIG_LEDS_QTI_FLASH) +int qti_flash_led_prepare(struct led_trigger *trig, + int options, int *max_current); +int qti_flash_led_set_param(struct led_trigger *trig, + struct flash_led_param param); +#else +static inline int qti_flash_led_prepare(struct led_trigger *trig, + int options, int *max_current) +{ + return -EINVAL; +} + +static inline int qti_flash_led_set_param(struct led_trigger *trig, + struct flash_led_param param); +{ + return -EINVAL; +} +#endif +#endif