mbox series

[v2,0/4] Add support for QCOM SPMI Flash LEDs

Message ID 20210126140240.1517044-1-nfraprado@protonmail.com
Headers show
Series Add support for QCOM SPMI Flash LEDs | expand

Message

Nícolas F. R. A. Prado Jan. 26, 2021, 2:03 p.m. UTC
Hi,

this patch series adds support for Qualcomm's SPMI Flash LEDs present in the
PM8941 PMIC. It is used as part of MSM8974 based devices, like the Nexus 5
(hammerhead), as a camera flash or as a lantern when in torch mode.

Patch 1 adds the dt-bindings for the driver, together with a header for the
values of some properties.

Patch 2 adds the driver, which was ported from downstream [1], and is now using
the flash LED class framework.

Patch 3 enables the driver as a module in qcom_defconfig, and also enables
CONFIG_LEDS_CLASS_FLASH since it is required by the driver.

Patch 4 adds the device tree nodes configuring the driver in the pm8941 dtsi.

After the feedback I received from the v1 RFC patch (thank you Jacek and
Bjorn!), I implemented the flash LED class framework, renamed the driver to
qcom-spmi-flash and added the dt-bindings. I also did a whole lot of cleanup.

Some caveats:
- I still didn't implement get_strobe() and get_fault() for the flash LEDs,
  because I'm still not sure how to do it. get_strobe() in particular I'm not
  even sure if is possible, since after the flash turns off automatically after
  the timeout, I don't see any change in the SPMI registers. So I'm unsure how
  one would get the current strobe state.
- I have yet to add the V4L2 flash wrapper for the flash LEDs. I still didn't do
  it because I wasn't sure if it was needed, so wanted to double check. But
  being a camera flash it seems that would be useful. Also, it would be great if
  someone could point me how I would go about testing the flash usage through
  V4L2.

Another thing worth mentioning: for v1 the dt nodes were added in hammerhead's
dts (just to simplify testing), but I have now moved them to pm8941's dtsi,
since it was like that in downstream. So if folks using devices based on
PM8941/MSM8974 other than the Nexus 5 could test it, that would be great, since
I have only tested on the Nexus 5.

v1 RFC: https://lore.kernel.org/lkml/20201106165737.1029106-1-nfraprado@protonmail.com/

[1] https://github.com/AICP/kernel_lge_hammerhead/blob/n7.1/drivers/leds/leds-qpnp.c

Nícolas F. R. A. Prado (4):
  dt-bindings: leds: Add binding for qcom-spmi-flash
  leds: Add driver for QCOM SPMI Flash LEDs
  ARM: qcom_defconfig: Enable QCOM SPMI Flash LEDs
  ARM: dts: qcom: pm8941: Add nodes for QCOM SPMI Flash LEDs

 .../bindings/leds/leds-qcom-spmi-flash.yaml   |   94 ++
 arch/arm/boot/dts/qcom-pm8941.dtsi            |   38 +
 arch/arm/configs/qcom_defconfig               |    2 +
 drivers/leds/Kconfig                          |    8 +
 drivers/leds/Makefile                         |    1 +
 drivers/leds/leds-qcom-spmi-flash.c           | 1153 +++++++++++++++++
 .../dt-bindings/leds/leds-qcom-spmi-flash.h   |   15 +
 7 files changed, 1311 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml
 create mode 100644 drivers/leds/leds-qcom-spmi-flash.c
 create mode 100644 include/dt-bindings/leds/leds-qcom-spmi-flash.h

Comments

Bjorn Andersson Jan. 27, 2021, 4:23 a.m. UTC | #1
On Tue 26 Jan 08:05 CST 2021, N?colas F. R. A. Prado wrote:

> Add driver for the Qualcomm SPMI Flash LEDs. These are controlled

> through an SPMI bus and are part of the PM8941 PMIC. There are two LEDs

> present in the chip, and can be used independently as camera flash or

> together in torch mode to act as a lantern.

> 

> Signed-off-by: Nícolas F. R. A. Prado <nfraprado@protonmail.com>

> ---

> Changes in v2:

> - Thanks to Jacek:

>   - Implemented flash LED class framework

> - Thanks to Bjorn:

>   - Renamed driver to "qcom spmi flash"

> - Refactored code

> - Added missing copyright

> 

>  drivers/leds/Kconfig                |    8 +

>  drivers/leds/Makefile               |    1 +

>  drivers/leds/leds-qcom-spmi-flash.c | 1153 +++++++++++++++++++++++++++

>  3 files changed, 1162 insertions(+)

>  create mode 100644 drivers/leds/leds-qcom-spmi-flash.c

> 

> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig

> index 849d3c5f908e..ad1c7846f9b3 100644

> --- a/drivers/leds/Kconfig

> +++ b/drivers/leds/Kconfig

> @@ -928,6 +928,14 @@ config LEDS_ACER_A500

>  	  This option enables support for the Power Button LED of

>  	  Acer Iconia Tab A500.

>  

> +config LEDS_QCOM_SPMI_FLASH

> +	tristate "Support for QCOM SPMI Flash LEDs"

> +	depends on SPMI

> +	depends on LEDS_CLASS_FLASH


depends on OF?

> +	help

> +	  This driver supports the Flash/Torch LED present in Qualcomm's PM8941

> +	  PMIC.

> +

>  comment "LED Triggers"

>  source "drivers/leds/trigger/Kconfig"

>  

> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile

> index 73e603e1727e..e86bcfba016b 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_QCOM_SPMI_FLASH)	+= leds-qcom-spmi-flash.o

>  

>  # LED SPI Drivers

>  obj-$(CONFIG_LEDS_CR0014114)		+= leds-cr0014114.o

> diff --git a/drivers/leds/leds-qcom-spmi-flash.c b/drivers/leds/leds-qcom-spmi-flash.c

> new file mode 100644

> index 000000000000..023fc107abce

> --- /dev/null

> +++ b/drivers/leds/leds-qcom-spmi-flash.c

> @@ -0,0 +1,1153 @@

> +// SPDX-License-Identifier: GPL-2.0-only

> +/*

> + * Qualcomm SPMI Flash Driver

> + *

> + * Copyright (c) 2020, Nícolas F. R. A. Prado <nfraprado@protonmail.com>

> + *

> + * Based on QPNP LEDs driver from downstream MSM kernel sources.

> + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.

> + */

> +

> +#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/led-class-flash.h>

> +#include <linux/regulator/consumer.h>

> +#include <linux/delay.h>

> +#include <linux/regmap.h>

> +#include <dt-bindings/leds/leds-qcom-spmi-flash.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_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 FLASH_DURATION_STEP		10000

> +#define FLASH_DURATION_MIN		10000

> +#define FLASH_DURATION_MAX		1280000 //TODO: find real value

> +

> +#define FLASH_CURRENT_STEP		12500

> +#define FLASH_CURRENT_MIN		12500

> +#define FLASH_CURRENT_MAX		1000000

> +#define FLASH_TORCH_CURRENT_MAX		200000

> +

> +#define FLASH_DEFAULT_CLAMP		200000

> +

> +enum qcom_flash_ids {

> +	QCOM_FLASH_ID_LED0,

> +	QCOM_FLASH_ID_LED1,

> +};

> +

> +static u8 flash_debug_regs[] = {

> +	0x40, 0x41, 0x42, 0x43, 0x44, 0x48, 0x49, 0x4b, 0x4c,

> +	0x4f, 0x46, 0x47,

> +};

> +

> +/**

> + * struct qcom_flash_led - Represents each individual flash LED

> + * @cdev: flash LED classdev

> + * @id: led ID as given by enum qcom_flash_ids

> + * @default_on: default state for the LED

> + * @turn_off_delay_ms: number of msec before turning off the LED

> + * @clamp_curr: Clamp current to use

> + * @headroom: Headroom value to use, as given by leds-qcom-spmi-flash.h

> + * @enable_module: enable address for particular flash

> + * @trigger_flash: trigger flash

> + * @startup_dly: startup delay for flash, as given by leds-qcom-spmi-flash.h

> + * @strobe_type: select between sw and hw strobe

> + * @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: whether torch mode is enabled or individual flash LED

> + */

> +struct qcom_flash_led {

> +	struct led_classdev_flash cdev;

> +	int id;

> +	bool default_on;

> +	int turn_off_delay_ms;

> +	u32 clamp_curr;

> +	u8 headroom;

> +	u8 enable_module;

> +	u8 trigger_flash;

> +	u8 startup_dly;

> +	u8 strobe_type;

> +	u16 current_addr;

> +	u16 second_addr;

> +	bool safety_timer;

> +	bool torch_enable;

> +};

> +

> +/**

> + * struct qcom_flash_device - QCOM SPMI Flash device, contains 2 flash LEDs

> + * @regmap: regmap used to access LED registers over SPMI

> + * @base: base register given in device tree

> + * @pdev: platform device from devicetree

> + * @flash_boost_reg: voltage regulator to supply the flashes

> + * @torch_boost_reg: voltage regulator to supply torch mode

> + * @leds: flash LEDs

> + * @num_leds: number of LEDs registered (between 0 and 2)

> + * @lock: lock to protect SPMI transactions

> + * @torch_enable: enable flash LED torch mode

> + * @peripheral_subtype: module peripheral subtype

> + * @flash_regulator_on: flash regulator status

> + * @torch_regulator_on: torch regulator status

> + */

> +struct qcom_flash_device {

> +	struct regmap *regmap;

> +	u16 base;

> +	struct platform_device *pdev;


You only ever use this to get to pdev->dev, so better store the "struct
device *" here.

> +	struct regulator *flash_boost_reg;


Rename flash_supply?

> +	struct regulator *torch_boost_reg;


torch_supply?

> +	struct qcom_flash_led leds[2];

> +	u8 num_leds;

> +	struct mutex lock;

> +	u8 torch_enable;

> +	u8 peripheral_subtype;

> +	bool flash_regulator_on;

> +	bool torch_regulator_on;

> +};

> +

> +static inline struct qcom_flash_led *flcdev_to_led(struct led_classdev_flash *fled_cdev)

> +{

> +	return container_of(fled_cdev, struct qcom_flash_led, cdev);

> +}

> +

> +static inline struct qcom_flash_device *led_to_leds_dev(struct qcom_flash_led *led)

> +{

> +	return container_of(led, struct qcom_flash_device, leds[led->id]);

> +}

> +

> +static int led_read_reg(struct qcom_flash_device *leds_dev, u16 offset, u8 *data)


Can't you just use unsigned int when handling the values and thereby use
regmap_read/write/update_bits directly in the code instead of wrapping
them like this?

> +{

> +	unsigned int val;

> +	int ret;

> +

> +	ret = regmap_read(leds_dev->regmap, leds_dev->base + offset, &val);

> +	if (ret < 0)

> +		return ret;

> +

> +	*data = val;

> +	return 0;

> +}

> +

> +static int led_write_reg(struct qcom_flash_device *leds_dev, u16 offset, u8 data)

> +{

> +	return regmap_write(leds_dev->regmap, leds_dev->base + offset, data);

> +}

> +

> +static int qcom_flash_masked_write(struct qcom_flash_device *leds_dev,

> +				   u16 addr,

> +				   u8 mask,

> +				   u8 val)


Use regmap_update_bits() directly for this?

> +{

> +	int rc;

> +	u8 reg;

> +

> +	rc = led_read_reg(leds_dev, addr, &reg);

> +	if (rc)

> +		dev_err(&leds_dev->pdev->dev,

> +			"Unable to read from addr=%x, rc(%d)\n", addr, rc);

> +

> +	reg &= ~mask;

> +	reg |= val;

> +

> +	rc = led_write_reg(leds_dev, addr, reg);

> +	if (rc)

> +		dev_err(&leds_dev->pdev->dev,

> +			"Unable to write to addr=%x, rc(%d)\n", addr, rc);

> +	return rc;

> +}

> +

> +static void qcom_flash_dump_regs(struct qcom_flash_device *leds_dev,


If you think this is valuable, please move it to debugfs.

> +				 u8 regs[],

> +				 u8 array_size)

> +{

> +	int i;

> +	u8 val;

> +

> +	pr_debug("===== LED register dump start =====\n");

> +	for (i = 0; i < array_size; i++) {

> +		led_read_reg(leds_dev, regs[i], &val);

> +		pr_debug("0x%x = 0x%x\n", leds_dev->base + regs[i], val);

> +	}

> +	pr_debug("===== LED register dump end =====\n");

> +}

> +

> +static u8 qcom_flash_duration_to_reg(u32 us)

> +{

> +	if (us < FLASH_DURATION_MIN)

> +		us = FLASH_DURATION_MIN;

> +	return (us - FLASH_DURATION_MIN) / FLASH_DURATION_STEP;

> +}

> +

> +static u8 qcom_flash_current_to_reg(u32 ua)

> +{

> +	if (ua < FLASH_CURRENT_MIN)

> +		ua = FLASH_CURRENT_MIN;

> +	return (ua - FLASH_CURRENT_MIN) / FLASH_CURRENT_STEP;

> +}

> +

> +static void clamp_align(u32 *v, u32 min, u32 max, u32 step)

> +{

> +	*v = clamp_val(*v, min, max);

> +	if (step > 1)

> +		*v = (*v - min) / step * step + min;

> +}

> +

> +static int qcom_flash_fled_regulator_operate(struct qcom_flash_device *leds_dev,

> +					     bool on)

> +{

> +	int rc;

> +

> +	if (!on)

> +		goto regulator_turn_off;


Given that this function comprises two completely separate code paths,
better split it into separate enable and disable functions..

> +

> +	if (!leds_dev->flash_regulator_on) {

> +		if (leds_dev->flash_boost_reg) {

> +			rc = regulator_enable(leds_dev->flash_boost_reg);

> +			if (rc) {

> +				dev_err(&leds_dev->pdev->dev,

> +					"Regulator enable failed(%d)\n", rc);

> +				return rc;

> +			}

> +			leds_dev->flash_regulator_on = true;

> +		}

> +	}

> +

> +	return 0;

> +

> +regulator_turn_off:

> +	if (leds_dev->flash_regulator_on) {

> +		if (leds_dev->flash_boost_reg) {

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_ENABLE_CONTROL,

> +				FLASH_ENABLE_MASK,

> +				FLASH_DISABLE_ALL);

> +			if (rc)

> +				dev_err(&leds_dev->pdev->dev,

> +					"Enable reg write failed(%d)\n", rc);

> +

> +			rc = regulator_disable(leds_dev->flash_boost_reg);

> +			if (rc) {

> +				dev_err(&leds_dev->pdev->dev,

> +					"Regulator disable failed(%d)\n", rc);

> +				return rc;

> +			}

> +			leds_dev->flash_regulator_on = false;

> +		}

> +	}

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_torch_regulator_operate(struct qcom_flash_device *leds_dev,

> +					      bool on)

