diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-06-18 14:45:08 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-06-18 14:45:08 -0700 |
| commit | 6e717507bfbe8d6955f3f4c5604857a392c7e6fa (patch) | |
| tree | a7dfa10b0fede59860521867cbcaf2aa425a52ce /drivers | |
| parent | 6beaec3aee9852438b89e4d7891caf5e84d45851 (diff) | |
| parent | d43f1d792902ba0a53fd311bff2cf96095c7606d (diff) | |
Merge tag 'leds-next-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds
Pull LED updates from Lee Jones:
"New Support & Features:
- Samsung S2MU005: Add support for the Samsung S2MU005 PMIC which
includes flash and RGB LED controllers
- Texas Instruments:
- LP5812: Add support for the TI LP5812 LED driver
- LP5860: Add support for the Texas Instruments LP5860 LED matrix
driver via SPI
Improvements & Fixes:
- Core:
- Adjust the brightness sysfs node documentation to clarify that
only decimal values are accepted
- Fix a race condition in the software blink logic when stopping
blinking and setting brightness simultaneously
- Introduce the `multi_max_intensity` sysfs attribute for
multicolor LEDs to support hardware-based global brightness
control
- Replace OF-based device lookup with firmware node equivalents
to support ACPI and software nodes
- Return `ENODATA` when reading brightness from
hardware-controlled LEDs
- Set the coherent DMA mask to zero for the Samsung PMIC device
to suppress unnecessary "DMA mask not set" messages
- ams OSRAM AS3668: Fix a Kconfig symbol name mismatch in the
Makefile that prevented the driver from being built
- BlinkM: Fix spelling and comment style issues in the driver
- DAC124S085: Declare the SPI command word as `__le16` to ensure
correct endianness and pass sparse checks
- GPIO Trigger: Use `GPIOD_FLAGS_BIT_NONEXCLUSIVE` to allow sharing
GPIOs between the LED trigger and other drivers
- NXP PCA9532: Fix an issue where the LED would stop blinking when
changing brightness to a non-zero value
- Qualcomm: Unify the user-visible company name to "Qualcomm" across
flash LED config options
- Qualcomm LPG: Optimize memory allocation by combining main
structure and channels into a single allocation using flexible
array members
- Texas Instruments
- LP5860: Add missing `CONFIG_OF` dependency to prevent build
warnings
- TPS6131x: Increase the overvoltage protection threshold to 6V
to avoid false triggers with 5V input supplies
- Userspace LEDs (uLEDs):
- Fix a potential buffer overread by using `strnchr()` for name
string validation
- Return `-EFAULT` on `copy_to_user()` failure to properly handle
read errors
Cleanups & Refactoring:
- Core:
- Convert various `i2c_device_id` arrays to use named
initializers for improved robustness and readability
- Multi-color: Fix incorrect `KernelVersion` and `Date` tags for
the `multi_max_intensity` ABI
- Broadcom BCM63138 / ChromeOS EC: Move `MODULE_DEVICE_TABLE`
declarations next to the ID tables for consistency
- LP5812: Fix a sysfs ABI reference in the documentation
- ST1202: Remove an unused legacy GPIO header include
Device Tree Binding Updates:
- Class: Document the keyboard backlight LED class naming
conventions, including a new scheme for zoned backlights
- Core: Dual-license the common LED bindings header under GPLv2
and BSD-2-Clause
- IR SPI LED: Add a new 30% duty-cycle value for the IR transmitter
used in Xiaomi Redmi Note 8
- Samsung S2M series:
- Document the flash LED device bindings for Samsung S2M series
PMICs
- Document the pattern behavior for Samsung S2M series PMIC RGB
- S2MU005: Add device tree bindings for the S2MU005 PMIC,
including its flash and RGB LED sub-devices
- TI LM3560: Document the TI LM3559 and LM3560 synchronous boost
flash drivers"
* tag 'leds-next-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (30 commits)
leds: tps6131x: Increase overvoltage protection threshold to 6V
leds: Fix sysfs ABI date
leds: Fix CONFIG_OF dependency for LEDS_LP5860_CORE
leds: uleds: Fix potential buffer overread
leds: Use named initializers for arrays of i2c_device_data
leds: uleds: Return -EFAULT on copy_to_user() failure
leds: core: Report ENODATA for brightness of hardware controlled LED
leds: class: Use firmware nodes for device lookup
Documentation: leds: Document pattern behavior of Samsung S2M series PMIC RGB LEDs
leds: rgb: Add support for Samsung S2M series PMIC RGB LED device
leds: flash: Add support for Samsung S2M series PMIC flash LED device
dt-bindings: leds: Document Samsung S2M series PMIC flash LED device
leds: core: Fix race condition for software blink
leds: Adjust documentation of brightness sysfs node
leds: dac124s085: Declare SPI command word as __le16
leds: Introduce the multi_max_intensity sysfs attribute
dt-bindings: leds: Document TI LM3560 Synchronous Boost Flash Driver
leds: bcm63138/cros_ec: Move MODULE_DEVICE_TABLE next to the table itself
leds: Add support for TI LP5860 LED driver chip
Documentation: leds: leds-class: Document keyboard backlight LED class naming
...
Diffstat (limited to 'drivers')
55 files changed, 1697 insertions, 201 deletions
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 8fdb45d5b439..7db3768912ca 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -15,7 +15,7 @@ obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o obj-$(CONFIG_LEDS_APU) += leds-apu.o obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o -obj-$(CONFIG_LEDS_AS3668) += leds-as3668.o +obj-$(CONFIG_LEDS_OSRAM_AMS_AS3668) += leds-as3668.o obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c index ef2e511438cc..45c0662df933 100644 --- a/drivers/leds/blink/leds-bcm63138.c +++ b/drivers/leds/blink/leds-bcm63138.c @@ -296,6 +296,7 @@ static const struct of_device_id bcm63138_leds_of_match_table[] = { { .compatible = "brcm,bcm63138-leds", }, { }, }; +MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table); static struct platform_driver bcm63138_leds_driver = { .probe = bcm63138_leds_probe, @@ -310,4 +311,3 @@ module_platform_driver(bcm63138_leds_driver); MODULE_AUTHOR("Rafał Miłecki"); MODULE_DESCRIPTION("Broadcom BCM63138 SoC LED driver"); MODULE_LICENSE("GPL"); -MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table); diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig index 5e08102a6784..9b6dd3f1ffe8 100644 --- a/drivers/leds/flash/Kconfig +++ b/drivers/leds/flash/Kconfig @@ -76,7 +76,7 @@ config LEDS_MT6370_FLASH will be called "leds-mt6370-flash". config LEDS_QCOM_FLASH - tristate "LED support for flash module inside Qualcomm Technologies, Inc. PMIC" + tristate "LED support for flash module inside Qualcomm PMIC" depends on MFD_SPMI_PMIC || COMPILE_TEST depends on LEDS_CLASS && OF depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS @@ -114,6 +114,17 @@ 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 + 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 712fb737a428..44e6c1b4beb3 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-as3645a.c b/drivers/leds/flash/leds-as3645a.c index 2f2d783c62c3..5fefcaef3714 100644 --- a/drivers/leds/flash/leds-as3645a.c +++ b/drivers/leds/flash/leds-as3645a.c @@ -741,7 +741,7 @@ static void as3645a_remove(struct i2c_client *client) } static const struct i2c_device_id as3645a_id_table[] = { - { AS_NAME }, + { .name = AS_NAME }, { } }; MODULE_DEVICE_TABLE(i2c, as3645a_id_table); diff --git a/drivers/leds/flash/leds-lm3601x.c b/drivers/leds/flash/leds-lm3601x.c index abf6b96ade3d..8d00510c8967 100644 --- a/drivers/leds/flash/leds-lm3601x.c +++ b/drivers/leds/flash/leds-lm3601x.c @@ -465,8 +465,8 @@ static void lm3601x_remove(struct i2c_client *client) } static const struct i2c_device_id lm3601x_id[] = { - { "LM36010", CHIP_LM36010 }, - { "LM36011", CHIP_LM36011 }, + { .name = "LM36010", .driver_data = CHIP_LM36010 }, + { .name = "LM36011", .driver_data = CHIP_LM36011 }, { } }; MODULE_DEVICE_TABLE(i2c, lm3601x_id); diff --git a/drivers/leds/flash/leds-s2m-flash.c b/drivers/leds/flash/leds-s2m-flash.c new file mode 100644 index 000000000000..6ee8db094611 --- /dev/null +++ b/drivers/leds/flash/leds-s2m-flash.c @@ -0,0 +1,350 @@ +// 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; + + mutex_lock(&led->lock); + led->flash_brightness = (brightness - setting->min) / setting->step; + mutex_unlock(&led->lock); + + 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; + + mutex_lock(&led->lock); + led->flash_timeout = (timeout - setting->min) / setting->step; + mutex_unlock(&led->lock); + + 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_s2m_led(v4l2_flash->fled_cdev); + + return led->fled.ops->strobe_set(&led->fled, enable); +} + +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) + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + dev_err(fled->led_cdev.dev, "failed to enable flash LED\n"); + goto unlock; + } + +unlock: + mutex_unlock(&led->lock); + + return ret; +} + +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) { + dev_err(fled->led_cdev.dev, "failed to fetch LED status\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 ret; + + ret = regmap_read(regmap, S2MU005_REG_ID, &val); + if (ret) + return dev_err_probe(dev, ret, "failed to read revision\n"); + + 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) + led[i].reg_enable = S2MU005_REG_FLED_CTRL4; + else + led[i].reg_enable = S2MU005_REG_FLED_CTRL6; + } + + /* Enable the LED channels. */ + ret = regmap_set_bits(regmap, S2MU005_REG_FLED_CTRL1, S2MU005_FLED_CH_EN); + if (ret) + return dev_err_probe(dev, ret, "failed to enable LED channels\n"); + + 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) + return dev_err_probe(dev, ret, "failed to create LED flash device\n"); + + v4l2_cfg.intensity.min = led->fled.brightness.min; + v4l2_cfg.intensity.step = led->fled.brightness.step; + v4l2_cfg.intensity.max = led->fled.brightness.max; + v4l2_cfg.intensity.val = led->fled.brightness.val; + + 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)) + return dev_err_probe(dev, PTR_ERR(led->v4l2_flash), + "failed to create V4L2 flash device\n"); + + ret = devm_add_action_or_reset(dev, s2m_fled_v4l2_flash_release, led->v4l2_flash); + if (ret) + 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; + + /* Initialize LED controller with variant-specific implementation. */ + ret = s2mu005_fled_init(led, dev, ddata->regmap_pmic, MAX_CHANNELS); + if (ret) + return ret; + + device_for_each_child_node_scoped(dev, child) { + u32 reg; + + if (fwnode_property_read_u32(child, "reg", ®)) + continue; + + if (reg >= MAX_CHANNELS) { + dev_warn(dev, "channel %d is non-existent\n", reg); + continue; + } + + if (led[reg].regmap) { + dev_warn(dev, "duplicate node for channel %d\n", reg); + continue; + } + + 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"); + + /* Initialize LED channel with variant-specific implementation. */ + ret = s2mu005_fled_init_channel(led + reg, dev, child); + if (ret) + return ret; + } + + 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"); diff --git a/drivers/leds/flash/leds-tps6131x.c b/drivers/leds/flash/leds-tps6131x.c index f0f1f2b77d5a..5c9a5af5af05 100644 --- a/drivers/leds/flash/leds-tps6131x.c +++ b/drivers/leds/flash/leds-tps6131x.c @@ -277,7 +277,7 @@ static int tps6131x_init_chip(struct tps6131x *tps6131x) if (ret) return ret; - val = TPS6131X_REG_6_ENTS; + val = TPS6131X_REG_6_ENTS | (TPS6131X_OV_4950MV << TPS6131X_REG_6_OV_SHIFT); ret = regmap_write(tps6131x->regmap, TPS6131X_REG_6, val); if (ret) diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c index 6b671f3f9c61..59d46f8a90d5 100644 --- a/drivers/leds/led-class-multicolor.c +++ b/drivers/leds/led-class-multicolor.c @@ -7,10 +7,29 @@ #include <linux/init.h> #include <linux/led-class-multicolor.h> #include <linux/math.h> +#include <linux/minmax.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/uaccess.h> +static unsigned int led_mc_get_max_intensity(struct led_classdev_mc *mcled_cdev, size_t index) +{ + unsigned int max_intensity; + + /* + * The maximum global brightness value might still be changed by + * led_classdev_register_ext() using devicetree properties. This + * prevents us from changing subled_info[X].max_intensity when + * registering a multicolor LED class device, so we have to do + * this during runtime. + */ + max_intensity = mcled_cdev->subled_info[index].max_intensity; + if (max_intensity) + return max_intensity; + + return mcled_cdev->led_cdev.max_brightness; +} + int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, enum led_brightness brightness) { @@ -27,6 +46,26 @@ int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, } EXPORT_SYMBOL_GPL(led_mc_calc_color_components); +static ssize_t multi_max_intensity_show(struct device *dev, + struct device_attribute *intensity_attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); + unsigned int max_intensity; + int len = 0; + + for (int i = 0; i < mcled_cdev->num_colors; i++) { + max_intensity = led_mc_get_max_intensity(mcled_cdev, i); + len += sysfs_emit_at(buf, len, "%u", max_intensity); + if (i < mcled_cdev->num_colors - 1) + len += sprintf(buf + len, " "); + } + + buf[len++] = '\n'; + return len; +} +static DEVICE_ATTR_RO(multi_max_intensity); + static ssize_t multi_intensity_store(struct device *dev, struct device_attribute *intensity_attr, const char *buf, size_t size) @@ -35,6 +74,7 @@ static ssize_t multi_intensity_store(struct device *dev, struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); int nrchars, offset = 0; unsigned int intensity_value[LED_COLOR_ID_MAX]; + unsigned int max_intensity; int i; ssize_t ret; @@ -56,8 +96,10 @@ static ssize_t multi_intensity_store(struct device *dev, goto err_out; } - for (i = 0; i < mcled_cdev->num_colors; i++) - mcled_cdev->subled_info[i].intensity = intensity_value[i]; + for (int i = 0; i < mcled_cdev->num_colors; i++) { + max_intensity = led_mc_get_max_intensity(mcled_cdev, i); + mcled_cdev->subled_info[i].intensity = min(intensity_value[i], max_intensity); + } if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags)) led_set_brightness(led_cdev, led_cdev->brightness); @@ -111,6 +153,7 @@ static ssize_t multi_index_show(struct device *dev, static DEVICE_ATTR_RO(multi_index); static struct attribute *led_multicolor_attrs[] = { + &dev_attr_multi_max_intensity.attr, &dev_attr_multi_intensity.attr, &dev_attr_multi_index.attr, NULL, diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 9e14ae588f78..a51b0ed53886 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -27,12 +27,25 @@ static LIST_HEAD(leds_lookup_list); static struct workqueue_struct *leds_wq; +static bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev) +{ +#ifdef CONFIG_LEDS_TRIGGERS + guard(rwsem_read)(&led_cdev->trigger_lock); + return led_cdev->trigger && led_cdev->trigger->trigger_type; +#else + return false; +#endif +} + static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); unsigned int brightness; + if (led_trigger_is_hw_controlled(led_cdev)) + return -ENODATA; + mutex_lock(&led_cdev->led_access); led_update_brightness(led_cdev); brightness = led_cdev->brightness; @@ -249,32 +262,34 @@ static const struct class leds_class = { }; /** - * of_led_get() - request a LED device via the LED framework - * @np: device node to get the LED device from + * fwnode_led_get() - request a LED device via the LED framework + * @fwnode: firmware node to get the LED device from * @index: the index of the LED * @name: the name of the LED used to map it to its function, if present * * Returns the LED device parsed from the phandle specified in the "leds" * property of a device tree node or a negative error-code on failure. */ -static struct led_classdev *of_led_get(struct device_node *np, int index, - const char *name) +static struct led_classdev *fwnode_led_get(struct fwnode_handle *fwnode, + int index, const char *name) { + struct fwnode_handle *led_node; struct device *led_dev; - struct device_node *led_node; /* * For named LEDs, first look up the name in the "led-names" property. - * If it cannot be found, then of_parse_phandle() will propagate the error. + * If it cannot be found, then fwnode_find_reference() will propagate + * the error. */ if (name) - index = of_property_match_string(np, "led-names", name); - led_node = of_parse_phandle(np, "leds", index); - if (!led_node) - return ERR_PTR(-ENOENT); + index = fwnode_property_match_string(fwnode, "led-names", + name); + led_node = fwnode_find_reference(fwnode, "leds", index); + if (IS_ERR(led_node)) + return ERR_CAST(led_node); - led_dev = class_find_device_by_fwnode(&leds_class, of_fwnode_handle(led_node)); - of_node_put(led_node); + led_dev = class_find_device_by_fwnode(&leds_class, led_node); + fwnode_handle_put(led_node); return led_module_get(led_dev); } @@ -332,7 +347,7 @@ struct led_classdev *__must_check devm_of_led_get(struct device *dev, if (!dev) return ERR_PTR(-EINVAL); - led = of_led_get(dev->of_node, index, NULL); + led = fwnode_led_get(dev_fwnode(dev), index, NULL); if (IS_ERR(led)) return led; @@ -354,7 +369,7 @@ struct led_classdev *led_get(struct device *dev, char *con_id) const char *provider = NULL; struct device *led_dev; - led_cdev = of_led_get(dev->of_node, -1, con_id); + led_cdev = fwnode_led_get(dev_fwnode(dev), -1, con_id); if (!IS_ERR(led_cdev) || PTR_ERR(led_cdev) != -ENOENT) return led_cdev; diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index 385e78af1dac..073c547068cc 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -303,24 +303,31 @@ EXPORT_SYMBOL_GPL(led_stop_software_blink); void led_set_brightness(struct led_classdev *led_cdev, unsigned int brightness) { - /* - * If software blink is active, delay brightness setting - * until the next timer tick. - */ - if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) { + if (brightness) { /* - * If we need to disable soft blinking delegate this to the - * work queue task to avoid problems in case we are called - * from hard irq context. + * If software blink disable is pending, also queue brightness setting. + * If software blink is active, delay brightness setting + * until the next timer tick. */ - if (!brightness) { - set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags); + if (test_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags) || + test_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) { + led_cdev->delayed_set_value = brightness; + set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); queue_work(led_cdev->wq, &led_cdev->set_brightness_work); - } else { - set_bit(LED_BLINK_BRIGHTNESS_CHANGE, - &led_cdev->work_flags); + return; + } else if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) { led_cdev->new_blink_brightness = brightness; + set_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags); + return; } + } else if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) { + /* + * If we need to disable soft blinking delegate this to the + * work queue task to avoid problems in case we are called + * from hard irq context. + */ + set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags); + queue_work(led_cdev->wq, &led_cdev->set_brightness_work); return; } diff --git a/drivers/leds/leds-an30259a.c b/drivers/leds/leds-an30259a.c index a42cc4bc6917..4654a732d1b1 100644 --- a/drivers/leds/leds-an30259a.c +++ b/drivers/leds/leds-an30259a.c @@ -331,7 +331,7 @@ static const struct of_device_id an30259a_match_table[] = { MODULE_DEVICE_TABLE(of, an30259a_match_table); static const struct i2c_device_id an30259a_id[] = { - { "an30259a" }, + { .name = "an30259a" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, an30259a_id); diff --git a/drivers/leds/leds-as3668.c b/drivers/leds/leds-as3668.c index b2794492370e..46427fe752a8 100644 --- a/drivers/leds/leds-as3668.c +++ b/drivers/leds/leds-as3668.c @@ -175,7 +175,7 @@ static void as3668_remove(struct i2c_client *client) } static const struct i2c_device_id as3668_idtable[] = { - { "as3668" }, + { .name = "as3668" }, { } }; MODULE_DEVICE_TABLE(i2c, as3668_idtable); diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index fe223d363a5d..0d90eeb6448f 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -637,11 +637,11 @@ static const struct aw200xx_chipdef aw20108_cdef = { }; static const struct i2c_device_id aw200xx_id[] = { - { "aw20036" }, - { "aw20054" }, - { "aw20072" }, - { "aw20108" }, - {} + { .name = "aw20036" }, + { .name = "aw20054" }, + { .name = "aw20072" }, + { .name = "aw20108" }, + { } }; MODULE_DEVICE_TABLE(i2c, aw200xx_id); diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c index 2a08c5f27608..9732f41a1143 100644 --- a/drivers/leds/leds-bd2802.c +++ b/drivers/leds/leds-bd2802.c @@ -776,7 +776,7 @@ static int bd2802_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(bd2802_pm, bd2802_suspend, bd2802_resume); static const struct i2c_device_id bd2802_id[] = { - { "BD2802" }, + { .name = "BD2802" }, { } }; MODULE_DEVICE_TABLE(i2c, bd2802_id); diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c index 577497b9d426..ee1589015826 100644 --- a/drivers/leds/leds-blinkm.c +++ b/drivers/leds/leds-blinkm.c @@ -356,7 +356,8 @@ static int blinkm_transfer_hw(struct i2c_client *client, int cmd) struct blinkm_data *data = i2c_get_clientdata(client); /* We start hardware transfers which are not to be - * mixed with other commands. Aquire a lock now. */ + * mixed with other commands. Acquire a lock now. + */ if (mutex_lock_interruptible(&data->update_lock) < 0) return -EAGAIN; @@ -805,8 +806,8 @@ static void blinkm_remove(struct i2c_client *client) } static const struct i2c_device_id blinkm_id[] = { - { "blinkm" }, - {} + { .name = "blinkm" }, + { } }; MODULE_DEVICE_TABLE(i2c, blinkm_id); diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c index bea3cc3fbfd2..6592ceee866a 100644 --- a/drivers/leds/leds-cros_ec.c +++ b/drivers/leds/leds-cros_ec.c @@ -249,6 +249,7 @@ static const struct platform_device_id cros_ec_led_id[] = { { "cros-ec-led", 0 }, {} }; +MODULE_DEVICE_TABLE(platform, cros_ec_led_id); static struct platform_driver cros_ec_led_driver = { .driver.name = "cros-ec-led", @@ -257,7 +258,6 @@ static struct platform_driver cros_ec_led_driver = { }; module_platform_driver(cros_ec_led_driver); -MODULE_DEVICE_TABLE(platform, cros_ec_led_id); MODULE_DESCRIPTION("ChromeOS EC LED Driver"); MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net"); MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-dac124s085.c b/drivers/leds/leds-dac124s085.c index cf5fb1195f87..192b43333aee 100644 --- a/drivers/leds/leds-dac124s085.c +++ b/drivers/leds/leds-dac124s085.c @@ -35,7 +35,7 @@ static int dac124s085_set_brightness(struct led_classdev *ldev, { struct dac124s085_led *led = container_of(ldev, struct dac124s085_led, ldev); - u16 word; + __le16 word; int ret; mutex_lock(&led->mutex); diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c index e411cee06dab..80f38dba0fba 100644 --- a/drivers/leds/leds-is31fl319x.c +++ b/drivers/leds/leds-is31fl319x.c @@ -565,17 +565,17 @@ static int is31fl319x_probe(struct i2c_client *client) * even though it is not used for DeviceTree based instantiation. */ static const struct i2c_device_id is31fl319x_id[] = { - { "is31fl3190" }, - { "is31fl3191" }, - { "is31fl3193" }, - { "is31fl3196" }, - { "is31fl3199" }, - { "sn3190" }, - { "sn3191" }, - { "sn3193" }, - { "sn3196" }, - { "sn3199" }, - {}, + { .name = "is31fl3190" }, + { .name = "is31fl3191" }, + { .name = "is31fl3193" }, + { .name = "is31fl3196" }, + { .name = "is31fl3199" }, + { .name = "sn3190" }, + { .name = "sn3191" }, + { .name = "sn3193" }, + { .name = "sn3196" }, + { .name = "sn3199" }, + { } }; MODULE_DEVICE_TABLE(i2c, is31fl319x_id); diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c index fe07acbb103a..6c8d6b833260 100644 --- a/drivers/leds/leds-is31fl32xx.c +++ b/drivers/leds/leds-is31fl32xx.c @@ -616,15 +616,15 @@ static void is31fl32xx_remove(struct i2c_client *client) * even though it is not used for DeviceTree based instantiation. */ static const struct i2c_device_id is31fl32xx_id[] = { - { "is31fl3293" }, - { "is31fl3236" }, - { "is31fl3236a" }, - { "is31fl3235" }, - { "is31fl3218" }, - { "sn3218" }, - { "is31fl3216" }, - { "sn3216" }, - {}, + { .name = "is31fl3293" }, + { .name = "is31fl3236" }, + { .name = "is31fl3236a" }, + { .name = "is31fl3235" }, + { .name = "is31fl3218" }, + { .name = "sn3218" }, + { .name = "is31fl3216" }, + { .name = "sn3216" }, + { } }; MODULE_DEVICE_TABLE(i2c, is31fl32xx_id); diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c index e44a3db106c3..481e9c0a41e7 100644 --- a/drivers/leds/leds-lm3530.c +++ b/drivers/leds/leds-lm3530.c @@ -478,8 +478,8 @@ static void lm3530_remove(struct i2c_client *client) } static const struct i2c_device_id lm3530_id[] = { - { LM3530_NAME }, - {} + { .name = LM3530_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, lm3530_id); diff --git a/drivers/leds/leds-lm3532.c b/drivers/leds/leds-lm3532.c index 24dc8ad27bb3..b51496910b08 100644 --- a/drivers/leds/leds-lm3532.c +++ b/drivers/leds/leds-lm3532.c @@ -722,8 +722,8 @@ static const struct of_device_id of_lm3532_leds_match[] = { MODULE_DEVICE_TABLE(of, of_lm3532_leds_match); static const struct i2c_device_id lm3532_id[] = { - { LM3532_NAME }, - {} + { .name = LM3532_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, lm3532_id); diff --git a/drivers/leds/leds-lm355x.c b/drivers/leds/leds-lm355x.c index f68771b9eac6..2b7cf42141e4 100644 --- a/drivers/leds/leds-lm355x.c +++ b/drivers/leds/leds-lm355x.c @@ -504,9 +504,9 @@ static void lm355x_remove(struct i2c_client *client) } static const struct i2c_device_id lm355x_id[] = { - {LM3554_NAME, CHIP_LM3554}, - {LM3556_NAME, CHIP_LM3556}, - {} + { .name = LM3554_NAME, .driver_data = CHIP_LM3554 }, + { .name = LM3556_NAME, .driver_data = CHIP_LM3556 }, + { } }; MODULE_DEVICE_TABLE(i2c, lm355x_id); diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c index 36246267b096..2a893399e05f 100644 --- a/drivers/leds/leds-lm3642.c +++ b/drivers/leds/leds-lm3642.c @@ -388,8 +388,8 @@ static void lm3642_remove(struct i2c_client *client) } static const struct i2c_device_id lm3642_id[] = { - { LM3642_NAME }, - {} + { .name = LM3642_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, lm3642_id); diff --git a/drivers/leds/leds-lm3692x.c b/drivers/leds/leds-lm3692x.c index 1d64ceb5ac85..95b850a3b31c 100644 --- a/drivers/leds/leds-lm3692x.c +++ b/drivers/leds/leds-lm3692x.c @@ -503,8 +503,8 @@ static void lm3692x_remove(struct i2c_client *client) } static const struct i2c_device_id lm3692x_id[] = { - { "lm36922", LM36922_MODEL }, - { "lm36923", LM36923_MODEL }, + { .name = "lm36922", .driver_data = LM36922_MODEL }, + { .name = "lm36923", .driver_data = LM36923_MODEL }, { } }; MODULE_DEVICE_TABLE(i2c, lm3692x_id); diff --git a/drivers/leds/leds-lm3697.c b/drivers/leds/leds-lm3697.c index 7ad232780a31..933191fb2be0 100644 --- a/drivers/leds/leds-lm3697.c +++ b/drivers/leds/leds-lm3697.c @@ -354,7 +354,7 @@ static void lm3697_remove(struct i2c_client *client) } static const struct i2c_device_id lm3697_id[] = { - { "lm3697" }, + { .name = "lm3697" }, { } }; MODULE_DEVICE_TABLE(i2c, lm3697_id); diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c index ccfeee49ea78..8e95dcedce40 100644 --- a/drivers/leds/leds-lp3944.c +++ b/drivers/leds/leds-lp3944.c @@ -417,8 +417,8 @@ static void lp3944_remove(struct i2c_client *client) /* lp3944 i2c driver struct */ static const struct i2c_device_id lp3944_id[] = { - { "lp3944" }, - {} + { .name = "lp3944" }, + { } }; MODULE_DEVICE_TABLE(i2c, lp3944_id); diff --git a/drivers/leds/leds-lp3952.c b/drivers/leds/leds-lp3952.c index 17219a582704..0a1af284f52b 100644 --- a/drivers/leds/leds-lp3952.c +++ b/drivers/leds/leds-lp3952.c @@ -266,8 +266,8 @@ static int lp3952_probe(struct i2c_client *client) } static const struct i2c_device_id lp3952_id[] = { - { LP3952_NAME }, - {} + { .name = LP3952_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, lp3952_id); diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c index e2a9c8592953..259169214aaf 100644 --- a/drivers/leds/leds-lp50xx.c +++ b/drivers/leds/leds-lp50xx.c @@ -525,6 +525,7 @@ static int lp50xx_probe_dt(struct lp50xx *priv) } mc_led_info[multi_index].color_index = color_id; + mc_led_info[multi_index].max_intensity = 255; num_colors++; } @@ -600,12 +601,12 @@ static void lp50xx_remove(struct i2c_client *client) } static const struct i2c_device_id lp50xx_id[] = { - { "lp5009", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5009] }, - { "lp5012", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5012] }, - { "lp5018", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5018] }, - { "lp5024", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5024] }, - { "lp5030", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5030] }, - { "lp5036", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5036] }, + { .name = "lp5009", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5009] }, + { .name = "lp5012", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5012] }, + { .name = "lp5018", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5018] }, + { .name = "lp5024", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5024] }, + { .name = "lp5030", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5030] }, + { .name = "lp5036", .driver_data = (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5036] }, { } }; MODULE_DEVICE_TABLE(i2c, lp50xx_id); diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index 7564b9953408..4937fc968011 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -253,7 +253,7 @@ static struct lp55xx_device_config lp5521_cfg = { }; static const struct i2c_device_id lp5521_id[] = { - { "lp5521", .driver_data = (kernel_ulong_t)&lp5521_cfg, }, /* Three channel chip */ + { .name = "lp5521", .driver_data = (kernel_ulong_t)&lp5521_cfg }, /* Three channel chip */ { } }; MODULE_DEVICE_TABLE(i2c, lp5521_id); diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 4ed3e735260c..39cb26d343c1 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -346,8 +346,8 @@ static struct lp55xx_device_config lp5523_cfg = { }; static const struct i2c_device_id lp5523_id[] = { - { "lp5523", .driver_data = (kernel_ulong_t)&lp5523_cfg, }, - { "lp55231", .driver_data = (kernel_ulong_t)&lp5523_cfg, }, + { .name = "lp5523", .driver_data = (kernel_ulong_t)&lp5523_cfg }, + { .name = "lp55231", .driver_data = (kernel_ulong_t)&lp5523_cfg }, { } }; diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c index 14a4af361b26..9e6056821eb5 100644 --- a/drivers/leds/leds-lp5562.c +++ b/drivers/leds/leds-lp5562.c @@ -395,7 +395,7 @@ static struct lp55xx_device_config lp5562_cfg = { }; static const struct i2c_device_id lp5562_id[] = { - { "lp5562", .driver_data = (kernel_ulong_t)&lp5562_cfg, }, + { .name = "lp5562", .driver_data = (kernel_ulong_t)&lp5562_cfg }, { } }; MODULE_DEVICE_TABLE(i2c, lp5562_id); diff --git a/drivers/leds/leds-lp5569.c b/drivers/leds/leds-lp5569.c index a252ba6c455d..199db3efca65 100644 --- a/drivers/leds/leds-lp5569.c +++ b/drivers/leds/leds-lp5569.c @@ -514,7 +514,7 @@ static struct lp55xx_device_config lp5569_cfg = { }; static const struct i2c_device_id lp5569_id[] = { - { "lp5569", .driver_data = (kernel_ulong_t)&lp5569_cfg, }, + { .name = "lp5569", .driver_data = (kernel_ulong_t)&lp5569_cfg }, { } }; diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c index ee4ff4586bc0..8009689db7b4 100644 --- a/drivers/leds/leds-lp8501.c +++ b/drivers/leds/leds-lp8501.c @@ -130,7 +130,7 @@ static struct lp55xx_device_config lp8501_cfg = { }; static const struct i2c_device_id lp8501_id[] = { - { "lp8501", .driver_data = (kernel_ulong_t)&lp8501_cfg, }, + { .name = "lp8501", .driver_data = (kernel_ulong_t)&lp8501_cfg }, { } }; MODULE_DEVICE_TABLE(i2c, lp8501_id); diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c index 7a436861c4b7..69f064781f69 100644 --- a/drivers/leds/leds-lp8860.c +++ b/drivers/leds/leds-lp8860.c @@ -333,7 +333,7 @@ static int lp8860_probe(struct i2c_client *client) } static const struct i2c_device_id lp8860_id[] = { - { "lp8860" }, + { .name = "lp8860" }, { } }; MODULE_DEVICE_TABLE(i2c, lp8860_id); diff --git a/drivers/leds/leds-lp8864.c b/drivers/leds/leds-lp8864.c index 3afd729d2f8a..204727f2f350 100644 --- a/drivers/leds/leds-lp8864.c +++ b/drivers/leds/leds-lp8864.c @@ -270,8 +270,8 @@ static int lp8864_probe(struct i2c_client *client) } static const struct i2c_device_id lp8864_id[] = { - { "lp8864" }, - {} + { .name = "lp8864" }, + { } }; MODULE_DEVICE_TABLE(i2c, lp8864_id); diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index 0344189bb991..f3bf59495b68 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -67,10 +67,10 @@ enum { }; static const struct i2c_device_id pca9532_id[] = { - { "pca9530", pca9530 }, - { "pca9531", pca9531 }, - { "pca9532", pca9532 }, - { "pca9533", pca9533 }, + { .name = "pca9530", .driver_data = pca9530 }, + { .name = "pca9531", .driver_data = pca9531 }, + { .name = "pca9532", .driver_data = pca9532 }, + { .name = "pca9533", .driver_data = pca9533 }, { } }; @@ -182,11 +182,13 @@ static int pca9532_set_brightness(struct led_classdev *led_cdev, int err = 0; struct pca9532_led *led = ldev_to_led(led_cdev); - if (value == LED_OFF) + if (value == LED_OFF) { led->state = PCA9532_OFF; - else if (value == LED_FULL) + } else if (led->state == PCA9532_PWM1) { + return 0; /* Non-zero brightness shall not stop HW blinking */ + } else if (value == LED_FULL) { led->state = PCA9532_ON; - else { + } else { led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */ err = pca9532_calcpwm(led->client, PCA9532_PWM_ID_0, 0, value); if (err) diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index 2007fe6217ec..273383351ba0 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -764,12 +764,12 @@ static int pca955x_probe(struct i2c_client *client) } static const struct i2c_device_id pca955x_id[] = { - { "pca9550", (kernel_ulong_t)&pca955x_chipdefs[pca9550] }, - { "pca9551", (kernel_ulong_t)&pca955x_chipdefs[pca9551] }, - { "pca9552", (kernel_ulong_t)&pca955x_chipdefs[pca9552] }, - { "ibm-pca9552", (kernel_ulong_t)&pca955x_chipdefs[ibm_pca9552] }, - { "pca9553", (kernel_ulong_t)&pca955x_chipdefs[pca9553] }, - {} + { .name = "pca9550", .driver_data = (kernel_ulong_t)&pca955x_chipdefs[pca9550] }, + { .name = "pca9551", .driver_data = (kernel_ulong_t)&pca955x_chipdefs[pca9551] }, + { .name = "pca9552", .driver_data = (kernel_ulong_t)&pca955x_chipdefs[pca9552] }, + { .name = "ibm-pca9552", .driver_data = (kernel_ulong_t)&pca955x_chipdefs[ibm_pca9552] }, + { .name = "pca9553", .driver_data = (kernel_ulong_t)&pca955x_chipdefs[pca9553] }, + { } }; MODULE_DEVICE_TABLE(i2c, pca955x_id); diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 050e93b04884..e3a81c60ee27 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -88,10 +88,10 @@ static struct pca963x_chipdef pca963x_chipdefs[] = { #define PCA963X_BLINK_PERIOD_MAX 10667 static const struct i2c_device_id pca963x_id[] = { - { "pca9632", pca9633 }, - { "pca9633", pca9633 }, - { "pca9634", pca9634 }, - { "pca9635", pca9635 }, + { .name = "pca9632", .driver_data = pca9633 }, + { .name = "pca9633", .driver_data = pca9633 }, + { .name = "pca9634", .driver_data = pca9634 }, + { .name = "pca9635", .driver_data = pca9635 }, { } }; MODULE_DEVICE_TABLE(i2c, pca963x_id); diff --git a/drivers/leds/leds-pca995x.c b/drivers/leds/leds-pca995x.c index 6ad06ce2bf64..59951207fd04 100644 --- a/drivers/leds/leds-pca995x.c +++ b/drivers/leds/leds-pca995x.c @@ -188,10 +188,10 @@ static int pca995x_probe(struct i2c_client *client) } static const struct i2c_device_id pca995x_id[] = { - { "pca9952", .driver_data = (kernel_ulong_t)&pca9952_chipdef }, - { "pca9955b", .driver_data = (kernel_ulong_t)&pca9955b_chipdef }, - { "pca9956b", .driver_data = (kernel_ulong_t)&pca9956b_chipdef }, - {} + { .name = "pca9952", .driver_data = (kernel_ulong_t)&pca9952_chipdef }, + { .name = "pca9955b", .driver_data = (kernel_ulong_t)&pca9955b_chipdef }, + { .name = "pca9956b", .driver_data = (kernel_ulong_t)&pca9956b_chipdef }, + { } }; MODULE_DEVICE_TABLE(i2c, pca995x_id); diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 4e5dd76d714d..7f68d956f694 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -9,7 +9,6 @@ #include <linux/ctype.h> #include <linux/delay.h> #include <linux/err.h> -#include <linux/gpio.h> #include <linux/i2c.h> #include <linux/leds.h> #include <linux/module.h> @@ -390,7 +389,7 @@ static int st1202_probe(struct i2c_client *client) } static const struct i2c_device_id st1202_id[] = { - { "st1202-i2c" }, + { .name = "st1202-i2c" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, st1202_id); diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index fd0e8bab9a4b..9afe2722986c 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -182,7 +182,7 @@ struct tca6507_chip { }; static const struct i2c_device_id tca6507_id[] = { - { "tca6507" }, + { .name = "tca6507" }, { } }; MODULE_DEVICE_TABLE(i2c, tca6507_id); diff --git a/drivers/leds/leds-tlc591xx.c b/drivers/leds/leds-tlc591xx.c index 6605e08a042a..2f272cfd2e91 100644 --- a/drivers/leds/leds-tlc591xx.c +++ b/drivers/leds/leds-tlc591xx.c @@ -214,9 +214,9 @@ tlc591xx_probe(struct i2c_client *client) } static const struct i2c_device_id tlc591xx_id[] = { - { "tlc59116" }, - { "tlc59108" }, - {}, + { .name = "tlc59116" }, + { .name = "tlc59108" }, + { } }; MODULE_DEVICE_TABLE(i2c, tlc591xx_id); diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 25ee5c1eb820..ed6a47bbb44f 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -532,7 +532,7 @@ static const struct of_device_id of_omnia_leds_match[] = { MODULE_DEVICE_TABLE(of, of_omnia_leds_match); static const struct i2c_device_id omnia_id[] = { - { "omnia" }, + { .name = "omnia" }, { } }; MODULE_DEVICE_TABLE(i2c, omnia_id); diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 28ef4c487367..6e9ab5f60714 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -39,6 +39,32 @@ config LEDS_LP5812 If unsure, say N. +config LEDS_LP5860_CORE + tristate "Core Driver for TI LP5860" + depends on LEDS_CLASS + depends on OF + select REGMAP + help + This option supports common operations for LP5860 devices. + The LP5860 is a LED matrix driver with 18 constant current + sinks and 11 scan switches for 198 LED dots. Each dot can be + controlled individually and supports 8/16-bit PWM dimming. + The chip supports individual LED open and short detection. + + The device can be used with SPI or I2C bus. + +config LEDS_LP5860_SPI + tristate "LED Support for TI LP5860 SPI LED driver chip" + depends on SPI + depends on OF + select LEDS_LP5860_CORE + help + If you say yes here you get support for the Texas Instruments + LP5860 LED driver for SPI bus connections. + + To compile this driver as a module, choose M here: the + module will be called leds-lp5860-spi. + config LEDS_NCP5623 tristate "LED support for NCP5623" depends on I2C @@ -75,6 +101,16 @@ 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 + help + This option enables support for the S2MU005 RGB LEDs. These devices + have three LED channels, with 8-bit brightness control for each + channel. The S2MU005 is usually found in mobile phones as 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 be45991f63f5..cc0f2df66286 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -3,7 +3,10 @@ obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o obj-$(CONFIG_LEDS_LP5812) += leds-lp5812.o +obj-$(CONFIG_LEDS_LP5860_CORE) += leds-lp5860-core.o +obj-$(CONFIG_LEDS_LP5860_SPI) += leds-lp5860-spi.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-ktd202x.c b/drivers/leds/rgb/leds-ktd202x.c index e4f0f25a5e45..143020945e23 100644 --- a/drivers/leds/rgb/leds-ktd202x.c +++ b/drivers/leds/rgb/leds-ktd202x.c @@ -605,9 +605,9 @@ static void ktd202x_shutdown(struct i2c_client *client) } static const struct i2c_device_id ktd202x_id[] = { - {"ktd2026", KTD2026_NUM_LEDS}, - {"ktd2027", KTD2027_NUM_LEDS}, - {} + { .name = "ktd2026", .driver_data = KTD2026_NUM_LEDS }, + { .name = "ktd2027", .driver_data = KTD2027_NUM_LEDS }, + { } }; MODULE_DEVICE_TABLE(i2c, ktd202x_id); diff --git a/drivers/leds/rgb/leds-lp5860-core.c b/drivers/leds/rgb/leds-lp5860-core.c new file mode 100644 index 000000000000..fd0e2f6e6e0f --- /dev/null +++ b/drivers/leds/rgb/leds-lp5860-core.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Pengutronix + * + * Author: Steffen Trumtrar <kernel@pengutronix.de> + */ + +#include <linux/led-class-multicolor.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_platform.h> +#include <linux/property.h> +#include <linux/regmap.h> + +#include "leds-lp5860.h" + +static struct lp5860_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev) +{ + return container_of(mc_cdev, struct lp5860_led, mc_cdev); +} + +static int lp5860_set_dot_onoff(struct lp5860_led *led, unsigned int dot, bool enable) +{ + unsigned int offset = dot / LP5860_MAX_DOT_ONOFF_GROUP_NUM; + unsigned int mask = BIT(dot % LP5860_MAX_DOT_ONOFF_GROUP_NUM); + + if (dot > LP5860_MAX_LED) + return -EINVAL; + + return regmap_update_bits(led->chip->regmap, + LP5860_REG_DOT_ONOFF_START + offset, mask, + enable ? LP5860_DOT_ALL_ON : LP5860_DOT_ALL_OFF); +} + +static int lp5860_set_mc_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct lp5860_led *led = mcled_cdev_to_led(mc_cdev); + + led_mc_calc_color_components(mc_cdev, brightness); + + guard(mutex)(&led->chip->lock); + for (int i = 0; i < led->mc_cdev.num_colors; i++) { + unsigned int channel = mc_cdev->subled_info[i].channel; + unsigned int led_brightness = mc_cdev->subled_info[i].brightness; + int ret; + + ret = lp5860_set_dot_onoff(led, channel, !!led_brightness); + if (ret) + return ret; + + ret = regmap_write(led->chip->regmap, + LP5860_REG_PWM_BRI_START + channel, led_brightness); + if (ret) + return ret; + } + + return 0; +} + +static int lp5860_chip_enable(struct lp5860 *lp, bool enable) +{ + guard(mutex)(&lp->lock); + return regmap_write(lp->regmap, LP5860_REG_CHIP_EN, enable); +} + +static int lp5860_led_init(struct lp5860_led *led, struct fwnode_handle *fwnode, + unsigned int channel) +{ + enum led_default_state default_state; + unsigned int brightness; + int ret; + + guard(mutex)(&led->chip->lock); + ret = regmap_read(led->chip->regmap, LP5860_REG_PWM_BRI_START + channel, &brightness); + if (ret) + return ret; + + default_state = led_init_default_state_get(fwnode); + + switch (default_state) { + case LEDS_DEFSTATE_ON: + led->brightness = LP5860_MAX_BRIGHTNESS; + break; + case LEDS_DEFSTATE_KEEP: + led->brightness = min(brightness, LP5860_MAX_BRIGHTNESS); + break; + default: + led->brightness = 0; + break; + } + + return 0; +} + +static int lp5860_iterate_subleds(struct lp5860_led *led, struct led_init_data *init_data) +{ + struct fwnode_handle *led_node = NULL; + struct fwnode_handle *multi_led = init_data->fwnode; + int subled = 0; + + fwnode_for_each_child_node(multi_led, led_node) { + u32 channel; + u32 color_index; + int ret; + + ret = fwnode_property_read_u32(led_node, "color", &color_index); + if (ret) { + dev_err_probe(led->chip->dev, ret, + "%pfwP: Cannot read 'color' property. Skipping.\n", led_node); + fwnode_handle_put(led_node); + return ret; + } + + ret = fwnode_property_read_u32(led_node, "reg", &channel); + if (ret < 0 || channel > LP5860_MAX_LED) { + dev_err_probe(led->chip->dev, ret, + "%pfwP: 'reg' property is missing. Skipping.\n", led_node); + fwnode_handle_put(led_node); + return ret; + } + + led->mc_cdev.subled_info[subled].color_index = color_index; + led->mc_cdev.subled_info[subled].channel = channel; + ret = lp5860_led_init(led, init_data->fwnode, channel); + if (ret) { + dev_err_probe(led->chip->dev, ret, + "%pfwP: Failed to init LED\n", led_node); + fwnode_handle_put(led_node); + return ret; + } + + subled++; + } + + return 0; +} + +static int lp5860_init_dt(struct lp5860 *lp) +{ + struct led_init_data init_data = {}; + struct led_classdev *led_cdev; + struct mc_subled *mc_led_info; + struct lp5860_led *led; + int led_index = 0; + int chan; + int ret; + + device_for_each_child_node_scoped(lp->dev, multi_led) { + led = &lp->leds[led_index]; + + init_data.fwnode = multi_led; + + /* Count the number of channels in this multi_led */ + chan = fwnode_get_child_node_count(multi_led); + if (!chan || chan > LP5860_MAX_LED_CHANNELS) + return -EINVAL; + + led->mc_cdev.num_colors = chan; + + mc_led_info = devm_kcalloc(lp->dev, chan, sizeof(*mc_led_info), GFP_KERNEL); + if (!mc_led_info) + return -ENOMEM; + + led->chip = lp; + led->mc_cdev.subled_info = mc_led_info; + led_cdev = &led->mc_cdev.led_cdev; + led_cdev->max_brightness = LP5860_MAX_BRIGHTNESS; + led_cdev->brightness_set_blocking = lp5860_set_mc_brightness; + + ret = lp5860_iterate_subleds(led, &init_data); + if (ret) + continue; + + ret = lp5860_set_mc_brightness(&led->mc_cdev.led_cdev, led->brightness); + if (ret) + return dev_err_probe(lp->dev, ret, "%pfwP: Failed to set Multi-Color brightness\n", + multi_led); + + ret = devm_led_classdev_multicolor_register_ext(lp->dev, &led->mc_cdev, &init_data); + if (ret) + return dev_err_probe(lp->dev, ret, "%pfwP: Failed to register Multi-Color LEDs\n", + multi_led); + led_index++; + } + + return 0; +} + +int lp5860_device_init(struct device *dev) +{ + struct lp5860 *lp = dev_get_drvdata(dev); + int ret; + + ret = lp5860_chip_enable(lp, LP5860_CHIP_ENABLE); + if (ret) + return ret; + + /* + * Set to 8-bit PWM data without VSYNC. + * Data is sent out for display instantly after received. + */ + mutex_lock(&lp->lock); + ret = regmap_update_bits(lp->regmap, LP5860_REG_DEV_INITIAL, LP5860_MODE_MASK, + LP5860_MODE_1 << LP5860_MODE_SHIFT); + if (ret) + goto err_disable; + mutex_unlock(&lp->lock); + + ret = lp5860_init_dt(lp); + if (ret) + goto err_disable; + + return 0; + +err_disable: + mutex_unlock(&lp->lock); + lp5860_chip_enable(lp, LP5860_CHIP_DISABLE); + return ret; +} +EXPORT_SYMBOL_GPL(lp5860_device_init); + +void lp5860_device_remove(struct device *dev) +{ + struct lp5860 *lp = dev_get_drvdata(dev); + + lp5860_chip_enable(lp, LP5860_CHIP_DISABLE); +} +EXPORT_SYMBOL_GPL(lp5860_device_remove); + +MODULE_AUTHOR("Steffen Trumtrar <kernel@pengutronix.de>"); +MODULE_DESCRIPTION("TI LP5860 RGB LED core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/rgb/leds-lp5860-spi.c b/drivers/leds/rgb/leds-lp5860-spi.c new file mode 100644 index 000000000000..5e0c44854a68 --- /dev/null +++ b/drivers/leds/rgb/leds-lp5860-spi.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Pengutronix + * + * Author: Steffen Trumtrar <kernel@pengutronix.de> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> + +#include "leds-lp5860.h" + +#define LP5860_SPI_WRITE_FLAG BIT(13) + +/* + * The lp5860 uses a rather uncommon SPI data format: The R/W flag is on BIT(5) in the two address + * bytes; BIT(4) to BIT(0) are don't care. Therefore it has 10 bits for the address and 6 bits for + * padding the address. The address bytes are sent MSB first. Matching the cores registers to regmap + * results in write_flag_mask being BIT(13). + */ +static const struct regmap_config lp5860_regmap_config = { + .name = "lp5860", + .reg_bits = 10, + .pad_bits = 6, + .val_bits = 8, + .write_flag_mask = LP5860_SPI_WRITE_FLAG, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .max_register = LP5860_MAX_REG, +}; + +static int lp5860_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct lp5860 *lp5860; + unsigned int multi_leds; + + multi_leds = device_get_child_node_count(dev); + if (!multi_leds) { + dev_err(dev, "LEDs are not defined in Device Tree!"); + return -ENODEV; + } + + if (multi_leds > LP5860_MAX_LED) { + dev_err(dev, "Too many LEDs specified.\n"); + return -EINVAL; + } + + lp5860 = devm_kzalloc(dev, struct_size(lp5860, leds, multi_leds), + GFP_KERNEL); + if (!lp5860) + return -ENOMEM; + + lp5860->regmap = devm_regmap_init_spi(spi, &lp5860_regmap_config); + if (IS_ERR(lp5860->regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(lp5860->regmap), + "Failed to initialise Regmap.\n"); + + lp5860->dev = dev; + mutex_init(&lp5860->lock); + + spi_set_drvdata(spi, lp5860); + + return lp5860_device_init(dev); +} + +static void lp5860_remove(struct spi_device *spi) +{ + struct lp5860 *lp5860 = spi_get_drvdata(spi); + + mutex_destroy(&lp5860->lock); + + lp5860_device_remove(&spi->dev); +} + +static const struct of_device_id lp5860_of_match[] = { + { .compatible = "ti,lp5860" }, + {} +}; +MODULE_DEVICE_TABLE(of, lp5860_of_match); + +static struct spi_driver lp5860_driver = { + .driver = { + .name = "lp5860-spi", + .of_match_table = lp5860_of_match, + }, + .probe = lp5860_probe, + .remove = lp5860_remove, +}; +module_spi_driver(lp5860_driver); + +MODULE_AUTHOR("Steffen Trumtrar <kernel@pengutronix.de>"); +MODULE_DESCRIPTION("TI LP5860 RGB LED SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/rgb/leds-lp5860.h b/drivers/leds/rgb/leds-lp5860.h new file mode 100644 index 000000000000..940be0c6e8da --- /dev/null +++ b/drivers/leds/rgb/leds-lp5860.h @@ -0,0 +1,268 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2025 Pengutronix + * + * Author: Steffen Trumtrar <kernel@pengutronix.de> + */ + +#ifndef _DRIVERS_LEDS_RGB_LP5860_H +#define _DRIVERS_LEDS_RGB_LP5860_H + +#include <linux/led-class-multicolor.h> +#include <linux/regmap.h> + +#define LP5860_REG_CHIP_EN 0x00 +#define LP5860_REG_DEV_INITIAL 0x01 +#define LP5860_REG_DEV_CONFIG1 0x02 +#define LP5860_REG_DEV_CONFIG2 0x03 +#define LP5860_REG_DEV_CONFIG3 0x04 +#define LP5860_REG_GLOBAL_BRI 0x05 +#define LP5860_REG_GROUP0_BRI 0x06 +#define LP5860_REG_GROUP1_BRI 0x07 +#define LP5860_REG_GROUP2_BRI 0x08 +#define LP5860_REG_R_CURRENT_SET 0x09 +#define LP5860_REG_G_CURRENT_SET 0x0A +#define LP5860_REG_B_CURRENT_SET 0x0B +#define LP5860_REG_GRP_SEL_START 0x0C +#define LP5860_REG_DOT_ONOFF_START 0x43 +#define LP5860_REG_DOT_ONOFF_MAX 0x63 +#define LP5860_REG_FAULT_STATE 0x64 +#define LP5860_REG_DOT_LOD_START 0x65 +#define LP5860_REG_DOT_LSD_START 0x86 +#define LP5860_REG_LOD_CLEAR 0xA7 +#define LP5860_REG_LSD_CLEAR 0xA8 +#define LP5860_REG_RESET 0xA9 +#define LP5860_REG_DC_START 0x0100 +#define LP5860_REG_PWM_BRI_START 0x0200 +#define LP5860_MAX_REG 0x038B + +/* Register chip_enable value */ +#define LP5860_CHIP_SHIFT 0 +#define LP5860_CHIP_MASK BIT(0) +#define LP5860_CHIP_DISABLE false +#define LP5860_CHIP_ENABLE true + +/* Register dev_initial value */ +#define LP5860_MAX_LINE_SHIFT 3 +#define LP5860_MAX_LINE_MASK GENMASK(6, 3) +#define LP5860_MAX_LINE_11 0x0B +#define LP5860_MAX_LINE_10 0x0A +#define LP5860_MAX_LINE_9 0x09 +#define LP5860_MAX_LINE_8 0x08 +#define LP5860_MAX_LINE_7 0x07 +#define LP5860_MAX_LINE_6 0x06 +#define LP5860_MAX_LINE_5 0x05 +#define LP5860_MAX_LINE_4 0x04 +#define LP5860_MAX_LINE_3 0x03 +#define LP5860_MAX_LINE_2 0x02 +#define LP5860_MAX_LINE_1 0x01 + +#define LP5860_MODE_SHIFT 1 +#define LP5860_MODE_MASK GENMASK(2, 1) +#define LP5860_MODE_3_1 0x03 +#define LP5860_MODE_3 0x02 +#define LP5860_MODE_2 0x01 +#define LP5860_MODE_1 0x00 + +#define LP5860_PWM_FREQUENCY_SHIFT 0 +#define LP5860_PWM_FREQUENCY_MASK BIT(0) +#define LP5860_PWM_FREQUENCY_62_5K 0x01 +#define LP5860_PWM_FREQUENCY_125K 0x00 + +/* Register dev_config1 value */ +#define LP5860_SW_BLK_SHIFT 3 +#define LP5860_SW_BLK_MASK BIT(3) +#define LP5860_SW_BLK_05US 0x01 +#define LP5860_SW_BLK_1US 0x00 + +#define LP5860_PWM_SCALE_MODE_SHIFT 2 +#define LP5860_PWM_SCALE_MODE_MASK BIT(2) +#define LP5860_PWM_SCALE_EXPONENTIAL 0x01 +#define LP5860_PWM_SCALE_LINEAR 0x00 + +#define LP5860_PWM_PHASESHIFT_SHIFT 1 +#define LP5860_PWM_PHASESHIFT_MASK BIT(1) +#define LP5860_PWM_PHASESHIFT_ON 0x01 +#define LP5860_PWM_PHASESHIFT_OFF 0x00 + +#define LP5860_CS_ON_SHIFT_SHIFT 0 +#define LP5860_CS_ON_SHIFT_MASK BIT(0) +#define LP5860_CS_DELAY_ON 0x01 +#define LP5860_CS_DELAY_OFF 0x00 + +/* Register dev_config2 value */ +#define LP5860_COMP_GROUP3_SHIFT 6 +#define LP5860_COMP_GROUP3_MASK GENMASK(7, 6) +#define LP5860_COMP_GROUP3_3CLOCK 0x03 +#define LP5860_COMP_GROUP3_2CLOCK 0x02 +#define LP5860_COMP_GROUP3_1CLOCK 0x01 +#define LP5860_COMP_GROUP3_OFF 0x00 + +#define LP5860_COMP_GROUP2_SHIFT 4 +#define LP5860_COMP_GROUP2_MASK GENMASK(5, 4) +#define LP5860_COMP_GROUP2_3CLOCK 0x03 +#define LP5860_COMP_GROUP2_2CLOCK 0x02 +#define LP5860_COMP_GROUP2_1CLOCK 0x01 +#define LP5860_COMP_GROUP2_OFF 0x00 + +#define LP5860_COMP_GROUP1_SHIFT 2 +#define LP5860_COMP_GROUP1_MASK GENMASK(3, 2) +#define LP5860_COMP_GROUP1_3CLOCK 0x03 +#define LP5860_COMP_GROUP1_2CLOCK 0x02 +#define LP5860_COMP_GROUP1_1CLOCK 0x01 +#define LP5860_COMP_GROUP1_OFF 0x00 + +#define LP5860_LOD_REMOVAL_SHIFT 1 +#define LP5860_LOD_REMOVAL_MASK BIT(1) +#define LP5860_LOD_REMOVAL_EN 0x01 +#define LP5860_LOD_REMOVAL_OFF 0x00 + +#define LP5860_LSD_REMOVAL_SHIFT 0 +#define LP5860_LSD_REMOVAL_MASK BIT(0) +#define LP5860_LSD_REMOVAL_EN 0x01 +#define LP5860_LSD_REMOVAL_OFF 0x00 + +/* Register dev_config3 value */ +#define LP5860_DOWN_DEGHOST_SHIFT 6 +#define LP5860_DOWN_DEGHOST_MASK GENMASK(7, 6) +#define LP5860_DOWN_DEGHOST_STRONG 0x03 +#define LP5860_DOWN_DEGHOST_MEDIUM 0x02 +#define LP5860_DOWN_DEGHOST_WEAK 0x01 +#define LP5860_DOWN_DEGHOST_OFF 0x00 + +#define LP5860_UP_DEGHOST_SHIFT 4 +#define LP5860_UP_DEGHOST_MASK GENMASK(5, 4) +#define LP5860_UP_DEGHOST_GND 0x03 +#define LP5860_UP_DEGHOST_3 0x02 +#define LP5860_UP_DEGHOST_2_5 0x01 +#define LP5860_UP_DEGHOST_2 0x00 + +#define LP5860_MAXIMUM_CURRENT_SHIFT 1 +#define LP5860_MAXIMUM_CURRENT_MASK GENMASK(3, 1) +#define LP5860_MAXIMUM_CURRENT_50 0x07 +#define LP5860_MAXIMUM_CURRENT_40 0x06 +#define LP5860_MAXIMUM_CURRENT_30 0x05 +#define LP5860_MAXIMUM_CURRENT_20 0x04 +#define LP5860_MAXIMUM_CURRENT_15 0x03 +#define LP5860_MAXIMUM_CURRENT_10 0x02 +#define LP5860_MAXIMUM_CURRENT_5 0x01 +#define LP5860_MAXIMUM_CURRENT_3 0x00 + +#define LP5860_UP_DEGHOST_ENABLE_SHIFT 0 +#define LP5860_UP_DEGHOST_ENABLE_MASK BIT(0) +#define LP5860_UP_DEGHOST_ENABLE_EN 0x01 +#define LP5860_UP_DEGHOST_ENABLE_OFF 0x00 + +/* Register PWM */ +#define LP5860_PWM_GLOBAL_MAX 0xff +#define LP5860_PWM_GROUP_MAX 0xff + +/* Register CC group select */ +#define LP5860_CC_GROUP_MASK GENMASK(7, 0) +#define LP5860_CC_GROUP_MAX 0x7F + +/* Register dot group select */ +#define LP5860_DOT_0_SHIFT 0 +#define LP5860_DOT_1_SHIFT 2 +#define LP5860_DOT_2_SHIFT 4 +#define LP5860_DOT_3_SHIFT 6 + +#define LP5860_DOT_GROUP3 0x03 +#define LP5860_DOT_GROUP2 0x02 +#define LP5860_DOT_GROUP1 0x01 +#define LP5860_DOT_GROUP_NONE 0x00 + +#define LP5860_DOT_ALL_ON 0xff +#define LP5860_DOT_ALL_OFF 0x0 +#define LP5860_PWM_DOT_MAX 0xff +/* Dot onoff value */ +#define LP5860_DOT_CS0_SHIFT 0 +#define LP5860_DOT_CS1_SHIFT 1 +#define LP5860_DOT_CS2_SHIFT 2 +#define LP5860_DOT_CS3_SHIFT 3 +#define LP5860_DOT_CS4_SHIFT 4 +#define LP5860_DOT_CS5_SHIFT 5 +#define LP5860_DOT_CS6_SHIFT 6 +#define LP5860_DOT_CS7_SHIFT 7 + +#define LP5860_DOT_CS_ON 0x01 +#define LP5860_DOT_CS_OFF 0x00 + +/* Dot lod value */ +#define LP5860_DOT_LOD0_SHIFT 0 +#define LP5860_DOT_LOD1_SHIFT 1 +#define LP5860_DOT_LOD2_SHIFT 2 +#define LP5860_DOT_LOD3_SHIFT 3 +#define LP5860_DOT_LOD4_SHIFT 4 +#define LP5860_DOT_LOD5_SHIFT 5 +#define LP5860_DOT_LOD6_SHIFT 6 +#define LP5860_DOT_LOD7_SHIFT 7 + +#define LP5860_DOT_LOD_ON 0x01 +#define LP5860_DOT_LOD_OFF 0x00 + +/* dot lsd value */ +#define LP5860_DOT_LSD0_SHIFT 0 +#define LP5860_DOT_LSD1_SHIFT 1 +#define LP5860_DOT_LSD2_SHIFT 2 +#define LP5860_DOT_LSD3_SHIFT 3 +#define LP5860_DOT_LSD4_SHIFT 4 +#define LP5860_DOT_LSD5_SHIFT 5 +#define LP5860_DOT_LSD6_SHIFT 6 +#define LP5860_DOT_LSD7_SHIFT 7 + +#define LP5860_DOT_LSD_ON 0x01 +#define LP5860_DOT_LSD_OFF 0x00 + +/* Register lod state */ +#define LP5860_GLOBAL_LOD_SHIFT 1 +#define LP5860_GLOBAL_LOD_STATE BIT(1) +#define LP5860_GLOBAL_LSD_SHIFT 0 +#define LP5860_GLOBAL_LSD_STATE BIT(0) + +#define LP5860_FAULT_STATE_ON 0x01 +#define LP5860_FAULT_STATE_OFF 0x00 + +#define LP5860_GLOBAL_LOD_CLEAR 0x00 +#define LP5860_GLOBAL_LSD_CLEAR 0x00 + + +#define LP5860_LOD_CLEAR_EN 0xff +#define LP5860_LSD_CLEAR_EN 0xff +#define LP5860_RESET_EN 0xff + +#define LP5860_MAX_BRIGHTNESS 255 +#define LP5860_REG_R_PWM 0x0 +#define LP5860_REG_G_PWM 0x1 +#define LP5860_REG_B_PWM 0x2 + +#define LP5860_MAX_LED_CONSTANT 18 +#define LP5860_MAX_LED_SCAN 11 +#define LP5860_MAX_LED (LP5860_MAX_LED_CONSTANT * LP5860_MAX_LED_SCAN) + +#define LP5860_MAX_DOT_ONOFF_GROUP_NUM 8 + +/* + * Theoretically, there is no max channel per LED, + * limit this to a reasonable value for RGBW LEDs + */ +#define LP5860_MAX_LED_CHANNELS 4 + +struct lp5860_led { + struct lp5860 *chip; + struct led_classdev_mc mc_cdev; + u8 brightness; +}; + +struct lp5860 { + struct device *dev; + struct regmap *regmap; + struct mutex lock; + + DECLARE_FLEX_ARRAY(struct lp5860_led, leds); +}; + +int lp5860_device_init(struct device *dev); +void lp5860_device_remove(struct device *dev); + +#endif /* _DRIVERS_LEDS_RGB_LP5860_H */ diff --git a/drivers/leds/rgb/leds-ncp5623.c b/drivers/leds/rgb/leds-ncp5623.c index 85d6be6fff2b..f2528f06507d 100644 --- a/drivers/leds/rgb/leds-ncp5623.c +++ b/drivers/leds/rgb/leds-ncp5623.c @@ -56,8 +56,7 @@ static int ncp5623_brightness_set(struct led_classdev *cdev, for (int i = 0; i < mc_cdev->num_colors; i++) { ret = ncp5623_write(ncp->client, NCP5623_PWM_REG(mc_cdev->subled_info[i].channel), - min(mc_cdev->subled_info[i].intensity, - NCP5623_MAX_BRIGHTNESS)); + mc_cdev->subled_info[i].intensity); if (ret) return ret; } @@ -190,6 +189,7 @@ static int ncp5623_probe(struct i2c_client *client) goto release_led_node; subled_info[ncp->mc_dev.num_colors].channel = reg; + subled_info[ncp->mc_dev.num_colors].max_intensity = NCP5623_MAX_BRIGHTNESS; subled_info[ncp->mc_dev.num_colors++].color_index = color_index; } diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index f6061c47f863..d7d6518de30f 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -80,59 +80,9 @@ #define SDAM_PAUSE_HI_MULTIPLIER_OFFSET 0x8 #define SDAM_PAUSE_LO_MULTIPLIER_OFFSET 0x9 -struct lpg_channel; struct lpg_data; /** - * struct lpg - LPG device context - * @dev: pointer to LPG device - * @map: regmap for register access - * @lock: used to synchronize LED and pwm callback requests - * @pwm: PWM-chip object, if operating in PWM mode - * @data: reference to version specific data - * @lut_base: base address of the LUT block (optional) - * @lut_size: number of entries in the LUT block - * @lut_bitmap: allocation bitmap for LUT entries - * @pbs_dev: PBS device - * @lpg_chan_sdam: LPG SDAM peripheral device - * @lut_sdam: LUT SDAM peripheral device - * @pbs_en_bitmap: bitmap for tracking PBS triggers - * @triled_base: base address of the TRILED block (optional) - * @triled_src: power-source for the TRILED - * @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register - * @triled_has_src_sel: true if there is TRI_LED_SRC_SEL register - * @channels: list of PWM channels - * @num_channels: number of @channels - */ -struct lpg { - struct device *dev; - struct regmap *map; - - struct mutex lock; - - struct pwm_chip *pwm; - - const struct lpg_data *data; - - u32 lut_base; - u32 lut_size; - unsigned long *lut_bitmap; - - struct pbs_dev *pbs_dev; - struct nvmem_device *lpg_chan_sdam; - struct nvmem_device *lut_sdam; - unsigned long pbs_en_bitmap; - - u32 triled_base; - u32 triled_src; - bool triled_has_atc_ctl; - bool triled_has_src_sel; - - struct lpg_channel *channels; - unsigned int num_channels; -}; - -/** * struct lpg_channel - per channel data * @lpg: reference to parent lpg * @base: base address of the PWM channel @@ -203,8 +153,8 @@ struct lpg_channel { * @lpg: lpg context reference * @cdev: LED class device * @mcdev: Multicolor LED class device - * @num_channels: number of @channels * @channels: list of channels associated with the LED + * @num_channels: number of @channels */ struct lpg_led { struct lpg *lpg; @@ -217,6 +167,55 @@ struct lpg_led { }; /** + * struct lpg - LPG device context + * @dev: pointer to LPG device + * @map: regmap for register access + * @lock: used to synchronize LED and pwm callback requests + * @pwm: PWM-chip object, if operating in PWM mode + * @data: reference to version specific data + * @lut_base: base address of the LUT block (optional) + * @lut_size: number of entries in the LUT block + * @lut_bitmap: allocation bitmap for LUT entries + * @pbs_dev: PBS device + * @lpg_chan_sdam: LPG SDAM peripheral device + * @lut_sdam: LUT SDAM peripheral device + * @pbs_en_bitmap: bitmap for tracking PBS triggers + * @triled_base: base address of the TRILED block (optional) + * @triled_src: power-source for the TRILED + * @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register + * @triled_has_src_sel: true if there is TRI_LED_SRC_SEL register + * @num_channels: number of @channels + * @channels: list of PWM channels + */ +struct lpg { + struct device *dev; + struct regmap *map; + + struct mutex lock; + + struct pwm_chip *pwm; + + const struct lpg_data *data; + + u32 lut_base; + u32 lut_size; + unsigned long *lut_bitmap; + + struct pbs_dev *pbs_dev; + struct nvmem_device *lpg_chan_sdam; + struct nvmem_device *lut_sdam; + unsigned long pbs_en_bitmap; + + u32 triled_base; + u32 triled_src; + bool triled_has_atc_ctl; + bool triled_has_src_sel; + + unsigned int num_channels; + struct lpg_channel channels[] __counted_by(num_channels); +}; + +/** * struct lpg_channel_data - per channel initialization data * @sdam_offset: Channel offset in LPG SDAM * @base: base address for PWM channel registers @@ -1475,12 +1474,6 @@ static int lpg_init_channels(struct lpg *lpg) struct lpg_channel *chan; int i; - lpg->num_channels = data->num_channels; - lpg->channels = devm_kcalloc(lpg->dev, data->num_channels, - sizeof(struct lpg_channel), GFP_KERNEL); - if (!lpg->channels) - return -ENOMEM; - for (i = 0; i < data->num_channels; i++) { chan = &lpg->channels[i]; @@ -1603,18 +1596,21 @@ static int lpg_init_sdam(struct lpg *lpg) static int lpg_probe(struct platform_device *pdev) { + const struct lpg_data *data; struct lpg *lpg; int ret; int i; - lpg = devm_kzalloc(&pdev->dev, sizeof(*lpg), GFP_KERNEL); + data = of_device_get_match_data(&pdev->dev); + if (!data) + return -EINVAL; + + lpg = devm_kzalloc(&pdev->dev, struct_size(lpg, channels, data->num_channels), GFP_KERNEL); if (!lpg) return -ENOMEM; - lpg->data = of_device_get_match_data(&pdev->dev); - if (!lpg->data) - return -EINVAL; - + lpg->num_channels = data->num_channels; + lpg->data = data; lpg->dev = &pdev->dev; mutex_init(&lpg->lock); diff --git a/drivers/leds/rgb/leds-s2m-rgb.c b/drivers/leds/rgb/leds-s2m-rgb.c new file mode 100644 index 000000000000..d239f54eee90 --- /dev/null +++ b/drivers/leds/rgb/leds-s2m-rgb.c @@ -0,0 +1,426 @@ +// 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; + /* + * 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; + + 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) { + dev_err(rgb->dev, "failed to reset RGB LEDs\n"); + return ret; + } + + for (int i = 0; i < rgb->mc.num_colors; i++) { + ret = regmap_write(regmap, S2MU005_REG_RGB_CH_CTRL(i), + rgb->mc.subled_info[i].brightness); + if (ret) { + 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) { + 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) { + dev_err(rgb->dev, "failed to set stay timings\n"); + return ret; + } + } + + ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_SLOPE_SMOOTH); + if (ret) { + 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) { + 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; +} + +/* + * s2m_rgb_lut_get_closest_duration - find closest duration in look-up table + * @lut: the look-up table to search for the closest timing + * @len: number of elements in the look-up table array + * @duration: the timing duration requested + * + * This function does a binary search on the given array, and finds the closest + * value to the requested timing. It is expected that the look-up table to be + * provided, is already sorted. + * + * This function returns a negative error code, or a non-negative index of the + * value in the look-up table closest to the one requested. + */ +static int s2m_rgb_lut_get_closest_duration(const u32 *lut, const size_t len, const u32 duration) +{ + u32 closest_distance = abs(duration - lut[0]); + int closest_index = 0; + int lo = 0; + int hi = len - 1; + + /* + * Allow a small amount of extrapolation beyond the highest timing value. + * + * Consider x and y to be the two last values in the table, and x < y. + * Since (y - x) / 2 integers, in the range [x + (y - x) / 2, y) + * returns y as the closest, allow extrapolation for the succeeding + * (y - x) / 2 integers as well, viz, up to (y, y + (y - x) / 2]. + * Anything beyond that is invalid. + */ + if (len >= 2 && duration > lut[len - 1] + (lut[len - 1] - lut[len - 2]) / 2) + return -EINVAL; + + while (lo <= hi) { + int mid = lo + (hi - lo) / 2; + + /* Narrow down search window as per binary-search algorithm. */ + if (duration < lut[mid]) + hi = mid - 1; + else + lo = mid + 1; + + if (abs(duration - lut[mid]) < closest_distance) { + closest_distance = abs(duration - lut[mid]); + closest_index = mid; + } + } + + return closest_index; +} + +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 = false, ramp_dn_en = false; + int ret; + + /* + * 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 (int i = 0; i < len; i++) { + int brightness = pattern[i].brightness; + u32 delta_t = pattern[i].delta_t; + + if (brightness) { + /* + * The pattern should 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; + } + } + + /* LUTs are specific to device variant. */ + 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); + + 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_get_closest_duration(lut_ramp_up, lut_ramp_up_len, time_hi / 3); + if (ret < 0) + goto param_fail; + rgb->ramp_up = (u8)ret; + } + + if (ramp_dn_en) { + ret = s2m_rgb_lut_get_closest_duration(lut_ramp_dn, lut_ramp_dn_len, time_hi / 3); + if (ret < 0) + goto param_fail; + rgb->ramp_dn = (u8)ret; + } + + /* + * 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_get_closest_duration(lut_stay_hi, lut_stay_hi_len, time_hi); + if (ret < 0) + goto param_fail; + rgb->stay_hi = (u8)ret; + + ret = s2m_rgb_lut_get_closest_duration(lut_stay_lo, lut_stay_lo_len, time_lo); + if (ret < 0) + goto param_fail; + rgb->stay_lo = (u8)ret; + + led_mc_calc_color_components(&rgb->mc, brightness_peak); + /* Apply params with variant-specific implementation. */ + ret = s2mu005_rgb_apply_params(rgb); + if (ret) + goto param_fail; + + 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); + + /* Reset params with variant-specific implementation. */ + ret = s2mu005_rgb_reset_params(rgb); + + 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); + /* Apply params with variant-specific implementation. */ + ret = s2mu005_rgb_apply_params(rgb); + + mutex_unlock(&rgb->lock); + + return ret; +} + +static const struct mc_subled s2mu005_rgb_subled_info[] = { + { .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; + + /* Configure variant-specific details. */ + rgb->mc.num_colors = ARRAY_SIZE(s2mu005_rgb_subled_info); + rgb->mc.subled_info = devm_kmemdup(dev, s2mu005_rgb_subled_info, + sizeof(s2mu005_rgb_subled_info), GFP_KERNEL); + if (!rgb->mc.subled_info) + return -ENOMEM; + + 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) + 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"); +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index fc911dfec0ef..3b3e6869e856 100644 --- a/drivers/leds/trigger/ledtrig-gpio.c +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -86,7 +86,8 @@ static int gpio_trig_activate(struct led_classdev *led) * The generic property "trigger-sources" is followed, * and we hope that this is a GPIO. */ - gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", GPIOD_IN); + gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", + GPIOD_IN | GPIOD_FLAGS_BIT_NONEXCLUSIVE); if (IS_ERR(gpio_data->gpiod)) { ret = PTR_ERR(gpio_data->gpiod); kfree(gpio_data); diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c index ace71ffc0591..6affa581b61d 100644 --- a/drivers/leds/uleds.c +++ b/drivers/leds/uleds.c @@ -102,7 +102,8 @@ static ssize_t uleds_write(struct file *file, const char __user *buffer, name = udev->user_dev.name; if (!name[0] || !strcmp(name, ".") || !strcmp(name, "..") || - strchr(name, '/')) { + strnchr(name, sizeof(udev->user_dev.name), '/') || + !strnchr(name, sizeof(udev->user_dev.name), '\0')) { ret = -EINVAL; goto out; } @@ -147,10 +148,13 @@ static ssize_t uleds_read(struct file *file, char __user *buffer, size_t count, } else if (!udev->new_data && (file->f_flags & O_NONBLOCK)) { retval = -EAGAIN; } else if (udev->new_data) { - retval = copy_to_user(buffer, &udev->brightness, - sizeof(udev->brightness)); - udev->new_data = false; - retval = sizeof(udev->brightness); + if (copy_to_user(buffer, &udev->brightness, + sizeof(udev->brightness))) { + retval = -EFAULT; + } else { + udev->new_data = false; + retval = sizeof(udev->brightness); + } } mutex_unlock(&udev->mutex); |
