2020-11-06 17:00:56

by Nícolas F. R. A. Prado

[permalink] [raw]
Subject: [RFC PATCH 1/3] leds: Add driver for QPNP flash led

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 <[email protected]>
---
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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spmi.h>
+#include <linux/of_device.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/workqueue.h>
+#include <linux/leds.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+
+#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, &reg);
+ 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", &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, &regulator_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");
--
2.29.2



2020-11-08 16:54:56

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [RFC PATCH 1/3] leds: Add driver for QPNP flash led

Hi Nicolas,

We have LED flash class framework since 2015. Please refer to the
following files:

Documentation/leds/leds-class-flash.rst
Documentation/ABI/testing/sysfs-class-led-flash
Documentation/devicetree/bindings/leds/common.yaml
drivers/leds/led-class-flash.c

Thare are also few LED flash drivers in the tree. Since there seems to
be boost feature present on the the device then you might want to
compare drivers/leds/leds-max77693.c with its bindings
Documentation/devicetree/bindings/mfd/max77693.txt (refer to LED part).

Please also remember to include DT bindings patch to your series.

On 11/6/20 5:58 PM, Nícolas F. R. A. Prado wrote:
> 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 <[email protected]>
> ---
> 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
>

--
Best regards,
Jacek Anaszewski

2020-11-22 04:51:02

by Bjorn Andersson

[permalink] [raw]
Subject: Re: [RFC PATCH 1/3] leds: Add driver for QPNP flash led

On Fri 06 Nov 10:58 CST 2020, N?colas F. R. A. Prado wrote:

> 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 <[email protected]>
> ---
> 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.
> +

Downstream they seem to have a single "led driver" dealing with all the
LED related interfaces in the PMIC. We have WLED upstream already and
I've been poking at an "LPG" driver.

So as you look into Jacek's request please make this a "qcom spmi flash
driver", instead of a "qpnp leds" driver.

Regards,
Bjorn