> +{

> +	int rc;

> +

> +	if (!on)

> +		goto regulator_turn_off;


As with the flash regulator function, please split this in two.

> +

> +	if (!leds_dev->torch_regulator_on) {

> +		rc = regulator_enable(leds_dev->torch_boost_reg);

> +		if (rc) {

> +			dev_err(&leds_dev->pdev->dev,

> +				"Regulator enable failed(%d)\n", rc);

> +			return rc;

> +		}

> +		leds_dev->torch_regulator_on = true;

> +	}

> +	return 0;

> +

> +regulator_turn_off:

> +	if (leds_dev->torch_regulator_on) {

> +		rc = qcom_flash_masked_write(leds_dev, FLASH_ENABLE_CONTROL,

> +				FLASH_ENABLE_MODULE_MASK, FLASH_DISABLE_ALL);

> +		if (rc)

> +			dev_err(&leds_dev->pdev->dev,

> +				"Enable reg write failed(%d)\n", rc);

> +

> +		rc = regulator_disable(leds_dev->torch_boost_reg);

> +		if (rc) {

> +			dev_err(&leds_dev->pdev->dev,

> +				"Regulator disable failed(%d)\n", rc);

> +			return rc;

> +		}

> +		leds_dev->torch_regulator_on = false;

> +	}

> +	return 0;

> +}

> +

> +static int qcom_flash_fled_set(struct qcom_flash_led *led, bool on)

> +{

> +	int rc, error;

> +	u8 curr;

> +	struct qcom_flash_device *leds_dev = led_to_leds_dev(led);

> +	struct device *dev = &leds_dev->pdev->dev;

> +

> +	/* dump flash registers */

> +	pr_debug("Regdump before\n");

> +	qcom_flash_dump_regs(leds_dev, flash_debug_regs,

> +			     ARRAY_SIZE(flash_debug_regs));

> +

> +	/* Set led current */

> +	if (on) {


Please split this into enable/disable torch and enable/disable flash.

> +		if (led->torch_enable)

> +			curr = qcom_flash_current_to_reg(led->cdev.led_cdev.brightness);

> +		else

> +			curr = qcom_flash_current_to_reg(led->cdev.brightness.val);

> +

> +		if (led->torch_enable) {

> +			if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_DUAL) {

> +				rc = qcom_flash_torch_regulator_operate(leds_dev, true);

> +				if (rc) {

> +					dev_err(dev,

> +					"Torch regulator operate failed(%d)\n",

> +					rc);

> +					return rc;

> +				}

> +			} else if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_SINGLE) {

> +				rc = qcom_flash_fled_regulator_operate(leds_dev, true);

> +				if (rc) {

> +					dev_err(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 = qcom_flash_masked_write(leds_dev,

> +					FLASH_ENABLE_CONTROL,

> +					FLASH_ENABLE_MODULE_MASK,

> +					FLASH_ENABLE_MODULE);

> +				if (rc) {

> +					dev_err(dev,

> +						"Enable reg write failed(%d)\n",

> +						rc);

> +					return rc;

> +				}

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_LED_UNLOCK_SECURE,

> +				FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);

> +			if (rc) {

> +				dev_err(dev, "Secure reg write failed(%d)\n", rc);

> +				goto error_reg_write;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_LED_TORCH,

> +				FLASH_TORCH_MASK, FLASH_LED_TORCH_ENABLE);

> +			if (rc) {

> +				dev_err(dev, "Torch reg write failed(%d)\n", rc);

> +				goto error_reg_write;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				led->current_addr,

> +				FLASH_CURRENT_MASK,

> +				curr);

> +			if (rc) {

> +				dev_err(dev, "Current reg write failed(%d)\n", rc);

> +				goto error_reg_write;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				led->second_addr,

> +				FLASH_CURRENT_MASK,

> +				curr);

> +			if (rc) {

> +				dev_err(dev,

> +					"2nd Current reg write failed(%d)\n",

> +					rc);

> +				goto error_reg_write;

> +			}

> +

> +			qcom_flash_masked_write(leds_dev, FLASH_MAX_CURR,

> +				FLASH_CURRENT_MASK,

> +				TORCH_MAX_LEVEL);

> +			if (rc) {

> +				dev_err(dev,

> +					"Max current reg write failed(%d)\n",

> +					rc);

> +				goto error_reg_write;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_ENABLE_CONTROL,

> +				FLASH_ENABLE_MASK,

> +				leds_dev->torch_enable);

> +			if (rc) {

> +				dev_err(dev, "Enable reg write failed(%d)\n", rc);

> +				goto error_reg_write;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_LED_STROBE_CTRL,

> +				FLASH_STROBE_SW,

> +				FLASH_STROBE_SW);

> +			if (rc) {

> +				dev_err(dev,

> +					"LED %d strobe reg write failed(%d)\n",

> +					led->id, rc);

> +				goto error_reg_write;

> +			}

> +		} else {

> +			rc = qcom_flash_fled_regulator_operate(leds_dev, true);

> +			if (rc) {

> +				dev_err(dev,

> +					"Flash regulator operate failed(%d)\n",

> +					rc);

> +				goto error_flash_set;

> +			}

> +

> +			/* Set flash safety timer */

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_SAFETY_TIMER,

> +				FLASH_SAFETY_TIMER_MASK,

> +				qcom_flash_duration_to_reg(led->cdev.timeout.val));

> +			if (rc) {

> +				dev_err(dev,

> +					"Safety timer reg write failed(%d)\n",

> +					rc);

> +				goto error_flash_set;

> +			}

> +

> +			/* Set max current */

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_MAX_CURR, FLASH_CURRENT_MASK,

> +				FLASH_MAX_LEVEL);

> +			if (rc) {

> +				dev_err(dev,

> +					"Max current reg write failed(%d)\n",

> +					rc);

> +				goto error_flash_set;

> +			}

> +

> +			/* Set clamp current */

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_CLAMP_CURR,

> +				FLASH_CURRENT_MASK,

> +				qcom_flash_current_to_reg(led->clamp_curr));

> +			if (rc) {

> +				dev_err(dev,

> +					"Clamp current reg write failed(%d)\n",

> +					rc);

> +				goto error_flash_set;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				led->current_addr,

> +				FLASH_CURRENT_MASK,

> +				curr);

> +			if (rc) {

> +				dev_err(dev, "Current reg write failed(%d)\n", rc);

> +				goto error_flash_set;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_ENABLE_CONTROL,

> +				led->enable_module,

> +				led->enable_module);

> +			if (rc) {

> +				dev_err(dev, "Enable reg write failed(%d)\n", rc);

> +				goto error_flash_set;

> +			}

> +

> +			/* TODO try to not busy wait*/

> +			mdelay(1);

> +

> +			if (!led->strobe_type) {

> +				rc = qcom_flash_masked_write(leds_dev,

> +					FLASH_LED_STROBE_CTRL,

> +					led->trigger_flash,

> +					led->trigger_flash);

> +				if (rc) {

> +					dev_err(dev,

> +					"LED %d strobe reg write failed(%d)\n",

> +					led->id, rc);

> +					goto error_flash_set;

> +				}

> +			} else {

> +				rc = qcom_flash_masked_write(leds_dev,

> +					FLASH_LED_STROBE_CTRL,

> +					(led->trigger_flash | FLASH_STROBE_HW),

> +					(led->trigger_flash | FLASH_STROBE_HW));

> +				if (rc) {

> +					dev_err(dev,

> +					"LED %d strobe reg write failed(%d)\n",

> +					led->id, rc);

> +					goto error_flash_set;

> +				}

> +			}

> +		}

> +	} else {

> +		rc = qcom_flash_masked_write(leds_dev,

> +			FLASH_LED_STROBE_CTRL,

> +			led->trigger_flash,

> +			FLASH_DISABLE_ALL);

> +		if (rc) {

> +			dev_err(dev,

> +				"LED %d flash write failed(%d)\n", led->id, rc);

> +			if (led->torch_enable)

> +				goto error_torch_set;

> +			else

> +				goto error_flash_set;

> +		}

> +

> +		/* TODO try to not busy wait*/

> +		mdelay(2);

> +		udelay(160);

> +

> +		if (led->torch_enable) {

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_LED_UNLOCK_SECURE,

> +				FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);

> +			if (rc) {

> +				dev_err(dev, "Secure reg write failed(%d)\n", rc);

> +				goto error_torch_set;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +					FLASH_LED_TORCH,

> +					FLASH_TORCH_MASK,

> +					FLASH_LED_TORCH_DISABLE);

> +			if (rc) {

> +				dev_err(dev, "Torch reg write failed(%d)\n", rc);

> +				goto error_torch_set;

> +			}

> +

> +			if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_DUAL) {

> +				rc = qcom_flash_torch_regulator_operate(leds_dev,false);

> +				if (rc) {

> +					dev_err(dev,

> +						"Torch regulator operate failed(%d)\n",

> +						rc);

> +					return rc;

> +				}

> +			} else if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_SINGLE) {

> +				rc = qcom_flash_fled_regulator_operate(leds_dev, false);

> +				if (rc) {

> +					dev_err(dev,

> +						"Flash regulator operate failed(%d)\n",

> +						rc);

> +					return rc;

> +				}

> +			}

> +		} else {

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_ENABLE_CONTROL,

> +				led->enable_module &

> +				~FLASH_ENABLE_MODULE_MASK,

> +				FLASH_DISABLE_ALL);

> +			if (rc) {

> +				dev_err(dev, "Enable reg write failed(%d)\n", rc);

> +				if (led->torch_enable)

> +					goto error_torch_set;

> +				else

> +					goto error_flash_set;

> +			}

> +

> +			rc = qcom_flash_fled_regulator_operate(leds_dev, false);

> +			if (rc) {

> +				dev_err(dev,

> +					"Flash regulator operate failed(%d)\n",

> +					rc);

> +				return rc;

> +			}

> +		}

> +	}

> +

> +	pr_debug("Regdump after\n");

> +	qcom_flash_dump_regs(leds_dev, flash_debug_regs, ARRAY_SIZE(flash_debug_regs));

> +

> +	return 0;

> +

> +error_reg_write:

> +	if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_SINGLE)

> +		goto error_flash_set;

> +

> +error_torch_set:

> +	error = qcom_flash_torch_regulator_operate(leds_dev, false);

> +	if (error) {

> +		dev_err(dev, "Torch regulator operate failed(%d)\n", rc);

> +		return error;

> +	}

> +	return rc;

> +

> +error_flash_set:

> +	error = qcom_flash_fled_regulator_operate(leds_dev, false);

> +	if (error) {

> +		dev_err(dev, "Flash regulator operate failed(%d)\n", rc);

> +		return error;

> +	}

> +	return rc;

> +}

> +

> +static int qcom_flash_led_set(struct led_classdev *led_cdev,

> +			      enum led_brightness value)

> +{

> +	struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);

> +	struct qcom_flash_led *led = flcdev_to_led(fled_cdev);

> +	struct qcom_flash_device *leds_dev = led_to_leds_dev(led);

> +	u32 val = value;

> +	int rc;

> +	bool on;

> +

> +	if (val > led_cdev->max_brightness) {

> +		dev_err(&leds_dev->pdev->dev, "Invalid brightness value\n");

> +		return -EINVAL;

> +	}

> +

> +	if (val) {

> +		on = true;

> +		clamp_align(&val, FLASH_CURRENT_MIN, led_cdev->max_brightness,

> +			    FLASH_CURRENT_STEP);

> +		led_cdev->brightness = val;

> +		led->torch_enable = true;

> +	} else {

> +		on = false;

> +	}

> +

> +	mutex_lock(&leds_dev->lock);

> +	rc = qcom_flash_fled_set(led, on);

> +	if (rc < 0)

> +		dev_err(&leds_dev->pdev->dev, "FLASH set brightness failed (%d)\n", rc);


I believe you're printing error messages in all cases of
qcom_flash_fled_set() returning an error and you're not printing in the
other call spots, so please drop this print.

> +	mutex_unlock(&leds_dev->lock);

> +	return rc;

> +}

> +

> +static int qcom_flash_fled_brightness_set(struct led_classdev_flash *fled_cdev,

> +					  u32 brightness)

> +{

> +	clamp_align(&brightness, FLASH_CURRENT_MIN, fled_cdev->brightness.max,

> +		    FLASH_CURRENT_STEP);

> +	fled_cdev->brightness.val = brightness;

> +	return 0;

> +}

> +

> +static int qcom_flash_fled_strobe_set(struct led_classdev_flash *fled_cdev,

> +				      bool state)

