From patchwork Fri Nov 6 16:58:32 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?TsOtY29sYXMgRi4gUi4gQS4gUHJhZG8=?= X-Patchwork-Id: 11887429 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 036686A2 for ; Fri, 6 Nov 2020 16:58:55 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B5CED22203 for ; Fri, 6 Nov 2020 16:58:54 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=protonmail.com header.i=@protonmail.com header.b="DUwb13uO" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726422AbgKFQ6x (ORCPT ); Fri, 6 Nov 2020 11:58:53 -0500 Received: from mail-02.mail-europe.com ([51.89.119.103]:42920 "EHLO mail-02.mail-europe.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727557AbgKFQ6p (ORCPT ); Fri, 6 Nov 2020 11:58:45 -0500 Date: Fri, 06 Nov 2020 16:58:32 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=protonmail.com; s=protonmail; t=1604681914; bh=xsLf4nlDUCOrHrV/3/k0x1f+6hMboUUSPp1jdSsv+no=; h=Date:To:From:Cc:Reply-To:Subject:In-Reply-To:References:From; b=DUwb13uOJR9og0cykF/L+IpWlgwsipj1az9taG7IzIPY+3JWQi2i16lmGshdGoMew hGnkjYlKABniqvg6xeLjC6svCOMUXE0uO2AG+tTs6d2OxxfmUYhtD1I5Rw7M1dY0TT R0qbFHLEksBJ60mSDrjKp8QBs63pr3y9NqxeX3PU= To: Pavel Machek , Dan Murphy , Bjorn Andersson , Andy Gross , Rob Herring From: =?utf-8?b?TsOtY29sYXMgRi4gUi4gQS4gUHJhZG8=?= Cc: linux-leds@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-arm-msm@vger.kernel.org, devicetree@vger.kernel.org, Brian Masney , Luca Weiss , Russell King , Georgi Djakov , linux-kernel@vger.kernel.org, lkcamp@lists.libreplanetbr.org, andrealmeid@collabora.com Reply-To: =?utf-8?b?TsOtY29sYXMgRi4gUi4gQS4gUHJhZG8=?= Subject: [RFC PATCH 1/3] leds: Add driver for QPNP flash led Message-ID: <20201106165737.1029106-2-nfraprado@protonmail.com> In-Reply-To: <20201106165737.1029106-1-nfraprado@protonmail.com> References: <20201106165737.1029106-1-nfraprado@protonmail.com> MIME-Version: 1.0 X-Spam-Status: No, score=-1.2 required=10.0 tests=ALL_TRUSTED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM shortcircuit=no autolearn=disabled version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on mailout.protonmail.ch Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org Add driver for the QPNP flash LED. It works over SPMI and is part of the PM8941 PMIC. Signed-off-by: NĂ­colas F. R. A. Prado --- drivers/leds/Kconfig | 9 + drivers/leds/Makefile | 1 + drivers/leds/leds-qpnp.c | 1351 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1361 insertions(+) create mode 100644 drivers/leds/leds-qpnp.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 849d3c5f908e..ca5f6e81c064 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -928,6 +928,15 @@ config LEDS_ACER_A500 This option enables support for the Power Button LED of Acer Iconia Tab A500. +config LEDS_QPNP + tristate "Support for QPNP LEDs" + depends on SPMI + help + This driver supports the flash/torch led of Qualcomm PNP PMIC. + + To compile this driver as a module, choose M here: the module will + be called leds-qpnp. + comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 73e603e1727e..055360240801 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -93,6 +93,7 @@ obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o +obj-$(CONFIG_LEDS_QPNP) += leds-qpnp.o # LED SPI Drivers obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o diff --git a/drivers/leds/leds-qpnp.c b/drivers/leds/leds-qpnp.c new file mode 100644 index 000000000000..9970688264aa --- /dev/null +++ b/drivers/leds/leds-qpnp.c @@ -0,0 +1,1351 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FLASH_SAFETY_TIMER 0x40 +#define FLASH_MAX_CURR 0x41 +#define FLASH_LED_0_CURR 0x42 +#define FLASH_LED_1_CURR 0x43 +#define FLASH_CLAMP_CURR 0x44 +#define FLASH_LED_TMR_CTRL 0x48 +#define FLASH_HEADROOM 0x4A +#define FLASH_STARTUP_DELAY 0x4B +#define FLASH_MASK_ENABLE 0x4C +#define FLASH_VREG_OK_FORCE 0x4F +#define FLASH_ENABLE_CONTROL 0x46 +#define FLASH_LED_STROBE_CTRL 0x47 +#define FLASH_LED_UNLOCK_SECURE 0xD0 +#define FLASH_LED_TORCH 0xE4 +#define FLASH_FAULT_DETECT 0x51 +#define FLASH_RAMP_RATE 0x54 +#define FLASH_PERIPHERAL_SUBTYPE 0x05 +#define FLASH_VPH_PWR_DROOP 0x5A + +#define FLASH_MAX_LEVEL 0x4F +#define TORCH_MAX_LEVEL 0x0F +#define FLASH_NO_MASK 0x00 + +#define FLASH_MASK_1 0x20 +#define FLASH_MASK_REG_MASK 0xE0 +#define FLASH_HEADROOM_MASK 0x03 +#define FLASH_SAFETY_TIMER_MASK 0x7F +#define FLASH_CURRENT_MASK 0xFF +#define FLASH_MAX_CURRENT_MASK 0x7F +#define FLASH_TMR_MASK 0x03 +#define FLASH_TMR_WATCHDOG 0x03 +#define FLASH_TMR_SAFETY 0x00 +#define FLASH_FAULT_DETECT_MASK 0X80 +#define FLASH_HW_VREG_OK 0x40 +#define FLASH_VREG_MASK 0xC0 +#define FLASH_STARTUP_DLY_MASK 0x02 +#define FLASH_RAMP_RATE_MASK 0xBF +#define FLASH_VPH_PWR_DROOP_MASK 0xF3 + +#define FLASH_ENABLE_ALL 0xE0 +#define FLASH_ENABLE_MODULE 0x80 +#define FLASH_ENABLE_MODULE_MASK 0x80 +#define FLASH_DISABLE_ALL 0x00 +#define FLASH_ENABLE_MASK 0xE0 +#define FLASH_ENABLE_LED_0 0xC0 +#define FLASH_ENABLE_LED_1 0xA0 +#define FLASH_INIT_MASK 0xE0 +#define FLASH_SELFCHECK_ENABLE 0x80 +#define FLASH_SELFCHECK_DISABLE 0x00 + +#define FLASH_STROBE_SW 0xC0 +#define FLASH_STROBE_HW 0x04 +#define FLASH_STROBE_MASK 0xC7 +#define FLASH_LED_0_OUTPUT 0x80 +#define FLASH_LED_1_OUTPUT 0x40 + +#define FLASH_CURRENT_PRGM_MIN 1 +#define FLASH_CURRENT_PRGM_SHIFT 1 +#define FLASH_CURRENT_MAX 0x4F +#define FLASH_CURRENT_TORCH 0x07 + +#define FLASH_DURATION_200ms 0x13 +#define FLASH_CLAMP_200mA 0x0F + +#define FLASH_TORCH_MASK 0x03 +#define FLASH_LED_TORCH_ENABLE 0x00 +#define FLASH_LED_TORCH_DISABLE 0x03 +#define FLASH_UNLOCK_SECURE 0xA5 +#define FLASH_SECURE_MASK 0xFF + +#define FLASH_SUBTYPE_DUAL 0x01 +#define FLASH_SUBTYPE_SINGLE 0x02 + +#define LED_TRIGGER_DEFAULT "none" + +/** + * enum qpnp_leds - QPNP supported led ids + * @QPNP_ID_WLED - White led backlight + */ +enum qpnp_leds { + QPNP_ID_FLASH1_LED0 = 1, + QPNP_ID_FLASH1_LED1, + QPNP_ID_MAX, +}; + +enum flash_headroom { + HEADROOM_250mV = 0, + HEADROOM_300mV, + HEADROOM_400mV, + HEADROOM_500mV, +}; + +enum flash_startup_dly { + DELAY_10us = 0, + DELAY_32us, + DELAY_64us, + DELAY_128us, +}; + +static u8 flash_debug_regs[] = { + 0x40, 0x41, 0x42, 0x43, 0x44, 0x48, 0x49, 0x4b, 0x4c, + 0x4f, 0x46, 0x47, +}; + +/** + * flash_config_data - flash configuration data + * @current_prgm - current to be programmed, scaled by max level + * @clamp_curr - clamp current to use + * @headroom - headroom value to use + * @duration - duration of the flash + * @enable_module - enable address for particular flash + * @trigger_flash - trigger flash + * @startup_dly - startup delay for flash + * @strobe_type - select between sw and hw strobe + * @peripheral_subtype - module peripheral subtype + * @current_addr - address to write for current + * @second_addr - address of secondary flash to be written + * @safety_timer - enable safety timer or watchdog timer + * @torch_enable - enable flash LED torch mode + * @flash_reg_get - flash regulator attached or not + * @flash_on - flash status, on or off + * @torch_on - torch status, on or off + * @flash_boost_reg - boost regulator for flash + * @torch_boost_reg - boost regulator for torch + */ +struct flash_config_data { + u8 current_prgm; + u8 clamp_curr; + u8 headroom; + u8 duration; + u8 enable_module; + u8 trigger_flash; + u8 startup_dly; + u8 strobe_type; + u8 peripheral_subtype; + u16 current_addr; + u16 second_addr; + bool safety_timer; + bool torch_enable; + bool flash_reg_get; + bool flash_on; + bool torch_on; + struct regulator *flash_boost_reg; + struct regulator *torch_boost_reg; +}; + +/** + * struct qpnp_led_data - internal led data structure + * @led_classdev - led class device + * @delayed_work - delayed work for turning off the LED + * @work - workqueue for led + * @id - led index + * @base_reg - base register given in device tree + * @lock - to protect the transactions + * @reg - cached value of led register + * @num_leds - number of leds in the module + * @max_current - maximum current supported by LED + * @default_on - true: default state max, false, default state 0 + * @turn_off_delay_ms - number of msec before turning off the LED + */ +struct qpnp_led_data { + struct led_classdev cdev; + struct regmap *regmap; + struct device *dev; + struct delayed_work dwork; + struct work_struct work; + int id; + u16 base; + u8 reg; + u8 num_leds; + struct mutex lock; + struct flash_config_data *flash_cfg; + int max_current; + bool default_on; + int turn_off_delay_ms; +}; + +static int led_read_reg(struct qpnp_led_data *led, u16 offset, u8 *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(led->regmap, led->base + offset, &val); + if (ret < 0) + return ret; + + *data = val; + return 0; +} + +static int led_write_reg(struct qpnp_led_data *led, u16 offset, u8 data) +{ + return regmap_write(led->regmap, led->base + offset, data); +} + +static void qpnp_dump_regs(struct qpnp_led_data *led, u8 regs[], u8 array_size) +{ + int i; + u8 val; + + pr_debug("===== %s LED register dump start =====\n", led->cdev.name); + for (i = 0; i < array_size; i++) { + led_read_reg(led, regs[i], &val); + pr_debug("%s: 0x%x = 0x%x\n", led->cdev.name, + led->base + regs[i], val); + } + pr_debug("===== %s LED register dump end =====\n", led->cdev.name); +} + + +static int qpnp_get_common_configs(struct qpnp_led_data *led, + struct device_node *node) +{ + int rc; + u32 val; + const char *temp_string; + + led->cdev.default_trigger = LED_TRIGGER_DEFAULT; + rc = of_property_read_string(node, "linux,default-trigger", + &temp_string); + if (!rc) + led->cdev.default_trigger = temp_string; + else if (rc != -EINVAL) + return rc; + + led->default_on = false; + rc = of_property_read_string(node, "qcom,default-state", + &temp_string); + if (!rc) { + if (strncmp(temp_string, "on", sizeof("on")) == 0) + led->default_on = true; + } else if (rc != -EINVAL) + return rc; + + led->turn_off_delay_ms = 0; + rc = of_property_read_u32(node, "qcom,turn-off-delay-ms", &val); + if (!rc) + led->turn_off_delay_ms = val; + else if (rc != -EINVAL) + return rc; + + return 0; +} + +static void qpnp_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct qpnp_led_data *led; + + led = container_of(led_cdev, struct qpnp_led_data, cdev); + if (value < LED_OFF || value > led->cdev.max_brightness) { + dev_err(led->dev, "Invalid brightness value\n"); + return; + } + + led->cdev.brightness = value; + schedule_work(&led->work); +} + +static enum led_brightness qpnp_led_get(struct led_classdev *led_cdev) +{ + struct qpnp_led_data *led; + + led = container_of(led_cdev, struct qpnp_led_data, cdev); + + return led->cdev.brightness; +} + +static int qpnp_get_config_flash(struct qpnp_led_data *led, + struct device_node *node, bool *reg_set) +{ + int rc; + u32 val; + + led->flash_cfg = devm_kzalloc(led->dev, + sizeof(struct flash_config_data), GFP_KERNEL); + if (!led->flash_cfg) { + dev_err(led->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + rc = led_read_reg(led, FLASH_PERIPHERAL_SUBTYPE, + &led->flash_cfg->peripheral_subtype); + if (rc) { + dev_err(led->dev, + "Unable to read from addr=%x, rc(%d)\n", + FLASH_PERIPHERAL_SUBTYPE, rc); + } + + led->flash_cfg->torch_enable = + of_property_read_bool(node, "qcom,torch-enable"); + + if (led->id == QPNP_ID_FLASH1_LED0) { + led->flash_cfg->enable_module = FLASH_ENABLE_LED_0; + led->flash_cfg->current_addr = FLASH_LED_0_CURR; + led->flash_cfg->trigger_flash = FLASH_LED_0_OUTPUT; + if (!*reg_set) { + led->flash_cfg->flash_boost_reg = + regulator_get(led->dev, + "flash-boost"); + if (IS_ERR(led->flash_cfg->flash_boost_reg)) { + rc = PTR_ERR(led->flash_cfg->flash_boost_reg); + dev_err(led->dev, + "Regulator get failed(%d)\n", rc); + goto error_get_flash_reg; + } + led->flash_cfg->flash_reg_get = true; + *reg_set = true; + } else + led->flash_cfg->flash_reg_get = false; + + if (led->flash_cfg->torch_enable) { + led->flash_cfg->second_addr = + FLASH_LED_1_CURR; + } + } else if (led->id == QPNP_ID_FLASH1_LED1) { + led->flash_cfg->enable_module = FLASH_ENABLE_LED_1; + led->flash_cfg->current_addr = FLASH_LED_1_CURR; + led->flash_cfg->trigger_flash = FLASH_LED_1_OUTPUT; + if (!*reg_set) { + led->flash_cfg->flash_boost_reg = + regulator_get(led->dev, + "flash-boost"); + if (IS_ERR(led->flash_cfg->flash_boost_reg)) { + rc = PTR_ERR(led->flash_cfg->flash_boost_reg); + dev_err(led->dev, + "Regulator get failed(%d)\n", rc); + goto error_get_flash_reg; + } + led->flash_cfg->flash_reg_get = true; + *reg_set = true; + } else + led->flash_cfg->flash_reg_get = false; + + if (led->flash_cfg->torch_enable) { + led->flash_cfg->second_addr = + FLASH_LED_0_CURR; + } + } else { + dev_err(led->dev, "Unknown flash LED name given\n"); + return -EINVAL; + } + + if (led->flash_cfg->torch_enable) { + if (of_find_property(of_get_parent(node), "torch-boost-supply", + NULL)) { + led->flash_cfg->torch_boost_reg = + regulator_get(led->dev, + "torch-boost"); + if (IS_ERR(led->flash_cfg->torch_boost_reg)) { + rc = PTR_ERR(led->flash_cfg->torch_boost_reg); + dev_err(led->dev, + "Torch regulator get failed(%d)\n", rc); + goto error_get_torch_reg; + } + led->flash_cfg->enable_module = FLASH_ENABLE_MODULE; + } else + led->flash_cfg->enable_module = FLASH_ENABLE_ALL; + led->flash_cfg->trigger_flash = FLASH_STROBE_SW; + } + + rc = of_property_read_u32(node, "qcom,current", &val); + if (!rc) { + if (led->flash_cfg->torch_enable) { + led->flash_cfg->current_prgm = (val * + TORCH_MAX_LEVEL / led->max_current); + return 0; + } + else + led->flash_cfg->current_prgm = (val * + FLASH_MAX_LEVEL / led->max_current); + } else + if (led->flash_cfg->torch_enable) + goto error_get_torch_reg; + else + goto error_get_flash_reg; + + rc = of_property_read_u32(node, "qcom,headroom", &val); + if (!rc) + led->flash_cfg->headroom = (u8) val; + else if (rc == -EINVAL) + led->flash_cfg->headroom = HEADROOM_500mV; + else + goto error_get_flash_reg; + + rc = of_property_read_u32(node, "qcom,duration", &val); + if (!rc) + led->flash_cfg->duration = (((u8) val) - 10) / 10; + else if (rc == -EINVAL) + led->flash_cfg->duration = FLASH_DURATION_200ms; + else + goto error_get_flash_reg; + + rc = of_property_read_u32(node, "qcom,clamp-curr", &val); + if (!rc) + led->flash_cfg->clamp_curr = (val * + FLASH_MAX_LEVEL / led->max_current); + else if (rc == -EINVAL) + led->flash_cfg->clamp_curr = FLASH_CLAMP_200mA; + else + goto error_get_flash_reg; + + rc = of_property_read_u32(node, "qcom,startup-dly", &val); + if (!rc) + led->flash_cfg->startup_dly = (u8) val; + else if (rc == -EINVAL) + led->flash_cfg->startup_dly = DELAY_128us; + else + goto error_get_flash_reg; + + led->flash_cfg->safety_timer = + of_property_read_bool(node, "qcom,safety-timer"); + + return 0; + +error_get_torch_reg: + regulator_put(led->flash_cfg->torch_boost_reg); + +error_get_flash_reg: + regulator_put(led->flash_cfg->flash_boost_reg); + return rc; + +} + +static ssize_t led_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct qpnp_led_data *led; + unsigned long state; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + ssize_t ret = -EINVAL; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + led = container_of(led_cdev, struct qpnp_led_data, cdev); + + /* '1' to enable torch mode; '0' to switch to flash mode */ + if (state == 1) + led->flash_cfg->torch_enable = true; + else + led->flash_cfg->torch_enable = false; + + return count; +} + +static ssize_t led_strobe_type_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct qpnp_led_data *led; + unsigned long state; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + ssize_t ret = -EINVAL; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + led = container_of(led_cdev, struct qpnp_led_data, cdev); + + /* '0' for sw strobe; '1' for hw strobe */ + if (state == 1) + led->flash_cfg->strobe_type = 1; + else + led->flash_cfg->strobe_type = 0; + + return count; +} + +static DEVICE_ATTR(led_mode, 0664, NULL, led_mode_store); +static DEVICE_ATTR(strobe, 0664, NULL, led_strobe_type_store); + +static struct attribute *led_attrs[] = { + &dev_attr_led_mode.attr, + &dev_attr_strobe.attr, + NULL +}; + +static const struct attribute_group led_attr_group = { + .attrs = led_attrs, +}; + +static int qpnp_led_set_max_brightness(struct qpnp_led_data *led) +{ + switch (led->id) { + case QPNP_ID_FLASH1_LED0: + case QPNP_ID_FLASH1_LED1: + led->cdev.max_brightness = led->max_current; + break; + default: + dev_err(led->dev, "Invalid LED(%d)\n", led->id); + return -EINVAL; + } + + return 0; +} + +static int +qpnp_led_masked_write(struct qpnp_led_data *led, u16 addr, u8 mask, u8 val) +{ + int rc; + u8 reg; + + rc = led_read_reg(led, addr, ®); + if (rc) { + dev_err(led->dev, + "Unable to read from addr=%x, rc(%d)\n", addr, rc); + } + + reg &= ~mask; + reg |= val; + + rc = led_write_reg(led, addr, reg); + if (rc) + dev_err(led->dev, + "Unable to write to addr=%x, rc(%d)\n", addr, rc); + return rc; +} + +static int qpnp_flash_init(struct qpnp_led_data *led) +{ + int rc; + + led->flash_cfg->flash_on = false; + + rc = qpnp_led_masked_write(led, + FLASH_LED_STROBE_CTRL, + FLASH_STROBE_MASK, FLASH_DISABLE_ALL); + if (rc) { + dev_err(led->dev, + "LED %d flash write failed(%d)\n", led->id, rc); + return rc; + } + + /* Disable flash LED module */ + rc = qpnp_led_masked_write(led, FLASH_ENABLE_CONTROL, + FLASH_ENABLE_MODULE_MASK, FLASH_DISABLE_ALL); + if (rc) { + dev_err(led->dev, + "Enable reg write failed(%d)\n", rc); + return rc; + } + + if (led->flash_cfg->torch_enable) + return 0; + + /* Set headroom */ + rc = qpnp_led_masked_write(led, FLASH_HEADROOM, + FLASH_HEADROOM_MASK, led->flash_cfg->headroom); + if (rc) { + dev_err(led->dev, + "Headroom reg write failed(%d)\n", rc); + return rc; + } + + /* Set startup delay */ + rc = qpnp_led_masked_write(led, + FLASH_STARTUP_DELAY, FLASH_STARTUP_DLY_MASK, + led->flash_cfg->startup_dly); + if (rc) { + dev_err(led->dev, + "Startup delay reg write failed(%d)\n", rc); + return rc; + } + + /* Set timer control - safety or watchdog */ + if (led->flash_cfg->safety_timer) { + rc = qpnp_led_masked_write(led, + FLASH_LED_TMR_CTRL, + FLASH_TMR_MASK, FLASH_TMR_SAFETY); + if (rc) { + dev_err(led->dev, + "LED timer ctrl reg write failed(%d)\n", + rc); + return rc; + } + } + + /* Set Vreg force */ + rc = qpnp_led_masked_write(led, FLASH_VREG_OK_FORCE, + FLASH_VREG_MASK, FLASH_HW_VREG_OK); + if (rc) { + dev_err(led->dev, + "Vreg OK reg write failed(%d)\n", rc); + return rc; + } + + /* Set self fault check */ + rc = qpnp_led_masked_write(led, FLASH_FAULT_DETECT, + FLASH_FAULT_DETECT_MASK, FLASH_SELFCHECK_DISABLE); + if (rc) { + dev_err(led->dev, + "Fault detect reg write failed(%d)\n", rc); + return rc; + } + + /* Set mask enable */ + rc = qpnp_led_masked_write(led, FLASH_MASK_ENABLE, + FLASH_MASK_REG_MASK, FLASH_MASK_1); + if (rc) { + dev_err(led->dev, + "Mask enable reg write failed(%d)\n", rc); + return rc; + } + + /* Set ramp rate */ + rc = qpnp_led_masked_write(led, FLASH_RAMP_RATE, + FLASH_RAMP_RATE_MASK, 0xBF); + if (rc) { + dev_err(led->dev, + "Ramp rate reg write failed(%d)\n", rc); + return rc; + } + + /* Enable VPH_PWR_DROOP and set threshold to 2.9V */ + rc = qpnp_led_masked_write(led, FLASH_VPH_PWR_DROOP, + FLASH_VPH_PWR_DROOP_MASK, 0xC2); + if (rc) { + dev_err(led->dev, + "FLASH_VPH_PWR_DROOP reg write failed(%d)\n", rc); + return rc; + } + + led->flash_cfg->strobe_type = 0; + + /* dump flash registers */ + qpnp_dump_regs(led, flash_debug_regs, ARRAY_SIZE(flash_debug_regs)); + + return 0; +} + +static int qpnp_led_initialize(struct qpnp_led_data *led) +{ + int rc = 0; + + switch (led->id) { + case QPNP_ID_FLASH1_LED0: + case QPNP_ID_FLASH1_LED1: + rc = qpnp_flash_init(led); + if (rc) + dev_err(led->dev, + "FLASH initialize failed(%d)\n", rc); + break; + default: + dev_err(led->dev, "Invalid LED(%d)\n", led->id); + return -EINVAL; + } + + return rc; +} + +static int qpnp_flash_regulator_operate(struct qpnp_led_data *led, bool on) +{ + int rc, i; + struct qpnp_led_data *led_array; + bool regulator_on = false; + + led_array = dev_get_drvdata(led->dev); + if (!led_array) { + dev_err(led->dev, + "Unable to get LED array\n"); + return -EINVAL; + } + + for (i = 0; i < led->num_leds; i++) + regulator_on |= led_array[i].flash_cfg->flash_on; + + if (!on) + goto regulator_turn_off; + + if (!regulator_on && !led->flash_cfg->flash_on) { + for (i = 0; i < led->num_leds; i++) { + if (led_array[i].flash_cfg->flash_reg_get) { + rc = regulator_enable( + led_array[i].flash_cfg->\ + flash_boost_reg); + if (rc) { + dev_err(led->dev, + "Regulator enable failed(%d)\n", + rc); + return rc; + } + led->flash_cfg->flash_on = true; + } + break; + } + } + + return 0; + +regulator_turn_off: + if (regulator_on && led->flash_cfg->flash_on) { + for (i = 0; i < led->num_leds; i++) { + if (led_array[i].flash_cfg->flash_reg_get) { + rc = qpnp_led_masked_write(led, + FLASH_ENABLE_CONTROL, + FLASH_ENABLE_MASK, + FLASH_DISABLE_ALL); + if (rc) { + dev_err(led->dev, + "Enable reg write failed(%d)\n", + rc); + } + + rc = regulator_disable(led_array[i].flash_cfg->\ + flash_boost_reg); + if (rc) { + dev_err(led->dev, + "Regulator disable failed(%d)\n", + rc); + return rc; + } + led->flash_cfg->flash_on = false; + } + break; + } + } + + return 0; +} + +static int qpnp_torch_regulator_operate(struct qpnp_led_data *led, bool on) +{ + int rc; + + if (!on) + goto regulator_turn_off; + + if (!led->flash_cfg->torch_on) { + rc = regulator_enable(led->flash_cfg->torch_boost_reg); + if (rc) { + dev_err(led->dev, + "Regulator enable failed(%d)\n", rc); + return rc; + } + led->flash_cfg->torch_on = true; + } + return 0; + +regulator_turn_off: + if (led->flash_cfg->torch_on) { + rc = qpnp_led_masked_write(led, FLASH_ENABLE_CONTROL, + FLASH_ENABLE_MODULE_MASK, FLASH_DISABLE_ALL); + if (rc) { + dev_err(led->dev, + "Enable reg write failed(%d)\n", rc); + } + + rc = regulator_disable(led->flash_cfg->torch_boost_reg); + if (rc) { + dev_err(led->dev, + "Regulator disable failed(%d)\n", rc); + return rc; + } + led->flash_cfg->torch_on = false; + } + return 0; +} + +static int qpnp_flash_set(struct qpnp_led_data *led) +{ + int rc, error; + int val = led->cdev.brightness; + + if (led->flash_cfg->torch_enable) + led->flash_cfg->current_prgm = + (val * TORCH_MAX_LEVEL / led->max_current); + else + led->flash_cfg->current_prgm = + (val * FLASH_MAX_LEVEL / led->max_current); + + /* Set led current */ + if (val > 0) { + if (led->flash_cfg->torch_enable) { + if (led->flash_cfg->peripheral_subtype == + FLASH_SUBTYPE_DUAL) { + rc = qpnp_torch_regulator_operate(led, true); + if (rc) { + dev_err(led->dev, + "Torch regulator operate failed(%d)\n", + rc); + return rc; + } + } else if (led->flash_cfg->peripheral_subtype == + FLASH_SUBTYPE_SINGLE) { + rc = qpnp_flash_regulator_operate(led, true); + if (rc) { + dev_err(led->dev, + "Flash regulator operate failed(%d)\n", + rc); + goto error_flash_set; + } + + /* + * Write 0x80 to MODULE_ENABLE before writing + * 0xE0 in order to avoid a hardware bug caused + * by register value going from 0x00 to 0xE0. + */ + rc = qpnp_led_masked_write(led, + FLASH_ENABLE_CONTROL, + FLASH_ENABLE_MODULE_MASK, + FLASH_ENABLE_MODULE); + if (rc) { + dev_err(led->dev, + "Enable reg write failed(%d)\n", + rc); + return rc; + } + } + + rc = qpnp_led_masked_write(led, + FLASH_LED_UNLOCK_SECURE, + FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE); + if (rc) { + dev_err(led->dev, + "Secure reg write failed(%d)\n", rc); + goto error_reg_write; + } + + rc = qpnp_led_masked_write(led, + FLASH_LED_TORCH, + FLASH_TORCH_MASK, FLASH_LED_TORCH_ENABLE); + if (rc) { + dev_err(led->dev, + "Torch reg write failed(%d)\n", rc); + goto error_reg_write; + } + + rc = qpnp_led_masked_write(led, + led->flash_cfg->current_addr, + FLASH_CURRENT_MASK, + led->flash_cfg->current_prgm); + if (rc) { + dev_err(led->dev, + "Current reg write failed(%d)\n", rc); + goto error_reg_write; + } + + rc = qpnp_led_masked_write(led, + led->flash_cfg->second_addr, + FLASH_CURRENT_MASK, + led->flash_cfg->current_prgm); + if (rc) { + dev_err(led->dev, + "2nd Current reg write failed(%d)\n", + rc); + goto error_reg_write; + } + + qpnp_led_masked_write(led, FLASH_MAX_CURR, + FLASH_CURRENT_MASK, + TORCH_MAX_LEVEL); + if (rc) { + dev_err(led->dev, + "Max current reg write failed(%d)\n", + rc); + goto error_reg_write; + } + + rc = qpnp_led_masked_write(led, + FLASH_ENABLE_CONTROL, + FLASH_ENABLE_MASK, + led->flash_cfg->enable_module); + if (rc) { + dev_err(led->dev, + "Enable reg write failed(%d)\n", + rc); + goto error_reg_write; + } + + rc = qpnp_led_masked_write(led, + FLASH_LED_STROBE_CTRL, + led->flash_cfg->trigger_flash, + led->flash_cfg->trigger_flash); + if (rc) { + dev_err(led->dev, + "LED %d strobe reg write failed(%d)\n", + led->id, rc); + goto error_reg_write; + } + } else { + rc = qpnp_flash_regulator_operate(led, true); + if (rc) { + dev_err(led->dev, + "Flash regulator operate failed(%d)\n", + rc); + goto error_flash_set; + } + + /* Set flash safety timer */ + rc = qpnp_led_masked_write(led, + FLASH_SAFETY_TIMER, + FLASH_SAFETY_TIMER_MASK, + led->flash_cfg->duration); + if (rc) { + dev_err(led->dev, + "Safety timer reg write failed(%d)\n", + rc); + goto error_flash_set; + } + + /* Set max current */ + rc = qpnp_led_masked_write(led, + FLASH_MAX_CURR, FLASH_CURRENT_MASK, + FLASH_MAX_LEVEL); + if (rc) { + dev_err(led->dev, + "Max current reg write failed(%d)\n", + rc); + goto error_flash_set; + } + + /* Set clamp current */ + rc = qpnp_led_masked_write(led, + FLASH_CLAMP_CURR, + FLASH_CURRENT_MASK, + led->flash_cfg->clamp_curr); + if (rc) { + dev_err(led->dev, + "Clamp current reg write failed(%d)\n", + rc); + goto error_flash_set; + } + + rc = qpnp_led_masked_write(led, + led->flash_cfg->current_addr, + FLASH_CURRENT_MASK, + led->flash_cfg->current_prgm); + if (rc) { + dev_err(led->dev, + "Current reg write failed(%d)\n", rc); + goto error_flash_set; + } + + rc = qpnp_led_masked_write(led, + FLASH_ENABLE_CONTROL, + led->flash_cfg->enable_module, + led->flash_cfg->enable_module); + if (rc) { + dev_err(led->dev, + "Enable reg write failed(%d)\n", rc); + goto error_flash_set; + } + + /* TODO try to not busy wait*/ + mdelay(1); + + if (!led->flash_cfg->strobe_type) { + rc = qpnp_led_masked_write(led, + FLASH_LED_STROBE_CTRL, + led->flash_cfg->trigger_flash, + led->flash_cfg->trigger_flash); + if (rc) { + dev_err(led->dev, + "LED %d strobe reg write failed(%d)\n", + led->id, rc); + goto error_flash_set; + } + } else { + rc = qpnp_led_masked_write(led, + FLASH_LED_STROBE_CTRL, + (led->flash_cfg->trigger_flash | + FLASH_STROBE_HW), + (led->flash_cfg->trigger_flash | + FLASH_STROBE_HW)); + if (rc) { + dev_err(led->dev, + "LED %d strobe reg write failed(%d)\n", + led->id, rc); + goto error_flash_set; + } + } + } + } else { + rc = qpnp_led_masked_write(led, + FLASH_LED_STROBE_CTRL, + led->flash_cfg->trigger_flash, + FLASH_DISABLE_ALL); + if (rc) { + dev_err(led->dev, + "LED %d flash write failed(%d)\n", led->id, rc); + if (led->flash_cfg->torch_enable) + goto error_torch_set; + else + goto error_flash_set; + } + + /* TODO try to not busy wait*/ + mdelay(2); + udelay(160); + + if (led->flash_cfg->torch_enable) { + rc = qpnp_led_masked_write(led, + FLASH_LED_UNLOCK_SECURE, + FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE); + if (rc) { + dev_err(led->dev, + "Secure reg write failed(%d)\n", rc); + goto error_torch_set; + } + + rc = qpnp_led_masked_write(led, + FLASH_LED_TORCH, + FLASH_TORCH_MASK, + FLASH_LED_TORCH_DISABLE); + if (rc) { + dev_err(led->dev, + "Torch reg write failed(%d)\n", rc); + goto error_torch_set; + } + + if (led->flash_cfg->peripheral_subtype == + FLASH_SUBTYPE_DUAL) { + rc = qpnp_torch_regulator_operate(led, false); + if (rc) { + dev_err(led->dev, + "Torch regulator operate failed(%d)\n", + rc); + return rc; + } + } else if (led->flash_cfg->peripheral_subtype == + FLASH_SUBTYPE_SINGLE) { + rc = qpnp_flash_regulator_operate(led, false); + if (rc) { + dev_err(led->dev, + "Flash regulator operate failed(%d)\n", + rc); + return rc; + } + } + } else { + rc = qpnp_led_masked_write(led, + FLASH_ENABLE_CONTROL, + led->flash_cfg->enable_module & + ~FLASH_ENABLE_MODULE_MASK, + FLASH_DISABLE_ALL); + if (rc) { + dev_err(led->dev, + "Enable reg write failed(%d)\n", rc); + if (led->flash_cfg->torch_enable) + goto error_torch_set; + else + goto error_flash_set; + } + + rc = qpnp_flash_regulator_operate(led, false); + if (rc) { + dev_err(led->dev, + "Flash regulator operate failed(%d)\n", + rc); + return rc; + } + } + } + + qpnp_dump_regs(led, flash_debug_regs, ARRAY_SIZE(flash_debug_regs)); + + return 0; + +error_reg_write: + if (led->flash_cfg->peripheral_subtype == FLASH_SUBTYPE_SINGLE) + goto error_flash_set; + +error_torch_set: + error = qpnp_torch_regulator_operate(led, false); + if (error) { + dev_err(led->dev, + "Torch regulator operate failed(%d)\n", rc); + return error; + } + return rc; + +error_flash_set: + error = qpnp_flash_regulator_operate(led, false); + if (error) { + dev_err(led->dev, + "Flash regulator operate failed(%d)\n", rc); + return error; + } + return rc; +} + +static void __qpnp_led_work(struct qpnp_led_data *led, + enum led_brightness value) +{ + int rc; + + mutex_lock(&led->lock); + + switch (led->id) { + case QPNP_ID_FLASH1_LED0: + case QPNP_ID_FLASH1_LED1: + rc = qpnp_flash_set(led); + if (rc < 0) + dev_err(led->dev, + "FLASH set brightness failed (%d)\n", rc); + break; + default: + dev_err(led->dev, "Invalid LED(%d)\n", led->id); + break; + } + mutex_unlock(&led->lock); + +} + +static void qpnp_led_work(struct work_struct *work) +{ + struct qpnp_led_data *led = container_of(work, + struct qpnp_led_data, work); + + __qpnp_led_work(led, led->cdev.brightness); + + return; +} + +static void qpnp_led_turn_off_delayed(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct qpnp_led_data *led + = container_of(dwork, struct qpnp_led_data, dwork); + + led->cdev.brightness = LED_OFF; + qpnp_led_set(&led->cdev, led->cdev.brightness); +} + +static void qpnp_led_turn_off(struct qpnp_led_data *led) +{ + INIT_DELAYED_WORK(&led->dwork, qpnp_led_turn_off_delayed); + schedule_delayed_work(&led->dwork, + msecs_to_jiffies(led->turn_off_delay_ms)); +} + +static int qpnp_leds_probe(struct platform_device *pdev) +{ + struct qpnp_led_data *led, *led_array; + struct device_node *node, *temp; + int rc, i, num_leds = 0, parsed_leds = 0; + int reg; + const char *led_label; + bool regulator_probe = false; + + node = pdev->dev.of_node; + if (node == NULL) + return -ENODEV; + + temp = NULL; + while ((temp = of_get_next_child(node, temp))) + num_leds++; + + if (!num_leds) + return -ECHILD; + + led_array = devm_kzalloc(&pdev->dev, + (sizeof(struct qpnp_led_data) * num_leds), GFP_KERNEL); + if (!led_array) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + for_each_child_of_node(node, temp) { + led = &led_array[parsed_leds]; + led->num_leds = num_leds; + led->dev = &pdev->dev; + led->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!led->regmap) + return -ENODEV; + + rc = of_property_read_u32(node, "reg", ®); + if (rc < 0) { + dev_err(led->dev, + "Failure reading reg, rc = %d\n", rc); + goto fail_id_check; + } + led->base = reg; + + rc = of_property_read_string(temp, "label", &led_label); + if (rc < 0) { + dev_err(led->dev, + "Failure reading label, rc = %d\n", rc); + goto fail_id_check; + } + + rc = of_property_read_string(temp, "linux,name", + &led->cdev.name); + if (rc < 0) { + dev_err(led->dev, + "Failure reading led name, rc = %d\n", rc); + goto fail_id_check; + } + + rc = of_property_read_u32(temp, "qcom,max-current", + &led->max_current); + if (rc < 0) { + dev_err(led->dev, + "Failure reading max_current, rc = %d\n", rc); + goto fail_id_check; + } + + rc = of_property_read_u32(temp, "qcom,id", &led->id); + if (rc < 0) { + dev_err(led->dev, + "Failure reading led id, rc = %d\n", rc); + goto fail_id_check; + } + + rc = qpnp_get_common_configs(led, temp); + if (rc) { + dev_err(led->dev, + "Failure reading common led configuration," \ + " rc = %d\n", rc); + goto fail_id_check; + } + + led->cdev.brightness_set = qpnp_led_set; + led->cdev.brightness_get = qpnp_led_get; + + if (strncmp(led_label, "flash", sizeof("flash")) == 0) { + if (!of_find_property(node, "flash-boost-supply", NULL)) + regulator_probe = true; + rc = qpnp_get_config_flash(led, temp, ®ulator_probe); + if (rc < 0) { + dev_err(led->dev, + "Unable to read flash config data\n"); + goto fail_id_check; + } + } else { + dev_err(led->dev, "No LED matching label\n"); + rc = -EINVAL; + goto fail_id_check; + } + + mutex_init(&led->lock); + INIT_WORK(&led->work, qpnp_led_work); + + rc = qpnp_led_initialize(led); + if (rc < 0) + goto fail_id_check; + + rc = qpnp_led_set_max_brightness(led); + if (rc < 0) + goto fail_id_check; + + rc = led_classdev_register(&pdev->dev, &led->cdev); + if (rc) { + dev_err(&pdev->dev, "unable to register led %d,rc=%d\n", + led->id, rc); + goto fail_id_check; + } + + if (led->id == QPNP_ID_FLASH1_LED0 || + led->id == QPNP_ID_FLASH1_LED1) { + rc = sysfs_create_group(&led->cdev.dev->kobj, + &led_attr_group); + if (rc) + goto fail_id_check; + + } + + /* configure default state */ + if (led->default_on) { + led->cdev.brightness = led->cdev.max_brightness; + __qpnp_led_work(led, led->cdev.brightness); + schedule_work(&led->work); + if (led->turn_off_delay_ms > 0) + qpnp_led_turn_off(led); + } else + led->cdev.brightness = LED_OFF; + + parsed_leds++; + } + dev_set_drvdata(&pdev->dev, led_array); + return 0; + +fail_id_check: + for (i = 0; i < parsed_leds; i++) { + mutex_destroy(&led_array[i].lock); + led_classdev_unregister(&led_array[i].cdev); + } + + return rc; +} + +static int qpnp_leds_remove(struct platform_device *pdev) +{ + struct qpnp_led_data *led_array = dev_get_drvdata(&pdev->dev); + int i, parsed_leds = led_array->num_leds; + + for (i = 0; i < parsed_leds; i++) { + cancel_work_sync(&led_array[i].work); + mutex_destroy(&led_array[i].lock); + led_classdev_unregister(&led_array[i].cdev); + switch (led_array[i].id) { + case QPNP_ID_FLASH1_LED0: + case QPNP_ID_FLASH1_LED1: + if (led_array[i].flash_cfg->flash_reg_get) + regulator_put(led_array[i].flash_cfg-> \ + flash_boost_reg); + if (led_array[i].flash_cfg->torch_enable) + regulator_put(led_array[i].flash_cfg->\ + torch_boost_reg); + sysfs_remove_group(&led_array[i].cdev.dev->kobj, + &led_attr_group); + break; + default: + dev_err(led_array[i].dev, + "Invalid LED(%d)\n", + led_array[i].id); + return -EINVAL; + } + } + + return 0; +} + +static const struct of_device_id qpnp_leds_spmi_of_match[] = { + { .compatible = "qcom,leds-qpnp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, qpnp_leds_spmi_of_match); + +static struct platform_driver qpnp_leds_driver = { + .driver = { + .name = "qcom,leds-qpnp", + .of_match_table = of_match_ptr(qpnp_leds_spmi_of_match), + }, + .probe = qpnp_leds_probe, + .remove = qpnp_leds_remove, +}; +module_platform_driver(qpnp_leds_driver); + +MODULE_DESCRIPTION("QPNP LED driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("leds:leds-qpnp");