* 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; 5+ 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] 5+ 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; 5+ 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] 5+ 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; 5+ 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] 5+ messages in thread
[parent not found: <20260424-s2mu005-pmic-v5-8-fcbc9da5a004@disroot.org>]
* 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; 5+ 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] 5+ 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; 5+ 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] 5+ messages in thread
end of thread, other threads:[~2026-05-08 17:27 UTC | newest]
Thread overview: 5+ 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
[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