> +{

> +	struct qcom_flash_led *led = flcdev_to_led(fled_cdev);

> +	struct qcom_flash_device *leds_dev = led_to_leds_dev(led);

> +	int rc;

> +

> +	led->torch_enable = false;

> +

> +	mutex_lock(&leds_dev->lock);

> +	rc = qcom_flash_fled_set(led, state);

> +	if (rc < 0)

> +		return rc;


You're returning with the lock held here, fall through and return rc at
the end instead.

> +	mutex_unlock(&leds_dev->lock);

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_fled_strobe_get(struct led_classdev_flash *fled_cdev,

> +				      bool *state)

> +{

> +	//TODO

> +	*state = 0;

> +	return 0;

> +}

> +

> +static int qcom_flash_fled_timeout_set(struct led_classdev_flash *fled_cdev,

> +				       u32 timeout)

> +{

> +	fled_cdev->timeout.val = timeout;

> +	return 0;

> +}

> +

> +static int qcom_flash_fled_fault_get(struct led_classdev_flash *fled_cdev,

> +				     u32 *fault)

> +{

> +	//TODO

> +	*fault = 0;

> +	return 0;

> +}

> +

> +static const struct led_flash_ops flash_ops = {

> +	.flash_brightness_set	= qcom_flash_fled_brightness_set,

> +	.strobe_set		= qcom_flash_fled_strobe_set,

> +	.strobe_get		= qcom_flash_fled_strobe_get,

> +	.timeout_set		= qcom_flash_fled_timeout_set,

> +	.fault_get		= qcom_flash_fled_fault_get,

> +};

> +

> +static int qcom_flash_flcdev_setup(struct qcom_flash_led *led,

> +				   struct device_node *node)

> +{

> +	int rc;

> +	struct platform_device *pdev = led_to_leds_dev(led)->pdev;

> +	struct led_init_data init_data = {};

> +

> +	init_data.fwnode = of_fwnode_handle(node);

> +	init_data.devicename = "qcom-spmi-flash";

> +	init_data.default_label = "flash";

> +

> +	led->cdev.led_cdev.brightness_set_blocking    = qcom_flash_led_set;

> +

> +	rc = of_property_read_u32(node, "led-max-microamp",

> +				  &led->cdev.led_cdev.max_brightness);

> +	if (rc < 0) {

> +		dev_err(&pdev->dev, "Failure reading max_current, rc =  %d\n", rc);

> +		return rc;

> +	}

> +	led->cdev.led_cdev.max_brightness = min((u32) led->cdev.led_cdev.max_brightness,

> +						(u32) FLASH_TORCH_CURRENT_MAX);

> +

> +	rc = of_property_read_u32(node, "flash-max-microamp", &led->cdev.brightness.max);

> +	if (rc < 0) {

> +		dev_err(&pdev->dev, "Failure reading max_current, rc =  %d\n", rc);

> +		return rc;

> +	}

> +	led->cdev.brightness.max = min((u32) led->cdev.brightness.max,

> +				(u32) FLASH_CURRENT_MAX);

> +	led->cdev.brightness.min = FLASH_CURRENT_MIN;

> +	led->cdev.brightness.step = FLASH_CURRENT_STEP;

> +	led->cdev.brightness.val = led->cdev.brightness.max;

> +

> +	rc = of_property_read_u32(node, "flash-max-timeout-us", &led->cdev.timeout.max);

> +	if (rc < 0) {

> +		dev_err(&pdev->dev, "Failure reading max_timeout, rc =  %d\n", rc);

> +		return rc;

> +	}

> +	led->cdev.timeout.max = min((u32) led->cdev.timeout.max,

> +				    (u32) FLASH_DURATION_MAX);

> +	led->cdev.timeout.min = FLASH_DURATION_MIN;

> +	led->cdev.timeout.step = FLASH_DURATION_STEP;

> +	led->cdev.timeout.val = led->cdev.timeout.max;

> +

> +	led->cdev.ops = &flash_ops;

> +	led->cdev.led_cdev.flags |= LED_DEV_CAP_FLASH;

> +

> +	rc = led_classdev_flash_register_ext(&pdev->dev, &led->cdev, &init_data);

> +	if (rc) {

> +		dev_err(&pdev->dev, "unable to register led %d,rc=%d\n", led->id, rc);

> +		return rc;

> +	}

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_setup_flash_regs(struct qcom_flash_led *led)

> +{

> +	int rc;

> +	struct qcom_flash_device *leds_dev = led_to_leds_dev(led);

> +

> +	/* Set headroom */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_HEADROOM,

> +		FLASH_HEADROOM_MASK, led->headroom);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "Headroom reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set startup delay */

> +	rc = qcom_flash_masked_write(leds_dev,

> +		FLASH_STARTUP_DELAY, FLASH_STARTUP_DLY_MASK,

> +		led->startup_dly);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev,

> +			"Startup delay reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set timer control - safety or watchdog */

> +	if (led->safety_timer) {

> +		rc = qcom_flash_masked_write(leds_dev,

> +			FLASH_LED_TMR_CTRL,

> +			FLASH_TMR_MASK, FLASH_TMR_SAFETY);

> +		if (rc) {

> +			dev_err(&leds_dev->pdev->dev,

> +				"LED timer ctrl reg write failed(%d)\n", rc);

> +			return rc;

> +		}

> +	}

> +

> +	/* dump flash registers */

> +	qcom_flash_dump_regs(leds_dev, flash_debug_regs, ARRAY_SIZE(flash_debug_regs));

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_get_config_flash(struct qcom_flash_led *led,

> +				       struct device_node *node)

> +{

> +	int rc;

> +	u32 val;

> +	const char *temp_string;

> +	struct device *dev = &led_to_leds_dev(led)->pdev->dev;

> +

> +	rc = of_property_read_u32(node, "led-sources", &led->id);

> +	if (rc < 0) {

> +		dev_err(dev, "Failure reading led id, rc =  %d\n", rc);

> +		return rc;

> +	}

> +

> +	led->default_on = false;

> +	rc = of_property_read_string(node, "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;

> +

> +	if (led->id == QCOM_FLASH_ID_LED0) {

> +		led->enable_module = FLASH_ENABLE_LED_0;

> +		led->current_addr = FLASH_LED_0_CURR;

> +		led->trigger_flash = FLASH_LED_0_OUTPUT;

> +

> +		led->second_addr = FLASH_LED_1_CURR;

> +	} else if (led->id == QCOM_FLASH_ID_LED1) {

> +		led->enable_module = FLASH_ENABLE_LED_1;

> +		led->current_addr = FLASH_LED_1_CURR;

> +		led->trigger_flash = FLASH_LED_1_OUTPUT;

> +

> +		led->second_addr = FLASH_LED_0_CURR;

> +	} else {

> +		dev_err(dev, "Unknown flash LED name given\n");

> +		return -EINVAL;

> +	}

> +

> +	rc = of_property_read_u32(node, "qcom,headroom", &val);

> +	if (!rc)

> +		led->headroom = (u8) val;

> +	else if (rc == -EINVAL)

> +		led->headroom = QCOM_SPMI_FLASH_HEADROOM_500MV;

> +

> +	rc = of_property_read_u32(node, "qcom,clamp-curr", &val);

> +	if (!rc)

> +		led->clamp_curr = val;

> +	else if (rc == -EINVAL)

> +		led->clamp_curr = FLASH_DEFAULT_CLAMP;

> +

> +	rc = of_property_read_u32(node, "qcom,startup-dly", &val);

> +	if (!rc)

> +		led->startup_dly = (u8) val;

> +	else if (rc == -EINVAL)

> +		led->startup_dly = QCOM_SPMI_FLASH_STARTUP_DLY_128US;

> +

> +	led->safety_timer = of_property_read_bool(node, "qcom,safety-timer");

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_setup_regs(struct qcom_flash_device *leds_dev)

> +{

> +	int rc;

> +

> +	rc = qcom_flash_masked_write(leds_dev,

> +		FLASH_LED_STROBE_CTRL,

> +		FLASH_STROBE_MASK, FLASH_DISABLE_ALL);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "LED flash write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Disable flash LED module */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_ENABLE_CONTROL,

> +		FLASH_ENABLE_MODULE_MASK, FLASH_DISABLE_ALL);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "Enable reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set Vreg force */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_VREG_OK_FORCE,

> +		FLASH_VREG_MASK, FLASH_HW_VREG_OK);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "Vreg OK reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set self fault check */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_FAULT_DETECT,

> +		FLASH_FAULT_DETECT_MASK, FLASH_SELFCHECK_DISABLE);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev,

> +			"Fault detect reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set mask enable */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_MASK_ENABLE,

> +		FLASH_MASK_REG_MASK, FLASH_MASK_1);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "Mask enable reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set ramp rate */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_RAMP_RATE,

> +		FLASH_RAMP_RATE_MASK, 0xBF);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "Ramp rate reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Enable VPH_PWR_DROOP and set threshold to 2.9V */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_VPH_PWR_DROOP,

> +					FLASH_VPH_PWR_DROOP_MASK, 0xC2);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev,

> +			"FLASH_VPH_PWR_DROOP reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_setup_led(struct qcom_flash_led *led,

> +				struct device_node *node)

