* Re: [PATCH v5 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device
[not found] ` <20260424-s2mu005-pmic-v5-7-fcbc9da5a004@disroot.org>
@ 2026-05-07 16:46 ` Lee Jones
2026-05-07 18:33 ` Kaustabh Chakraborty
2026-05-07 19:39 ` Jacek Anaszewski
0 siblings, 2 replies; 6+ messages in thread
From: Lee Jones @ 2026-05-07 16:46 UTC (permalink / raw)
To: Kaustabh Chakraborty
Cc: Pavel Machek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński, linux-leds, devicetree,
linux-kernel, linux-pm, linux-samsung-soc, linux-rtc, linux-doc
On Fri, 24 Apr 2026, Kaustabh Chakraborty wrote:
> Add support for flash LEDs in certain Samsung S2M series PMICs.
> The device has two channels for LEDs, typically for the back and front
> cameras in mobile devices. Both channels can be independently
> controlled, and can be operated in torch or flash modes.
>
> The driver includes initial support for the S2MU005 PMIC flash LEDs.
>
> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
> ---
> drivers/leds/flash/Kconfig | 12 ++
> drivers/leds/flash/Makefile | 1 +
> drivers/leds/flash/leds-s2m-flash.c | 358 ++++++++++++++++++++++++++++++++++++
> 3 files changed, 371 insertions(+)
>
> diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
> index 5e08102a67841..be62e05277429 100644
> --- a/drivers/leds/flash/Kconfig
> +++ b/drivers/leds/flash/Kconfig
> @@ -114,6 +114,18 @@ config LEDS_RT8515
> To compile this driver as a module, choose M here: the module
> will be called leds-rt8515.
>
> +config LEDS_S2M_FLASH
> + tristate "Samsung S2M series PMICs flash/torch LED support"
> + depends on LEDS_CLASS
> + depends on MFD_SEC_CORE
> + depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
The `|| !V4L2_FLASH_LED_CLASS` part of this dependency makes it
unconditionally true. Was this intended? Perhaps this dependency can be
removed entirely.
> + select REGMAP_IRQ
> + help
> + This option enables support for the flash/torch LEDs found in
> + certain Samsung S2M series PMICs, such as the S2MU005. It has
> + a LED channel dedicated for every physical LED. The LEDs can
> + be controlled in flash and torch modes.
> +
> config LEDS_SGM3140
> tristate "LED support for the SGM3140"
> depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
> diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile
> index 712fb737a428e..44e6c1b4beb37 100644
> --- a/drivers/leds/flash/Makefile
> +++ b/drivers/leds/flash/Makefile
> @@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
> obj-$(CONFIG_LEDS_QCOM_FLASH) += leds-qcom-flash.o
> obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
> obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
> +obj-$(CONFIG_LEDS_S2M_FLASH) += leds-s2m-flash.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-s2m-flash.c b/drivers/leds/flash/leds-s2m-flash.c
> new file mode 100644
> index 0000000000000..177d23b432ce6
> --- /dev/null
> +++ b/drivers/leds/flash/leds-s2m-flash.c
> @@ -0,0 +1,358 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Flash and Torch LED Driver for Samsung S2M series PMICs.
> + *
> + * Copyright (c) 2015 Samsung Electronics Co., Ltd
> + * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org>
> + */
> +
> +#include <linux/container_of.h>
> +#include <linux/led-class-flash.h>
> +#include <linux/mfd/samsung/core.h>
> +#include <linux/mfd/samsung/s2mu005.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <media/v4l2-flash-led-class.h>
> +
> +#define MAX_CHANNELS 2
> +
> +struct s2m_led {
> + struct regmap *regmap;
> + struct led_classdev_flash fled;
> + struct v4l2_flash *v4l2_flash;
> + /*
> + * The mutex object prevents the concurrent access of flash control
> + * registers by the LED and V4L2 subsystems.
> + */
> + struct mutex lock;
> + unsigned int reg_enable;
> + u8 channel;
> + u8 flash_brightness;
> + u8 flash_timeout;
> +};
> +
> +static struct s2m_led *to_s2m_led(struct led_classdev_flash *fled)
> +{
> + return container_of(fled, struct s2m_led, fled);
> +}
> +
> +static struct led_classdev_flash *to_s2m_fled(struct led_classdev *cdev)
> +{
> + return container_of(cdev, struct led_classdev_flash, led_cdev);
> +}
> +
> +static int s2m_fled_flash_brightness_set(struct led_classdev_flash *fled, u32 brightness)
> +{
> + struct s2m_led *led = to_s2m_led(fled);
> + struct led_flash_setting *setting = &fled->brightness;
> +
> + led->flash_brightness = (brightness - setting->min) / setting->step;
> +
> + return 0;
> +}
> +
> +static int s2m_fled_flash_timeout_set(struct led_classdev_flash *fled, u32 timeout)
> +{
> + struct s2m_led *led = to_s2m_led(fled);
> + struct led_flash_setting *setting = &fled->timeout;
> +
> + led->flash_timeout = (timeout - setting->min) / setting->step;
> +
> + return 0;
> +}
> +
> +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
> +static int s2m_fled_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable)
> +{
> + struct s2m_led *led = to_led(v4l2_flash->fled_cdev);
What is to_led()?
Was this tested?
> + mutex_lock(&led->lock);
> +
> + led->fled.ops->strobe_set(&led->fled, enable);
> +
> + mutex_unlock(&led->lock);
> +
> + return 0;
> +}
> +
> +static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops = {
> + .external_strobe_set = s2m_fled_flash_external_strobe_set,
> +};
> +#else
> +static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops;
> +#endif
> +
> +static void s2m_fled_v4l2_flash_release(void *v4l2_flash)
> +{
> + v4l2_flash_release(v4l2_flash);
> +}
> +
> +static int s2mu005_fled_torch_brightness_set(struct led_classdev *cdev, enum led_brightness value)
> +{
> + struct s2m_led *led = to_s2m_led(to_s2m_fled(cdev));
> + int ret;
> +
> + mutex_lock(&led->lock);
> +
> + if (!value) {
> + ret = regmap_clear_bits(led->regmap, led->reg_enable,
> + S2MU005_FLED_TORCH_EN(led->channel));
> + if (ret < 0)
> + dev_err(cdev->dev, "failed to disable torch LED\n");
> + goto unlock;
> + }
> +
> + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL1(led->channel),
> + S2MU005_FLED_TORCH_IOUT,
> + FIELD_PREP(S2MU005_FLED_TORCH_IOUT, value - 1));
> + if (ret < 0) {
Is a positive number even possible?
> + dev_err(cdev->dev, "failed to set torch current\n");
> + goto unlock;
> + }
> +
> + ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_TORCH_EN(led->channel));
> + if (ret < 0) {
> + dev_err(cdev->dev, "failed to enable torch LED\n");
> + goto unlock;
> + }
> +
> +unlock:
> + mutex_unlock(&led->lock);
> +
> + return ret;
> +}
> +
> +static int s2mu005_fled_flash_strobe_set(struct led_classdev_flash *fled, bool state)
> +{
> + struct s2m_led *led = to_s2m_led(fled);
> + int ret;
> +
> + mutex_lock(&led->lock);
> +
> + ret = regmap_clear_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel));
> + if (ret < 0) {
> + dev_err(fled->led_cdev.dev, "failed to disable flash LED\n");
> + goto unlock;
> + }
> +
> + if (!state)
> + goto unlock;
> +
> + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL0(led->channel),
> + S2MU005_FLED_FLASH_IOUT,
> + FIELD_PREP(S2MU005_FLED_FLASH_IOUT, led->flash_brightness));
> + if (ret < 0) {
> + dev_err(fled->led_cdev.dev, "failed to set flash brightness\n");
> + goto unlock;
> + }
> +
> + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL3(led->channel),
> + S2MU005_FLED_FLASH_TIMEOUT,
> + FIELD_PREP(S2MU005_FLED_FLASH_TIMEOUT, led->flash_timeout));
> + if (ret < 0) {
> + dev_err(fled->led_cdev.dev, "failed to set flash timeout\n");
> + goto unlock;
> + }
> +
> + ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel));
> + if (ret < 0) {
> + dev_err(fled->led_cdev.dev, "failed to enable flash LED\n");
> + goto unlock;
> + }
> +
> +unlock:
> + mutex_unlock(&led->lock);
> +
> + return 0;
It seems like this function swallows error codes.
Better if they're propagated properly.
> +}
> +
> +static int s2mu005_fled_flash_strobe_get(struct led_classdev_flash *fled, bool *state)
> +{
> + struct s2m_led *led = to_s2m_led(fled);
> + u32 val;
> + int ret;
> +
> + mutex_lock(&led->lock);
> +
> + ret = regmap_read(led->regmap, S2MU005_REG_FLED_STATUS, &val);
> + if (ret < 0) {
> + dev_err(fled->led_cdev.dev, "failed to fetch LED status");
Missed '/n'.
> + goto unlock;
> + }
> +
> + *state = !!(val & S2MU005_FLED_FLASH_STATUS(led->channel));
> +
> +unlock:
> + mutex_unlock(&led->lock);
> +
> + return ret;
> +}
> +
> +static const struct led_flash_ops s2mu005_fled_flash_ops = {
> + .flash_brightness_set = s2m_fled_flash_brightness_set,
> + .timeout_set = s2m_fled_flash_timeout_set,
> + .strobe_set = s2mu005_fled_flash_strobe_set,
> + .strobe_get = s2mu005_fled_flash_strobe_get,
> +};
> +
> +static int s2mu005_fled_init(struct s2m_led *led, struct device *dev, struct regmap *regmap,
> + unsigned int nr_channels)
> +{
> + unsigned int val;
> + int i, ret;
> +
> + /* Enable the LED channels. */
> + ret = regmap_set_bits(regmap, S2MU005_REG_FLED_CTRL1, S2MU005_FLED_CH_EN);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to enable LED channels\n");
> +
> + ret = regmap_read(regmap, S2MU005_REG_ID, &val);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to read revision\n");
> +
> + for (i = 0; i < nr_channels; i++) {
for (int i = 0; i < nr_channels; i++)
> + /*
> + * Read the revision register. Revision EVT0 has the register
> + * at CTRL4, while EVT1 and higher have it at CTRL6.
> + */
> + if (FIELD_GET(S2MU005_ID_MASK, val) == 0)
Why not remove the " == 0" and reverse the branches?
> + led[i].reg_enable = S2MU005_REG_FLED_CTRL4;
> + else
> + led[i].reg_enable = S2MU005_REG_FLED_CTRL6;
> + }
> +
> + return 0;
> +}
> +
> +static int s2mu005_fled_init_channel(struct s2m_led *led, struct device *dev,
> + struct fwnode_handle *fwnp)
> +{
> + struct led_classdev *cdev = &led->fled.led_cdev;
> + struct led_init_data init_data = {};
> + struct v4l2_flash_config v4l2_cfg = {};
> + int ret;
> +
> + cdev->max_brightness = 16;
> + cdev->brightness_set_blocking = s2mu005_fled_torch_brightness_set;
> + cdev->flags |= LED_DEV_CAP_FLASH;
> +
> + led->fled.timeout.min = 62000;
> + led->fled.timeout.step = 62000;
> + led->fled.timeout.max = 992000;
> + led->fled.timeout.val = 992000;
> +
> + led->fled.brightness.min = 25000;
> + led->fled.brightness.step = 25000;
> + led->fled.brightness.max = 375000; /* 400000 causes flickering */
> + led->fled.brightness.val = 375000;
> +
> + s2m_fled_flash_timeout_set(&led->fled, led->fled.timeout.val);
> + s2m_fled_flash_brightness_set(&led->fled, led->fled.brightness.val);
> +
> + led->fled.ops = &s2mu005_fled_flash_ops;
> +
> + init_data.fwnode = fwnp;
> + ret = devm_led_classdev_flash_register_ext(dev, &led->fled, &init_data);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to create LED flash device\n");
> +
> + v4l2_cfg.intensity.min = led->fled.timeout.min;
> + v4l2_cfg.intensity.step = led->fled.timeout.step;
> + v4l2_cfg.intensity.max = led->fled.timeout.max;
> + v4l2_cfg.intensity.val = led->fled.timeout.val;
Is it correct to configure the V4L2 intensity settings from the timeout
values? I would expect these to be based on the brightness settings.
> +
> + v4l2_cfg.has_external_strobe = true;
> +
> + led->v4l2_flash = v4l2_flash_init(dev, fwnp, &led->fled, &s2m_fled_v4l2_flash_ops,
> + &v4l2_cfg);
> + if (IS_ERR(led->v4l2_flash)) {
> + v4l2_flash_release(led->v4l2_flash);
So you're going to try and release an error?
> + return dev_err_probe(dev, PTR_ERR(led->v4l2_flash),
> + "failed to create V4L2 flash device\n");
> + }
> +
> + ret = devm_add_action_or_reset(dev, (void *)s2m_fled_v4l2_flash_release, led->v4l2_flash);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to add cleanup action\n");
> +
> + return 0;
> +}
> +
> +static int s2m_fled_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct sec_pmic_dev *ddata = dev_get_drvdata(dev->parent);
> + struct s2m_led *led;
> + int ret;
> +
> + led = devm_kzalloc(dev, sizeof(*led) * MAX_CHANNELS, GFP_KERNEL);
> + if (!led)
> + return -ENOMEM;
> +
> + switch (platform_get_device_id(pdev)->driver_data) {
> + case S2MU005:
> + ret = s2mu005_fled_init(led, dev, ddata->regmap_pmic, MAX_CHANNELS);
> + if (ret)
> + return ret;
> + break;
> + default:
> + return dev_err_probe(dev, -ENODEV, "device type %d is not supported by driver\n",
> + ddata->device_type);
> + }
Will this be expanded in the very near future?
If not, having a switch () with only one entry seems odd.
if (platform_get_device_id(pdev)->driver_data != S2MU005)
dev_err_probe()
> + device_for_each_child_node_scoped(dev, child) {
> + u32 reg;
> +
> + if (fwnode_property_read_u32(child, "reg", ®))
> + continue;
> +
> + if (led[reg].regmap) {
> + dev_warn(dev, "duplicate node for channel %d\n", reg);
> + continue;
> + }
If reg > MAX_CHANNELS, you just created an OOB condition.
> +
> + led[reg].regmap = ddata->regmap_pmic;
> + led[reg].channel = (u8)reg;
> +
> + ret = devm_mutex_init(dev, &led[reg].lock);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to create mutex\n");
> +
> + switch (platform_get_device_id(pdev)->driver_data) {
> + case S2MU005:
> + ret = s2mu005_fled_init_channel(led + reg, dev, child);
> + if (ret < 0)
> + return ret;
> + break;
> + }
This is even more odd!
> + }
> +
> + return 0;
> +}
> +
> +static const struct platform_device_id s2m_fled_id_table[] = {
> + { "s2mu005-flash", S2MU005 },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(platform, s2m_fled_id_table);
> +
> +static const struct of_device_id s2m_fled_of_match_table[] = {
> + { .compatible = "samsung,s2mu005-flash", .data = (void *)S2MU005 },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, s2m_fled_of_match_table);
> +
> +static struct platform_driver s2m_fled_driver = {
> + .driver = {
> + .name = "s2m-flash",
> + },
> + .probe = s2m_fled_probe,
> + .id_table = s2m_fled_id_table,
> +};
> +module_platform_driver(s2m_fled_driver);
> +
> +MODULE_DESCRIPTION("Flash/Torch LED Driver For Samsung S2M Series PMICs");
> +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
> +MODULE_LICENSE("GPL");
--
Lee Jones
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v5 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device
2026-05-07 16:46 ` [PATCH v5 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device Lee Jones
@ 2026-05-07 18:33 ` Kaustabh Chakraborty
2026-05-07 19:39 ` Jacek Anaszewski
1 sibling, 0 replies; 6+ messages in thread
From: Kaustabh Chakraborty @ 2026-05-07 18:33 UTC (permalink / raw)
To: Lee Jones, Kaustabh Chakraborty
Cc: Pavel Machek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński, linux-leds, devicetree,
linux-kernel, linux-pm, linux-samsung-soc, linux-rtc, linux-doc
On 2026-05-07 17:46 +01:00, Lee Jones wrote:
> On Fri, 24 Apr 2026, Kaustabh Chakraborty wrote:
>
>> Add support for flash LEDs in certain Samsung S2M series PMICs.
>> The device has two channels for LEDs, typically for the back and front
>> cameras in mobile devices. Both channels can be independently
>> controlled, and can be operated in torch or flash modes.
>>
>> The driver includes initial support for the S2MU005 PMIC flash LEDs.
>>
>> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
>> ---
>> drivers/leds/flash/Kconfig | 12 ++
>> drivers/leds/flash/Makefile | 1 +
>> drivers/leds/flash/leds-s2m-flash.c | 358 ++++++++++++++++++++++++++++++++++++
>> 3 files changed, 371 insertions(+)
>>
>> diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
>> index 5e08102a67841..be62e05277429 100644
>> --- a/drivers/leds/flash/Kconfig
>> +++ b/drivers/leds/flash/Kconfig
>> @@ -114,6 +114,18 @@ config LEDS_RT8515
>> To compile this driver as a module, choose M here: the module
>> will be called leds-rt8515.
>>
>> +config LEDS_S2M_FLASH
>> + tristate "Samsung S2M series PMICs flash/torch LED support"
>> + depends on LEDS_CLASS
>> + depends on MFD_SEC_CORE
>> + depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
>
> The `|| !V4L2_FLASH_LED_CLASS` part of this dependency makes it
> unconditionally true. Was this intended? Perhaps this dependency can be
> removed entirely.
Right? Similar lines are also present in entries of other drivers too.
It is indeed weird, but I disregarded my doubts and added it anyway.
>> + select REGMAP_IRQ
>> + help
>> + This option enables support for the flash/torch LEDs found in
>> + certain Samsung S2M series PMICs, such as the S2MU005. It has
>> + a LED channel dedicated for every physical LED. The LEDs can
>> + be controlled in flash and torch modes.
>> +
>> config LEDS_SGM3140
>> tristate "LED support for the SGM3140"
>> depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
>> diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile
>> index 712fb737a428e..44e6c1b4beb37 100644
>> --- a/drivers/leds/flash/Makefile
>> +++ b/drivers/leds/flash/Makefile
>> @@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
>> obj-$(CONFIG_LEDS_QCOM_FLASH) += leds-qcom-flash.o
>> obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
>> obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
>> +obj-$(CONFIG_LEDS_S2M_FLASH) += leds-s2m-flash.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-s2m-flash.c b/drivers/leds/flash/leds-s2m-flash.c
>> new file mode 100644
>> index 0000000000000..177d23b432ce6
>> --- /dev/null
>> +++ b/drivers/leds/flash/leds-s2m-flash.c
>> @@ -0,0 +1,358 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Flash and Torch LED Driver for Samsung S2M series PMICs.
>> + *
>> + * Copyright (c) 2015 Samsung Electronics Co., Ltd
>> + * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org>
>> + */
>> +
>> +#include <linux/container_of.h>
>> +#include <linux/led-class-flash.h>
>> +#include <linux/mfd/samsung/core.h>
>> +#include <linux/mfd/samsung/s2mu005.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regmap.h>
>> +#include <media/v4l2-flash-led-class.h>
>> +
>> +#define MAX_CHANNELS 2
>> +
>> +struct s2m_led {
>> + struct regmap *regmap;
>> + struct led_classdev_flash fled;
>> + struct v4l2_flash *v4l2_flash;
>> + /*
>> + * The mutex object prevents the concurrent access of flash control
>> + * registers by the LED and V4L2 subsystems.
>> + */
>> + struct mutex lock;
>> + unsigned int reg_enable;
>> + u8 channel;
>> + u8 flash_brightness;
>> + u8 flash_timeout;
>> +};
>> +
>> +static struct s2m_led *to_s2m_led(struct led_classdev_flash *fled)
>> +{
>> + return container_of(fled, struct s2m_led, fled);
>> +}
>> +
>> +static struct led_classdev_flash *to_s2m_fled(struct led_classdev *cdev)
>> +{
>> + return container_of(cdev, struct led_classdev_flash, led_cdev);
>> +}
>> +
>> +static int s2m_fled_flash_brightness_set(struct led_classdev_flash *fled, u32 brightness)
>> +{
>> + struct s2m_led *led = to_s2m_led(fled);
>> + struct led_flash_setting *setting = &fled->brightness;
>> +
>> + led->flash_brightness = (brightness - setting->min) / setting->step;
>> +
>> + return 0;
>> +}
>> +
>> +static int s2m_fled_flash_timeout_set(struct led_classdev_flash *fled, u32 timeout)
>> +{
>> + struct s2m_led *led = to_s2m_led(fled);
>> + struct led_flash_setting *setting = &fled->timeout;
>> +
>> + led->flash_timeout = (timeout - setting->min) / setting->step;
>> +
>> + return 0;
>> +}
>> +
>> +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
>> +static int s2m_fled_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable)
>> +{
>> + struct s2m_led *led = to_led(v4l2_flash->fled_cdev);
>
> What is to_led()?
>
> Was this tested?
I honestly don't know what happened here, and yes this (well, not this
precisely) was tested. I had changed something later? Don't know, its
odd. Will fix.
>> + mutex_lock(&led->lock);
>> +
>> + led->fled.ops->strobe_set(&led->fled, enable);
>> +
>> + mutex_unlock(&led->lock);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops = {
>> + .external_strobe_set = s2m_fled_flash_external_strobe_set,
>> +};
>> +#else
>> +static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops;
>> +#endif
>> +
>> +static void s2m_fled_v4l2_flash_release(void *v4l2_flash)
>> +{
>> + v4l2_flash_release(v4l2_flash);
>> +}
>> +
>> +static int s2mu005_fled_torch_brightness_set(struct led_classdev *cdev, enum led_brightness value)
>> +{
>> + struct s2m_led *led = to_s2m_led(to_s2m_fled(cdev));
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + if (!value) {
>> + ret = regmap_clear_bits(led->regmap, led->reg_enable,
>> + S2MU005_FLED_TORCH_EN(led->channel));
>> + if (ret < 0)
>> + dev_err(cdev->dev, "failed to disable torch LED\n");
>> + goto unlock;
>> + }
>> +
>> + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL1(led->channel),
>> + S2MU005_FLED_TORCH_IOUT,
>> + FIELD_PREP(S2MU005_FLED_TORCH_IOUT, value - 1));
>> + if (ret < 0) {
>
> Is a positive number even possible?
As per the docs, no. Will fix here and in other instances as well.
>> + dev_err(cdev->dev, "failed to set torch current\n");
>> + goto unlock;
>> + }
>> +
>> + ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_TORCH_EN(led->channel));
>> + if (ret < 0) {
>> + dev_err(cdev->dev, "failed to enable torch LED\n");
>> + goto unlock;
>> + }
>> +
>> +unlock:
>> + mutex_unlock(&led->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int s2mu005_fled_flash_strobe_set(struct led_classdev_flash *fled, bool state)
>> +{
>> + struct s2m_led *led = to_s2m_led(fled);
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + ret = regmap_clear_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel));
>> + if (ret < 0) {
>> + dev_err(fled->led_cdev.dev, "failed to disable flash LED\n");
>> + goto unlock;
>> + }
>> +
>> + if (!state)
>> + goto unlock;
>> +
>> + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL0(led->channel),
>> + S2MU005_FLED_FLASH_IOUT,
>> + FIELD_PREP(S2MU005_FLED_FLASH_IOUT, led->flash_brightness));
>> + if (ret < 0) {
>> + dev_err(fled->led_cdev.dev, "failed to set flash brightness\n");
>> + goto unlock;
>> + }
>> +
>> + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL3(led->channel),
>> + S2MU005_FLED_FLASH_TIMEOUT,
>> + FIELD_PREP(S2MU005_FLED_FLASH_TIMEOUT, led->flash_timeout));
>> + if (ret < 0) {
>> + dev_err(fled->led_cdev.dev, "failed to set flash timeout\n");
>> + goto unlock;
>> + }
>> +
>> + ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel));
>> + if (ret < 0) {
>> + dev_err(fled->led_cdev.dev, "failed to enable flash LED\n");
>> + goto unlock;
>> + }
>> +
>> +unlock:
>> + mutex_unlock(&led->lock);
>> +
>> + return 0;
>
> It seems like this function swallows error codes.
>
> Better if they're propagated properly.
>
>> +}
>> +
>> +static int s2mu005_fled_flash_strobe_get(struct led_classdev_flash *fled, bool *state)
>> +{
>> + struct s2m_led *led = to_s2m_led(fled);
>> + u32 val;
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + ret = regmap_read(led->regmap, S2MU005_REG_FLED_STATUS, &val);
>> + if (ret < 0) {
>> + dev_err(fled->led_cdev.dev, "failed to fetch LED status");
>
> Missed '/n'.
>
>> + goto unlock;
>> + }
>> +
>> + *state = !!(val & S2MU005_FLED_FLASH_STATUS(led->channel));
>> +
>> +unlock:
>> + mutex_unlock(&led->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct led_flash_ops s2mu005_fled_flash_ops = {
>> + .flash_brightness_set = s2m_fled_flash_brightness_set,
>> + .timeout_set = s2m_fled_flash_timeout_set,
>> + .strobe_set = s2mu005_fled_flash_strobe_set,
>> + .strobe_get = s2mu005_fled_flash_strobe_get,
>> +};
>> +
>> +static int s2mu005_fled_init(struct s2m_led *led, struct device *dev, struct regmap *regmap,
>> + unsigned int nr_channels)
>> +{
>> + unsigned int val;
>> + int i, ret;
>> +
>> + /* Enable the LED channels. */
>> + ret = regmap_set_bits(regmap, S2MU005_REG_FLED_CTRL1, S2MU005_FLED_CH_EN);
>> + if (ret < 0)
>> + return dev_err_probe(dev, ret, "failed to enable LED channels\n");
>> +
>> + ret = regmap_read(regmap, S2MU005_REG_ID, &val);
>> + if (ret < 0)
>> + return dev_err_probe(dev, ret, "failed to read revision\n");
>> +
>> + for (i = 0; i < nr_channels; i++) {
>
> for (int i = 0; i < nr_channels; i++)
Is that allowed in the kernel source? I have never seen variable
declaration in a for loop.
>> + /*
>> + * Read the revision register. Revision EVT0 has the register
>> + * at CTRL4, while EVT1 and higher have it at CTRL6.
>> + */
>> + if (FIELD_GET(S2MU005_ID_MASK, val) == 0)
>
> Why not remove the " == 0" and reverse the branches?
My intention was to make it explicit that the value used is an integer
value, as opposed to a boolean.
>> + led[i].reg_enable = S2MU005_REG_FLED_CTRL4;
>> + else
>> + led[i].reg_enable = S2MU005_REG_FLED_CTRL6;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int s2mu005_fled_init_channel(struct s2m_led *led, struct device *dev,
>> + struct fwnode_handle *fwnp)
>> +{
>> + struct led_classdev *cdev = &led->fled.led_cdev;
>> + struct led_init_data init_data = {};
>> + struct v4l2_flash_config v4l2_cfg = {};
>> + int ret;
>> +
>> + cdev->max_brightness = 16;
>> + cdev->brightness_set_blocking = s2mu005_fled_torch_brightness_set;
>> + cdev->flags |= LED_DEV_CAP_FLASH;
>> +
>> + led->fled.timeout.min = 62000;
>> + led->fled.timeout.step = 62000;
>> + led->fled.timeout.max = 992000;
>> + led->fled.timeout.val = 992000;
>> +
>> + led->fled.brightness.min = 25000;
>> + led->fled.brightness.step = 25000;
>> + led->fled.brightness.max = 375000; /* 400000 causes flickering */
>> + led->fled.brightness.val = 375000;
>> +
>> + s2m_fled_flash_timeout_set(&led->fled, led->fled.timeout.val);
>> + s2m_fled_flash_brightness_set(&led->fled, led->fled.brightness.val);
>> +
>> + led->fled.ops = &s2mu005_fled_flash_ops;
>> +
>> + init_data.fwnode = fwnp;
>> + ret = devm_led_classdev_flash_register_ext(dev, &led->fled, &init_data);
>> + if (ret < 0)
>> + return dev_err_probe(dev, ret, "failed to create LED flash device\n");
>> +
>> + v4l2_cfg.intensity.min = led->fled.timeout.min;
>> + v4l2_cfg.intensity.step = led->fled.timeout.step;
>> + v4l2_cfg.intensity.max = led->fled.timeout.max;
>> + v4l2_cfg.intensity.val = led->fled.timeout.val;
>
> Is it correct to configure the V4L2 intensity settings from the timeout
> values? I would expect these to be based on the brightness settings.
Stupid me. Admittedly, I am unable to test the v4l2 functionality
properly for now. I will remove all related code in the next revision.
Will add them back when they're needed and I'm able to test.
>> +
>> + v4l2_cfg.has_external_strobe = true;
>> +
>> + led->v4l2_flash = v4l2_flash_init(dev, fwnp, &led->fled, &s2m_fled_v4l2_flash_ops,
>> + &v4l2_cfg);
>> + if (IS_ERR(led->v4l2_flash)) {
>> + v4l2_flash_release(led->v4l2_flash);
>
> So you're going to try and release an error?
>
>> + return dev_err_probe(dev, PTR_ERR(led->v4l2_flash),
>> + "failed to create V4L2 flash device\n");
>> + }
>> +
>> + ret = devm_add_action_or_reset(dev, (void *)s2m_fled_v4l2_flash_release, led->v4l2_flash);
>> + if (ret < 0)
>> + return dev_err_probe(dev, ret, "failed to add cleanup action\n");
>> +
>> + return 0;
>> +}
>> +
>> +static int s2m_fled_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct sec_pmic_dev *ddata = dev_get_drvdata(dev->parent);
>> + struct s2m_led *led;
>> + int ret;
>> +
>> + led = devm_kzalloc(dev, sizeof(*led) * MAX_CHANNELS, GFP_KERNEL);
>> + if (!led)
>> + return -ENOMEM;
>> +
>> + switch (platform_get_device_id(pdev)->driver_data) {
>> + case S2MU005:
>> + ret = s2mu005_fled_init(led, dev, ddata->regmap_pmic, MAX_CHANNELS);
>> + if (ret)
>> + return ret;
>> + break;
>> + default:
>> + return dev_err_probe(dev, -ENODEV, "device type %d is not supported by driver\n",
>> + ddata->device_type);
>> + }
>
> Will this be expanded in the very near future?
>
> If not, having a switch () with only one entry seems odd.
I have plans to introduce support for S2MU004 at one point. I'll change
it now, and re-introduce the switch block later.
> if (platform_get_device_id(pdev)->driver_data != S2MU005)
> dev_err_probe()
>
>> + device_for_each_child_node_scoped(dev, child) {
>> + u32 reg;
>> +
>> + if (fwnode_property_read_u32(child, "reg", ®))
>> + continue;
>> +
>> + if (led[reg].regmap) {
>> + dev_warn(dev, "duplicate node for channel %d\n", reg);
>> + continue;
>> + }
>
> If reg > MAX_CHANNELS, you just created an OOB condition.
>
>> +
>> + led[reg].regmap = ddata->regmap_pmic;
>> + led[reg].channel = (u8)reg;
>> +
>> + ret = devm_mutex_init(dev, &led[reg].lock);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "failed to create mutex\n");
>> +
>> + switch (platform_get_device_id(pdev)->driver_data) {
>> + case S2MU005:
>> + ret = s2mu005_fled_init_channel(led + reg, dev, child);
>> + if (ret < 0)
>> + return ret;
>> + break;
>> + }
>
> This is even more odd!
What's exactly odd about it?
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static const struct platform_device_id s2m_fled_id_table[] = {
>> + { "s2mu005-flash", S2MU005 },
>> + { /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(platform, s2m_fled_id_table);
>> +
>> +static const struct of_device_id s2m_fled_of_match_table[] = {
>> + { .compatible = "samsung,s2mu005-flash", .data = (void *)S2MU005 },
>> + { /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(of, s2m_fled_of_match_table);
>> +
>> +static struct platform_driver s2m_fled_driver = {
>> + .driver = {
>> + .name = "s2m-flash",
>> + },
>> + .probe = s2m_fled_probe,
>> + .id_table = s2m_fled_id_table,
>> +};
>> +module_platform_driver(s2m_fled_driver);
>> +
>> +MODULE_DESCRIPTION("Flash/Torch LED Driver For Samsung S2M Series PMICs");
>> +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
>> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v5 08/11] leds: rgb: add support for Samsung S2M series PMIC RGB LED device
[not found] ` <20260424-s2mu005-pmic-v5-8-fcbc9da5a004@disroot.org>
@ 2026-05-07 19:00 ` Lee Jones
2026-05-08 17:27 ` Kaustabh Chakraborty
0 siblings, 1 reply; 6+ messages in thread
From: Lee Jones @ 2026-05-07 19:00 UTC (permalink / raw)
To: Kaustabh Chakraborty
Cc: Pavel Machek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński, linux-leds, devicetree,
linux-kernel, linux-pm, linux-samsung-soc, linux-rtc, linux-doc
On Fri, 24 Apr 2026, Kaustabh Chakraborty wrote:
> Add support for the RGB LEDs found in certain Samsung S2M series PMICs.
> The device has three LED channels, controlled as a single device. These
> LEDs are typically used as status indicators in mobile phones.
>
> The driver includes initial support for the S2MU005 PMIC RGB LEDs.
>
> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
> ---
> drivers/leds/rgb/Kconfig | 11 +
> drivers/leds/rgb/Makefile | 1 +
> drivers/leds/rgb/leds-s2m-rgb.c | 446 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 458 insertions(+)
>
> diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
> index 28ef4c487367c..30051342f4e4d 100644
> --- a/drivers/leds/rgb/Kconfig
> +++ b/drivers/leds/rgb/Kconfig
> @@ -75,6 +75,17 @@ config LEDS_QCOM_LPG
>
> If compiled as a module, the module will be named leds-qcom-lpg.
>
> +config LEDS_S2M_RGB
> + tristate "Samsung S2M series PMICs RGB LED support"
> + depends on LEDS_CLASS
> + depends on MFD_SEC_CORE
> + select REGMAP_IRQ
> + help
> + This option enables support for the S2MU005 RGB LEDs. These
> + devices have three LED channels, with 8-bit brightness control
> + for each channel. It's usually found in mobile phones as
"The S2MU005 is ..."
> + status indicators.
> +
> config LEDS_MT6370_RGB
> tristate "LED Support for MediaTek MT6370 PMIC"
> depends on MFD_MT6370
> diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile
> index be45991f63f50..98050e1aa4255 100644
> --- a/drivers/leds/rgb/Makefile
> +++ b/drivers/leds/rgb/Makefile
> @@ -6,4 +6,5 @@ obj-$(CONFIG_LEDS_LP5812) += leds-lp5812.o
> obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o
> obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o
> obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o
> +obj-$(CONFIG_LEDS_S2M_RGB) += leds-s2m-rgb.o
> obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o
> diff --git a/drivers/leds/rgb/leds-s2m-rgb.c b/drivers/leds/rgb/leds-s2m-rgb.c
> new file mode 100644
> index 0000000000000..51d12f2ef762a
> --- /dev/null
> +++ b/drivers/leds/rgb/leds-s2m-rgb.c
> @@ -0,0 +1,446 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * RGB LED Driver for Samsung S2M series PMICs.
> + *
> + * Copyright (c) 2015 Samsung Electronics Co., Ltd
> + * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org>
> + */
> +
> +#include <linux/container_of.h>
> +#include <linux/led-class-multicolor.h>
> +#include <linux/mfd/samsung/core.h>
> +#include <linux/mfd/samsung/s2mu005.h>
> +#include <linux/minmax.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +struct s2m_rgb {
> + struct device *dev;
> + struct regmap *regmap;
> + struct led_classdev_mc mc;
> + enum sec_device_type device_type;
> + /*
> + * The mutex object prevents race conditions when evaluation and
> + * application of LED pattern state.
> + */
> + struct mutex lock;
> + /*
> + * State variables representing the current LED pattern, these only to
> + * be accessed when lock is held.
> + */
> + u8 ramp_up;
> + u8 ramp_dn;
> + u8 stay_hi;
> + u8 stay_lo;
> +};
> +
> +static struct led_classdev_mc *to_s2m_mc(struct led_classdev *cdev)
> +{
> + return container_of(cdev, struct led_classdev_mc, led_cdev);
> +}
> +
> +static struct s2m_rgb *to_s2m_rgb(struct led_classdev_mc *mc)
> +{
> + return container_of(mc, struct s2m_rgb, mc);
> +}
> +
> +static const u32 s2mu005_rgb_lut_ramp[] = {
> + 0, 100, 200, 300, 400, 500, 600, 700,
> + 800, 1000, 1200, 1400, 1600, 1800, 2000, 2200,
> +};
> +
> +static const u32 s2mu005_rgb_lut_stay_hi[] = {
> + 100, 200, 300, 400, 500, 750, 1000, 1250,
> + 1500, 1750, 2000, 2250, 2500, 2750, 3000, 3250,
> +};
> +
> +static const u32 s2mu005_rgb_lut_stay_lo[] = {
> + 0, 500, 1000, 1500, 2000, 2500, 3000, 3500,
> + 4000, 4500, 5000, 6000, 7000, 8000, 10000, 12000,
> +};
> +
> +static int s2mu005_rgb_apply_params(struct s2m_rgb *rgb)
> +{
> + struct regmap *regmap = rgb->regmap;
> + unsigned int ramp_val = 0;
> + unsigned int stay_val = 0;
> + int ret;
> + int i;
> +
> + ramp_val |= FIELD_PREP(S2MU005_RGB_CH_RAMP_UP, rgb->ramp_up);
> + ramp_val |= FIELD_PREP(S2MU005_RGB_CH_RAMP_DN, rgb->ramp_dn);
> +
> + stay_val |= FIELD_PREP(S2MU005_RGB_CH_STAY_HI, rgb->stay_hi);
> + stay_val |= FIELD_PREP(S2MU005_RGB_CH_STAY_LO, rgb->stay_lo);
> +
> + ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_RESET);
> + if (ret < 0) {
> + dev_err(rgb->dev, "failed to reset RGB LEDs\n");
> + return ret;
> + }
> +
> + for (i = 0; i < rgb->mc.num_colors; i++) {
for (int i = 0; ...)
> + ret = regmap_write(regmap, S2MU005_REG_RGB_CH_CTRL(i),
> + rgb->mc.subled_info[i].brightness);
> + if (ret < 0) {
> + dev_err(rgb->dev, "failed to set LED brightness\n");
> + return ret;
> + }
> +
> + ret = regmap_write(regmap, S2MU005_REG_RGB_CH_RAMP(i), ramp_val);
> + if (ret < 0) {
> + dev_err(rgb->dev, "failed to set ramp timings\n");
> + return ret;
> + }
> +
> + ret = regmap_write(regmap, S2MU005_REG_RGB_CH_STAY(i), stay_val);
> + if (ret < 0) {
> + dev_err(rgb->dev, "failed to set stay timings\n");
> + return ret;
> + }
> + }
> +
> + ret = regmap_update_bits(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_SLOPE,
> + S2MU005_RGB_SLOPE_SMOOTH);
> + if (ret < 0) {
> + dev_err(rgb->dev, "failed to set ramp slope\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int s2mu005_rgb_reset_params(struct s2m_rgb *rgb)
> +{
> + struct regmap *regmap = rgb->regmap;
> + int ret;
> +
> + ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_RESET);
> + if (ret < 0) {
> + dev_err(rgb->dev, "failed to reset RGB LEDs\n");
> + return ret;
> + }
> +
> + rgb->ramp_up = 0;
> + rgb->ramp_dn = 0;
> + rgb->stay_hi = 0;
> + rgb->stay_lo = 0;
> +
> + return 0;
> +}
> +
> +static int s2m_rgb_lut_calc_timing(const u32 *lut, const size_t len,
> + const u32 req_time, u8 *idx)
> +{
> + int lo = 0;
> + int hi = len - 2;
> +
> + /* Bounds checking */
> + if (req_time < lut[0] || req_time > lut[len - 1])
> + return -EINVAL;
> +
> + /*
> + * Perform a binary search to pick the best timing from the LUT.
> + *
> + * The search algorithm picks two consecutive elements of the
> + * LUT and tries to search the pair between which the requested
> + * time lies.
> + */
> + while (lo <= hi) {
> + *idx = (lo + hi) / 2;
> +
> + if ((lut[*idx] <= req_time) && (req_time <= lut[*idx + 1]))
> + break;
> +
> + if ((req_time < lut[*idx]) && (req_time < lut[*idx + 1]))
> + hi = *idx - 1;
> + else
> + lo = *idx + 1;
> + }
> +
> + /*
> + * The searched timing is always less than the requested time. At
> + * times, the succeeding timing in the LUT is closer thus more
> + * accurate. Adjust the resulting value if that's the case.
> + */
> + if (abs(req_time - lut[*idx]) > abs(lut[*idx + 1] - req_time))
> + (*idx)++;
As much as I appreciate the comments, most of the function is pretty
unreadable. Are you able to use better variable nomenclature and layout
to better describe your aims?
> + return 0;
> +}
> +
> +static int s2m_rgb_pattern_set(struct led_classdev *cdev, struct led_pattern *pattern,
> + u32 len, int repeat)
> +{
> + struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
> + const u32 *lut_ramp_up, *lut_ramp_dn, *lut_stay_hi, *lut_stay_lo;
> + size_t lut_ramp_up_len, lut_ramp_dn_len, lut_stay_hi_len, lut_stay_lo_len;
> + int brightness_peak = 0;
> + u32 time_hi = 0, time_lo = 0;
> + bool ramp_up_en, ramp_dn_en;
> + int ret;
> + int i;
> +
> + /*
> + * The typical pattern supported by this device can be
> + * represented with the following graph:
> + *
> + * 255 T ''''''-. .-'''''''-.
> + * | '. .' '.
> + * | \ / \
> + * | '. .' '.
> + * | '-...........-' '-
> + * 0 +----------------------------------------------------> time (s)
> + *
> + * <---- HIGH ----><-- LOW --><-------- HIGH --------->
> + * <-----><-------><---------><-------><-----><------->
> + * stay_hi ramp_dn stay_lo ramp_up stay_hi ramp_dn
> + *
> + * There are two states, named HIGH and LOW. HIGH has a non-zero
> + * brightness level, while LOW is of zero brightness. The
> + * pattern provided should mention only one zero and non-zero
> + * brightness level. The hardware always starts the pattern from
> + * the HIGH state, as shown in the graph.
> + *
> + * The HIGH state can be divided in three somewhat equal timings:
> + * ramp_up, stay_hi, and ramp_dn. The LOW state has only one
> + * timing: stay_lo.
> + */
> +
> + /* Only indefinitely looping patterns are supported. */
> + if (repeat != -1)
> + return -EINVAL;
> +
> + /* Pattern should consist of at least two tuples. */
> + if (len < 2)
> + return -EINVAL;
> +
> + for (i = 0; i < len; i++) {
for (int i = 0; ...) would be preferable.
> + int brightness = pattern[i].brightness;
> + u32 delta_t = pattern[i].delta_t;
> +
> + if (brightness) {
> + /*
> + * The pattern shold define only one non-zero
> + * brightness in the HIGH state. The device
> + * doesn't have any provisions to handle
> + * multiple peak brightness levels.
> + */
> + if (brightness_peak && brightness_peak != brightness)
> + return -EINVAL;
> +
> + brightness_peak = brightness;
> + time_hi += delta_t;
> + ramp_dn_en = !!delta_t;
> + } else {
> + time_lo += delta_t;
> + ramp_up_en = !!delta_t;
> + }
> + }
> +
> + switch (rgb->device_type) {
> + case S2MU005:
> + lut_ramp_up = s2mu005_rgb_lut_ramp;
> + lut_ramp_up_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp);
> + lut_ramp_dn = s2mu005_rgb_lut_ramp;
> + lut_ramp_dn_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp);
> + lut_stay_hi = s2mu005_rgb_lut_stay_hi;
> + lut_stay_hi_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_hi);
> + lut_stay_lo = s2mu005_rgb_lut_stay_lo;
> + lut_stay_lo_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_lo);
> + break;
> + default:
> + /* execution shouldn't reach here */
Instead of a comment, perhaps a WARN_ON_ONCE(1); or similar would be
more robust here to catch unexpected device types?
> + break;
> + }
> +
> + mutex_lock(&rgb->lock);
> +
> + /*
> + * The timings ramp_up, stay_hi, and ramp_dn of the HIGH state
> + * are roughly equal. Firstly, calculate and set timings for
> + * ramp_up and ramp_dn (making sure they're exactly equal).
> + */
> + rgb->ramp_up = 0;
> + rgb->ramp_dn = 0;
> +
> + if (ramp_up_en) {
> + ret = s2m_rgb_lut_calc_timing(lut_ramp_up, lut_ramp_up_len, time_hi / 3,
> + &rgb->ramp_up);
> + if (ret < 0)
> + goto param_fail;
> + }
> +
> + if (ramp_dn_en) {
> + ret = s2m_rgb_lut_calc_timing(lut_ramp_dn, lut_ramp_dn_len, time_hi / 3,
> + &rgb->ramp_dn);
> + if (ret < 0)
> + goto param_fail;
> + }
> +
> + /*
> + * Subtract the allocated ramp timings from time_hi (and also
> + * making sure it doesn't underflow!). The remaining time is
> + * allocated to stay_hi.
> + */
> + time_hi -= min(time_hi, lut_ramp_up[rgb->ramp_up]);
> + time_hi -= min(time_hi, lut_ramp_dn[rgb->ramp_dn]);
> +
> + ret = s2m_rgb_lut_calc_timing(lut_stay_hi, lut_stay_hi_len, time_hi, &rgb->stay_hi);
> + if (ret < 0)
> + goto param_fail;
> +
> + ret = s2m_rgb_lut_calc_timing(lut_stay_lo, lut_stay_lo_len, time_lo, &rgb->stay_lo);
> + if (ret < 0)
> + goto param_fail;
> +
> + led_mc_calc_color_components(&rgb->mc, brightness_peak);
> + switch (rgb->device_type) {
> + case S2MU005:
> + ret = s2mu005_rgb_apply_params(rgb);
> + break;
> + default:
> + /* execution shouldn't reach here */
> + break;
> + }
> + if (ret < 0)
> + goto param_fail;
Are we expecting positive values in these 'ret's?
If not if (!ret) will do.
> +
> + mutex_unlock(&rgb->lock);
> +
> + return 0;
> +
> +param_fail:
> + rgb->ramp_up = 0;
> + rgb->ramp_dn = 0;
> + rgb->stay_hi = 0;
> + rgb->stay_lo = 0;
> +
> + mutex_unlock(&rgb->lock);
> +
> + return ret;
> +}
> +
> +static int s2m_rgb_pattern_clear(struct led_classdev *cdev)
> +{
> + struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
> + int ret = 0;
> +
> + mutex_lock(&rgb->lock);
> +
> + switch (rgb->device_type) {
> + case S2MU005:
> + ret = s2mu005_rgb_reset_params(rgb);
> + break;
> + default:
> + /* execution shouldn't reach here */
> + break;
As above.
And a single branch switch () makes little sense.
> + }
> +
> + mutex_unlock(&rgb->lock);
> +
> + return ret;
> +}
> +
> +static int s2m_rgb_brightness_set(struct led_classdev *cdev, enum led_brightness value)
> +{
> + struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
> + int ret = 0;
> +
> + if (!value)
> + return s2m_rgb_pattern_clear(cdev);
> +
> + mutex_lock(&rgb->lock);
> +
> + led_mc_calc_color_components(&rgb->mc, value);
> + switch (rgb->device_type) {
> + case S2MU005:
> + ret = s2mu005_rgb_apply_params(rgb);
> + break;
> + default:
> + /* execution shouldn't reach here */
> + break;
> + }
> +
> + mutex_unlock(&rgb->lock);
> +
> + return ret;
> +}
> +
> +static struct mc_subled s2mu005_rgb_subled_info[] = {
const?
> + { .channel = 0, .color_index = LED_COLOR_ID_BLUE },
> + { .channel = 1, .color_index = LED_COLOR_ID_GREEN },
> + { .channel = 2, .color_index = LED_COLOR_ID_RED },
> +};
> +
> +static int s2m_rgb_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct sec_pmic_dev *pmic_drvdata = dev_get_drvdata(dev->parent);
> + struct s2m_rgb *rgb;
> + struct led_init_data init_data = {};
> + int ret;
> +
> + rgb = devm_kzalloc(dev, sizeof(*rgb), GFP_KERNEL);
> + if (!rgb)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, rgb);
> + rgb->dev = dev;
> + rgb->regmap = pmic_drvdata->regmap_pmic;
> + rgb->device_type = platform_get_device_id(pdev)->driver_data;
We don't tend to use these object oriented-type constructs in the
kernel. Also, we have helper functions of extracting driver_data.
Please use them.
> +
> + switch (rgb->device_type) {
> + case S2MU005:
> + rgb->mc.subled_info = s2mu005_rgb_subled_info;
> + rgb->mc.num_colors = ARRAY_SIZE(s2mu005_rgb_subled_info);
> + break;
> + default:
> + return dev_err_probe(dev, -ENODEV, "device type %d is not supported by driver\n",
> + pmic_drvdata->device_type);
Small point, but for consistency, would it be better to print
`rgb->device_type` here, since that is the value being checked in the
switch statement?
Also, same single branch comment as before.
> + }
> +
> + rgb->mc.led_cdev.max_brightness = 255;
> + rgb->mc.led_cdev.brightness_set_blocking = s2m_rgb_brightness_set;
> + rgb->mc.led_cdev.pattern_set = s2m_rgb_pattern_set;
> + rgb->mc.led_cdev.pattern_clear = s2m_rgb_pattern_clear;
> +
> + ret = devm_mutex_init(dev, &rgb->lock);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to create mutex lock\n");
> +
> + init_data.fwnode = of_fwnode_handle(dev->of_node);
> + ret = devm_led_classdev_multicolor_register_ext(dev, &rgb->mc, &init_data);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to create LED device\n");
> +
> + return 0;
> +}
> +
> +static const struct platform_device_id s2m_rgb_id_table[] = {
> + { "s2mu005-rgb", S2MU005 },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(platform, s2m_rgb_id_table);
> +
> +static const struct of_device_id s2m_rgb_of_match_table[] = {
> + { .compatible = "samsung,s2mu005-rgb", .data = (void *)S2MU005 },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, s2m_rgb_of_match_table);
> +
> +static struct platform_driver s2m_rgb_driver = {
> + .driver = {
> + .name = "s2m-rgb",
> + },
> + .probe = s2m_rgb_probe,
> + .id_table = s2m_rgb_id_table,
> +};
> +module_platform_driver(s2m_rgb_driver);
> +
> +MODULE_DESCRIPTION("RGB LED Driver For Samsung S2M Series PMICs");
"for"
> +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
> +MODULE_LICENSE("GPL");
--
Lee Jones
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v5 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device
2026-05-07 16:46 ` [PATCH v5 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device Lee Jones
2026-05-07 18:33 ` Kaustabh Chakraborty
@ 2026-05-07 19:39 ` Jacek Anaszewski
2026-05-13 14:00 ` Lee Jones
1 sibling, 1 reply; 6+ messages in thread
From: Jacek Anaszewski @ 2026-05-07 19:39 UTC (permalink / raw)
To: Lee Jones, Kaustabh Chakraborty
Cc: Pavel Machek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński, linux-leds, devicetree,
linux-kernel, linux-pm, linux-samsung-soc, linux-rtc, linux-doc
Hi Lee,
On 5/7/26 6:46 PM, Lee Jones wrote:
> On Fri, 24 Apr 2026, Kaustabh Chakraborty wrote:
>
>> Add support for flash LEDs in certain Samsung S2M series PMICs.
>> The device has two channels for LEDs, typically for the back and front
>> cameras in mobile devices. Both channels can be independently
>> controlled, and can be operated in torch or flash modes.
>>
>> The driver includes initial support for the S2MU005 PMIC flash LEDs.
>>
>> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
>> ---
>> drivers/leds/flash/Kconfig | 12 ++
>> drivers/leds/flash/Makefile | 1 +
>> drivers/leds/flash/leds-s2m-flash.c | 358 ++++++++++++++++++++++++++++++++++++
>> 3 files changed, 371 insertions(+)
>>
>> diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
>> index 5e08102a67841..be62e05277429 100644
>> --- a/drivers/leds/flash/Kconfig
>> +++ b/drivers/leds/flash/Kconfig
>> @@ -114,6 +114,18 @@ config LEDS_RT8515
>> To compile this driver as a module, choose M here: the module
>> will be called leds-rt8515.
>>
>> +config LEDS_S2M_FLASH
>> + tristate "Samsung S2M series PMICs flash/torch LED support"
>> + depends on LEDS_CLASS
>> + depends on MFD_SEC_CORE
>> + depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
>
> The `|| !V4L2_FLASH_LED_CLASS` part of this dependency makes it
> unconditionally true. Was this intended? Perhaps this dependency can be
> removed entirely.
This is for a reason to allow building the driver if
V4L2_FLASH_LED_CLASS is turned off, or build it as a module
if V4L2_FLASH_LED_CLASS=m. You will get nice explanation from
Google AI if you type just
"V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS".
See e.g. [0], which fixes undefined symbol error by adding this.
[0]
https://git.paulk.fr/projects/linux.git/commit/drivers?h=sunxi/cedrus/jpeg-nv16&id=dbeb02a0bc41b9e9b9c05e460890351efecf1352
--
Best regards,
Jacek Anaszewski
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v5 08/11] leds: rgb: add support for Samsung S2M series PMIC RGB LED device
2026-05-07 19:00 ` [PATCH v5 08/11] leds: rgb: add support for Samsung S2M series PMIC RGB " Lee Jones
@ 2026-05-08 17:27 ` Kaustabh Chakraborty
0 siblings, 0 replies; 6+ messages in thread
From: Kaustabh Chakraborty @ 2026-05-08 17:27 UTC (permalink / raw)
To: Lee Jones, Kaustabh Chakraborty
Cc: Pavel Machek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
Krzysztof Kozlowski, André Draszik, Alexandre Belloni,
Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński, linux-leds, devicetree,
linux-kernel, linux-pm, linux-samsung-soc, linux-rtc, linux-doc
On 2026-05-07 20:00 +01:00, Lee Jones wrote:
> On Fri, 24 Apr 2026, Kaustabh Chakraborty wrote:
[...]
>> +
>> + switch (rgb->device_type) {
>> + case S2MU005:
>> + lut_ramp_up = s2mu005_rgb_lut_ramp;
>> + lut_ramp_up_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp);
>> + lut_ramp_dn = s2mu005_rgb_lut_ramp;
>> + lut_ramp_dn_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp);
>> + lut_stay_hi = s2mu005_rgb_lut_stay_hi;
>> + lut_stay_hi_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_hi);
>> + lut_stay_lo = s2mu005_rgb_lut_stay_lo;
>> + lut_stay_lo_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_lo);
>> + break;
>> + default:
>> + /* execution shouldn't reach here */
>
> Instead of a comment, perhaps a WARN_ON_ONCE(1); or similar would be
> more robust here to catch unexpected device types?
>
[...]
>> +static int s2m_rgb_pattern_clear(struct led_classdev *cdev)
>> +{
>> + struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
>> + int ret = 0;
>> +
>> + mutex_lock(&rgb->lock);
>> +
>> + switch (rgb->device_type) {
>> + case S2MU005:
>> + ret = s2mu005_rgb_reset_params(rgb);
>> + break;
>> + default:
>> + /* execution shouldn't reach here */
>> + break;
>
> As above.
>
> And a single branch switch () makes little sense.
Even with an `if`, since only one variant is supported we're sure that
the control would never go to `else` anyway. I will flatten this block,
and expect the switch to be added when another variant is added.
>> +static struct mc_subled s2mu005_rgb_subled_info[] = {
>
> const?
No, this is fed to (struct led_classdev_mc)::subled_info, which is not a
const pointer. Relevant snip is marked below.
"Assigning to 'struct mc_subled *' from const struct mc_subled[3]
discards qualifiers."
>> + { .channel = 0, .color_index = LED_COLOR_ID_BLUE },
>> + { .channel = 1, .color_index = LED_COLOR_ID_GREEN },
>> + { .channel = 2, .color_index = LED_COLOR_ID_RED },
>> +};
[...]
>> + switch (rgb->device_type) {
>> + case S2MU005:
>> + rgb->mc.subled_info = s2mu005_rgb_subled_info;
Here.
>> + rgb->mc.num_colors = ARRAY_SIZE(s2mu005_rgb_subled_info);
>> + break;
>> + default:
>> + return dev_err_probe(dev, -ENODEV, "device type %d is not supported by driver\n",
>> + pmic_drvdata->device_type);
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v5 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device
2026-05-07 19:39 ` Jacek Anaszewski
@ 2026-05-13 14:00 ` Lee Jones
0 siblings, 0 replies; 6+ messages in thread
From: Lee Jones @ 2026-05-13 14:00 UTC (permalink / raw)
To: Jacek Anaszewski
Cc: Kaustabh Chakraborty, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, MyungJoo Ham, Chanwoo Choi,
Sebastian Reichel, Krzysztof Kozlowski, André Draszik,
Alexandre Belloni, Jonathan Corbet, Shuah Khan, Nam Tran,
Łukasz Lebiedziński, linux-leds, devicetree,
linux-kernel, linux-pm, linux-samsung-soc, linux-rtc, linux-doc
On Thu, 07 May 2026, Jacek Anaszewski wrote:
> Hi Lee,
>
> On 5/7/26 6:46 PM, Lee Jones wrote:
> > On Fri, 24 Apr 2026, Kaustabh Chakraborty wrote:
> >
> > > Add support for flash LEDs in certain Samsung S2M series PMICs.
> > > The device has two channels for LEDs, typically for the back and front
> > > cameras in mobile devices. Both channels can be independently
> > > controlled, and can be operated in torch or flash modes.
> > >
> > > The driver includes initial support for the S2MU005 PMIC flash LEDs.
> > >
> > > Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
> > > ---
> > > drivers/leds/flash/Kconfig | 12 ++
> > > drivers/leds/flash/Makefile | 1 +
> > > drivers/leds/flash/leds-s2m-flash.c | 358 ++++++++++++++++++++++++++++++++++++
> > > 3 files changed, 371 insertions(+)
> > >
> > > diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
> > > index 5e08102a67841..be62e05277429 100644
> > > --- a/drivers/leds/flash/Kconfig
> > > +++ b/drivers/leds/flash/Kconfig
> > > @@ -114,6 +114,18 @@ config LEDS_RT8515
> > > To compile this driver as a module, choose M here: the module
> > > will be called leds-rt8515.
> > > +config LEDS_S2M_FLASH
> > > + tristate "Samsung S2M series PMICs flash/torch LED support"
> > > + depends on LEDS_CLASS
> > > + depends on MFD_SEC_CORE
> > > + depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
> >
> > The `|| !V4L2_FLASH_LED_CLASS` part of this dependency makes it
> > unconditionally true. Was this intended? Perhaps this dependency can be
> > removed entirely.
> This is for a reason to allow building the driver if
> V4L2_FLASH_LED_CLASS is turned off, or build it as a module
> if V4L2_FLASH_LED_CLASS=m. You will get nice explanation from
> Google AI if you type just
> "V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS".
>
> See e.g. [0], which fixes undefined symbol error by adding this.
>
> [0] https://git.paulk.fr/projects/linux.git/commit/drivers?h=sunxi/cedrus/jpeg-nv16&id=dbeb02a0bc41b9e9b9c05e460890351efecf1352
I see. Thanks for the explanation.
--
Lee Jones
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-05-13 14:00 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260424-s2mu005-pmic-v5-0-fcbc9da5a004@disroot.org>
[not found] ` <20260424-s2mu005-pmic-v5-7-fcbc9da5a004@disroot.org>
2026-05-07 16:46 ` [PATCH v5 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device Lee Jones
2026-05-07 18:33 ` Kaustabh Chakraborty
2026-05-07 19:39 ` Jacek Anaszewski
2026-05-13 14:00 ` Lee Jones
[not found] ` <20260424-s2mu005-pmic-v5-8-fcbc9da5a004@disroot.org>
2026-05-07 19:00 ` [PATCH v5 08/11] leds: rgb: add support for Samsung S2M series PMIC RGB " Lee Jones
2026-05-08 17:27 ` Kaustabh Chakraborty
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox