From a0506cb3d476b9d08c60ad9be03a4cc7d1af8307 Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Tue, 19 Nov 2019 22:02:13 +0530 Subject: [PATCH 01/15] leds: add support for QTI flash LED driver QTI flash LED driver supports flash LED module on PM8350C. It is used for illumination by camera sensor and also for torch application. Change-Id: Iaa4754591e37db48c0ab360cdf4660d50c908337 Signed-off-by: Shyam Kumar Thella --- drivers/leds/Kconfig | 15 + drivers/leds/Makefile | 1 + drivers/leds/leds-qti-flash.c | 1120 +++++++++++++++++++++++++++++++++ 3 files changed, 1136 insertions(+) create mode 100644 drivers/leds/leds-qti-flash.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 6090e647daee..540516d1a427 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 e58ecb36360f..c6f2d8e1d145 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..5468711a2375 --- /dev/null +++ b/drivers/leds/leds-qti-flash.c @@ -0,0 +1,1120 @@ +// 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 "leds.h" + +#define FLASH_LED_STATUS1 0x06 + +#define FLASH_LED_STATUS2 0x07 +#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 + +/* Default timer duration is 200ms */ +#define SAFETY_TIMER_DEFAULT_DURATION 0x13 + +#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 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 + +enum flash_led_type { + FLASH_LED_TYPE_UNKNOWN, + FLASH_LED_TYPE_FLASH, + FLASH_LED_TYPE_TORCH, +}; + +enum strobe_type { + SW_STROBE = 0, + HW_STROBE, +}; + +/* Configurations for each individual flash or torch device */ +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; + 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 + * @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 + * @base : Base address of the flash LED module + * @max_channels : Maximum number of channels supported by flash module + * @ref_count : Reference count used to enable/disable flash LED + */ +struct qti_flash_led { + struct platform_device *pdev; + struct regmap *regmap; + struct flash_node_data *fnode; + struct flash_switch_data *snode; + 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; + u16 base; + u8 max_channels; + u8 ref_count; +}; + +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 < SAFETY_TIMER_MIN_TIMEOUT_MS || + 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; + u32 val; + + rc = regmap_bulk_read(led->regmap, (led->base + offset), &val, len); + if (rc < 0) { + pr_err("Failed to read from 0x%04X rc = %d\n", + (led->base + offset), rc); + } else { + pr_debug("Read 0x%02X from addr 0x%04X\n", val, + (led->base + offset)); + *data = (u8)val; + } + + 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 0x%02X to addr 0x%04X\n", 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 0x%02X to addr 0x%04X\n", data, + (led->base + offset)); + + return rc; +} + +static int qti_flash_led_strobe(struct flash_node_data *fnode, + bool enable) +{ + struct qti_flash_led *led = fnode->led; + int rc; + u8 val; + + if (fnode->enabled == enable) + return 0; + + spin_lock(&led->lock); + + if (enable) { + if (!led->ref_count) { + val = FLASH_MODULE_ENABLE; + rc = qti_flash_led_write(led, FLASH_ENABLE_CONTROL, + &val, 1); + if (rc < 0) + goto error; + } + led->ref_count++; + + rc = qti_flash_led_masked_write(led, FLASH_EN_LED_CTRL, + FLASH_LED_ENABLE(fnode->id), + FLASH_LED_ENABLE(fnode->id)); + if (rc < 0) + goto error; + } else { + rc = qti_flash_led_masked_write(led, FLASH_EN_LED_CTRL, + FLASH_LED_ENABLE(fnode->id), + FLASH_LED_DISABLE); + if (rc < 0) + goto error; + + if (led->ref_count) + led->ref_count--; + + if (!led->ref_count) { + val = FLASH_MODULE_DISABLE; + rc = qti_flash_led_write(led, FLASH_ENABLE_CONTROL, + &val, 1); + if (rc < 0) + goto error; + } + } + + if (!rc) + fnode->enabled = enable; +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 && fnode->duration) { + 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; + } else { + rc = qti_flash_led_masked_write(led, + FLASH_LED_SAFETY_TIMER(addr_offset), + FLASH_LED_SAFETY_TIMER_EN_MASK, 0); + 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; + + 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; + +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 <= 0) { + rc = qti_flash_led_disable(fnode); + if (rc < 0) + pr_err("Failed to set brightness %d to LED\n", + brightness); + 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) { + pr_err("Error in symmetry configuration for switch device\n"); + return -EINVAL; + } + + for (i = 0; i < led->num_fnodes; i++) { + if ((snode->led_mask & BIT(led->fnode[i].id)) && + (led->fnode[i].type == type)) { + total_curr_ma += led->fnode[i].current_ma; + 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 void qti_flash_led_switch_brightness_set( + struct led_classdev *led_cdev, enum led_brightness value) +{ + struct qti_flash_led *led = NULL; + struct flash_switch_data *snode = NULL; + int rc = 0, i; + 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; + } + + led = snode->led; + + /* If symmetry enabled switch, then turn ON all its LEDs */ + if (state && 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; + } + } + + for (i = 0; i < led->num_fnodes; i++) { + if (!(snode->led_mask & BIT(led->fnode[i].id)) || + !led->fnode[i].configured) + continue; + + rc = qti_flash_led_strobe(&led->fnode[i], state); + if (rc < 0) { + pr_err("Failed to %s LED%d\n", + state ? "strobe" : "destrobe", + &led->fnode[i].id); + break; + } + + if (!state) { + rc = qti_flash_led_disable(&led->fnode[i]); + if (rc < 0) { + pr_err("Failed to disable LED%d\n", + &led->fnode[i].id); + break; + } + } + } + + if (!rc) + snode->enabled = state; +} + +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; + + fnode = container_of(fdev, struct flash_node_data, fdev); + + return qti_flash_led_strobe(fnode, state); +} + +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; + + if (timeout < SAFETY_TIMER_MIN_TIMEOUT_MS || + timeout > SAFETY_TIMER_MAX_TIMEOUT_MS) + return -EINVAL; + + fnode = container_of(fdev, struct flash_node_data, fdev); + led = fnode->led; + + 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; + + 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; + } + + 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, "all_ramp_up_done", 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, "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, "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; + + 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->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; + } + + return 0; +} + +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; + + 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; + + fnode->duration = SAFETY_TIMER_DEFAULT_DURATION; + rc = of_property_read_u32(node, "qcom,duration-ms", &val); + if (!rc) { + rc = timeout_to_code(val); + if (rc < 0) { + pr_err("Incorrect timeout configured %u\n", val); + 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 = SAFETY_TIMER_MIN_TIMEOUT_MS; + setting->max = SAFETY_TIMER_MAX_TIMEOUT_MS; + setting->step = 1; + setting->val = SAFETY_TIMER_DEFAULT_DURATION; + + 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; + + for (i = 0; (i < led->num_snodes); i++) + 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"); From 5f5a820f0fbcb53f88e43edfa51f79fbc163fdbe Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Wed, 5 Feb 2020 16:11:28 +0530 Subject: [PATCH 02/15] leds: qti-flash: Fix updating flags when disabling flash/torch device Currently when brightness is zeroed-out for a flash/torch device its "configured" flag is set to false. Hence the flash/torch device is ignored when its switch is disabled. This leads to the corresponding LED channel left enabled. Fix this by setting "configured" flag to false only when led strobe is de-asserted. Change-Id: I46c640d3e0db8611343e2e67321db820161dd144 Signed-off-by: Shyam Kumar Thella --- drivers/leds/leds-qti-flash.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 5468711a2375..29ae94f083df 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -257,6 +257,8 @@ static int qti_flash_led_strobe(struct flash_node_data *fnode, if (rc < 0) goto error; + fnode->configured = false; + if (led->ref_count) led->ref_count--; @@ -353,7 +355,7 @@ static int qti_flash_led_disable(struct flash_node_data *fnode) if (rc < 0) goto out; - fnode->configured = false; + fnode->current_ma = 0; out: spin_unlock(&led->lock); From 94dbc769e4750911b3c9f6be8e4d47bdef6d990a Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Tue, 11 Feb 2020 18:09:32 -0800 Subject: [PATCH 03/15] leds: qti-flash: Update interrupt names Update interrupt names to have "flash" prefix so that all the interrupts for flash LED can be monitored easily. Change-Id: Ib7c17ea48110461823702c8d11b7554ddebbbb26 Signed-off-by: Subbaraman Narayanamurthy --- drivers/leds/leds-qti-flash.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 29ae94f083df..78f1ee7213c1 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -677,7 +677,7 @@ static int qti_flash_led_register_interrupts(struct qti_flash_led *led) rc = devm_request_threaded_irq(&led->pdev->dev, led->all_ramp_up_done_irq, NULL, qti_flash_led_irq_handler, - IRQF_ONESHOT, "all_ramp_up_done", led); + 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); @@ -686,7 +686,7 @@ static int qti_flash_led_register_interrupts(struct qti_flash_led *led) rc = devm_request_threaded_irq(&led->pdev->dev, led->all_ramp_down_done_irq, NULL, qti_flash_led_irq_handler, - IRQF_ONESHOT, "all_ramp_down", led); + 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, @@ -696,7 +696,7 @@ static int qti_flash_led_register_interrupts(struct qti_flash_led *led) rc = devm_request_threaded_irq(&led->pdev->dev, led->led_fault_irq, NULL, qti_flash_led_irq_handler, - IRQF_ONESHOT, "fault", led); + 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); From 6a1350e53da1ac00b9969cd9aff03f92f768c50c Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Fri, 7 Feb 2020 16:26:00 +0530 Subject: [PATCH 04/15] leds: qti-flash: Add individual APIs to enable and disable switch Currently qti_flash_led_switch_brightness_set() is used to enable or disable switch. Split it into separate APIs to enable and disable switch. This will make it easier to control switches which is required for implementing slow-sync flash feature. Change-Id: I668ab872d1d646f97ed8fbfba4dfca6b4db5bc70 Signed-off-by: Shyam Kumar Thella --- drivers/leds/leds-qti-flash.c | 191 ++++++++++++++++++++++------------ 1 file changed, 125 insertions(+), 66 deletions(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 78f1ee7213c1..9c9d8e073b41 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -223,56 +223,66 @@ static int qti_flash_led_masked_write(struct qti_flash_led *led, return rc; } -static int qti_flash_led_strobe(struct flash_node_data *fnode, +static int qti_flash_led_module_control(struct qti_flash_led *led, bool enable) { - struct qti_flash_led *led = fnode->led; - int rc; + int rc = 0; u8 val; - if (fnode->enabled == enable) - return 0; - - spin_lock(&led->lock); - if (enable) { if (!led->ref_count) { val = FLASH_MODULE_ENABLE; rc = qti_flash_led_write(led, FLASH_ENABLE_CONTROL, - &val, 1); + &val, 1); if (rc < 0) - goto error; + return rc; } + led->ref_count++; - - rc = qti_flash_led_masked_write(led, FLASH_EN_LED_CTRL, - FLASH_LED_ENABLE(fnode->id), - FLASH_LED_ENABLE(fnode->id)); - if (rc < 0) - goto error; } else { - rc = qti_flash_led_masked_write(led, FLASH_EN_LED_CTRL, - FLASH_LED_ENABLE(fnode->id), - FLASH_LED_DISABLE); - if (rc < 0) - goto error; - - fnode->configured = false; - if (led->ref_count) led->ref_count--; if (!led->ref_count) { val = FLASH_MODULE_DISABLE; rc = qti_flash_led_write(led, FLASH_ENABLE_CONTROL, - &val, 1); + &val, 1); if (rc < 0) - goto error; + return rc; } } - if (!rc) - fnode->enabled = enable; + return rc; +} + +static int qti_flash_led_strobe(struct qti_flash_led *led, + u8 mask, u8 value) +{ + int rc; + bool enable = mask & value; + + spin_lock(&led->lock); + + if (enable) { + rc = qti_flash_led_module_control(led, enable); + if (rc < 0) + goto error; + + rc = qti_flash_led_masked_write(led, FLASH_EN_LED_CTRL, + mask, value); + if (rc < 0) + goto error; + } else { + 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); @@ -468,12 +478,73 @@ static int qti_flash_led_symmetry_config( 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->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++) { + /* + * 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; + } + + led_dis |= (1 << led->fnode[i].id); + led->fnode[i].configured = false; + } + + return qti_flash_led_strobe(led, led_dis, ~led_dis); +} + static void qti_flash_led_switch_brightness_set( struct led_classdev *led_cdev, enum led_brightness value) { - struct qti_flash_led *led = NULL; struct flash_switch_data *snode = NULL; - int rc = 0, i; + int rc = 0; bool state = value > 0; snode = container_of(led_cdev, struct flash_switch_data, cdev); @@ -484,42 +555,15 @@ static void qti_flash_led_switch_brightness_set( return; } - led = snode->led; + if (state) + rc = qti_flash_switch_enable(snode); + else + rc = qti_flash_switch_disable(snode); - /* If symmetry enabled switch, then turn ON all its LEDs */ - if (state && 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; - } - } - - for (i = 0; i < led->num_fnodes; i++) { - if (!(snode->led_mask & BIT(led->fnode[i].id)) || - !led->fnode[i].configured) - continue; - - rc = qti_flash_led_strobe(&led->fnode[i], state); - if (rc < 0) { - pr_err("Failed to %s LED%d\n", - state ? "strobe" : "destrobe", - &led->fnode[i].id); - break; - } - - if (!state) { - rc = qti_flash_led_disable(&led->fnode[i]); - if (rc < 0) { - pr_err("Failed to disable LED%d\n", - &led->fnode[i].id); - break; - } - } - } - - if (!rc) + if (rc < 0) + pr_err("Failed to %s switch, rc=%d\n", + state ? "enable" : "disable", rc); + else snode->enabled = state; } @@ -551,10 +595,25 @@ 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); - return qti_flash_led_strobe(fnode, state); + if (fnode->enabled == state) + return 0; + + mask = FLASH_LED_ENABLE(fnode->id); + value = state ? FLASH_LED_ENABLE(fnode->id) : 0; + + rc = qti_flash_led_strobe(fnode->led, mask, value); + if (!rc) { + fnode->enabled = state; + if (!state) + fnode->configured = false; + } + + return rc; } static int qti_flash_strobe_get(struct led_classdev_flash *fdev, From 685aaa4e194af7bf0a35ed10f7ef57434d0ae3e6 Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Mon, 10 Feb 2020 22:29:50 +0530 Subject: [PATCH 05/15] leds: qti-flash: Add support for qti_flash_led_prepare() qti_flash_led_prepare() API is used by flash clients (E.g. Camera) to query maximum current available for flash. Add support for this API which initially returns maximum current that is supported for flash LED module (2000 mA). Also, add a sysfs parameter under switch devices to show max available flash current. Change-Id: I3aed009a0d7e0f34a6d05a45af34fcd0f37a6fe7 Signed-off-by: Shyam Kumar Thella Signed-off-by: Guru Das Srinagesh --- drivers/leds/leds-qti-flash.c | 90 ++++++++++++++++++++++++++++++++-- include/linux/leds-qti-flash.h | 23 +++++++++ 2 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 include/linux/leds-qti-flash.h diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 9c9d8e073b41..612af5be619f 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,7 @@ #define IRES_12P5_UA 12500 #define IRES_5P0_UA 5000 #define IRES_DEFAULT_UA IRES_12P5_UA +#define MAX_FLASH_CURRENT_MA 2000 enum flash_led_type { FLASH_LED_TYPE_UNKNOWN, @@ -133,6 +135,7 @@ struct qti_flash_led { int all_ramp_up_done_irq; int all_ramp_down_done_irq; int led_fault_irq; + int max_current; u16 base; u8 max_channels; u8 ref_count; @@ -567,6 +570,65 @@ static void qti_flash_led_switch_brightness_set( 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 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); + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", snode->led->max_current); +} + +int qti_flash_led_prepare(struct led_trigger *trig, int options, + int *max_current) +{ + struct led_classdev *led_cdev; + struct flash_switch_data *snode; + + 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) { + *max_current = snode->led->max_current; + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL(qti_flash_led_prepare); + +static struct device_attribute qti_flash_led_attrs[] = { + __ATTR(max_current, 0664, qti_flash_led_max_current_show, NULL), +}; + static int qti_flash_brightness_set_blocking( struct led_classdev *led_cdev, enum led_brightness value) { @@ -685,6 +747,8 @@ static int qti_flash_led_setup(struct qti_flash_led *led) return rc; } + led->max_current = MAX_FLASH_CURRENT_MA; + return rc; } @@ -768,7 +832,7 @@ static int qti_flash_led_register_interrupts(struct qti_flash_led *led) static int register_switch_device(struct qti_flash_led *led, struct flash_switch_data *snode, struct device_node *node) { - int rc; + int rc, i; rc = of_property_read_string(node, "qcom,led-name", &snode->cdev.name); @@ -808,7 +872,22 @@ static int register_switch_device(struct qti_flash_led *led, 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, @@ -1150,10 +1229,15 @@ static int qti_flash_led_probe(struct platform_device *pdev) static int qti_flash_led_remove(struct platform_device *pdev) { struct qti_flash_led *led = dev_get_drvdata(&pdev->dev); - int i; + int i, j; + + 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); - for (i = 0; (i < led->num_snodes); i++) led_classdev_unregister(&led->snode[i].cdev); + } for (i = 0; (i < led->num_fnodes); i++) led_classdev_flash_unregister(&led->fnode[i].fdev); diff --git a/include/linux/leds-qti-flash.h b/include/linux/leds-qti-flash.h new file mode 100644 index 000000000000..f313d79de7c1 --- /dev/null +++ b/include/linux/leds-qti-flash.h @@ -0,0 +1,23 @@ +/* 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 QUERY_MAX_AVAIL_CURRENT BIT(0) + +#if IS_ENABLED(CONFIG_LEDS_QTI_FLASH) +int qti_flash_led_prepare(struct led_trigger *trig, + int options, int *max_current); +#else +static inline int qti_flash_led_prepare(struct led_trigger *trig, + int options, int *max_current) +{ + return -EINVAL; +} +#endif +#endif From 7bcf81b20e4a59c44f4e7841b62b4ed89b3e40f8 Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Fri, 14 Feb 2020 15:20:35 +0530 Subject: [PATCH 06/15] leds: qti-flash: Add maximum available flash current prediction Add support to calculate maximum current available for flash module. Clients of flash LED makes use of this API qti_flash_led_prepare() to query maximum available current for different flash LED channels. Change-Id: I26988b1afb1e57a190aa25061189b71ca3f7ea95 Signed-off-by: Shyam Kumar Thella --- drivers/leds/leds-qti-flash.c | 316 ++++++++++++++++++++++++++++++++-- 1 file changed, 305 insertions(+), 11 deletions(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 612af5be619f..db25696b88e6 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -15,14 +15,22 @@ #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_VPH_PWR_LOW BIT(0) +#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) @@ -59,6 +67,17 @@ #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 @@ -67,6 +86,13 @@ #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, @@ -74,12 +100,19 @@ enum flash_led_type { 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, }; -/* Configurations for each individual flash or torch device */ struct flash_node_data { struct qti_flash_led *led; struct led_classdev_flash fdev; @@ -112,6 +145,7 @@ struct flash_switch_data { * @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 @@ -119,15 +153,21 @@ struct flash_switch_data { * @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 * @ref_count : Reference count used to enable/disable flash LED + * @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; @@ -136,9 +176,22 @@ struct qti_flash_led { 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 ref_count; + 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] = { @@ -586,15 +639,214 @@ static struct led_classdev *trigger_to_lcdev(struct led_trigger *trig) return NULL; } -static ssize_t qti_flash_led_max_current_show(struct device *dev, - struct device_attribute *attr, char *buf) +#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) { - struct flash_switch_data *snode; - struct led_classdev *led_cdev = dev_get_drvdata(dev); + static const struct flash_current_headroom *hdrm_map; + int i, j, voltage_hdrm_mv = 0, voltage_hdrm_max = 0; + u32 map_size = 0; - snode = container_of(led_cdev, struct flash_switch_data, cdev); + if (led->subtype == FLASH_LED_4_CHAN) { + hdrm_map = pm8350c_map; + map_size = ARRAY_SIZE(pm8350c_map); + } - return scnprintf(buf, PAGE_SIZE, "%d\n", snode->led->max_current); + 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, @@ -602,6 +854,7 @@ int qti_flash_led_prepare(struct led_trigger *trig, int options, { struct led_classdev *led_cdev; struct flash_switch_data *snode; + int rc = 0; if (!trig) { pr_err("Invalid led_trigger\n"); @@ -617,14 +870,37 @@ int qti_flash_led_prepare(struct led_trigger *trig, int options, snode = container_of(led_cdev, struct flash_switch_data, cdev); if (options & QUERY_MAX_AVAIL_CURRENT) { - *max_current = snode->led->max_current; - return 0; + 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 -EINVAL; + 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 struct device_attribute qti_flash_led_attrs[] = { __ATTR(max_current, 0664, qti_flash_led_max_current_show, NULL), }; @@ -728,6 +1004,18 @@ 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, @@ -749,6 +1037,9 @@ static int qti_flash_led_setup(struct qti_flash_led *led) 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; } @@ -1231,6 +1522,9 @@ 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, From ec59d570b66de1a3fa32b073e403d1ed900a4c3b Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Wed, 12 Feb 2020 12:17:48 +0530 Subject: [PATCH 07/15] leds: qti-flash: Add support for on_time and off_time parameters Add support to enable switch after a delay of on_time milliseconds and to disable switch after a delay of off_time milliseconds. Adding delays to turn ON and OFF the switch is required by flash client (camera) for fine precision flash control. Add sysfs support to configure on_time and off_time for switch devices. E.g. To configure ON time of 1 second and OFF time of 0.5 seconds on switch 0 and strobing flash LED 0, echo 300 > /sys/class/leds/leds:flash_0/flash_brightness echo 1000 > /sys/class/leds/led:switch_0/on_time echo 500 > /sys/class/leds/led_switch_0/off_time echo 1 > /sys/class/leds/led_switch_0/brightness Change-Id: I343eddf097bc7bb9960cd3fd4934c9b0a40cf450 Signed-off-by: Shyam Kumar Thella --- drivers/leds/leds-qti-flash.c | 166 +++++++++++++++++++++++++++++++-- include/linux/leds-qti-flash.h | 18 ++++ 2 files changed, 178 insertions(+), 6 deletions(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index db25696b88e6..321f357ef378 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -134,6 +135,10 @@ struct flash_node_data { 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; @@ -312,6 +317,7 @@ static int qti_flash_led_module_control(struct qti_flash_led *led, } static int qti_flash_led_strobe(struct qti_flash_led *led, + struct flash_switch_data *snode, u8 mask, u8 value) { int rc; @@ -324,6 +330,14 @@ static int qti_flash_led_strobe(struct qti_flash_led *led, 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) @@ -563,7 +577,7 @@ static int qti_flash_switch_enable(struct flash_switch_data *snode) led_en |= (1 << led->fnode[i].id); } - return qti_flash_led_strobe(led, snode->led_mask, led_en); + return qti_flash_led_strobe(led, snode, snode->led_mask, led_en); } static int qti_flash_switch_disable(struct flash_switch_data *snode) @@ -593,7 +607,10 @@ static int qti_flash_switch_disable(struct flash_switch_data *snode) led->fnode[i].configured = false; } - return qti_flash_led_strobe(led, led_dis, ~led_dis); + snode->on_time_ms = 0; + snode->off_time_ms = 0; + + return qti_flash_led_strobe(led, NULL, led_dis, ~led_dis); } static void qti_flash_led_switch_brightness_set( @@ -611,10 +628,21 @@ static void qti_flash_led_switch_brightness_set( return; } - if (state) + 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 + } else { rc = qti_flash_switch_disable(snode); + } if (rc < 0) pr_err("Failed to %s switch, rc=%d\n", @@ -639,6 +667,60 @@ static struct led_classdev *trigger_to_lcdev(struct led_trigger *trig) 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 @@ -901,8 +983,73 @@ static ssize_t qti_flash_led_max_current_show(struct device *dev, 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; + + 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, 0664, qti_flash_led_max_current_show, NULL), + __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( @@ -944,7 +1091,7 @@ static int qti_flash_strobe_set(struct led_classdev_flash *fdev, mask = FLASH_LED_ENABLE(fnode->id); value = state ? FLASH_LED_ENABLE(fnode->id) : 0; - rc = qti_flash_led_strobe(fnode->led, mask, value); + rc = qti_flash_led_strobe(fnode->led, NULL, mask, value); if (!rc) { fnode->enabled = state; if (!state) @@ -1152,6 +1299,13 @@ static int register_switch_device(struct qti_flash_led *led, 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; diff --git a/include/linux/leds-qti-flash.h b/include/linux/leds-qti-flash.h index f313d79de7c1..898f30fde893 100644 --- a/include/linux/leds-qti-flash.h +++ b/include/linux/leds-qti-flash.h @@ -10,14 +10,32 @@ #define QUERY_MAX_AVAIL_CURRENT BIT(0) +/** + * 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 From b2ea9ae60026bc569caaf552217eb4d19599b7f2 Mon Sep 17 00:00:00 2001 From: Kiran Gunda Date: Mon, 30 Mar 2020 21:02:32 +0530 Subject: [PATCH 08/15] leds: qti-flash: Update camera flash client interface options The value of "QUERY_MAX_AVAIL_CURRENT" option in the "leds-qti-flash" driver is different from the legacy "leds-qpnp-flash" driver. As a result, the led flash does not work. To fix this issue update the option values of "leds-qti-flash" to align with the legacy driver. Also add logic to handle other options "ENABLE_REGULATOR" and "DISABLE_REGULATOR" to have backward compatibility. Change-Id: I8213d53c6b19ccc8da74784e3b428b99fcebcbb7 Signed-off-by: Kiran Gunda --- drivers/leds/leds-qti-flash.c | 5 +++++ include/linux/leds-qti-flash.h | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 321f357ef378..551818fa2495 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -952,6 +952,11 @@ int qti_flash_led_prepare(struct led_trigger *trig, int options, 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) { diff --git a/include/linux/leds-qti-flash.h b/include/linux/leds-qti-flash.h index 898f30fde893..337ba0369f91 100644 --- a/include/linux/leds-qti-flash.h +++ b/include/linux/leds-qti-flash.h @@ -8,7 +8,9 @@ #include -#define QUERY_MAX_AVAIL_CURRENT BIT(0) +#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 From e9a3973e990cd43919832b8baf08a14b21401a8f Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Fri, 15 May 2020 12:13:14 +0530 Subject: [PATCH 09/15] leds: qti-flash: Limit off_time of switch to a maximum of 1280 ms Currently, Flash LED peripheral supports a maximum safety timer expiry of 1280 ms for flash mode where the target current can go as high as 1500 mA. If off_time is allowed more than that time period, then flash LED can stay ON more than that safety timer period. Hence, clamp off_time to not exceed 1280 ms. Change-Id: Idbd8b41c5041c50e3f714be9efb0ad04b1ee9181 Signed-off-by: Shyam Kumar Thella --- drivers/leds/leds-qti-flash.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 551818fa2495..03d2a60b8184 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -1032,6 +1032,8 @@ static ssize_t qti_flash_off_time_store(struct device *dev, 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; From 1581b111c3879cde6cca73b0770e1e032fe48635 Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Fri, 15 May 2020 12:13:56 +0530 Subject: [PATCH 10/15] leds: qti-flash: Do not enable flash without the timeout When the user strobes a flash LED device with a timeout of 0, configure the safety timer with default timeout to avoid turning flash ON for long duration. Also clear different parameters of flash/torch device like current, timeout when the device is disabled. Change units of min, max, step of flash timeout to micro seconds as flash LED class expects in micro seconds. Change-Id: I780fb2639a1beda38e391e1e0818176f147175ef Signed-off-by: Shyam Kumar Thella --- drivers/leds/leds-qti-flash.c | 59 +++++++++++++++++------------------ 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 03d2a60b8184..55dc0a59b697 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -44,9 +44,7 @@ #define SAFETY_TIMER_MAX_TIMEOUT_MS 1280 #define SAFETY_TIMER_MIN_TIMEOUT_MS 10 #define SAFETY_TIMER_STEP_SIZE 10 - -/* Default timer duration is 200ms */ -#define SAFETY_TIMER_DEFAULT_DURATION 0x13 +#define SAFETY_TIMER_DEFAULT_TIMEOUT_MS 200 #define FLASH_LED_ITARGET(id) (0x42 + id) #define FLASH_LED_ITARGET_MASK GENMASK(6, 0) @@ -389,18 +387,12 @@ static int qti_flash_led_enable(struct flash_node_data *fnode) return 0; } - if (fnode->type == FLASH_LED_TYPE_FLASH && fnode->duration) { + 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; - } else { - rc = qti_flash_led_masked_write(led, - FLASH_LED_SAFETY_TIMER(addr_offset), - FLASH_LED_SAFETY_TIMER_EN_MASK, 0); - if (rc < 0) - goto out; } fnode->configured = true; @@ -1095,15 +1087,20 @@ static int qti_flash_strobe_set(struct led_classdev_flash *fdev, if (fnode->enabled == state) return 0; + if (!state) { + rc = qti_flash_led_disable(fnode); + if (rc < 0) { + pr_err("Failed to disable LED %u\n", fnode->id); + return rc; + } + } + 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) { + if (!rc) fnode->enabled = state; - if (!state) - fnode->configured = false; - } return rc; } @@ -1127,13 +1124,11 @@ static int qti_flash_timeout_set(struct led_classdev_flash *fdev, int rc = 0; u8 val; - if (timeout < SAFETY_TIMER_MIN_TIMEOUT_MS || - timeout > SAFETY_TIMER_MAX_TIMEOUT_MS) - return -EINVAL; - fnode = container_of(fdev, struct flash_node_data, fdev); led = fnode->led; + timeout = timeout / 1000; + rc = timeout_to_code(timeout); if (rc < 0) return rc; @@ -1348,7 +1343,7 @@ static int register_flash_device(struct qti_flash_led *led, struct led_flash_setting *setting; const char *temp_string; int rc; - u32 val, default_curr_ma; + u32 val, default_curr_ma, duration; rc = of_property_read_string(node, "qcom,led-name", &fnode->fdev.led_cdev.name); @@ -1424,16 +1419,18 @@ static int register_flash_device(struct qti_flash_led *led, fnode->max_current = val; fnode->fdev.led_cdev.max_brightness = val; - fnode->duration = SAFETY_TIMER_DEFAULT_DURATION; + duration = SAFETY_TIMER_DEFAULT_TIMEOUT_MS; rc = of_property_read_u32(node, "qcom,duration-ms", &val); - if (!rc) { - rc = timeout_to_code(val); - if (rc < 0) { - pr_err("Incorrect timeout configured %u\n", val); - return rc; - } - fnode->duration = rc; + 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); @@ -1472,10 +1469,10 @@ static int register_flash_device(struct qti_flash_led *led, setting->val = default_curr_ma; setting = &fnode->fdev.timeout; - setting->min = SAFETY_TIMER_MIN_TIMEOUT_MS; - setting->max = SAFETY_TIMER_MAX_TIMEOUT_MS; - setting->step = 1; - setting->val = SAFETY_TIMER_DEFAULT_DURATION; + setting->min = SAFETY_TIMER_MIN_TIMEOUT_MS * 1000; + 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) { From b8d85a03a69bb146e61dd2ef83eac533fad0d650 Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Fri, 15 May 2020 12:13:28 +0530 Subject: [PATCH 11/15] leds: qti-flash: Disable flash LED when brightness is set to 0 Disable the flash LED channel when a brightness of 0 is set. As flash/torch devices can be enabled and disabled using switch as well as from sysfs, reset the state of flash/torch devices when they are disabled instead of doing it in switch disable. Change-Id: If8ffb22d8c72a94325a1e9237778b810a152a293 Signed-off-by: Shyam Kumar Thella --- drivers/leds/leds-qti-flash.c | 47 +++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 55dc0a59b697..5bdbe430a426 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -162,7 +162,8 @@ struct flash_switch_data { * @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 - * @ref_count : Reference count used to enable/disable flash LED + * @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 { @@ -184,7 +185,8 @@ struct qti_flash_led { u8 revision; u8 subtype; u8 max_channels; - u8 ref_count; + u8 chan_en_map; + bool module_en; bool trigger_lmh; }; @@ -289,25 +291,24 @@ static int qti_flash_led_module_control(struct qti_flash_led *led, u8 val; if (enable) { - if (!led->ref_count) { + 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; } - - led->ref_count++; } else { - if (led->ref_count) - led->ref_count--; - - if (!led->ref_count) { + 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; } } @@ -318,12 +319,16 @@ static int qti_flash_led_strobe(struct qti_flash_led *led, struct flash_switch_data *snode, u8 mask, u8 value) { - int rc; + 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; @@ -341,6 +346,11 @@ static int qti_flash_led_strobe(struct qti_flash_led *led, 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) @@ -411,6 +421,9 @@ 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])) @@ -427,6 +440,7 @@ static int qti_flash_led_disable(struct flash_node_data *fnode) if (rc < 0) goto out; + fnode->configured = false; fnode->current_ma = 0; out: @@ -454,11 +468,19 @@ static void qti_flash_led_brightness_set(struct led_classdev *led_cdev, fnode = container_of(fdev, struct flash_node_data, fdev); led = fnode->led; - if (brightness <= 0) { + if (!brightness) { rc = qti_flash_led_disable(fnode); - if (rc < 0) + if (rc < 0) { pr_err("Failed to set brightness %d to LED\n", brightness); + return; + } + + 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; } @@ -596,7 +618,6 @@ static int qti_flash_switch_disable(struct flash_switch_data *snode) } led_dis |= (1 << led->fnode[i].id); - led->fnode[i].configured = false; } snode->on_time_ms = 0; From 70c0ce0f8a1a016e310693bbd9a6c3a0ac61b1da Mon Sep 17 00:00:00 2001 From: Shyam Kumar Thella Date: Mon, 25 May 2020 09:03:31 +0530 Subject: [PATCH 12/15] leds: qti-flash: Disable flash LED channel after De-strobe Currently when a flash/torch device is disabled, ITARGET of the channel is programmed to 0 by qti_flash_led_disable() before the channel is de-strobed. This is causing a ramp down of the flash LED channel during disable. Fix this by programming target current to 0 only after channel is de-strobed. Change-Id: Ie566f4e75b8af8c27b7b9a828823a9bfddc48bb6 Signed-off-by: Shyam Kumar Thella --- drivers/leds/leds-qti-flash.c | 55 +++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 5bdbe430a426..b0a08042c3ff 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -469,17 +469,16 @@ static void qti_flash_led_brightness_set(struct led_classdev *led_cdev, led = fnode->led; if (!brightness) { - rc = qti_flash_led_disable(fnode); + rc = qti_flash_led_strobe(fnode->led, NULL, + FLASH_LED_ENABLE(fnode->id), 0); if (rc < 0) { - pr_err("Failed to set brightness %d to LED\n", - brightness); + pr_err("Failed to destrobe LED, rc=%d\n", rc); return; } - rc = qti_flash_led_strobe(fnode->led, NULL, - FLASH_LED_ENABLE(fnode->id), 0); + rc = qti_flash_led_disable(fnode); if (rc < 0) - pr_err("Failed to destrobe LED, rc=%d\n", rc); + pr_err("Failed to disable LED\n"); return; } @@ -600,6 +599,21 @@ static int qti_flash_switch_disable(struct flash_switch_data *snode) 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 @@ -607,7 +621,7 @@ static int qti_flash_switch_disable(struct flash_switch_data *snode) * ii. brightness is not configured for device under this switch */ if (!(snode->led_mask & BIT(led->fnode[i].id)) || - !led->fnode[i].configured) + !led->fnode[i].configured) continue; rc = qti_flash_led_disable(&led->fnode[i]); @@ -616,14 +630,12 @@ static int qti_flash_switch_disable(struct flash_switch_data *snode) &led->fnode[i].id); break; } - - led_dis |= (1 << led->fnode[i].id); } snode->on_time_ms = 0; snode->off_time_ms = 0; - return qti_flash_led_strobe(led, NULL, led_dis, ~led_dis); + return rc; } static void qti_flash_led_switch_brightness_set( @@ -1108,20 +1120,25 @@ static int qti_flash_strobe_set(struct led_classdev_flash *fdev, if (fnode->enabled == state) return 0; - if (!state) { - rc = qti_flash_led_disable(fnode); - if (rc < 0) { - pr_err("Failed to disable LED %u\n", fnode->id); - return rc; - } - } + if (state && !fnode->configured) + 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) - fnode->enabled = state; + 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; } From 5391dbba0e5e121c5e7f2638b18cd06609e4500f Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Mon, 13 Jul 2020 19:02:34 -0700 Subject: [PATCH 13/15] leds: qti-flash: Allow symmetrical switch on with no channels configured Currently, with LED symmetry enabled, turning on a switch without configuring current in a channel first results in errors being seen. Without LED symmetry enabled, the same action results only in qti_flash_led_strobe() being called with the value argument set to zero. Bring about the same behaviour for LED symmetry as well. Change-Id: I2d6301ec3cc19d89acbd0545509485866ffa9085 Signed-off-by: Guru Das Srinagesh --- drivers/leds/leds-qti-flash.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index b0a08042c3ff..5a00af910a91 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -522,8 +522,8 @@ static int qti_flash_led_symmetry_config( } if (type == FLASH_LED_TYPE_UNKNOWN) { - pr_err("Error in symmetry configuration for switch device\n"); - return -EINVAL; + /* No channels are configured */ + return 0; } for (i = 0; i < led->num_fnodes; i++) { From b5379d18b01924ee4fa4502ffc5b2b2d67cdadec Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Tue, 7 Jul 2020 15:15:01 -0700 Subject: [PATCH 14/15] leds: qti-flash: Fix debug prints, read more than one byte Print the data instead of the pointer in qti_flash_led_write(), update the print in qti_flash_led_masked_write(), and fix qti_flash_led_read() so that it returns a pointer to more than one byte. Change-Id: Ic9cbc35a6552f8978ff9e36da2aafeec69885b9a Signed-off-by: Guru Das Srinagesh --- drivers/leds/leds-qti-flash.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 5a00af910a91..30a669ac2d09 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -235,17 +235,14 @@ static int qti_flash_led_read(struct qti_flash_led *led, u16 offset, u8 *data, u8 len) { int rc; - u32 val; - rc = regmap_bulk_read(led->regmap, (led->base + offset), &val, len); - if (rc < 0) { + 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 0x%02X from addr 0x%04X\n", val, + else + pr_debug("Read %*ph from addr %#x\n", len, data, (led->base + offset)); - *data = (u8)val; - } return rc; } @@ -261,7 +258,7 @@ static int qti_flash_led_write(struct qti_flash_led *led, u16 offset, pr_err("Failed to write to 0x%04X rc = %d\n", (led->base + offset), rc); else - pr_debug("Wrote 0x%02X to addr 0x%04X\n", data, + pr_debug("Wrote %*ph to addr %#x\n", len, data, (led->base + offset)); return rc; @@ -278,7 +275,7 @@ static int qti_flash_led_masked_write(struct qti_flash_led *led, pr_err("Failed to update bits from 0x%04X, rc = %d\n", (led->base + offset), rc); else - pr_debug("Wrote 0x%02X to addr 0x%04X\n", data, + pr_debug("Wrote %#x mask %#x to addr %#x\n", data, mask, (led->base + offset)); return rc; From f47498593af163d4679b682df53db3fd24bde562 Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Tue, 7 Jul 2020 15:19:13 -0700 Subject: [PATCH 15/15] leds: qti-flash: Support zero duration for flash safety timer The minimum timeout that can be set is 10 ms due to the clamping being done in led_set_flash_timeout() in the flash LED class framework. Change the minimum to allow zero timeout to be set, and also disallow setting strobe in this case. Change-Id: Ic34057197c05daf640c2a7d21cbae8e4638319ee Signed-off-by: Guru Das Srinagesh --- drivers/leds/leds-qti-flash.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 30a669ac2d09..646a2aac058f 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -205,8 +205,7 @@ static const u32 flash_led_max_ires_values[MAX_IRES_LEVELS] = { static int timeout_to_code(u32 timeout) { - if (timeout < SAFETY_TIMER_MIN_TIMEOUT_MS || - timeout > SAFETY_TIMER_MAX_TIMEOUT_MS) + if (!timeout || timeout > SAFETY_TIMER_MAX_TIMEOUT_MS) return -EINVAL; return DIV_ROUND_CLOSEST(timeout, SAFETY_TIMER_STEP_SIZE) - 1; @@ -1120,6 +1119,11 @@ static int qti_flash_strobe_set(struct led_classdev_flash *fdev, 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; @@ -1162,6 +1166,11 @@ static int qti_flash_timeout_set(struct led_classdev_flash *fdev, 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); @@ -1504,7 +1513,7 @@ static int register_flash_device(struct qti_flash_led *led, setting->val = default_curr_ma; setting = &fnode->fdev.timeout; - setting->min = SAFETY_TIMER_MIN_TIMEOUT_MS * 1000; + 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;