> +{

> +	int rc;

> +

> +	rc = qcom_flash_get_config_flash(led, node);

> +	if (rc < 0) {

> +		dev_err(&led_to_leds_dev(led)->pdev->dev,


Stash led_to_leds_dev() in a local variable to reduce duplication.

> +			"Unable to read flash config data\n");

> +		return rc;

> +	}

> +

> +	rc = qcom_flash_setup_flash_regs(led);

> +	if (rc) {

> +		dev_err(&led_to_leds_dev(led)->pdev->dev,

> +			"FLASH initialize failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	rc = qcom_flash_flcdev_setup(led, node);

> +	if (rc < 0)

> +		return rc;

> +

> +	/* configure default state */

> +	if (led->default_on) {

> +		led->cdev.led_cdev.brightness = led->cdev.led_cdev.max_brightness;

> +		led->torch_enable = true;

> +		mutex_lock(&led_to_leds_dev(led)->lock);

> +		rc = qcom_flash_fled_set(led, true);

> +		if (rc < 0)


You're returning with lock held.

> +			return rc;

> +		mutex_unlock(&led_to_leds_dev(led)->lock);

> +	} else {

> +		led->cdev.led_cdev.brightness = LED_OFF;

> +	}

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_setup_leds_device(struct qcom_flash_device *leds_dev,

> +					struct device_node *node,

> +					struct platform_device *pdev)

> +{

> +	u32 reg;

> +	int rc;

> +

> +	leds_dev->pdev = pdev;

> +

> +	leds_dev->regmap = dev_get_regmap(pdev->dev.parent, NULL);

> +	if (!leds_dev->regmap)

> +		return -ENODEV;

> +

> +	rc = of_property_read_u32(node, "reg", &reg);

> +	if (rc < 0) {

> +		dev_err(&pdev->dev, "Failure reading reg, rc = %d\n", rc);

> +		return rc;

> +	}

> +	leds_dev->base = reg;

> +

> +	qcom_flash_setup_regs(leds_dev);

> +

> +	if (of_find_property(node, "flash-boost-supply", NULL)) {


The idiomatic form is:

	rc = of_find_property();
	if (rc)

But I think you can use regulator_get_optional() instead, for which you
can look at the return value to determine if flash-boost wasn't
specified.


That said, I don't see why you need to give it special treatment, it
should be fine to just expect them to be specified and use the standard
form - which would give you a dummy regulator if it's not specified.

Finally you should use the devm_regulator_get() form, so that you don't
have to regulator_put() in remove.

> +		leds_dev->flash_boost_reg = regulator_get(&pdev->dev, "flash-boost");

> +		if (IS_ERR(leds_dev->flash_boost_reg)) {

> +			rc = PTR_ERR(leds_dev->flash_boost_reg);

> +			dev_err(&pdev->dev, "Regulator get failed(%d)\n", rc);

> +			regulator_put(leds_dev->flash_boost_reg);

> +			leds_dev->flash_boost_reg = NULL;

> +			return rc;

> +		}

> +	}

> +

> +	if (of_find_property(node, "torch-boost-supply", NULL)) {

> +		leds_dev->torch_boost_reg = regulator_get(&pdev->dev, "torch-boost");

> +		if (IS_ERR(leds_dev->torch_boost_reg)) {

> +			rc = PTR_ERR(leds_dev->torch_boost_reg);

> +			dev_err(&pdev->dev, "Torch regulator get failed(%d)\n", rc);

> +			regulator_put(leds_dev->flash_boost_reg);

> +			regulator_put(leds_dev->torch_boost_reg);

> +			leds_dev->flash_boost_reg = NULL;

> +			leds_dev->torch_boost_reg = NULL;

> +			return rc;

> +		}

> +		leds_dev->torch_enable = FLASH_ENABLE_MODULE;


This seems to warrant the use of devm_regulator_get_optional()

> +	} else {

> +		leds_dev->torch_enable = FLASH_ENABLE_ALL;

> +	}

> +

> +	rc = led_read_reg(leds_dev, FLASH_PERIPHERAL_SUBTYPE,

> +			  &leds_dev->peripheral_subtype);

> +	if (rc) {

> +		dev_err(&pdev->dev,

> +			"Unable to read from addr=%x, rc(%d)\n",

> +			FLASH_PERIPHERAL_SUBTYPE, rc);

> +	}

> +

> +	mutex_init(&leds_dev->lock);

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_probe(struct platform_device *pdev)

> +{

> +	struct device *dev = &pdev->dev;

> +	struct qcom_flash_device *leds_dev;

> +	struct device_node *node = dev->of_node;

> +	struct qcom_flash_led *led;

> +	struct device_node *temp;

> +	int rc, i, parsed_leds = 0;

> +

> +	leds_dev = devm_kzalloc(dev, sizeof(struct qcom_flash_device), GFP_KERNEL);


It's idiomatic to use sizeof(*leds_dev)

> +	if (!leds_dev)

> +		return -ENOMEM;

> +

> +	rc = qcom_flash_setup_leds_device(leds_dev, node, pdev);

> +	if (rc) {

> +		pr_debug("Probe failed setting up base (%d)\n", rc);


This is an error, but all paths out of qcom_flash_setup_leds_device()
already prints, so no need for you to do as well.

> +		return rc;

> +	}

> +

> +	platform_set_drvdata(pdev, leds_dev);

> +

> +	for_each_child_of_node(node, temp) {

> +		led = &leds_dev->leds[parsed_leds];

> +

> +		rc = qcom_flash_setup_led(led, temp);

> +		if (rc) {

> +			for (i = 0; i < parsed_leds; i++)

> +				led_classdev_flash_unregister(&leds_dev->leds[i].cdev);


Do a "goto err_unregister" here instead and clean up at the bottom of
the function.

> +			pr_debug("Probe failed setting up leds (%d)\n", rc);


You have dev, so use dev_err. But I think you already wrote error
messages on the way back here.

> +			return rc;

> +		}

> +

> +		parsed_leds++;

> +	}

> +	leds_dev->num_leds = parsed_leds;

> +	return 0;

> +}

> +

> +static int qcom_flash_remove(struct platform_device *pdev)

> +{

> +	struct qcom_flash_device *leds_dev  = platform_get_drvdata(pdev);

> +	int i, parsed_leds = leds_dev->num_leds;

> +

> +	mutex_destroy(&leds_dev->lock);

> +	if (leds_dev->flash_boost_reg)

> +		regulator_put(leds_dev->flash_boost_reg);


Use devm_regulator_get() and you don't have put it here.

> +	if (leds_dev->torch_boost_reg)

> +		regulator_put(leds_dev->torch_boost_reg);

> +	for (i = 0; i < parsed_leds; i++)

> +		led_classdev_flash_unregister(&leds_dev->leds[i].cdev);

> +

> +	return 0;

> +}

> +

> +static const struct of_device_id qcom_flash_spmi_of_match[] = {

> +	{ .compatible = "qcom,spmi-flash" },

> +	{},

> +};

> +MODULE_DEVICE_TABLE(of, qcom_flash_spmi_of_match);

> +

> +static struct platform_driver qcom_flash_driver = {

> +	.driver		= {

> +		.name	= "qcom,spmi-flash",

> +		.of_match_table = of_match_ptr(qcom_flash_spmi_of_match),


Please skip the of_match_ptr(), otherwise if the driver is compiled
without CONFIG_OF you'll get a warning that there's no references to
qcom_flash_driver.

Regards,
Bjorn

> +	},

> +	.probe		= qcom_flash_probe,

> +	.remove		= qcom_flash_remove,

> +};

> +module_platform_driver(qcom_flash_driver);

> +

> +MODULE_DESCRIPTION("Qualcomm SPMI Flash LED driver");

> +MODULE_LICENSE("GPL v2");

> +MODULE_AUTHOR("Nícolas F. R. A. Prado <nfraprado@protonmail.com>");

> -- 

> 2.30.0

> 

>
Rob Herring (Arm) Jan. 27, 2021, 2 p.m. UTC | #2
On Tue, 26 Jan 2021 14:04:08 +0000, Nícolas F. R. A. Prado wrote:
> Add devicetree binding for QCOM SPMI Flash LEDs, which are part of

> PM8941, and are used both as lantern and camera flash.

> 

> Signed-off-by: Nícolas F. R. A. Prado <nfraprado@protonmail.com>

> ---

> Changes in v2:

> - Add this commit

> 

>  .../bindings/leds/leds-qcom-spmi-flash.yaml   | 94 +++++++++++++++++++

>  .../dt-bindings/leds/leds-qcom-spmi-flash.h   | 15 +++

>  2 files changed, 109 insertions(+)

>  create mode 100644 Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml

>  create mode 100644 include/dt-bindings/leds/leds-qcom-spmi-flash.h

> 


My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml: patternProperties:^led[0-1]$:properties:qcom,startup-dly: 'oneOf' conditional failed, one must be fixed:
	'type' is a required property
	Additional properties are not allowed ('maximum', '$ref', 'minimum' were unexpected)
	/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml: patternProperties:^led[0-1]$:properties:qcom,startup-dly: 'oneOf' conditional failed, one must be fixed:
		'enum' is a required property
		'const' is a required property
	'/schemas/types.yaml#definitions/uint32' does not match 'types.yaml#/definitions/'
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml: patternProperties:^led[0-1]$:properties:qcom,clamp-curr: 'oneOf' conditional failed, one must be fixed:
	'type' is a required property
	Additional properties are not allowed ('$ref' was unexpected)
	/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml: patternProperties:^led[0-1]$:properties:qcom,clamp-curr: 'oneOf' conditional failed, one must be fixed:
		'enum' is a required property
		'const' is a required property
	'/schemas/types.yaml#definitions/uint32' does not match 'types.yaml#/definitions/'
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml: patternProperties:^led[0-1]$:properties:qcom,headroom: 'oneOf' conditional failed, one must be fixed:
	'type' is a required property
	Additional properties are not allowed ('maximum', '$ref', 'minimum' were unexpected)
	/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml: patternProperties:^led[0-1]$:properties:qcom,headroom: 'oneOf' conditional failed, one must be fixed:
		'enum' is a required property
		'const' is a required property
	'/schemas/types.yaml#definitions/uint32' does not match 'types.yaml#/definitions/'
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml: ignoring, error in schema: patternProperties: ^led[0-1]$: properties: qcom,startup-dly
warning: no schema found in file: ./Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml

See https://patchwork.ozlabs.org/patch/1431711

This check can fail if there are any dependencies. The base for a patch
series is generally the most recent rc1.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.
Bjorn Andersson Jan. 27, 2021, 2:28 p.m. UTC | #3
On Tue 26 Jan 08:04 CST 2021, N?colas F. R. A. Prado wrote:

> Add devicetree binding for QCOM SPMI Flash LEDs, which are part of

> PM8941, and are used both as lantern and camera flash.

> 

> Signed-off-by: Nícolas F. R. A. Prado <nfraprado@protonmail.com>

> ---

> Changes in v2:

> - Add this commit

> 

>  .../bindings/leds/leds-qcom-spmi-flash.yaml   | 94 +++++++++++++++++++

>  .../dt-bindings/leds/leds-qcom-spmi-flash.h   | 15 +++

>  2 files changed, 109 insertions(+)

>  create mode 100644 Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml

>  create mode 100644 include/dt-bindings/leds/leds-qcom-spmi-flash.h

> 

> diff --git a/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml b/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml

> new file mode 100644

> index 000000000000..169716e14f67

> --- /dev/null

> +++ b/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml

> @@ -0,0 +1,94 @@

> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)

> +%YAML 1.2

> +---

> +$id: http://devicetree.org/schemas/leds/leds-qcom-spmi-flash.yaml#

> +$schema: http://devicetree.org/meta-schemas/core.yaml#

> +

> +title: Qualcomm SPMI Flash LEDs

> +

> +maintainers:

> +  - Nícolas F. R. A. Prado <nfraprado@protonmail.com>

> +

> +description: |

> +  The Qualcomm SPMI Flash LEDs are part of Qualcomm PMICs and are used primarily

> +  as a camera or video flash. They can also be used as a lantern when on torch

> +  mode.

> +  The PMIC is connected to Host processor via SPMI bus.

> +

> +properties:

> +  compatible:

> +    const: qcom,spmi-flash

> +

> +  reg:

> +    maxItems: 1

> +

> +  flash-boost-supply:

> +    description: SMBB regulator for LED flash mode

> +

> +  torch-boost-supply:

> +    description: SMBB regulator for LED torch mode

> +

> +patternProperties:

> +  "^led[0-1]$":

> +    type: object

> +    $ref: common.yaml#

> +

> +    properties:

> +      qcom,clamp-curr:

> +        description: current to clamp at, in uA

> +        $ref: /schemas/types.yaml#definitions/uint32

> +

> +      qcom,headroom:

> +        description: |

> +          headroom to use. Use one of QCOM_SPMI_FLASH_HEADROOM_* defined in

> +          include/dt-bindings/leds/leds-qcom-spmi-flash.h


Please make the unit of this property millivolts, instead of describing
it indirectly using the defines in the header file.

> +        $ref: /schemas/types.yaml#definitions/uint32

> +        minimum: 0

> +        maximum: 3


And you can then list out the valid values here.

> +

> +      qcom,startup-dly:

> +        description: |

> +          delay before flashing. Use one of QCOM_SPMI_FLASH_STARTUP_DLY_*

> +          defined in include/dt-bindings/leds/leds-qcom-spmi-flash.h


As above, please describe this in microseconds.

> +        $ref: /schemas/types.yaml#definitions/uint32

> +        minimum: 0

> +        maximum: 3

> +

> +      qcom,safety-timer:

> +        description: include for safety timer use, otherwise watchdog timer will be used

> +        type: boolean

> +

> +required:

> +  - compatible

> +  - reg

> +  - flash-boost-supply

> +  - torch-boost-supply

> +

> +additionalProperties: false

> +

> +examples:

> +  - |

> +    #include <dt-bindings/leds/common.h>

> +    #include <dt-bindings/leds/leds-qcom-spmi-flash.h>

> +

> +    qcom,leds@d300 {


Please no "qcom," in the node name.

Regards,
Bjorn

> +        compatible = "qcom,spmi-flash";

> +        reg = <0xd300 0x100>;

> +        flash-boost-supply = <&pm8941_5vs1>;

> +        torch-boost-supply = <&pm8941_5v>;

> +

> +        led0 {

> +            led-sources = <0>;

> +            function = LED_FUNCTION_FLASH;

> +            color = <LED_COLOR_ID_WHITE>;

> +            led-max-microamp = <200000>;

> +            flash-max-microamp = <1000000>;

> +            flash-max-timeout-us = <1280000>;

> +            default-state = "off";

> +            qcom,clamp-curr = <200000>;

> +            qcom,headroom = <QCOM_SPMI_FLASH_HEADROOM_500MV>;

> +            qcom,startup-dly = <QCOM_SPMI_FLASH_STARTUP_DLY_128US>;

> +            qcom,safety-timer;

> +        };

> +    };

> +...

> diff --git a/include/dt-bindings/leds/leds-qcom-spmi-flash.h b/include/dt-bindings/leds/leds-qcom-spmi-flash.h

> new file mode 100644

> index 000000000000..8bd54a8e831d

> --- /dev/null

> +++ b/include/dt-bindings/leds/leds-qcom-spmi-flash.h

> @@ -0,0 +1,15 @@

> +/* SPDX-License-Identifier: GPL-2.0 */

> +#ifndef _DT_BINDINGS_LEDS_QCOM_SPMI_FLASH_H

> +#define _DT_BINDINGS_LEDS_QCOM_SPMI_FLASH_H

> +

> +#define QCOM_SPMI_FLASH_HEADROOM_250MV	0

> +#define QCOM_SPMI_FLASH_HEADROOM_300MV	1

> +#define QCOM_SPMI_FLASH_HEADROOM_400MV	2

> +#define QCOM_SPMI_FLASH_HEADROOM_500MV	3

> +

> +#define QCOM_SPMI_FLASH_STARTUP_DLY_10US	0

> +#define QCOM_SPMI_FLASH_STARTUP_DLY_32US	1

> +#define QCOM_SPMI_FLASH_STARTUP_DLY_64US	2

> +#define QCOM_SPMI_FLASH_STARTUP_DLY_128US	3

> +

> +#endif

> -- 

> 2.30.0

> 

>
Jacek Anaszewski Jan. 30, 2021, 8:31 p.m. UTC | #4
Hi Nicolas.

On 1/26/21 3:03 PM, Nícolas F. R. A. Prado wrote:
> Hi,

> 

> this patch series adds support for Qualcomm's SPMI Flash LEDs present in the

> PM8941 PMIC. It is used as part of MSM8974 based devices, like the Nexus 5

> (hammerhead), as a camera flash or as a lantern when in torch mode.

> 

> Patch 1 adds the dt-bindings for the driver, together with a header for the

> values of some properties.

> 

> Patch 2 adds the driver, which was ported from downstream [1], and is now using

> the flash LED class framework.

> 

> Patch 3 enables the driver as a module in qcom_defconfig, and also enables

> CONFIG_LEDS_CLASS_FLASH since it is required by the driver.

> 

> Patch 4 adds the device tree nodes configuring the driver in the pm8941 dtsi.

> 

> After the feedback I received from the v1 RFC patch (thank you Jacek and

> Bjorn!), I implemented the flash LED class framework, renamed the driver to

> qcom-spmi-flash and added the dt-bindings. I also did a whole lot of cleanup.

> 

> Some caveats:

> - I still didn't implement get_strobe() and get_fault() for the flash LEDs,

>    because I'm still not sure how to do it. get_strobe() in particular I'm not

>    even sure if is possible, since after the flash turns off automatically after

>    the timeout, I don't see any change in the SPMI registers. So I'm unsure how

>    one would get the current strobe state.


strobe_get is optional - you can leave it uninitialized if there is no
obvious way to get strobe status.

Regarding faults - I see you have FLASH_FAULT_DETECT but have no
information on its impact whatsoever. Usually devices report the faults
by settings some register bits and then we can map those errors to
LED flash framework generic errors.

> - I have yet to add the V4L2 flash wrapper for the flash LEDs. I still didn't do

>    it because I wasn't sure if it was needed, so wanted to double check. But

>    being a camera flash it seems that would be useful. Also, it would be great if

>    someone could point me how I would go about testing the flash usage through

>    V4L2.


You need a V4L2 media device driver with which this driver would
register a V4L2 flash LED sub-device. Such a device is usually
implemented for platform ISP devices. Provided it is present in the
mainline you would have to associate this driver DT node with the
media device DT node. Then you can test the V4L2 Flash control with
v4l2-ctl or yavta user space tools.

Let's skip the V4L2 support for now - it can be added later, if needed.

> Another thing worth mentioning: for v1 the dt nodes were added in hammerhead's

> dts (just to simplify testing), but I have now moved them to pm8941's dtsi,

> since it was like that in downstream. So if folks using devices based on

> PM8941/MSM8974 other than the Nexus 5 could test it, that would be great, since

> I have only tested on the Nexus 5.

> 

> v1 RFC: https://lore.kernel.org/lkml/20201106165737.1029106-1-nfraprado@protonmail.com/

> 

> [1] https://github.com/AICP/kernel_lge_hammerhead/blob/n7.1/drivers/leds/leds-qpnp.c

> 

> Nícolas F. R. A. Prado (4):

>    dt-bindings: leds: Add binding for qcom-spmi-flash

>    leds: Add driver for QCOM SPMI Flash LEDs

>    ARM: qcom_defconfig: Enable QCOM SPMI Flash LEDs

>    ARM: dts: qcom: pm8941: Add nodes for QCOM SPMI Flash LEDs

> 

>   .../bindings/leds/leds-qcom-spmi-flash.yaml   |   94 ++

>   arch/arm/boot/dts/qcom-pm8941.dtsi            |   38 +

>   arch/arm/configs/qcom_defconfig               |    2 +

>   drivers/leds/Kconfig                          |    8 +

>   drivers/leds/Makefile                         |    1 +

>   drivers/leds/leds-qcom-spmi-flash.c           | 1153 +++++++++++++++++

>   .../dt-bindings/leds/leds-qcom-spmi-flash.h   |   15 +

>   7 files changed, 1311 insertions(+)

>   create mode 100644 Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml

>   create mode 100644 drivers/leds/leds-qcom-spmi-flash.c

>   create mode 100644 include/dt-bindings/leds/leds-qcom-spmi-flash.h

> 


-- 
Best regards,
Jacek Anaszewski
Jacek Anaszewski Jan. 30, 2021, 8:32 p.m. UTC | #5
Hi Nicolas,

On 1/26/21 3:04 PM, Nícolas F. R. A. Prado wrote:
> Add devicetree binding for QCOM SPMI Flash LEDs, which are part of

> PM8941, and are used both as lantern and camera flash.

> 

> Signed-off-by: Nícolas F. R. A. Prado <nfraprado@protonmail.com>

> ---

> Changes in v2:

> - Add this commit

> 

>   .../bindings/leds/leds-qcom-spmi-flash.yaml   | 94 +++++++++++++++++++

>   .../dt-bindings/leds/leds-qcom-spmi-flash.h   | 15 +++

>   2 files changed, 109 insertions(+)

>   create mode 100644 Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml

>   create mode 100644 include/dt-bindings/leds/leds-qcom-spmi-flash.h

> 

> diff --git a/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml b/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml

> new file mode 100644

> index 000000000000..169716e14f67

> --- /dev/null

> +++ b/Documentation/devicetree/bindings/leds/leds-qcom-spmi-flash.yaml

> @@ -0,0 +1,94 @@

> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)

> +%YAML 1.2

> +---

> +$id: http://devicetree.org/schemas/leds/leds-qcom-spmi-flash.yaml#

> +$schema: http://devicetree.org/meta-schemas/core.yaml#

> +

> +title: Qualcomm SPMI Flash LEDs

> +

> +maintainers:

> +  - Nícolas F. R. A. Prado <nfraprado@protonmail.com>

> +

> +description: |

> +  The Qualcomm SPMI Flash LEDs are part of Qualcomm PMICs and are used primarily

> +  as a camera or video flash. They can also be used as a lantern when on torch

> +  mode.

> +  The PMIC is connected to Host processor via SPMI bus.

> +

> +properties:

> +  compatible:

> +    const: qcom,spmi-flash

> +

> +  reg:

> +    maxItems: 1

> +

> +  flash-boost-supply:

> +    description: SMBB regulator for LED flash mode

> +

> +  torch-boost-supply:

> +    description: SMBB regulator for LED torch mode

> +

> +patternProperties:

> +  "^led[0-1]$":

> +    type: object

> +    $ref: common.yaml#

> +

> +    properties:

> +      qcom,clamp-curr:

> +        description: current to clamp at, in uA


You're already using led-max-microamp and flash-max-microamp,
so I'm not sure how this is related to those.

> +        $ref: /schemas/types.yaml#definitions/uint32

> +

> +      qcom,headroom:

> +        description: |

> +          headroom to use. Use one of QCOM_SPMI_FLASH_HEADROOM_* defined in

> +          include/dt-bindings/leds/leds-qcom-spmi-flash.h


More information would be welcome here on how it impacts device
operation.

> +        $ref: /schemas/types.yaml#definitions/uint32

> +        minimum: 0

> +        maximum: 3

> +

> +      qcom,startup-dly:

> +        description: |

> +          delay before flashing. Use one of QCOM_SPMI_FLASH_STARTUP_DLY_*

> +          defined in include/dt-bindings/leds/leds-qcom-spmi-flash.h


I don't see much gain in having this exposed.
We don't have related constructs in the API to handle that, so I propose
to always set it to 0 and not expose this setting at all.

> +        $ref: /schemas/types.yaml#definitions/uint32

> +        minimum: 0

> +        maximum: 3

> +

> +      qcom,safety-timer:

> +        description: include for safety timer use, otherwise watchdog timer will be used

> +        type: boolean


What's the difference between watchdog timer and safety timer?
Either way, I propose to choose the option best fitting for handling
flash timeout setting and hide this inside the driver.

> +required:

> +  - compatible

> +  - reg

> +  - flash-boost-supply

> +  - torch-boost-supply

> +

> +additionalProperties: false

> +

> +examples:

> +  - |

> +    #include <dt-bindings/leds/common.h>

> +    #include <dt-bindings/leds/leds-qcom-spmi-flash.h>

> +

> +    qcom,leds@d300 {

> +        compatible = "qcom,spmi-flash";

> +        reg = <0xd300 0x100>;

> +        flash-boost-supply = <&pm8941_5vs1>;

> +        torch-boost-supply = <&pm8941_5v>;

> +

> +        led0 {

> +            led-sources = <0>;


Please use reg property for this purpose instead.

> +            function = LED_FUNCTION_FLASH;

> +            color = <LED_COLOR_ID_WHITE>;

> +            led-max-microamp = <200000>;

> +            flash-max-microamp = <1000000>;

> +            flash-max-timeout-us = <1280000>;


You should mention this properties above and provide constraints
(min, max, step).

> +            default-state = "off";


There's no point in having this in the example.

> +            qcom,clamp-curr = <200000>; > +            qcom,headroom = <QCOM_SPMI_FLASH_HEADROOM_500MV>;

> +            qcom,startup-dly = <QCOM_SPMI_FLASH_STARTUP_DLY_128US>;

> +            qcom,safety-timer;

> +        };

> +    };

> +...

> diff --git a/include/dt-bindings/leds/leds-qcom-spmi-flash.h b/include/dt-bindings/leds/leds-qcom-spmi-flash.h

> new file mode 100644

> index 000000000000..8bd54a8e831d

> --- /dev/null

> +++ b/include/dt-bindings/leds/leds-qcom-spmi-flash.h

> @@ -0,0 +1,15 @@

> +/* SPDX-License-Identifier: GPL-2.0 */

> +#ifndef _DT_BINDINGS_LEDS_QCOM_SPMI_FLASH_H

> +#define _DT_BINDINGS_LEDS_QCOM_SPMI_FLASH_H

> +

> +#define QCOM_SPMI_FLASH_HEADROOM_250MV	0

> +#define QCOM_SPMI_FLASH_HEADROOM_300MV	1

> +#define QCOM_SPMI_FLASH_HEADROOM_400MV	2

> +#define QCOM_SPMI_FLASH_HEADROOM_500MV	3

> +

> +#define QCOM_SPMI_FLASH_STARTUP_DLY_10US	0

> +#define QCOM_SPMI_FLASH_STARTUP_DLY_32US	1

> +#define QCOM_SPMI_FLASH_STARTUP_DLY_64US	2

> +#define QCOM_SPMI_FLASH_STARTUP_DLY_128US	3

> +

> +#endif

> 


-- 
Best regards,
Jacek Anaszewski
Jacek Anaszewski Jan. 30, 2021, 8:37 p.m. UTC | #6
Hi Nicolas,

On 1/26/21 3:05 PM, Nícolas F. R. A. Prado wrote:
> Add driver for the Qualcomm SPMI Flash LEDs. These are controlled

> through an SPMI bus and are part of the PM8941 PMIC. There are two LEDs

> present in the chip, and can be used independently as camera flash or

> together in torch mode to act as a lantern.

> 

> Signed-off-by: Nícolas F. R. A. Prado <nfraprado@protonmail.com>

> ---

> Changes in v2:

> - Thanks to Jacek:

>    - Implemented flash LED class framework

> - Thanks to Bjorn:

>    - Renamed driver to "qcom spmi flash"

> - Refactored code

> - Added missing copyright

> 

>   drivers/leds/Kconfig                |    8 +

>   drivers/leds/Makefile               |    1 +

>   drivers/leds/leds-qcom-spmi-flash.c | 1153 +++++++++++++++++++++++++++

>   3 files changed, 1162 insertions(+)

>   create mode 100644 drivers/leds/leds-qcom-spmi-flash.c

> 

> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig

> index 849d3c5f908e..ad1c7846f9b3 100644

> --- a/drivers/leds/Kconfig

> +++ b/drivers/leds/Kconfig

> @@ -928,6 +928,14 @@ config LEDS_ACER_A500

>   	  This option enables support for the Power Button LED of

>   	  Acer Iconia Tab A500.

>   

> +config LEDS_QCOM_SPMI_FLASH

> +	tristate "Support for QCOM SPMI Flash LEDs"

> +	depends on SPMI

> +	depends on LEDS_CLASS_FLASH

> +	help

> +	  This driver supports the Flash/Torch LED present in Qualcomm's PM8941

> +	  PMIC.

> +

>   comment "LED Triggers"

>   source "drivers/leds/trigger/Kconfig"

>   

> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile

> index 73e603e1727e..e86bcfba016b 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_QCOM_SPMI_FLASH)	+= leds-qcom-spmi-flash.o

>   

>   # LED SPI Drivers

>   obj-$(CONFIG_LEDS_CR0014114)		+= leds-cr0014114.o

> diff --git a/drivers/leds/leds-qcom-spmi-flash.c b/drivers/leds/leds-qcom-spmi-flash.c

> new file mode 100644

> index 000000000000..023fc107abce

> --- /dev/null

> +++ b/drivers/leds/leds-qcom-spmi-flash.c

> @@ -0,0 +1,1153 @@

> +// SPDX-License-Identifier: GPL-2.0-only

> +/*

> + * Qualcomm SPMI Flash Driver

> + *

> + * Copyright (c) 2020, Nícolas F. R. A. Prado <nfraprado@protonmail.com>

> + *

> + * Based on QPNP LEDs driver from downstream MSM kernel sources.

> + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.

> + */

> +

> +#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/led-class-flash.h>

> +#include <linux/regulator/consumer.h>

> +#include <linux/delay.h>

> +#include <linux/regmap.h>

> +#include <dt-bindings/leds/leds-qcom-spmi-flash.h>


Please sort includes alphabetically.

> +

> +#define FLASH_SAFETY_TIMER		0x40


Namespacing prefix is needed for macros, e.g. QCOM_FLASH*.

> +#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_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 FLASH_DURATION_STEP		10000

> +#define FLASH_DURATION_MIN		10000

> +#define FLASH_DURATION_MAX		1280000 //TODO: find real value

> +

> +#define FLASH_CURRENT_STEP		12500

> +#define FLASH_CURRENT_MIN		12500

> +#define FLASH_CURRENT_MAX		1000000

> +#define FLASH_TORCH_CURRENT_MAX		200000

> +

> +#define FLASH_DEFAULT_CLAMP		200000

> +

> +enum qcom_flash_ids {

> +	QCOM_FLASH_ID_LED0,

> +	QCOM_FLASH_ID_LED1,

> +};

> +

> +static u8 flash_debug_regs[] = {

> +	0x40, 0x41, 0x42, 0x43, 0x44, 0x48, 0x49, 0x4b, 0x4c,

> +	0x4f, 0x46, 0x47,

> +};

> +

> +/**

> + * struct qcom_flash_led - Represents each individual flash LED

> + * @cdev: flash LED classdev

> + * @id: led ID as given by enum qcom_flash_ids


s/led/LED/

> + * @default_on: default state for the LED

> + * @turn_off_delay_ms: number of msec before turning off the LED

> + * @clamp_curr: Clamp current to use


 From what I infer this is flash LED max current, so it should
be stored in struct led_classdev_flash's brightness.max property.
Please drop clamp_curr to avoid this redundancy.

> + * @headroom: Headroom value to use, as given by leds-qcom-spmi-flash.h

> + * @enable_module: enable address for particular flash


flash_enable_cmd ?

> + * @trigger_flash: trigger flash


flash_strobe_cmd ? Anyway, trigger has different meaning in the LED
subsystem so its use should be avoided in other contexts.

> + * @startup_dly: startup delay for flash, as given by leds-qcom-spmi-flash.h


Out of curiosity - these values range from 28us to 128us - is the
difference between settings at all noticeable ? What's its purpose?
Maybe it has some meaning for the associated ISP when hardware strobe
mdoe is used?

> + * @strobe_type: select between sw and hw strobe


Please change it to strobe_mode.

> + * @current_addr: address to write for current

> + * @second_addr: address of secondary flash to be written


What is secondary flash? Maybe it is for boost mode when one LED is
connected to the to iouts ?

> + * @safety_timer: enable safety timer or watchdog timer


Could you please describe the difference between the two?

> + * @torch_enable: whether torch mode is enabled or individual flash LED


Does it mean that torch mode cannot be enabled for individual LED ?

> + */

> +struct qcom_flash_led {

> +	struct led_classdev_flash cdev;


Please use fled_cdev here to avoid confusion.

> +	int id;


s/int/enum qcom_flash_ids/

> +	bool default_on;

> +	int turn_off_delay_ms;


You're not using this value anywhere in the driver after parsing
from DT.

> +	u32 clamp_curr;

> +	u8 headroom;

> +	u8 enable_module;

> +	u8 trigger_flash;

> +	u8 startup_dly;

> +	u8 strobe_type;

> +	u16 current_addr;

> +	u16 second_addr;

> +	bool safety_timer;

> +	bool torch_enable;

> +};

> +

> +/**

> + * struct qcom_flash_device - QCOM SPMI Flash device, contains 2 flash LEDs

> + * @regmap: regmap used to access LED registers over SPMI

> + * @base: base register given in device tree

> + * @pdev: platform device from devicetree

> + * @flash_boost_reg: voltage regulator to supply the flashes

> + * @torch_boost_reg: voltage regulator to supply torch mode

> + * @leds: flash LEDs

> + * @num_leds: number of LEDs registered (between 0 and 2)

> + * @lock: lock to protect SPMI transactions

> + * @torch_enable: enable flash LED torch mode

> + * @peripheral_subtype: module peripheral subtype


What determines the peripherial subtype? I see this is read from the
device. Is this value burnt in the chipset, or the device detects
whether the LED iouts are connected?

> + * @flash_regulator_on: flash regulator status

> + * @torch_regulator_on: torch regulator status

> + */

> +struct qcom_flash_device {

> +	struct regmap *regmap;

> +	u16 base;

> +	struct platform_device *pdev;

> +	struct regulator *flash_boost_reg;

> +	struct regulator *torch_boost_reg;

> +	struct qcom_flash_led leds[2];

> +	u8 num_leds;

> +	struct mutex lock;

> +	u8 torch_enable;

> +	u8 peripheral_subtype;

> +	bool flash_regulator_on;

> +	bool torch_regulator_on;

> +};

> +

> +static inline struct qcom_flash_led *flcdev_to_led(struct led_classdev_flash *fled_cdev)

> +{

> +	return container_of(fled_cdev, struct qcom_flash_led, cdev);

> +}

> +

> +static inline struct qcom_flash_device *led_to_leds_dev(struct qcom_flash_led *led)

> +{

> +	return container_of(led, struct qcom_flash_device, leds[led->id]);

> +}

> +

> +static int led_read_reg(struct qcom_flash_device *leds_dev, u16 offset, u8 *data)

> +{

> +	unsigned int val;

> +	int ret;

> +

> +	ret = regmap_read(leds_dev->regmap, leds_dev->base + offset, &val);

> +	if (ret < 0)

> +		return ret;

> +

> +	*data = val;

> +	return 0;

> +}

> +

> +static int led_write_reg(struct qcom_flash_device *leds_dev, u16 offset, u8 data)

> +{

> +	return regmap_write(leds_dev->regmap, leds_dev->base + offset, data);

> +}

> +

> +static int qcom_flash_masked_write(struct qcom_flash_device *leds_dev,

> +				   u16 addr,

> +				   u8 mask,

> +				   u8 val)

> +{

> +	int rc;

> +	u8 reg;

> +

> +	rc = led_read_reg(leds_dev, addr, &reg);

> +	if (rc)

> +		dev_err(&leds_dev->pdev->dev,

> +			"Unable to read from addr=%x, rc(%d)\n", addr, rc);

> +

> +	reg &= ~mask;

> +	reg |= val;

> +

> +	rc = led_write_reg(leds_dev, addr, reg);

> +	if (rc)

> +		dev_err(&leds_dev->pdev->dev,

> +			"Unable to write to addr=%x, rc(%d)\n", addr, rc);

> +	return rc;

> +}

> +

> +static void qcom_flash_dump_regs(struct qcom_flash_device *leds_dev,

> +				 u8 regs[],

> +				 u8 array_size)

> +{

> +	int i;

> +	u8 val;

> +

> +	pr_debug("===== LED register dump start =====\n");

> +	for (i = 0; i < array_size; i++) {

> +		led_read_reg(leds_dev, regs[i], &val);

> +		pr_debug("0x%x = 0x%x\n", leds_dev->base + regs[i], val);

> +	}

> +	pr_debug("===== LED register dump end =====\n");

> +}

> +

> +static u8 qcom_flash_duration_to_reg(u32 us)

> +{

> +	if (us < FLASH_DURATION_MIN)

> +		us = FLASH_DURATION_MIN;

> +	return (us - FLASH_DURATION_MIN) / FLASH_DURATION_STEP;

> +}

> +

> +static u8 qcom_flash_current_to_reg(u32 ua)

> +{

> +	if (ua < FLASH_CURRENT_MIN)

> +		ua = FLASH_CURRENT_MIN;

> +	return (ua - FLASH_CURRENT_MIN) / FLASH_CURRENT_STEP;

> +}

> +

> +static void clamp_align(u32 *v, u32 min, u32 max, u32 step)

> +{

> +	*v = clamp_val(*v, min, max);

> +	if (step > 1)

> +		*v = (*v - min) / step * step + min;

> +}

> +

> +static int qcom_flash_fled_regulator_operate(struct qcom_flash_device *leds_dev,

> +					     bool on)

> +{

> +	int rc;

> +

> +	if (!on)

> +		goto regulator_turn_off;

> +

> +	if (!leds_dev->flash_regulator_on) {

> +		if (leds_dev->flash_boost_reg) {

> +			rc = regulator_enable(leds_dev->flash_boost_reg);

> +			if (rc) {

> +				dev_err(&leds_dev->pdev->dev,

> +					"Regulator enable failed(%d)\n", rc);

> +				return rc;

> +			}

> +			leds_dev->flash_regulator_on = true;

> +		}

> +	}

> +

> +	return 0;

> +

> +regulator_turn_off:

> +	if (leds_dev->flash_regulator_on) {

> +		if (leds_dev->flash_boost_reg) {

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_ENABLE_CONTROL,

> +				FLASH_ENABLE_MASK,

> +				FLASH_DISABLE_ALL);

> +			if (rc)

> +				dev_err(&leds_dev->pdev->dev,

> +					"Enable reg write failed(%d)\n", rc);

> +

> +			rc = regulator_disable(leds_dev->flash_boost_reg);

> +			if (rc) {

> +				dev_err(&leds_dev->pdev->dev,

> +					"Regulator disable failed(%d)\n", rc);

> +				return rc;

> +			}

> +			leds_dev->flash_regulator_on = false;

> +		}

> +	}

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_torch_regulator_operate(struct qcom_flash_device *leds_dev,

> +					      bool on)

> +{

> +	int rc;

> +

> +	if (!on)

> +		goto regulator_turn_off;

> +

> +	if (!leds_dev->torch_regulator_on) {

> +		rc = regulator_enable(leds_dev->torch_boost_reg);

> +		if (rc) {

> +			dev_err(&leds_dev->pdev->dev,

> +				"Regulator enable failed(%d)\n", rc);

> +			return rc;

> +		}

> +		leds_dev->torch_regulator_on = true;

> +	}

> +	return 0;

> +

> +regulator_turn_off:

> +	if (leds_dev->torch_regulator_on) {

> +		rc = qcom_flash_masked_write(leds_dev, FLASH_ENABLE_CONTROL,

> +				FLASH_ENABLE_MODULE_MASK, FLASH_DISABLE_ALL);

> +		if (rc)

> +			dev_err(&leds_dev->pdev->dev,

> +				"Enable reg write failed(%d)\n", rc);

> +

> +		rc = regulator_disable(leds_dev->torch_boost_reg);

> +		if (rc) {

> +			dev_err(&leds_dev->pdev->dev,

> +				"Regulator disable failed(%d)\n", rc);

> +			return rc;

> +		}

> +		leds_dev->torch_regulator_on = false;

> +	}

> +	return 0;

> +}

> +

> +static int qcom_flash_fled_set(struct qcom_flash_led *led, bool on)

> +{

> +	int rc, error;

> +	u8 curr;

> +	struct qcom_flash_device *leds_dev = led_to_leds_dev(led);

> +	struct device *dev = &leds_dev->pdev->dev;

> +

> +	/* dump flash registers */

> +	pr_debug("Regdump before\n");

> +	qcom_flash_dump_regs(leds_dev, flash_debug_regs,

> +			     ARRAY_SIZE(flash_debug_regs));

> +

> +	/* Set led current */

> +	if (on) {


As Bjorn has already noticed this function should be refactored.

> +		if (led->torch_enable)

> +			curr = qcom_flash_current_to_reg(led->cdev.led_cdev.brightness);

> +		else

> +			curr = qcom_flash_current_to_reg(led->cdev.brightness.val);

> +

> +		if (led->torch_enable) {

> +			if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_DUAL) {

> +				rc = qcom_flash_torch_regulator_operate(leds_dev, true);

> +				if (rc) {

> +					dev_err(dev,

> +					"Torch regulator operate failed(%d)\n",

> +					rc);

> +					return rc;

> +				}

> +			} else if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_SINGLE) {

> +				rc = qcom_flash_fled_regulator_operate(leds_dev, true);

> +				if (rc) {

> +					dev_err(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 = qcom_flash_masked_write(leds_dev,

> +					FLASH_ENABLE_CONTROL,

> +					FLASH_ENABLE_MODULE_MASK,

> +					FLASH_ENABLE_MODULE);

> +				if (rc) {

> +					dev_err(dev,

> +						"Enable reg write failed(%d)\n",

> +						rc);

> +					return rc;

> +				}

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_LED_UNLOCK_SECURE,

> +				FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);

> +			if (rc) {

> +				dev_err(dev, "Secure reg write failed(%d)\n", rc);

> +				goto error_reg_write;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_LED_TORCH,

> +				FLASH_TORCH_MASK, FLASH_LED_TORCH_ENABLE);

> +			if (rc) {

> +				dev_err(dev, "Torch reg write failed(%d)\n", rc);

> +				goto error_reg_write;

> +			}


Do you have to do it always, even if torch is already enabled and you
want to only change brightness?

> +			rc = qcom_flash_masked_write(leds_dev,

> +				led->current_addr,

> +				FLASH_CURRENT_MASK,

> +				curr);

> +			if (rc) {

> +				dev_err(dev, "Current reg write failed(%d)\n", rc);

> +				goto error_reg_write;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				led->second_addr,

> +				FLASH_CURRENT_MASK,

> +				curr);

> +			if (rc) {

> +				dev_err(dev,

> +					"2nd Current reg write failed(%d)\n",

> +					rc);

> +				goto error_reg_write;

> +			}

> +

> +			qcom_flash_masked_write(leds_dev, FLASH_MAX_CURR,

> +				FLASH_CURRENT_MASK,

> +				TORCH_MAX_LEVEL);


Can't this be set once at probe?

> +			if (rc) {

> +				dev_err(dev,

> +					"Max current reg write failed(%d)\n",

> +					rc);

> +				goto error_reg_write;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_ENABLE_CONTROL,

> +				FLASH_ENABLE_MASK,

> +				leds_dev->torch_enable);

> +			if (rc) {

> +				dev_err(dev, "Enable reg write failed(%d)\n", rc);

> +				goto error_reg_write;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_LED_STROBE_CTRL,

> +				FLASH_STROBE_SW,

> +				FLASH_STROBE_SW);


For now, let's configure strobe mode always to software. Can it be done 
once at probe? Hardware strobe mode is useful only in cooperation with
ISP device, and thus needs to be implemented only if v4l2 flash support 
is added.

> +			if (rc) {

> +				dev_err(dev,

> +					"LED %d strobe reg write failed(%d)\n",

> +					led->id, rc);

> +				goto error_reg_write;

> +			}

> +		} else {

> +			rc = qcom_flash_fled_regulator_operate(leds_dev, true);

> +			if (rc) {

> +				dev_err(dev,

> +					"Flash regulator operate failed(%d)\n",

> +					rc);

> +				goto error_flash_set;

> +			}

> +

> +			/* Set flash safety timer */

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_SAFETY_TIMER,

> +				FLASH_SAFETY_TIMER_MASK,


Can't it be set in timeout_set op?

> +				qcom_flash_duration_to_reg(led->cdev.timeout.val));

> +			if (rc) {

> +				dev_err(dev,

> +					"Safety timer reg write failed(%d)\n",

> +					rc);

> +				goto error_flash_set;

> +			}

> +

> +			/* Set max current */

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_MAX_CURR, FLASH_CURRENT_MASK,

> +				FLASH_MAX_LEVEL);

> +			if (rc) {

> +				dev_err(dev,

> +					"Max current reg write failed(%d)\n",

> +					rc);

> +				goto error_flash_set;

> +			}

> +

> +			/* Set clamp current */

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_CLAMP_CURR,

> +				FLASH_CURRENT_MASK,


Can't it be set once, at probe?

> +				qcom_flash_current_to_reg(led->clamp_curr));

> +			if (rc) {

> +				dev_err(dev,

> +					"Clamp current reg write failed(%d)\n",

> +					rc);

> +				goto error_flash_set;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				led->current_addr,

> +				FLASH_CURRENT_MASK,

> +				curr);


Can't it be set in flash_brightness_set op ?

> +			if (rc) {

> +				dev_err(dev, "Current reg write failed(%d)\n", rc);

> +				goto error_flash_set;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_ENABLE_CONTROL,

> +				led->enable_module,

> +				led->enable_module);

> +			if (rc) {

> +				dev_err(dev, "Enable reg write failed(%d)\n", rc);

> +				goto error_flash_set;

> +			}

> +

> +			/* TODO try to not busy wait*/

> +			mdelay(1);

> +

> +			if (!led->strobe_type) {

> +				rc = qcom_flash_masked_write(leds_dev,

> +					FLASH_LED_STROBE_CTRL,

> +					led->trigger_flash,

> +					led->trigger_flash);

> +				if (rc) {

> +					dev_err(dev,

> +					"LED %d strobe reg write failed(%d)\n",

> +					led->id, rc);

> +					goto error_flash_set;

> +				}

> +			} else {

> +				rc = qcom_flash_masked_write(leds_dev,

> +					FLASH_LED_STROBE_CTRL,

> +					(led->trigger_flash | FLASH_STROBE_HW),

> +					(led->trigger_flash | FLASH_STROBE_HW));

> +				if (rc) {

> +					dev_err(dev,

> +					"LED %d strobe reg write failed(%d)\n",

> +					led->id, rc);

> +					goto error_flash_set;

> +				}

> +			}

> +		}

