summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChun Zhang <chunz@codeaurora.org>2016-03-01 02:34:54 -0800
committerJeevan Shriram <jshriram@codeaurora.org>2016-05-11 17:44:05 -0700
commitc7ff58f93f1f3efb7c81bf4e5ab67f6c7b85228c (patch)
treea1e40e09a2239aeef516dd8cc2c094c348fc0b59
parent9be27fb6e74eed5ffbdbb4f0a934c56072fa9b15 (diff)
leds: leds-qpnp-flash-v2: create v2 QPNP flash LED driver
There is a new Qualcomm Technology Inc. Plug-n-play(QPNP) PMIC chip, which introduces brand new flash LED hardware. The new hardware comes with up to 3 LEDs support, different register mapping layout, and different torch enablement requirement. Therefore, a new driver is introduced to cover this need. Change-Id: Ic878f1a946955edff3a9228e7fe54b7a525e37b1 Signed-off-by: Chun Zhang <chunz@codeaurora.org> Signed-off-by: Mohan Pallaka <mpallaka@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/leds/leds-qpnp-flash-v2.txt153
-rw-r--r--drivers/leds/Kconfig10
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/leds-qpnp-flash-v2.c639
-rw-r--r--include/linux/leds-qpnp-flash-v2.h44
5 files changed, 847 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/leds/leds-qpnp-flash-v2.txt b/Documentation/devicetree/bindings/leds/leds-qpnp-flash-v2.txt
new file mode 100644
index 000000000000..827f96c341b6
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-qpnp-flash-v2.txt
@@ -0,0 +1,153 @@
+Qualcomm Technologies Inc. PNP v2 Flash LED
+
+QPNP (Qualcomm Technologies Inc. Plug N Play) Flash LED (Light
+Emitting Diode) driver v2 is used to provide illumination to
+camera sensor when background light is dim to capture good
+picture. It can also be used for flashlight/torch application.
+It is part of PMIC on Qualcomm Technologies Inc. reference platforms.
+
+Required properties:
+- compatible : Should be "qcom,qpnp-flash-led-v2"
+- reg : Base address and size for flash LED modules
+
+Optional properties:
+- qcom,hdrm-auto-mode : Boolean type to select headroom auto mode enabled or not
+- qcom,isc-delay : Integer type to specify short circuit delay. Valid values are 32, 64,
+ 128, 192. Unit is us.
+
+Required properties inside child node. Child node contains settings for each individual LED.
+Each LED channel needs a flash node and torch node for itself, and an individual switch node to
+serve as an overall switch.
+- label : Type of led that will be used, either "flash", "torch", or "switch.
+- qcom,led-name : Name of the LED.
+- qcom,default-led-trigger : Trigger for the camera flash and torch. Accepted values are
+ "flash0_trigger", "flash1_trigger", "flash2_trigger, "torch0_trigger",
+ "torch1_trigger", "torch2_trigger", and "switch_trigger".
+- qcom,id : ID for each physical LED equipped. In order to handle situation when
+ only 1 or 2 LEDs are installed, flash and torch nodes on LED channel 0
+ should be specified with ID 0; nodes on channel 1 be ID 1, etc. This is
+ not required for switch node.
+- qcom,max-current : Maximum current allowed on this LED. Valid values should be
+ integer from 0 to 1500 inclusive. Flash 2 should have maximum current of
+ 750 per hardware requirement. Unit is mA. This is not required for switch
+ node.
+- qcom,duration-ms : Required property for flash nodes but not needed for torch. Integer
+ type specifying flash duration. Values are from 10ms to 1280ms with
+ 10ms resolution. This is not required for switch node.
+
+Optional properties inside child node:
+- qcom,ires-ua : Integer type to specify current resolution. Accepted values should be
+ 12500, 10000, 7500, and 5000. Unit is uA.
+- qcom,hdrm-voltage-mv : Integer type specifying headroom voltage. Values are from 125mV to 500mV
+ with 25mV resolution. Default setting is 325mV
+- qcom,hdrm-vol-hi-lo-win-mv : Integer type to specify headroom voltage swing range. Values are
+ from 0mV to 375mV with 25mV resolution. Default setting is 100mV.
+- pinctrl-names : This should be defined if a target uses pinctrl framework and there is GPIO
+ requirement for flash LEDs. See "pinctrl" in
+ Documentation/devicetree/bindings/pinctrl/msm-pinctrl.txt. It should specify
+ the names of the configs that pinctrl can install in driver.
+ Following are the pinctrl configs that can be installed:
+ "led_enable" : Enablement configuration of pins. This should specify active
+ config defined in each pin or pin group.
+ "led_disable" : Disablement configuration of pins. This should specify inactive
+ config defined in each pin or pin groups.
+
+Example:
+ qcom,leds@d300 {
+ compatible = "qcom,qpnp-flash-led-v2";
+ status = "okay";
+ reg = <0xd300 0x100>;
+ label = "flash";
+
+ qcom,hdrm-auto-mode;
+ qcom,isc-delay = <192>;
+
+ pmi8998_flash0: qcom,flash_0 {
+ label = "flash";
+ qcom,led-name = "led:flash_0";
+ qcom,max-current = <1500>;
+ qcom,default-led-trigger =
+ "flash0_trigger";
+ qcom,id = <0>;
+ qcom,duration-ms = <1280>;
+ qcom,ires-ua = <12500>;
+ qcom,hdrm-voltage-mv = <325>;
+ qcom,hdrm-vol-hi-lo-win-mv = <100>;
+ };
+
+ pmi8998_flash1: qcom,flash_1 {
+ label = "flash";
+ qcom,led-name = "led:flash_1";
+ qcom,max-current = <1500>;
+ qcom,default-led-trigger =
+ "flash1_trigger";
+ qcom,id = <1>;
+ qcom,duration-ms = <1280>;
+ qcom,ires-ua = <12500>;
+ qcom,hdrm-voltage-mv = <325>;
+ qcom,hdrm-vol-hi-lo-win-mv = <100>;
+ };
+
+ pmi8998_flash2: qcom,flash_2 {
+ label = "flash";
+ qcom,led-name = "led:flash_2";
+ qcom,max-current = <750>;
+ qcom,default-led-trigger =
+ "flash2_trigger";
+ qcom,id = <2>;
+ qcom,duration-ms = <1280>;
+ qcom,ires-ua = <12500>;
+ qcom,hdrm-voltage-mv = <325>;
+ qcom,hdrm-vol-hi-lo-win-mv = <100>;
+ pinctrl-names = "led_enable","led_disable";
+ pinctrl-0 = <&led_enable>;
+ pinctrl-1 = <&led_disable>;
+ };
+
+ pmi8998_torch0: qcom,torch_0 {
+ label = "torch";
+ qcom,led-name = "led:torch_0";
+ qcom,max-current = <200>;
+ qcom,default-led-trigger =
+ "torch0_trigger";
+ qcom,id = <0>;
+ qcom,ires-ua = <12500>;
+ qcom,hdrm-voltage-mv = <325>;
+ qcom,hdrm-vol-hi-lo-win-mv = <100>;
+ };
+
+ pmi8998_torch1: qcom,torch_1 {
+ label = "torch";
+ qcom,led-name = "led:torch_1";
+ qcom,max-current = <200>;
+ qcom,default-led-trigger =
+ "torch1_trigger";
+ qcom,id = <1>;
+ qcom,ires-ua = <12500>;
+ qcom,hdrm-voltage-mv = <325>;
+ qcom,hdrm-vol-hi-lo-win-mv = <100>;
+ };
+
+ pmi8998_torch2: qcom,torch_2 {
+ label = "torch";
+ qcom,led-name = "led:torch_2";
+ qcom,max-current = <200>;
+ qcom,default-led-trigger =
+ "torch2_trigger";
+ qcom,id = <2>;
+ qcom,ires-ua = <12500>;
+ qcom,hdrm-voltage-mv = <325>;
+ qcom,hdrm-vol-hi-lo-win-mv = <100>;
+ pinctrl-names = "led_enable","led_disable";
+ pinctrl-0 = <&led_enable>;
+ pinctrl-1 = <&led_disable>;
+ };
+
+ pmi8998_switch: qcom,led_switch {
+ label = "switch";
+ qcom,led-name = "led:switch";
+ qcom,default-led-trigger =
+ "switch_trigger";
+ };
+ };
+
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 608f0c12e3ab..0fc18922801e 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -602,6 +602,16 @@ config LEDS_QPNP_FLASH
To compile this driver as a module, choose M here: the module will
be called leds-qpnp-flash.
+config LEDS_QPNP_FLASH_V2
+ tristate "Support for QPNP V2 Flash LEDs"
+ depends on LEDS_CLASS && MFD_SPMI_PMIC
+ help
+ This driver supports the leds functionality of Qualcomm Technologies
+ PNP PMIC. It includes Flash Led.
+
+ To compile this driver as a module, choose M here: the module will
+ be called leds-qpnp-flash-v2.
+
config LEDS_QPNP_WLED
tristate "Support for QPNP WLED"
depends on LEDS_CLASS && SPMI
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 04a0f62c4035..8d8ba9175810 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
obj-$(CONFIG_LEDS_QPNP) += leds-qpnp.o
obj-$(CONFIG_LEDS_QPNP_FLASH) += leds-qpnp-flash.o
+obj-$(CONFIG_LEDS_QPNP_FLASH_V2) += leds-qpnp-flash-v2.o
obj-$(CONFIG_LEDS_QPNP_WLED) += leds-qpnp-wled.o
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o
diff --git a/drivers/leds/leds-qpnp-flash-v2.c b/drivers/leds/leds-qpnp-flash-v2.c
new file mode 100644
index 000000000000..89636557dec1
--- /dev/null
+++ b/drivers/leds/leds-qpnp-flash-v2.c
@@ -0,0 +1,639 @@
+/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/platform_device.h>
+#include <linux/leds-qpnp-flash-v2.h>
+
+#define FLASH_LED_REG_SAFETY_TMR(base) (base + 0x40)
+#define FLASH_LED_REG_TGR_CURRENT(base) (base + 0x43)
+#define FLASH_LED_REG_MOD_CTRL(base) (base + 0x46)
+#define FLASH_LED_REG_IRES(base) (base + 0x47)
+#define FLASH_LED_REG_STROBE_CTRL(base) (base + 0x49)
+#define FLASH_LED_REG_CHANNEL_CTRL(base) (base + 0x4C)
+#define FLASH_LED_REG_HDRM_PRGM(base) (base + 0x4D)
+#define FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(base) (base + 0x50)
+#define FLASH_LED_REG_ISC_DELAY(base) (base + 0x52)
+
+#define FLASH_LED_HDRM_MODE_PRGM_MASK 0xFF
+#define FLASH_LED_HDRM_VOL_MASK 0xF0
+#define FLASH_LED_CURRENT_MASK 0x3F
+#define FLASH_LED_STROBE_CTRL_MASK 0x07
+#define FLASH_LED_SAFETY_TMR_MASK_MASK 0x7F
+#define FLASH_LED_MOD_CTRL_MASK 0x80
+#define FLASH_LED_ISC_DELAY_MASK 0x03
+
+#define FLASH_LED_TYPE_FLASH 0
+#define FLASH_LED_TYPE_TORCH 1
+#define FLASH_LED_HEADROOM_AUTO_MODE_ENABLED true
+#define FLASH_LED_ISC_DELAY_SHIFT 6
+#define FLASH_LED_ISC_DELAY_DEFAULT_US 3
+#define FLASH_LED_SAFETY_TMR_VAL_OFFSET 1
+#define FLASH_LED_SAFETY_TMR_VAL_DIVISOR 10
+#define FLASH_LED_SAFETY_TMR_ENABLED 0x08
+#define FLASH_LED_IRES_BASE 3
+#define FLASH_LED_IRES_DIVISOR 2500
+#define FLASH_LED_IRES_MIN_UA 5000
+#define FLASH_LED_IRES_DEFAULT_UA 12500
+#define FLASH_LED_IRES_DEFAULT_VAL 0x00
+#define FLASH_LED_HDRM_VOL_SHIFT 4
+#define FLASH_LED_HDRM_VOL_DEFAULT_MV 0x80
+#define FLASH_LED_HDRM_VOL_HI_LO_WIN_DEFAULT_MV 0x04
+#define FLASH_LED_HDRM_VOL_BASE_MV 125
+#define FLASH_LED_HDRM_VOL_STEP_MV 25
+#define FLASH_LED_STROBE_ENABLE 0x01
+#define FLASH_LED_MOD_ENABLE 0x80
+#define FLASH_LED_DISABLE 0x00
+#define FLASH_LED_SAFETY_TMR_DISABLED 0x13
+
+/*
+ * Flash LED configuration read from device tree
+ */
+struct flash_led_platform_data {
+ u8 isc_delay_us;
+ bool hdrm_auto_mode_en;
+};
+
+/*
+ * Flash LED data structure containing flash LED attributes
+ */
+struct qpnp_flash_led {
+ struct flash_led_platform_data *pdata;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct flash_node_data *fnode;
+ struct flash_switch_data *snode;
+ spinlock_t lock;
+ int num_led_nodes;
+ int num_avail_leds;
+ u16 base;
+};
+
+static int
+qpnp_flash_led_masked_write(struct qpnp_flash_led *led, u16 addr, u8 mask,
+ u8 val)
+{
+ int rc;
+
+ rc = regmap_update_bits(led->regmap, addr, mask, val);
+ if (rc)
+ dev_err(&led->pdev->dev,
+ "Unable to update bits from 0x%02X, rc = %d\n",
+ addr, rc);
+
+ dev_dbg(&led->pdev->dev, "Write 0x%02X to addr 0x%02X\n", val, addr);
+
+ return rc;
+}
+
+static enum
+led_brightness qpnp_flash_led_brightness_get(struct led_classdev *led_cdev)
+{
+ return led_cdev->brightness;
+}
+
+static int qpnp_flash_led_init_settings(struct qpnp_flash_led *led)
+{
+ int rc, i, addr_offset;
+ u8 val = 0;
+
+ for (i = 0; i < led->num_avail_leds; i++) {
+ addr_offset = led->fnode[i].id;
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_HDRM_PRGM(led->base + addr_offset),
+ FLASH_LED_HDRM_MODE_PRGM_MASK,
+ led->fnode[i].hdrm_val);
+ if (rc)
+ return rc;
+
+ val |= 0x1 << led->fnode[i].id;
+ }
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(led->base),
+ FLASH_LED_HDRM_MODE_PRGM_MASK, val);
+ if (rc)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_ISC_DELAY(led->base),
+ FLASH_LED_ISC_DELAY_MASK, led->pdata->isc_delay_us);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static void qpnp_flash_led_node_set(struct flash_node_data *fnode, int value)
+{
+ int prgm_current_ma;
+
+ prgm_current_ma = value < 0 ? 0 : value;
+ prgm_current_ma = value > fnode->cdev.max_brightness ?
+ fnode->cdev.max_brightness : value;
+ fnode->cdev.brightness = prgm_current_ma;
+ fnode->brightness = prgm_current_ma * 1000 / fnode->ires_ua + 1;
+ fnode->led_on = prgm_current_ma != 0;
+}
+
+static int qpnp_flash_led_switch_set(struct flash_switch_data *snode, bool on)
+{
+ struct qpnp_flash_led *led = dev_get_drvdata(&snode->pdev->dev);
+ int rc, i, addr_offset;
+ u8 val;
+
+ if (!on)
+ goto leds_turn_off;
+
+ val = 0;
+ for (i = 0; i < led->num_led_nodes; i++)
+ val |= led->fnode[i].ires << (led->fnode[i].id * 2);
+ rc = qpnp_flash_led_masked_write(led, FLASH_LED_REG_IRES(led->base),
+ FLASH_LED_CURRENT_MASK, val);
+ if (rc)
+ return rc;
+
+ val = 0;
+ for (i = 0; i < led->num_avail_leds; i++) {
+ if (!led->fnode[i].led_on)
+ continue;
+
+ addr_offset = led->fnode[i].id;
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_STROBE_CTRL(led->base + addr_offset),
+ FLASH_LED_STROBE_CTRL_MASK, FLASH_LED_STROBE_ENABLE);
+ if (rc)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_TGR_CURRENT(led->base + addr_offset),
+ FLASH_LED_CURRENT_MASK, led->fnode[i].brightness);
+ if (rc)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_SAFETY_TMR(led->base + addr_offset),
+ FLASH_LED_SAFETY_TMR_MASK_MASK, led->fnode[i].duration);
+ if (rc)
+ return rc;
+
+ val |= FLASH_LED_STROBE_ENABLE << led->fnode[i].id;
+
+ if (led->fnode[i].pinctrl) {
+ rc = pinctrl_select_state(led->fnode[i].pinctrl,
+ led->fnode[i].gpio_state_active);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "failed to enable GPIO\n");
+ return rc;
+ }
+ }
+ }
+
+ rc = qpnp_flash_led_masked_write(led, FLASH_LED_REG_MOD_CTRL(led->base),
+ FLASH_LED_MOD_CTRL_MASK, FLASH_LED_MOD_ENABLE);
+ if (rc)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_CHANNEL_CTRL(led->base),
+ FLASH_LED_STROBE_CTRL_MASK, val);
+ if (rc)
+ return rc;
+
+ return 0;
+
+leds_turn_off:
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_CHANNEL_CTRL(led->base),
+ FLASH_LED_STROBE_CTRL_MASK, FLASH_LED_DISABLE);
+ if (rc)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led, FLASH_LED_REG_MOD_CTRL(led->base),
+ FLASH_LED_MOD_CTRL_MASK, FLASH_LED_DISABLE);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < led->num_led_nodes; i++) {
+ if (!led->fnode[i].led_on)
+ continue;
+
+ addr_offset = led->fnode[i].id;
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_TGR_CURRENT(led->base + addr_offset),
+ FLASH_LED_CURRENT_MASK, 0);
+ if (rc)
+ return rc;
+ led->fnode[i].led_on = false;
+
+ if (led->fnode[i].pinctrl) {
+ rc = pinctrl_select_state(led->fnode[i].pinctrl,
+ led->fnode[i].gpio_state_suspend);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "failed to disable GPIO\n");
+ return rc;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void qpnp_flash_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct flash_node_data *fnode = NULL;
+ struct flash_switch_data *snode = NULL;
+ struct qpnp_flash_led *led = dev_get_drvdata(&fnode->pdev->dev);
+ int rc;
+
+ if (!strcmp(led_cdev->name, "led:switch")) {
+ snode = container_of(led_cdev, struct flash_switch_data, cdev);
+ led = dev_get_drvdata(&snode->pdev->dev);
+ } else {
+ fnode = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&fnode->pdev->dev);
+ }
+
+ spin_lock(&led->lock);
+ if (!fnode) {
+ rc = qpnp_flash_led_switch_set(snode, value > 0);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to set flash LED switch\n");
+ goto exit;
+ }
+ } else {
+ qpnp_flash_led_node_set(fnode, value);
+ }
+
+exit:
+ spin_unlock(&led->lock);
+}
+
+static int qpnp_flash_led_parse_each_led_dt(struct qpnp_flash_led *led,
+ struct flash_node_data *fnode, struct device_node *node)
+{
+ const char *temp_string;
+ int rc;
+ u32 val;
+
+ fnode->pdev = led->pdev;
+ fnode->cdev.brightness_set = qpnp_flash_led_brightness_set;
+ fnode->cdev.brightness_get = qpnp_flash_led_brightness_get;
+
+ rc = of_property_read_string(node, "qcom,led-name", &fnode->cdev.name);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Unable to read flash LED names\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,max-current", &val);
+ if (!rc) {
+ fnode->cdev.max_brightness = val;
+ } else {
+ dev_err(&led->pdev->dev, "Unable to read max current\n");
+ return rc;
+ }
+
+ rc = of_property_read_string(node, "label", &temp_string);
+ if (!rc) {
+ if (!strcmp(temp_string, "flash"))
+ fnode->type = FLASH_LED_TYPE_FLASH;
+ else if (!strcmp(temp_string, "torch"))
+ fnode->type = FLASH_LED_TYPE_TORCH;
+ else {
+ dev_err(&led->pdev->dev, "Wrong flash LED type\n");
+ return rc;
+ }
+ } else {
+ dev_err(&led->pdev->dev, "Unable to read flash LED label\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,id", &val);
+ if (!rc) {
+ fnode->id = (u8)val;
+ } else {
+ dev_err(&led->pdev->dev, "Unable to read flash LED ID\n");
+ return rc;
+ }
+
+ rc = of_property_read_string(node, "qcom,default-led-trigger",
+ &fnode->cdev.default_trigger);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Unable to read trigger name\n");
+ return rc;
+ }
+
+ fnode->ires_ua = FLASH_LED_IRES_DEFAULT_UA;
+ fnode->ires = FLASH_LED_IRES_DEFAULT_VAL;
+ rc = of_property_read_u32(node, "qcom,ires-ua", &val);
+ if (!rc) {
+ fnode->ires_ua = val;
+ fnode->ires = FLASH_LED_IRES_BASE -
+ (val - FLASH_LED_IRES_MIN_UA) / FLASH_LED_IRES_DIVISOR;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read current resolution\n");
+ return rc;
+ }
+
+ fnode->duration = FLASH_LED_SAFETY_TMR_DISABLED;
+ rc = of_property_read_u32(node, "qcom,duration-ms", &val);
+ if (!rc) {
+ fnode->duration = (u8)(((val -
+ FLASH_LED_SAFETY_TMR_VAL_OFFSET) /
+ FLASH_LED_SAFETY_TMR_VAL_DIVISOR) |
+ FLASH_LED_SAFETY_TMR_ENABLED);
+ } else if (rc == -EINVAL) {
+ if (fnode->type == FLASH_LED_TYPE_FLASH) {
+ dev_err(&led->pdev->dev,
+ "Timer duration is required for flash LED\n");
+ return rc;
+ }
+ } else {
+ dev_err(&led->pdev->dev,
+ "Unable to read timer duration\n");
+ return rc;
+ }
+
+ fnode->hdrm_val = FLASH_LED_HDRM_VOL_DEFAULT_MV;
+ rc = of_property_read_u32(node, "qcom,hdrm-voltage-mv", &val);
+ if (!rc) {
+ val = (val - FLASH_LED_HDRM_VOL_BASE_MV) /
+ FLASH_LED_HDRM_VOL_STEP_MV;
+ fnode->hdrm_val = (val << FLASH_LED_HDRM_VOL_SHIFT) &
+ FLASH_LED_HDRM_VOL_MASK;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read headroom voltage\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,hdrm-vol-hi-lo-win-mv", &val);
+ if (!rc) {
+ fnode->hdrm_val |= (val / FLASH_LED_HDRM_VOL_STEP_MV) &
+ ~FLASH_LED_HDRM_VOL_MASK;
+ } else if (rc == -EINVAL) {
+ fnode->hdrm_val |= FLASH_LED_HDRM_VOL_HI_LO_WIN_DEFAULT_MV;
+ } else {
+ dev_err(&led->pdev->dev,
+ "Unable to read hdrm hi-lo window voltage\n");
+ return rc;
+ }
+
+ rc = led_classdev_register(&led->pdev->dev, &fnode->cdev);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Unable to register led node %d\n",
+ fnode->id);
+ return rc;
+ }
+ fnode->cdev.dev->of_node = node;
+
+ fnode->pinctrl = devm_pinctrl_get(fnode->cdev.dev);
+ if (IS_ERR_OR_NULL(fnode->pinctrl)) {
+ dev_err(&led->pdev->dev, "No pinctrl defined\n");
+ fnode->pinctrl = NULL;
+ } else {
+ fnode->gpio_state_active =
+ pinctrl_lookup_state(fnode->pinctrl, "led_enable");
+ if (IS_ERR_OR_NULL(fnode->gpio_state_active)) {
+ dev_err(&led->pdev->dev,
+ "Cannot lookup LED active state\n");
+ devm_pinctrl_put(fnode->pinctrl);
+ fnode->pinctrl = NULL;
+ return PTR_ERR(fnode->gpio_state_active);
+ }
+
+ fnode->gpio_state_suspend =
+ pinctrl_lookup_state(fnode->pinctrl, "led_disable");
+ if (IS_ERR_OR_NULL(fnode->gpio_state_suspend)) {
+ dev_err(&led->pdev->dev,
+ "Cannot lookup LED disable state\n");
+ devm_pinctrl_put(fnode->pinctrl);
+ fnode->pinctrl = NULL;
+ return PTR_ERR(fnode->gpio_state_suspend);
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_flash_led_parse_and_register_switch(struct qpnp_flash_led *led,
+ struct device_node *node)
+{
+ int rc;
+
+ rc = of_property_read_string(node, "qcom,led-name",
+ &led->snode->cdev.name);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Failed to read switch node name\n");
+ return rc;
+ }
+
+ rc = of_property_read_string(node, "qcom,default-led-trigger",
+ &led->snode->cdev.default_trigger);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Unable to read trigger name\n");
+ return rc;
+ }
+
+ led->snode->pdev = led->pdev;
+ led->snode->cdev.brightness_set = qpnp_flash_led_brightness_set;
+ led->snode->cdev.brightness_get = qpnp_flash_led_brightness_get;
+ rc = led_classdev_register(&led->pdev->dev, &led->snode->cdev);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to register led switch node\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qpnp_flash_led_parse_common_dt(struct qpnp_flash_led *led,
+ struct device_node *node)
+{
+ int rc;
+ u32 val;
+
+ led->pdata->hdrm_auto_mode_en = FLASH_LED_HEADROOM_AUTO_MODE_ENABLED;
+ led->pdata->hdrm_auto_mode_en = of_property_read_bool(node,
+ "qcom,hdrm-auto-mode");
+
+ led->pdata->isc_delay_us = FLASH_LED_ISC_DELAY_DEFAULT_US;
+ rc = of_property_read_u32(node, "qcom,isc-delay", &val);
+ if (!rc) {
+ led->pdata->isc_delay_us = val >> FLASH_LED_ISC_DELAY_SHIFT;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read ISC delay\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qpnp_flash_led_probe(struct platform_device *pdev)
+{
+ struct qpnp_flash_led *led;
+ struct device_node *node, *temp;
+ unsigned int base;
+ int rc, i = 0;
+
+ node = pdev->dev.of_node;
+ if (!node) {
+ dev_info(&pdev->dev, "No flash LED nodes defined\n");
+ return -ENODEV;
+ }
+
+ rc = of_property_read_u32(node, "reg", &base);
+ if (rc) {
+ dev_err(&pdev->dev, "Couldn't find reg in node %s, rc = %d\n",
+ node->full_name, rc);
+ return rc;
+ }
+
+ led = devm_kzalloc(&pdev->dev, sizeof(struct qpnp_flash_led),
+ GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!led->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ led->base = base;
+ led->pdev = pdev;
+ led->pdata = devm_kzalloc(&pdev->dev,
+ sizeof(struct flash_led_platform_data), GFP_KERNEL);
+ if (!led->pdata)
+ return -ENOMEM;
+
+ rc = qpnp_flash_led_parse_common_dt(led, node);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Failed to parse common flash LED device tree\n");
+ return rc;
+ }
+
+ for_each_child_of_node(node, temp)
+ led->num_led_nodes++;
+ if (!led->num_led_nodes) {
+ dev_err(&pdev->dev, "No LED nodes defined\n");
+ return -ECHILD;
+ }
+
+ led->fnode = devm_kzalloc(&pdev->dev,
+ sizeof(struct flash_node_data) * (--led->num_led_nodes),
+ GFP_KERNEL);
+ if (!led->fnode)
+ return -ENOMEM;
+
+ temp = NULL;
+ for (i = 0; i < led->num_led_nodes; i++) {
+ temp = of_get_next_child(node, temp);
+ rc = qpnp_flash_led_parse_each_led_dt(led,
+ &led->fnode[i], temp);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Unable to parse flash node %d\n", i);
+ goto error_led_register;
+ }
+ }
+ led->num_avail_leds = i;
+
+ led->snode = devm_kzalloc(&pdev->dev,
+ sizeof(struct flash_switch_data), GFP_KERNEL);
+ if (!led->snode) {
+ rc = -ENOMEM;
+ goto error_led_register;
+ }
+
+ temp = of_get_next_child(node, temp);
+ rc = qpnp_flash_led_parse_and_register_switch(led, temp);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Unable to parse and register switch node\n");
+ goto error_led_register;
+ }
+
+ rc = qpnp_flash_led_init_settings(led);
+ if (rc) {
+ dev_err(&pdev->dev, "Failed to initialize flash LED\n");
+ goto error_switch_register;
+ }
+
+ spin_lock_init(&led->lock);
+
+ dev_set_drvdata(&pdev->dev, led);
+
+ return 0;
+
+error_switch_register:
+ led_classdev_unregister(&led->snode->cdev);
+error_led_register:
+ while (i > 0)
+ led_classdev_unregister(&led->fnode[--i].cdev);
+
+ return rc;
+}
+
+static int qpnp_flash_led_remove(struct platform_device *pdev)
+{
+ struct qpnp_flash_led *led = dev_get_drvdata(&pdev->dev);
+ int i = led->num_led_nodes;
+
+ led_classdev_unregister(&led->snode->cdev);
+ while (i > 0)
+ led_classdev_unregister(&led->fnode[--i].cdev);
+
+ return 0;
+}
+
+const struct of_device_id qpnp_flash_led_match_table[] = {
+ { .compatible = "qcom,qpnp-flash-led-v2",},
+ { },
+};
+
+static struct platform_driver qpnp_flash_led_driver = {
+ .driver = {
+ .name = "qcom,qpnp-flash-led-v2",
+ .of_match_table = qpnp_flash_led_match_table,
+ },
+ .probe = qpnp_flash_led_probe,
+ .remove = qpnp_flash_led_remove,
+};
+
+static int __init qpnp_flash_led_init(void)
+{
+ return platform_driver_register(&qpnp_flash_led_driver);
+}
+late_initcall(qpnp_flash_led_init);
+
+static void __exit qpnp_flash_led_exit(void)
+{
+ platform_driver_unregister(&qpnp_flash_led_driver);
+}
+module_exit(qpnp_flash_led_exit);
+
+MODULE_DESCRIPTION("QPNP Flash LED driver v2");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("leds:leds-qpnp-flash-v2");
diff --git a/include/linux/leds-qpnp-flash-v2.h b/include/linux/leds-qpnp-flash-v2.h
new file mode 100644
index 000000000000..353466f6c108
--- /dev/null
+++ b/include/linux/leds-qpnp-flash-v2.h
@@ -0,0 +1,44 @@
+/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details. 1
+ */
+
+#ifndef __LEDS_QPNP_FLASH_V2_H
+#define __LEDS_QPNP_FLASH_V2_H
+
+#include <linux/leds.h>
+#include "leds.h"
+
+/*
+ * Configurations for each individual LED
+ */
+struct flash_node_data {
+ struct platform_device *pdev;
+ struct led_classdev cdev;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *gpio_state_active;
+ struct pinctrl_state *gpio_state_suspend;
+ int ires_ua;
+ u16 prgm_current;
+ u8 duration;
+ u8 id;
+ u8 type;
+ u8 ires;
+ u8 hdrm_val;
+ u8 brightness;
+ bool led_on;
+};
+
+struct flash_switch_data {
+ struct platform_device *pdev;
+ struct led_classdev cdev;
+};
+
+#endif