Re: [PATCH v3 2/2] leds: tps6131x: add support for Texas Instruments TPS6131X flash LED driver

Matthias Fend
2025-05-09T07:36:59Z

Hi Lee,

thank you for your answers and additional explanations.
Except for one point, I think I understand the rest and will amend it
accordingly.

Am 08.05.2025 um 16:31 schrieb Lee Jones:
> On Fri, 02 May 2025, Matthias Fend wrote:
>
>> Hi Lee,
>>
>> thank you for taking the time for this review.
>>
>> Am 01.05.2025 um 13:03 schrieb Lee Jones:
>>> On Wed, 23 Apr 2025, Matthias Fend wrote:
>>>
>>>> The TPS61310/TPS61311 is a flash LED driver with I2C interface. Its power
>>>> stage is capable of supplying a maximum total current of roughly 1500mA.
>>>> The TPS6131x provides three constant-current sinks, capable of sinking up
>>>> to 2 × 400mA (LED1 and LED3) and 800mA (LED2) in flash mode. In torch mode
>>>> each sink (LED1, LED2, LED3) supports currents up to 175mA.
>>>>
>>>> Signed-off-by: Matthias Fend <matthias.fend@emfend.at>
>>>> ---
>>>> MAINTAINERS | 7 +
>>>> drivers/leds/flash/Kconfig | 11 +
>>>> drivers/leds/flash/Makefile | 1 +
>>>> drivers/leds/flash/leds-tps6131x.c | 798 +++++++++++++++++++++++++++++++++++++
>>>> 4 files changed, 817 insertions(+)
>>>
>>> Looks pretty good in general. Just a few nits, really.
>>>
>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>> index fa1e04e87d1d63c1b01b59744d7ade00fe5a1885..67e7c165efd332dfe3bc5ec64a4cc10845d7bbd6 100644
>>>> --- a/MAINTAINERS
>>>> +++ b/MAINTAINERS
>>>> @@ -23983,6 +23983,13 @@ F: Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml
>>>> F: Documentation/hwmon/tps23861.rst
>>>> F: drivers/hwmon/tps23861.c
>>>> +TEXAS INSTRUMENTS TPS6131X FLASH LED DRIVER
>>>> +M: Matthias Fend <matthias.fend@emfend.at>
>>>> +L: linux-leds@vger.kernel.org
>>>> +S: Maintained
>>>> +F: Documentation/devicetree/bindings/leds/ti,tps6131x.yaml
>>>> +F: drivers/leds/flash/leds-tps6131x.c
>>>> +
>>>> TEXAS INSTRUMENTS' DAC7612 DAC DRIVER
>>>> M: Ricardo Ribalda <ribalda@kernel.org>
>>>> L: linux-iio@vger.kernel.org
>>>> diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
>>>> index f39f0bfe6eefcd376405d9d35dc36e323a485002..55ca663ca506ad8be627f58f6d6308368ea2b928 100644
>>>> --- a/drivers/leds/flash/Kconfig
>>>> +++ b/drivers/leds/flash/Kconfig
>>>> @@ -132,4 +132,15 @@ config LEDS_SY7802
>>>> This driver can be built as a module, it will be called "leds-sy7802".
>>>> +config LEDS_TPS6131X
>>>> + tristate "LED support for TI TPS6131x flash LED driver"
>>>> + depends on I2C && OF
>>>> + depends on GPIOLIB
>>>> + select REGMAP_I2C
>>>> + help
>>>> + This option enables support for Texas Instruments TPS61310/TPS61311
>>>> + flash LED driver.
>>>> +
>>>> + This driver can be built as a module, it will be called "leds-tps6131x".
>>>> +
>>>> endif # LEDS_CLASS_FLASH
>>>> diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile
>>>> index 48860eeced79513a0ed303e4af3db9bfe9724b7e..712fb737a428e42747e1aa339058dc4306ade9c8 100644
>>>> --- a/drivers/leds/flash/Makefile
>>>> +++ b/drivers/leds/flash/Makefile
>>>> @@ -12,3 +12,4 @@ obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
>>>> obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
>>>> obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o
>>>> obj-$(CONFIG_LEDS_SY7802) += leds-sy7802.o
>>>> +obj-$(CONFIG_LEDS_TPS6131X) += leds-tps6131x.o
>>>> diff --git a/drivers/leds/flash/leds-tps6131x.c b/drivers/leds/flash/leds-tps6131x.c
>>>> new file mode 100644
>>>> index 0000000000000000000000000000000000000000..c123fb3f908c865f8a9e60b0339067cdb5a864f6
>>>> --- /dev/null
>>>> +++ b/drivers/leds/flash/leds-tps6131x.c
>>>> @@ -0,0 +1,798 @@
>>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>>> +/*
>>>> + * Texas Instruments TPS61310/TPS61311 flash LED driver with I2C interface
>>>> + *
>>>> + * Copyright 2025 Matthias Fend <matthias.fend@emfend.at>
>>>> + */
>>>> +
>>>> +#include <linux/gpio/consumer.h>
>>>> +#include <linux/i2c.h>
>>>> +#include <linux/led-class-flash.h>
>>>> +#include <linux/leds.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/regmap.h>
>>>> +#include <media/v4l2-flash-led-class.h>
>>>> +
>>>> +/* Registers */
>>>
>>> This little header comments are superfluous IMHO.
>>>
>>> The nomenclature of the defines is good enough.
>>
>> Okay, I'll remove them.
>>
>>>
>>>> +#define TPS6131X_REG_0 0x00
>>>> +#define TPS6131X_REG_0_RESET BIT(7)
>>>
>>> Suggest adding a space or two before the bit defines, like:
>>>
>>> #define TPS6131X_REG_0 0x00
>>> #define TPS6131X_REG_0_RESET BIT(7)
>>
>> I actually like that too, but I couldn't convince clang-format to keep the
>> spaces. The automatic formatting was more valuable to me in the end. If you
>> know how to have both, I'd be very happy :)
>
> I wouldn't let the tooling push you around.
>
> IMHO that's a false positive and should be reported.
>
>>>> +#define TPS6131X_REG_0_DCLC13 GENMASK(5, 3)
>>>> +#define TPS6131X_REG_0_DCLC13_SHIFT 3
>>>> +#define TPS6131X_REG_0_DCLC2 GENMASK(2, 0)
>>>> +#define TPS6131X_REG_0_DCLC2_SHIFT 0
>>>> +
>>>> +#define TPS6131X_REG_1 0x01
>>>> +#define TPS6131X_REG_1_MODE GENMASK(7, 6)
>>>> +#define TPS6131X_REG_1_MODE_SHIFT 6
>>>> +#define TPS6131X_REG_1_FC2 GENMASK(5, 0)
>>>> +#define TPS6131X_REG_1_FC2_SHIFT 0
>>>> +
>>>> +#define TPS6131X_REG_2 0x02
>>>> +#define TPS6131X_REG_2_MODE GENMASK(7, 6)
>>>> +#define TPS6131X_REG_2_MODE_SHIFT 6
>>>> +#define TPS6131X_REG_2_ENVM BIT(5)
>>>> +#define TPS6131X_REG_2_FC13 GENMASK(4, 0)
>>>> +#define TPS6131X_REG_2_FC13_SHIFT 0
>>>> +
>>>> +#define TPS6131X_REG_3 0x03
>>>> +#define TPS6131X_REG_3_STIM GENMASK(7, 5)
>>>> +#define TPS6131X_REG_3_STIM_SHIFT 5
>>>> +#define TPS6131X_REG_3_HPFL BIT(4)
>>>> +#define TPS6131X_REG_3_SELSTIM_TO BIT(3)
>>>> +#define TPS6131X_REG_3_STT BIT(2)
>>>> +#define TPS6131X_REG_3_SFT BIT(1)
>>>> +#define TPS6131X_REG_3_TXMASK BIT(0)
>>>> +
>>>> +#define TPS6131X_REG_4 0x04
>>>> +#define TPS6131X_REG_4_PG BIT(7)
>>>> +#define TPS6131X_REG_4_HOTDIE_HI BIT(6)
>>>> +#define TPS6131X_REG_4_HOTDIE_LO BIT(5)
>>>> +#define TPS6131X_REG_4_ILIM BIT(4)
>>>> +#define TPS6131X_REG_4_INDC GENMASK(3, 0)
>>>> +#define TPS6131X_REG_4_INDC_SHIFT 0
>>>> +
>>>> +#define TPS6131X_REG_5 0x05
>>>> +#define TPS6131X_REG_5_SELFCAL BIT(7)
>>>> +#define TPS6131X_REG_5_ENPSM BIT(6)
>>>> +#define TPS6131X_REG_5_STSTRB1_DIR BIT(5)
>>>> +#define TPS6131X_REG_5_GPIO BIT(4)
>>>> +#define TPS6131X_REG_5_GPIOTYPE BIT(3)
>>>> +#define TPS6131X_REG_5_ENLED3 BIT(2)
>>>> +#define TPS6131X_REG_5_ENLED2 BIT(1)
>>>> +#define TPS6131X_REG_5_ENLED1 BIT(0)
>>>> +
>>>> +#define TPS6131X_REG_6 0x06
>>>> +#define TPS6131X_REG_6_ENTS BIT(7)
>>>> +#define TPS6131X_REG_6_LEDHOT BIT(6)
>>>> +#define TPS6131X_REG_6_LEDWARN BIT(5)
>>>> +#define TPS6131X_REG_6_LEDHDR BIT(4)
>>>> +#define TPS6131X_REG_6_OV GENMASK(3, 0)
>>>> +#define TPS6131X_REG_6_OV_SHIFT 0
>>>> +
>>>> +#define TPS6131X_REG_7 0x07
>>>> +#define TPS6131X_REG_7_ENBATMON BIT(7)
>>>> +#define TPS6131X_REG_7_BATDROOP GENMASK(6, 4)
>>>> +#define TPS6131X_REG_7_BATDROOP_SHIFT 4
>>>> +#define TPS6131X_REG_7_REVID GENMASK(2, 0)
>>>> +#define TPS6131X_REG_7_REVID_SHIFT 0
>>>> +
>>>> +/* Constants */
>>>> +
>>>> +#define TPS6131X_MAX_CHANNELS 3
>>>> +
>>>> +#define TPS6131X_FLASH_MAX_I_CHAN13_MA 400
>>>> +#define TPS6131X_FLASH_MAX_I_CHAN2_MA 800
>>>> +#define TPS6131X_FLASH_STEP_I_MA 25
>>>> +
>>>> +#define TPS6131X_TORCH_MAX_I_CHAN13_MA 175
>>>> +#define TPS6131X_TORCH_MAX_I_CHAN2_MA 175
>>>> +#define TPS6131X_TORCH_STEP_I_MA 25
>>>> +
>>>> +#define TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES msecs_to_jiffies(10000)
>>>
>>> Why 10s?
>>
>> At the point where the timer starts, there's a comment that says the
>> register must be updated within a timeout of 13 seconds.
>> So I thought 10 seconds would be a nice, appropriate interval.
>
> Maybe put this useful info in a small comment.
>
>>>
>>>> +#define UA_TO_MA(UA) ((UA) / 1000)
>>>
>>> I'm surprised a generic macro like this doesn't exist already.
>>>
>>>> +enum tps6131x_mode {
>>>> + TPS6131X_MODE_SHUTDOWN = 0x0,
>>>> + TPS6131X_MODE_TORCH = 0x1,
>>>> + TPS6131X_MODE_FLASH = 0x2,
>>>
>>> Are these set in stone or are they arbitrary?
>>
>> The values ​​are fixed because they are written directly to a register.
>> In V1, I used an enum without values ​​and mapped it to the register value in
>> a function. I was asked to omit this mapping and use the enum directly.
>>
>> TLDR:
>> [x] stone
>> [ ] arbitrary
>>
>>>
>>>> +};
>>>> +
>>>> +struct tps6131x {
>>>> + struct device *dev;
>>>> + struct regmap *regmap;
>>>> + struct gpio_desc *reset_gpio;
>>>> + /*
>>>> + * Registers 0, 1, 2, and 3 control parts of the controller that are not completely
>>>> + * independent of each other. Since some operations require the registers to be written in
>>>> + * a specific order to avoid unwanted side effects, they are synchronized with a lock.
>>>> + */
>>>> + struct mutex lock; /* Hardware access lock for register 0, 1, 2 and 3 */
>>>> + struct delayed_work torch_refresh_work;
>>>> + bool valley_current_limit;
>>>> + bool chan1_en;
>>>> + bool chan2_en;
>>>> + bool chan3_en;
>>>> + struct fwnode_handle *led_node;
>>>> + u32 max_flash_current_ma;
>>>> + u32 step_flash_current_ma;
>>>> + u32 max_torch_current_ma;
>>>> + u32 step_torch_current_ma;
>>>> + u32 max_timeout_us;
>>>> + struct led_classdev_flash fled_cdev;
>>>> + struct v4l2_flash *v4l2_flash;
>>>> +};
>>>> +
>>>> +static struct tps6131x *fled_cdev_to_tps6131x(struct led_classdev_flash *fled_cdev)
>>>> +{
>>>> + return container_of(fled_cdev, struct tps6131x, fled_cdev);
>>>> +}
>>>> +
>>>> +/*
>>>> + * Register contents after a power on/reset. These values cannot be changed.
>>>> + */
>>>> +static const struct reg_default tps6131x_regmap_defaults[] = {
>>>> + { TPS6131X_REG_0, (1 << TPS6131X_REG_0_DCLC13_SHIFT) | (2 << TPS6131X_REG_0_DCLC2_SHIFT) },
>>>
>>> Real values should be defined. What does 2 even do, etc?
>>
>> Initially, I only had the bare values ​​here, so that no one would mistakenly
>> think that something could be changed here.
>> This information only contains the information about the reset state of the
>> registers from the datasheet.
>> The contents are only needed for correct cache handling and have no meaning
>> for the driver itself.
>> If I now create a separate 'INITIAL' define for each value, which is only
>> used here, it would probably make it a bit harder to read, wouldn't it? What
>> do you think?
>
> I think that I like magic numbers to be defined. :)
>
>>>> + { TPS6131X_REG_1, (0 << TPS6131X_REG_1_MODE_SHIFT) | (16 << TPS6131X_REG_1_FC2_SHIFT) },
>>>
>>> What's the point in oring 0 values? Can't you just omit them?
>>
>> Of course, I could simply omit that. But the point was that I had to
>> describe which fields have which values.
>> If I omitted that, the information would somehow disappear.
>
> It's okay to leave things like this in for documentation purposes,
> however since '0' is not documented with a define, it's effectively
> meaningless. What is mode 0? If you define all of these magic numbers,
> you can use the 'documentation' ~excuse~ reason guilt-free. :)
>
>>>> + { TPS6131X_REG_2, (0 << TPS6131X_REG_2_MODE_SHIFT) | (8 << TPS6131X_REG_2_FC13_SHIFT) },
>>>> + { TPS6131X_REG_3, (6 << TPS6131X_REG_3_STIM_SHIFT) | TPS6131X_REG_3_TXMASK },
>>>> + { TPS6131X_REG_4, (0 << TPS6131X_REG_4_INDC_SHIFT) },
>>>> + { TPS6131X_REG_5, TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR |
>>>> + TPS6131X_REG_5_GPIOTYPE | TPS6131X_REG_5_ENLED2 },
>>>> + { TPS6131X_REG_6, (9 << TPS6131X_REG_6_OV_SHIFT) },
>>>> + { TPS6131X_REG_7, (4 << TPS6131X_REG_7_BATDROOP_SHIFT) },
>>>> +};
>>>> +
>>>> +/*
>>>> + * These registers contain flags that are reset when read. Ensure that these registers are not read
>>>> + * outside of a call from the driver.
>>>
>>> You can simplify to just the first sentence. The rest is implied.
>>
>> ACK.
>>
>>>
>>>> + */
>>>> +static bool tps6131x_regmap_precious(struct device *dev, unsigned int reg)
>>>> +{
>>>> + switch (reg) {
>>>> + case TPS6131X_REG_3:
>>>> + case TPS6131X_REG_4:
>>>> + case TPS6131X_REG_6:
>>>> + return true;
>>>> + default:
>>>> + return false;
>>>> + }
>>>> +}
>>>> +
>>>> +static const struct regmap_config tps6131x_regmap = {
>>>> + .reg_bits = 8,
>>>> + .val_bits = 8,
>>>> + .max_register = TPS6131X_REG_7,
>>>> + .reg_defaults = tps6131x_regmap_defaults,
>>>> + .num_reg_defaults = ARRAY_SIZE(tps6131x_regmap_defaults),
>>>> + .cache_type = REGCACHE_FLAT,
>>>
>>> This is new to me. Why was this chosen over the usual REGCACHE_MAPLE?
>>
>> Since the register space for this chip is very small, simple, compact, and
>> contiguous, I think this is the right cache type for it. Did I miss
>> something here?
>
> No, it's just unusual. It's probably correct (you decide).
>
>>>> + .precious_reg = &tps6131x_regmap_precious,
>>>> +};
>>>> +
>>>> +struct tps6131x_timer_config {
>>>> + u8 val;
>>>> + u8 range;
>>>> + u32 time_us;
>>>> +};
>>>> +
>>>> +static const struct tps6131x_timer_config tps6131x_timer_configs[] = {
>>>> + { .val = 0, .range = 1, .time_us = 5300 },
>>>> + { .val = 1, .range = 1, .time_us = 10700 },
>>>> + { .val = 2, .range = 1, .time_us = 16000 },
>>>> + { .val = 3, .range = 1, .time_us = 21300 },
>>>> + { .val = 4, .range = 1, .time_us = 26600 },
>>>> + { .val = 5, .range = 1, .time_us = 32000 },
>>>> + { .val = 6, .range = 1, .time_us = 37300 },
>>>> + { .val = 0, .range = 0, .time_us = 68200 },
>>>> + { .val = 7, .range = 1, .time_us = 71500 },
>>>> + { .val = 1, .range = 0, .time_us = 102200 },
>>>> + { .val = 2, .range = 0, .time_us = 136300 },
>>>> + { .val = 3, .range = 0, .time_us = 170400 },
>>>> + { .val = 4, .range = 0, .time_us = 204500 },
>>>> + { .val = 5, .range = 0, .time_us = 340800 },
>>>> + { .val = 6, .range = 0, .time_us = 579300 },
>>>> + { .val = 7, .range = 0, .time_us = 852000 },
>>>> +};
>>>> +
>>>> +static const struct tps6131x_timer_config *tps6131x_find_closest_timer_config(u32 timeout_us)
>>>> +{
>>>> + const struct tps6131x_timer_config *timer_config = &tps6131x_timer_configs[0];
>>>> + u32 diff, min_diff = U32_MAX;
>>>> + int i;
>>>> +
>>>> + for (i = 0; i < ARRAY_SIZE(tps6131x_timer_configs); i++) {
>>>> + diff = abs(tps6131x_timer_configs[i].time_us - timeout_us);
>>>> + if (diff < min_diff) {
>>>> + timer_config = &tps6131x_timer_configs[i];
>>>> + min_diff = diff;
>>>> + if (!min_diff)
>>>> + break;
>>>> + }
>>>> + }
>>>> +
>>>> + return timer_config;
>>>> +}
>>>> +
>>>> +static int tps6131x_reset_chip(struct tps6131x *tps6131x)
>>>> +{
>>>> + int ret;
>>>> +
>>>> + if (tps6131x->reset_gpio) {
>>>> + gpiod_set_value_cansleep(tps6131x->reset_gpio, 1);
>>>> + fsleep(10);
>>>> + gpiod_set_value_cansleep(tps6131x->reset_gpio, 0);
>>>> + fsleep(100);
>>>> + } else {
>>>> + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET,
>>>> + TPS6131X_REG_0_RESET);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + fsleep(100);
>>>> +
>>>> + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET, 0);
>>>> + if (ret)
>>>> + return ret;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int tps6131x_init_chip(struct tps6131x *tps6131x)
>>>> +{
>>>> + u32 reg4, reg5, reg6;
>>>
>>> Why cant we just reuse a single variable called 'value'?
>>
>> This way, it's easy to spot a mistake. Each line can only contain parts that
>> use the same 'reg<x>'.
>> But I can easily change it and use only one variable.
>
> Yes please. It's common practice.
>
>>>> + int ret;
>>>> +
>>>> + reg4 = tps6131x->valley_current_limit ? TPS6131X_REG_4_ILIM : 0;
>>>
>>> Nicer on the eye if we un-squidge these.
>>>
>>> No need to group everything like this.
>>
>> I'm happy to adapt the code here as well, but unfortunately I haven't quite
>> figured out how you'd like it to look. Could you please tell me more
>> specifically what I should change?
>
> Like this:
>
>>>> + ret = regmap_write(tps6131x->regmap, TPS6131X_REG_4, reg4);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + reg5 = TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR | TPS6131X_REG_5_GPIOTYPE;
>
>>>> + if (tps6131x->chan1_en)
>>>> + reg5 |= TPS6131X_REG_5_ENLED1;
>>>> +
>>>> + if (tps6131x->chan2_en)
>>>> + reg5 |= TPS6131X_REG_5_ENLED2;
>>>> +
>>>> + if (tps6131x->chan3_en)
>>>> + reg5 |= TPS6131X_REG_5_ENLED3;
>
>>>> + ret = regmap_write(tps6131x->regmap, TPS6131X_REG_5, reg5);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + reg6 = TPS6131X_REG_6_ENTS;
>
>>>> + ret = regmap_write(tps6131x->regmap, TPS6131X_REG_6, reg6);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int tps6131x_set_mode(struct tps6131x *tps6131x, enum tps6131x_mode mode, bool force)
>>>> +{
>>>> + u8 val;
>>>> +
>>>> + val = mode << TPS6131X_REG_1_MODE_SHIFT;
>>>
>>> You could do this during assignment. Or, seeing as there is already a
>>> line break required, in the call itself.
>>
>> ACK.
>>
>>>
>>>> +
>>>> + return regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_MODE, val,
>>>> + NULL, false, force);
>>>> +}
>>>> +
>>>> +static void tps6131x_torch_refresh_handler(struct work_struct *work)
>>>> +{
>>>> + struct tps6131x *tps6131x = container_of(work, struct tps6131x, torch_refresh_work.work);
>>>> +
>>>> + guard(mutex)(&tps6131x->lock);
>>>> +
>>>> + tps6131x_set_mode(tps6131x, TPS6131X_MODE_TORCH, true);
>>>
>>> This can fail.
>>
>> That's true. I wasn't sure how to respond to this, other than just keep
>> trying.
>> What do you think about not starting the timer in case of an error and
>> instead outputting a message with dev_err?
>
> Either check the value and/or provide a comment why checking is not required.
>
>>>> +
>>>> + schedule_delayed_work(&tps6131x->torch_refresh_work,
>>>> + TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES);
>>>> +}
>>>> +
>>>> +static int tps6131x_brightness_set(struct led_classdev *cdev, enum led_brightness brightness)
>>>> +{
>>>> + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev);
>>>> + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
>>>
>>>> + u32 num_chans;
>>>> + u32 steps_chan13, steps_chan2;
>>>> + u32 steps_remaining;
>>>
>>> Why not group these?
>>
>> I will do so.
>>
>>>
>>>> + u8 reg0;
>>>> + int ret;
>>>> +
>>>> + cancel_delayed_work_sync(&tps6131x->torch_refresh_work);
>>>> +
>>>> + /*
>>>> + * The brightness parameter uses the number of current steps as the unit (not the current
>>>> + * value itself). Since the reported step size can vary depending on the configuration,
>>>> + * this value must be converted into actual register steps.
>>>> + */
>>>> + steps_remaining = (brightness * tps6131x->step_torch_current_ma) / TPS6131X_TORCH_STEP_I_MA;
>>>> +
>>>> + num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en;
>>>> +
>>>> + /*
>>>> + * The currents are distributed as evenly as possible across the activated channels.
>>>> + * Since channels 1 and 3 share the same register setting, they always use the same current
>>>> + * value. Channel 2 supports higher currents and thus takes over the remaining additional
>>>> + * portion that cannot be covered by the other channels.
>>>> + */
>>>> + steps_chan13 = min_t(u32, steps_remaining / num_chans,
>>>> + TPS6131X_TORCH_MAX_I_CHAN13_MA / TPS6131X_TORCH_STEP_I_MA);
>>>> + if (tps6131x->chan1_en)
>>>> + steps_remaining -= steps_chan13;
>>>> + if (tps6131x->chan3_en)
>>>> + steps_remaining -= steps_chan13;
>>>> +
>>>> + steps_chan2 = min_t(u32, steps_remaining,
>>>> + TPS6131X_TORCH_MAX_I_CHAN2_MA / TPS6131X_TORCH_STEP_I_MA);
>>>> +
>>>> + guard(mutex)(&tps6131x->lock);
>>>> +
>>>> + reg0 = (steps_chan13 << TPS6131X_REG_0_DCLC13_SHIFT) |
>>>> + (steps_chan2 << TPS6131X_REG_0_DCLC2_SHIFT);
>>>
>>> Indent.
>>
>> The indent was intention ;) The operands of arithmetic assignments are at
>> least intentionally and automatically aligned to be on the same column. This
>> is also the case elsewhere in the code.
>> What is the alternative expectation here?
>
> I'd normally expect line feeds to be indented.
>
> This is an odd one because nothing is in front of the open parenthesis.
>
> I'm struggling to find any other examples like it - perhaps it is correct?!
>
> [..]
>
>>>> +static int tps6131x_v4l2_setup(struct tps6131x *tps6131x)
>>>> +{
>>>> + struct v4l2_flash_config v4l2_cfg = { 0 };
>>>> + struct led_flash_setting *intensity = &v4l2_cfg.intensity;
>>>> +
>>>> + intensity->min = tps6131x->step_torch_current_ma;
>>>> + intensity->max = tps6131x->max_torch_current_ma;
>>>> + intensity->step = tps6131x->step_torch_current_ma;
>>>> + intensity->val = intensity->min;
>>>> +
>>>> + strscpy(v4l2_cfg.dev_name, tps6131x->fled_cdev.led_cdev.dev->kobj.name,
>>>> + sizeof(v4l2_cfg.dev_name));
>>>> +
>>>> + v4l2_cfg.has_external_strobe = true;
>>>> + v4l2_cfg.flash_faults = LED_FAULT_TIMEOUT | LED_FAULT_OVER_TEMPERATURE |
>>>> + LED_FAULT_SHORT_CIRCUIT | LED_FAULT_UNDER_VOLTAGE |
>>>> + LED_FAULT_LED_OVER_TEMPERATURE;
>>>> +
>>>> + tps6131x->v4l2_flash = v4l2_flash_init(tps6131x->dev, tps6131x->led_node,
>>>> + &tps6131x->fled_cdev, &tps6131x_v4l2_flash_ops,
>>>> + &v4l2_cfg);
>>>> + if (IS_ERR(tps6131x->v4l2_flash)) {
>>>> + dev_err(tps6131x->dev, "Failed to initialize v4l2 flash LED\n");
>>>> + return PTR_ERR(tps6131x->v4l2_flash);
>>>> + }
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int tps6131x_probe(struct i2c_client *client)
>>>> +{
>>>> + struct tps6131x *tps6131x;
>>>> + int ret;
>>>> +
>>>> + tps6131x = devm_kzalloc(&client->dev, sizeof(*tps6131x), GFP_KERNEL);
>>>> + if (!tps6131x)
>>>> + return -ENOMEM;
>>>> +
>>>> + tps6131x->dev = &client->dev;
>>>> + i2c_set_clientdata(client, tps6131x);
>>>
>>> If you already have client, to fetch this, you'll already have access to dev.
>>
>> I understand that in principle. However, I'm still not entirely sure what
>> exactly I should change. Could you please provide me with some further
>> guidance?
>
> Yes, don't store 'dev' in 'tps6131x'.

Ah, I see. Yes, the functions currently using 'dev' are all called from
the probe path, so I could just pass 'dev' as a separate argument and
remove it from 'tps6131x'.
But since I now also output a message with dev_err in
tps6131x_torch_refresh_handler() in case of an error, I need
'tps6131x->dev' there. I haven't thought of any other way to get 'dev' here.

In this context, is it okay for you if 'dev' remains a member of 'tps6131x'?

Thanks
~Matthias

>
>>>> + mutex_init(&tps6131x->lock);
>>>> + INIT_DELAYED_WORK(&tps6131x->torch_refresh_work, tps6131x_torch_refresh_handler);
>>>> +
>>>> + ret = tps6131x_parse_node(tps6131x);
>>>> + if (ret)
>>>> + return -ENODEV;
>>>
>>> Why aren't we propagating the real error?
>>
>> Good point. I'll change that.
>>
>>>
>>>> + tps6131x->regmap = devm_regmap_init_i2c(client, &tps6131x_regmap);
>>>> + if (IS_ERR(tps6131x->regmap)) {
>>>> + ret = PTR_ERR(tps6131x->regmap);
>>>
>>> Use dev_err_ptr_probe() instead.
>>
>> Hmm, I don't quite understand that. dev_err_ptr_probe() returns an error
>> pointer instead of an int, right?
>
> Yes, disregard - I had a senior moment!
>