> +	} else {

> +		rc = qcom_flash_masked_write(leds_dev,

> +			FLASH_LED_STROBE_CTRL,

> +			led->trigger_flash,

> +			FLASH_DISABLE_ALL);

> +		if (rc) {

> +			dev_err(dev,

> +				"LED %d flash write failed(%d)\n", led->id, rc);

> +			if (led->torch_enable)

> +				goto error_torch_set;

> +			else

> +				goto error_flash_set;

> +		}

> +

> +		/* TODO try to not busy wait*/

> +		mdelay(2);

> +		udelay(160);

> +

> +		if (led->torch_enable) {

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_LED_UNLOCK_SECURE,

> +				FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);

> +			if (rc) {

> +				dev_err(dev, "Secure reg write failed(%d)\n", rc);

> +				goto error_torch_set;

> +			}

> +

> +			rc = qcom_flash_masked_write(leds_dev,

> +					FLASH_LED_TORCH,

> +					FLASH_TORCH_MASK,

> +					FLASH_LED_TORCH_DISABLE);

> +			if (rc) {

> +				dev_err(dev, "Torch reg write failed(%d)\n", rc);

> +				goto error_torch_set;

> +			}

> +

> +			if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_DUAL) {

> +				rc = qcom_flash_torch_regulator_operate(leds_dev,false);

> +				if (rc) {

> +					dev_err(dev,

> +						"Torch regulator operate failed(%d)\n",

> +						rc);

> +					return rc;

> +				}

> +			} else if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_SINGLE) {

> +				rc = qcom_flash_fled_regulator_operate(leds_dev, false);

> +				if (rc) {

> +					dev_err(dev,

> +						"Flash regulator operate failed(%d)\n",

> +						rc);

> +					return rc;

> +				}

> +			}

> +		} else {

> +			rc = qcom_flash_masked_write(leds_dev,

> +				FLASH_ENABLE_CONTROL,

> +				led->enable_module &

> +				~FLASH_ENABLE_MODULE_MASK,

> +				FLASH_DISABLE_ALL);

> +			if (rc) {

> +				dev_err(dev, "Enable reg write failed(%d)\n", rc);

> +				if (led->torch_enable)

> +					goto error_torch_set;

> +				else

> +					goto error_flash_set;

> +			}

> +

> +			rc = qcom_flash_fled_regulator_operate(leds_dev, false);

> +			if (rc) {

> +				dev_err(dev,

> +					"Flash regulator operate failed(%d)\n",

> +					rc);

> +				return rc;

> +			}

> +		}

> +	}

> +

> +	pr_debug("Regdump after\n");

> +	qcom_flash_dump_regs(leds_dev, flash_debug_regs, ARRAY_SIZE(flash_debug_regs));

> +

> +	return 0;

> +

> +error_reg_write:

> +	if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_SINGLE)

> +		goto error_flash_set;

> +

> +error_torch_set:

> +	error = qcom_flash_torch_regulator_operate(leds_dev, false);

> +	if (error) {

> +		dev_err(dev, "Torch regulator operate failed(%d)\n", rc);

> +		return error;

> +	}

> +	return rc;

> +

> +error_flash_set:

> +	error = qcom_flash_fled_regulator_operate(leds_dev, false);

> +	if (error) {

> +		dev_err(dev, "Flash regulator operate failed(%d)\n", rc);

> +		return error;

> +	}

> +	return rc;

> +}

> +

> +static int qcom_flash_led_set(struct led_classdev *led_cdev,

> +			      enum led_brightness value)

> +{

> +	struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);

> +	struct qcom_flash_led *led = flcdev_to_led(fled_cdev);

> +	struct qcom_flash_device *leds_dev = led_to_leds_dev(led);

> +	u32 val = value;

> +	int rc;

> +	bool on;

> +

> +	if (val > led_cdev->max_brightness) {

> +		dev_err(&leds_dev->pdev->dev, "Invalid brightness value\n");

> +		return -EINVAL;

> +	}

> +

> +	if (val) {

> +		on = true;

> +		clamp_align(&val, FLASH_CURRENT_MIN, led_cdev->max_brightness,

> +			    FLASH_CURRENT_STEP);

> +		led_cdev->brightness = val;

> +		led->torch_enable = true;

> +	} else {

> +		on = false;

> +	}

> +

> +	mutex_lock(&leds_dev->lock);

> +	rc = qcom_flash_fled_set(led, on);

> +	if (rc < 0)

> +		dev_err(&leds_dev->pdev->dev, "FLASH set brightness failed (%d)\n", rc);

> +	mutex_unlock(&leds_dev->lock);

> +	return rc;

> +}

> +

> +static int qcom_flash_fled_brightness_set(struct led_classdev_flash *fled_cdev,

> +					  u32 brightness)

> +{

> +	clamp_align(&brightness, FLASH_CURRENT_MIN, fled_cdev->brightness.max,

> +		    FLASH_CURRENT_STEP);

> +	fled_cdev->brightness.val = brightness;

> +	return 0;

> +}

> +

> +static int qcom_flash_fled_strobe_set(struct led_classdev_flash *fled_cdev,

> +				      bool state)

> +{

> +	struct qcom_flash_led *led = flcdev_to_led(fled_cdev);

> +	struct qcom_flash_device *leds_dev = led_to_leds_dev(led);

> +	int rc;

> +

> +	led->torch_enable = false;

> +

> +	mutex_lock(&leds_dev->lock);

> +	rc = qcom_flash_fled_set(led, state);

> +	if (rc < 0)

> +		return rc;

> +	mutex_unlock(&leds_dev->lock);

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_fled_strobe_get(struct led_classdev_flash *fled_cdev,

> +				      bool *state)

> +{

> +	//TODO

> +	*state = 0;

> +	return 0;

> +}

> +

> +static int qcom_flash_fled_timeout_set(struct led_classdev_flash *fled_cdev,

> +				       u32 timeout)

> +{

> +	fled_cdev->timeout.val = timeout;

> +	return 0;

> +}

> +

> +static int qcom_flash_fled_fault_get(struct led_classdev_flash *fled_cdev,

> +				     u32 *fault)

> +{

> +	//TODO

> +	*fault = 0;

> +	return 0;

> +}

> +

> +static const struct led_flash_ops flash_ops = {

> +	.flash_brightness_set	= qcom_flash_fled_brightness_set,

> +	.strobe_set		= qcom_flash_fled_strobe_set,

> +	.strobe_get		= qcom_flash_fled_strobe_get,

> +	.timeout_set		= qcom_flash_fled_timeout_set,

> +	.fault_get		= qcom_flash_fled_fault_get,

> +};

> +

> +static int qcom_flash_flcdev_setup(struct qcom_flash_led *led,

> +				   struct device_node *node)

> +{

> +	int rc;

> +	struct platform_device *pdev = led_to_leds_dev(led)->pdev;

> +	struct led_init_data init_data = {};

> +

> +	init_data.fwnode = of_fwnode_handle(node);

> +	init_data.devicename = "qcom-spmi-flash";

> +	init_data.default_label = "flash";


Please drop devicename and default_label - they are for backwards
compatibility only, for older drivers.

> +

> +	led->cdev.led_cdev.brightness_set_blocking    = qcom_flash_led_set;

> +

> +	rc = of_property_read_u32(node, "led-max-microamp",

> +				  &led->cdev.led_cdev.max_brightness);

> +	if (rc < 0) {

> +		dev_err(&pdev->dev, "Failure reading max_current, rc =  %d\n", rc);

> +		return rc;

> +	}

> +	led->cdev.led_cdev.max_brightness = min((u32) led->cdev.led_cdev.max_brightness,

> +						(u32) FLASH_TORCH_CURRENT_MAX);


LED class brightness is expressed in levels so you need to convert this
to the number of brightness levels (i.e. divide by FLASH_CURRENT_STEP).
And I can't see why you're using min() here ? It will result in
max_brightness being always set to 0.

> +

> +	rc = of_property_read_u32(node, "flash-max-microamp", &led->cdev.brightness.max);

> +	if (rc < 0) {

> +		dev_err(&pdev->dev, "Failure reading max_current, rc =  %d\n", rc);

> +		return rc;

> +	}

> +	led->cdev.brightness.max = min((u32) led->cdev.brightness.max,

> +				(u32) FLASH_CURRENT_MAX);

> +	led->cdev.brightness.min = FLASH_CURRENT_MIN;

> +	led->cdev.brightness.step = FLASH_CURRENT_STEP;

> +	led->cdev.brightness.val = led->cdev.brightness.max;

> +

> +	rc = of_property_read_u32(node, "flash-max-timeout-us", &led->cdev.timeout.max);

> +	if (rc < 0) {

> +		dev_err(&pdev->dev, "Failure reading max_timeout, rc =  %d\n", rc);

> +		return rc;

> +	}

> +	led->cdev.timeout.max = min((u32) led->cdev.timeout.max,

> +				    (u32) FLASH_DURATION_MAX);

> +	led->cdev.timeout.min = FLASH_DURATION_MIN;

> +	led->cdev.timeout.step = FLASH_DURATION_STEP;

> +	led->cdev.timeout.val = led->cdev.timeout.max;

> +

> +	led->cdev.ops = &flash_ops;

> +	led->cdev.led_cdev.flags |= LED_DEV_CAP_FLASH;

> +

> +	rc = led_classdev_flash_register_ext(&pdev->dev, &led->cdev, &init_data);


Please use devm_* version.

> +	if (rc) {

> +		dev_err(&pdev->dev, "unable to register led %d,rc=%d\n", led->id, rc);

> +		return rc;

> +	}

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_setup_flash_regs(struct qcom_flash_led *led)

> +{

> +	int rc;

> +	struct qcom_flash_device *leds_dev = led_to_leds_dev(led);

> +

> +	/* Set headroom */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_HEADROOM,

> +		FLASH_HEADROOM_MASK, led->headroom);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "Headroom reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set startup delay */

> +	rc = qcom_flash_masked_write(leds_dev,

> +		FLASH_STARTUP_DELAY, FLASH_STARTUP_DLY_MASK,

> +		led->startup_dly);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev,

> +			"Startup delay reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set timer control - safety or watchdog */

> +	if (led->safety_timer) {

> +		rc = qcom_flash_masked_write(leds_dev,

> +			FLASH_LED_TMR_CTRL,

> +			FLASH_TMR_MASK, FLASH_TMR_SAFETY);

> +		if (rc) {

> +			dev_err(&leds_dev->pdev->dev,

> +				"LED timer ctrl reg write failed(%d)\n", rc);

> +			return rc;

> +		}

> +	}

> +

> +	/* dump flash registers */

> +	qcom_flash_dump_regs(leds_dev, flash_debug_regs, ARRAY_SIZE(flash_debug_regs));

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_get_config_flash(struct qcom_flash_led *led,

> +				       struct device_node *node)

> +{

> +	int rc;

> +	u32 val;

> +	const char *temp_string;

> +	struct device *dev = &led_to_leds_dev(led)->pdev->dev;

> +

> +	rc = of_property_read_u32(node, "led-sources", &led->id);

> +	if (rc < 0) {

> +		dev_err(dev, "Failure reading led id, rc =  %d\n", rc);

> +		return rc;

> +	}

> +

> +	led->default_on = false;

> +	rc = of_property_read_string(node, "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;

> +

> +	if (led->id == QCOM_FLASH_ID_LED0) {

> +		led->enable_module = FLASH_ENABLE_LED_0;

> +		led->current_addr = FLASH_LED_0_CURR;

> +		led->trigger_flash = FLASH_LED_0_OUTPUT;

> +

> +		led->second_addr = FLASH_LED_1_CURR;

> +	} else if (led->id == QCOM_FLASH_ID_LED1) {

> +		led->enable_module = FLASH_ENABLE_LED_1;

> +		led->current_addr = FLASH_LED_1_CURR;

> +		led->trigger_flash = FLASH_LED_1_OUTPUT;

> +

> +		led->second_addr = FLASH_LED_0_CURR;

> +	} else {

> +		dev_err(dev, "Unknown flash LED name given\n");

> +		return -EINVAL;

> +	}

> +

> +	rc = of_property_read_u32(node, "qcom,headroom", &val);

> +	if (!rc)

> +		led->headroom = (u8) val;

> +	else if (rc == -EINVAL)

> +		led->headroom = QCOM_SPMI_FLASH_HEADROOM_500MV;

> +

> +	rc = of_property_read_u32(node, "qcom,clamp-curr", &val);

> +	if (!rc)

> +		led->clamp_curr = val;

> +	else if (rc == -EINVAL)

> +		led->clamp_curr = FLASH_DEFAULT_CLAMP;

> +

> +	rc = of_property_read_u32(node, "qcom,startup-dly", &val);

> +	if (!rc)

> +		led->startup_dly = (u8) val;

> +	else if (rc == -EINVAL)

> +		led->startup_dly = QCOM_SPMI_FLASH_STARTUP_DLY_128US;

> +

> +	led->safety_timer = of_property_read_bool(node, "qcom,safety-timer");

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_setup_regs(struct qcom_flash_device *leds_dev)

> +{

> +	int rc;

> +

> +	rc = qcom_flash_masked_write(leds_dev,

> +		FLASH_LED_STROBE_CTRL,

> +		FLASH_STROBE_MASK, FLASH_DISABLE_ALL);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "LED flash write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Disable flash LED module */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_ENABLE_CONTROL,

> +		FLASH_ENABLE_MODULE_MASK, FLASH_DISABLE_ALL);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "Enable reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set Vreg force */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_VREG_OK_FORCE,

> +		FLASH_VREG_MASK, FLASH_HW_VREG_OK);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "Vreg OK reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set self fault check */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_FAULT_DETECT,

> +		FLASH_FAULT_DETECT_MASK, FLASH_SELFCHECK_DISABLE);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev,

> +			"Fault detect reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set mask enable */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_MASK_ENABLE,

> +		FLASH_MASK_REG_MASK, FLASH_MASK_1);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "Mask enable reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Set ramp rate */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_RAMP_RATE,

> +		FLASH_RAMP_RATE_MASK, 0xBF);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev, "Ramp rate reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	/* Enable VPH_PWR_DROOP and set threshold to 2.9V */

> +	rc = qcom_flash_masked_write(leds_dev, FLASH_VPH_PWR_DROOP,

> +					FLASH_VPH_PWR_DROOP_MASK, 0xC2);

> +	if (rc) {

> +		dev_err(&leds_dev->pdev->dev,

> +			"FLASH_VPH_PWR_DROOP reg write failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_setup_led(struct qcom_flash_led *led,

> +				struct device_node *node)

> +{

> +	int rc;

> +

> +	rc = qcom_flash_get_config_flash(led, node);

> +	if (rc < 0) {

> +		dev_err(&led_to_leds_dev(led)->pdev->dev,

> +			"Unable to read flash config data\n");

> +		return rc;

> +	}

> +

> +	rc = qcom_flash_setup_flash_regs(led);

> +	if (rc) {

> +		dev_err(&led_to_leds_dev(led)->pdev->dev,

> +			"FLASH initialize failed(%d)\n", rc);

> +		return rc;

> +	}

> +

> +	rc = qcom_flash_flcdev_setup(led, node);

> +	if (rc < 0)

> +		return rc;

> +

> +	/* configure default state */

> +	if (led->default_on) {

> +		led->cdev.led_cdev.brightness = led->cdev.led_cdev.max_brightness;

> +		led->torch_enable = true;

> +		mutex_lock(&led_to_leds_dev(led)->lock);

> +		rc = qcom_flash_fled_set(led, true);

> +		if (rc < 0)

> +			return rc;

> +		mutex_unlock(&led_to_leds_dev(led)->lock);

> +	} else {

> +		led->cdev.led_cdev.brightness = LED_OFF;

> +	}

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_setup_leds_device(struct qcom_flash_device *leds_dev,

> +					struct device_node *node,

> +					struct platform_device *pdev)

> +{

> +	u32 reg;

> +	int rc;

> +

> +	leds_dev->pdev = pdev;

> +

> +	leds_dev->regmap = dev_get_regmap(pdev->dev.parent, NULL);

> +	if (!leds_dev->regmap)

> +		return -ENODEV;

> +

> +	rc = of_property_read_u32(node, "reg", &reg);

> +	if (rc < 0) {

> +		dev_err(&pdev->dev, "Failure reading reg, rc = %d\n", rc);

> +		return rc;

> +	}

> +	leds_dev->base = reg;

> +

> +	qcom_flash_setup_regs(leds_dev);

> +

> +	if (of_find_property(node, "flash-boost-supply", NULL)) {

> +		leds_dev->flash_boost_reg = regulator_get(&pdev->dev, "flash-boost");

> +		if (IS_ERR(leds_dev->flash_boost_reg)) {

> +			rc = PTR_ERR(leds_dev->flash_boost_reg);

> +			dev_err(&pdev->dev, "Regulator get failed(%d)\n", rc);

> +			regulator_put(leds_dev->flash_boost_reg);

> +			leds_dev->flash_boost_reg = NULL;

> +			return rc;

> +		}

> +	}

> +

> +	if (of_find_property(node, "torch-boost-supply", NULL)) {

> +		leds_dev->torch_boost_reg = regulator_get(&pdev->dev, "torch-boost");

> +		if (IS_ERR(leds_dev->torch_boost_reg)) {

> +			rc = PTR_ERR(leds_dev->torch_boost_reg);

> +			dev_err(&pdev->dev, "Torch regulator get failed(%d)\n", rc);

> +			regulator_put(leds_dev->flash_boost_reg);

> +			regulator_put(leds_dev->torch_boost_reg);

> +			leds_dev->flash_boost_reg = NULL;

> +			leds_dev->torch_boost_reg = NULL;

> +			return rc;

> +		}

> +		leds_dev->torch_enable = FLASH_ENABLE_MODULE;

> +	} else {

> +		leds_dev->torch_enable = FLASH_ENABLE_ALL;

> +	}

> +

> +	rc = led_read_reg(leds_dev, FLASH_PERIPHERAL_SUBTYPE,

> +			  &leds_dev->peripheral_subtype);

> +	if (rc) {

> +		dev_err(&pdev->dev,

> +			"Unable to read from addr=%x, rc(%d)\n",

> +			FLASH_PERIPHERAL_SUBTYPE, rc);

> +	}

> +

> +	mutex_init(&leds_dev->lock);

> +

> +	return 0;

> +}

> +

> +static int qcom_flash_probe(struct platform_device *pdev)

> +{

> +	struct device *dev = &pdev->dev;

> +	struct qcom_flash_device *leds_dev;

> +	struct device_node *node = dev->of_node;

> +	struct qcom_flash_led *led;

> +	struct device_node *temp;

> +	int rc, i, parsed_leds = 0;

> +

> +	leds_dev = devm_kzalloc(dev, sizeof(struct qcom_flash_device), GFP_KERNEL);

> +	if (!leds_dev)

> +		return -ENOMEM;

> +

> +	rc = qcom_flash_setup_leds_device(leds_dev, node, pdev);

> +	if (rc) {

> +		pr_debug("Probe failed setting up base (%d)\n", rc);

> +		return rc;

> +	}


Please implement qcom_flash_probe_dt() function that will first parse
all DT settings global for this device and then iterate through the
child nodes and parse child LED properties. Afterwards you can call a
function that will configure all device global settings and particular
LEDs. You can compare existing LED class drivers. It simplifies code
analysis if all DT parsing is in one place.

> +	platform_set_drvdata(pdev, leds_dev);

> +

> +	for_each_child_of_node(node, temp) {

> +		led = &leds_dev->leds[parsed_leds];

> +

> +		rc = qcom_flash_setup_led(led, temp);

> +		if (rc) {

> +			for (i = 0; i < parsed_leds; i++)

> +				led_classdev_flash_unregister(&leds_dev->leds[i].cdev);

> +			pr_debug("Probe failed setting up leds (%d)\n", rc);

> +			return rc;

> +		}

> +

> +		parsed_leds++;

> +	}

> +	leds_dev->num_leds = parsed_leds;

> +	return 0;

> +}

> +

> +static int qcom_flash_remove(struct platform_device *pdev)

> +{

> +	struct qcom_flash_device *leds_dev  = platform_get_drvdata(pdev);

> +	int i, parsed_leds = leds_dev->num_leds;

> +

> +	mutex_destroy(&leds_dev->lock);

> +	if (leds_dev->flash_boost_reg)

> +		regulator_put(leds_dev->flash_boost_reg);

> +	if (leds_dev->torch_boost_reg)

> +		regulator_put(leds_dev->torch_boost_reg);

> +	for (i = 0; i < parsed_leds; i++)

> +		led_classdev_flash_unregister(&leds_dev->leds[i].cdev);

> +

> +	return 0;

> +}

> +

> +static const struct of_device_id qcom_flash_spmi_of_match[] = {

> +	{ .compatible = "qcom,spmi-flash" },

> +	{},

> +};

> +MODULE_DEVICE_TABLE(of, qcom_flash_spmi_of_match);

> +

> +static struct platform_driver qcom_flash_driver = {

> +	.driver		= {

> +		.name	= "qcom,spmi-flash",

> +		.of_match_table = of_match_ptr(qcom_flash_spmi_of_match),

> +	},

> +	.probe		= qcom_flash_probe,

> +	.remove		= qcom_flash_remove,

> +};

> +module_platform_driver(qcom_flash_driver);

> +

> +MODULE_DESCRIPTION("Qualcomm SPMI Flash LED driver");

> +MODULE_LICENSE("GPL v2");

> +MODULE_AUTHOR("Nícolas F. R. A. Prado <nfraprado@protonmail.com>");

> 


-- 
Best regards,
Jacek Anaszewski
Pavel Machek Feb. 19, 2021, 11:01 a.m. UTC | #7
On Tue 2021-01-26 14:05:54, Nícolas F. R. A. Prado wrote:
> Add driver for the Qualcomm SPMI Flash LEDs. These are controlled
> through an SPMI bus and are part of the PM8941 PMIC. There are two LEDs
> present in the chip, and can be used independently as camera flash or
> together in torch mode to act as a lantern.

>  drivers/leds/Kconfig                |    8 +
>  drivers/leds/Makefile               |    1 +
>  drivers/leds/leds-qcom-spmi-flash.c | 1153 +++++++++++++++++++++++++++
>  3 files changed, 1162 insertions(+)

Ok, please make this go to drivers/leds/flash/


> +static int qcom_flash_fled_regulator_operate(struct qcom_flash_device *leds_dev,
> +					     bool on)
> +{
> +	int rc;
> +
> +	if (!on)
> +		goto regulator_turn_off;
> +
> +	if (!leds_dev->flash_regulator_on) {
> +		if (leds_dev->flash_boost_reg) {
> +			rc = regulator_enable(leds_dev->flash_boost_reg);
> +			if (rc) {
> +				dev_err(&leds_dev->pdev->dev,
> +					"Regulator enable failed(%d)\n", rc);
> +				return rc;
> +			}
> +			leds_dev->flash_regulator_on = true;
> +		}
> +	}
> +
> +	return 0;
> +
> +regulator_turn_off:
> +	if (leds_dev->flash_regulator_on) {
> +		if (leds_dev->flash_boost_reg) {
> +			rc = qcom_flash_masked_write(leds_dev,
> +				FLASH_ENABLE_CONTROL,
> +				FLASH_ENABLE_MASK,
> +				FLASH_DISABLE_ALL);
> +			if (rc)
> +				dev_err(&leds_dev->pdev->dev,
> +					"Enable reg write failed(%d)\n", rc);
> +
> +			rc = regulator_disable(leds_dev->flash_boost_reg);
> +			if (rc) {
> +				dev_err(&leds_dev->pdev->dev,
> +					"Regulator disable failed(%d)\n", rc);
> +				return rc;
> +			}
> +			leds_dev->flash_regulator_on = false;
> +		}
> +	}
> +
> +	return 0;
> +}

Try to find a way to write this without gotos and with less
indentation. Separate functions may be useful.

> +static int qcom_flash_fled_set(struct qcom_flash_led *led, bool on)
> +{
> +	int rc, error;
> +	u8 curr;
> +	struct qcom_flash_device *leds_dev = led_to_leds_dev(led);
> +	struct device *dev = &leds_dev->pdev->dev;
> +
> +	/* dump flash registers */
> +	pr_debug("Regdump before\n");
> +	qcom_flash_dump_regs(leds_dev, flash_debug_regs,
> +			     ARRAY_SIZE(flash_debug_regs));

I believe this kind of debugging is not needed for production.

> +	/* Set led current */
> +	if (on) {
> +		if (led->torch_enable)
> +			curr = qcom_flash_current_to_reg(led->cdev.led_cdev.brightness);
> +		else
> +			curr = qcom_flash_current_to_reg(led->cdev.brightness.val);
> +
> +		if (led->torch_enable) {
> +			if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_DUAL) {
> +				rc = qcom_flash_torch_regulator_operate(leds_dev, true);
> +				if (rc) {
> +					dev_err(dev,
> +					"Torch regulator operate failed(%d)\n",
> +					rc);
> +					return rc;
> +				}

No need to goto here?

> +			} else if (leds_dev->peripheral_subtype == FLASH_SUBTYPE_SINGLE) {
> +				rc = qcom_flash_fled_regulator_operate(leds_dev, true);
> +				if (rc) {
> +					dev_err(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 = qcom_flash_masked_write(leds_dev,
> +					FLASH_ENABLE_CONTROL,
> +					FLASH_ENABLE_MODULE_MASK,
> +					FLASH_ENABLE_MODULE);
> +				if (rc) {
> +					dev_err(dev,
> +						"Enable reg write failed(%d)\n",
> +						rc);
> +					return rc;
> +				}
> +			}

Anyway, pleae find a way to split this function so that it is less
indented.

> +		/* TODO try to not busy wait*/
> +		mdelay(2);
> +		udelay(160);

What?

Best regards,
								Pavel
Pavel Machek Feb. 19, 2021, 11:02 a.m. UTC | #8
Hi!

> > + */

> > +

> > +#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/led-class-flash.h>

> > +#include <linux/regulator/consumer.h>

> > +#include <linux/delay.h>

> > +#include <linux/regmap.h>

> > +#include <dt-bindings/leds/leds-qcom-spmi-flash.h>

> 

> Please sort includes alphabetically.


No need to do that.

> > +#define FLASH_SAFETY_TIMER		0x40

> 

> Namespacing prefix is needed for macros, e.g. QCOM_FLASH*.


No need for that in .c files.

Best regards,
							Pavel

-- 
http://www.livejournal.com/~pavelmachek
Pavel Machek Feb. 19, 2021, 11:04 a.m. UTC | #9
Hi!

> Another thing worth mentioning: for v1 the dt nodes were added in hammerhead's

> dts (just to simplify testing), but I have now moved them to pm8941's dtsi,

> since it was like that in downstream. So if folks using devices based on

> PM8941/MSM8974 other than the Nexus 5 could test it, that would be great, since

> I have only tested on the Nexus 5.


Please cc phone-devel, there may be people there to help with testing.

Best regards,
								Pavel

-- 
http://www.livejournal.com/~pavelmachek
Jacek Anaszewski Feb. 21, 2021, 11:28 a.m. UTC | #10
On 2/19/21 12:02 PM, Pavel Machek wrote:
> Hi!

> 

>>> + */

>>> +

>>> +#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/led-class-flash.h>

>>> +#include <linux/regulator/consumer.h>

>>> +#include <linux/delay.h>

>>> +#include <linux/regmap.h>

>>> +#include <dt-bindings/leds/leds-qcom-spmi-flash.h>

>>

>> Please sort includes alphabetically.

> 

> No need to do that.


Keeping the includes sorted eliminates the risk of introducing 
duplicates and allows for faster lookup.

What gain is in having them unsorted?

>>> +#define FLASH_SAFETY_TIMER		0x40

>>

>> Namespacing prefix is needed for macros, e.g. QCOM_FLASH*.

> 

> No need for that in .c files.


In general it eliminates the risk of name clash with other subsystems
headers.

And actually the prefix here should be QCOM_LED_FLASH to avoid ambiguity
with flash memory. If you dropped the vendor prefix then you'd get
possible name clash with led-class-flash.h namespace prefix.

-- 
Best regards,
Jacek Anaszewski
Pavel Machek April 25, 2021, 8:19 p.m. UTC | #11
Hi!

> >>>+#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/led-class-flash.h>
> >>>+#include <linux/regulator/consumer.h>
> >>>+#include <linux/delay.h>
> >>>+#include <linux/regmap.h>
> >>>+#include <dt-bindings/leds/leds-qcom-spmi-flash.h>
> >>
> >>Please sort includes alphabetically.
> >
> >No need to do that.
> 
> Keeping the includes sorted eliminates the risk of introducing duplicates
> and allows for faster lookup.
> 
> What gain is in having them unsorted?

It is not there is gain in them unsorted; it is that keeping sorted
order is not worth the effort.

> >>>+#define FLASH_SAFETY_TIMER		0x40
> >>
> >>Namespacing prefix is needed for macros, e.g. QCOM_FLASH*.
> >
> >No need for that in .c files.
> 
> In general it eliminates the risk of name clash with other subsystems
> headers.
> 
> And actually the prefix here should be QCOM_LED_FLASH to avoid ambiguity
> with flash memory. If you dropped the vendor prefix then you'd get
> possible name clash with led-class-flash.h namespace prefix.

I believe the cost (longer macro names) outweights the benefits here.

Best regards,
								Pavel