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 | |
| 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
...
67 files changed, 2106 insertions, 211 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led index 0313b82644f2..d4c918cc11a1 100644 --- a/Documentation/ABI/testing/sysfs-class-led +++ b/Documentation/ABI/testing/sysfs-class-led @@ -22,8 +22,8 @@ Description: For additional details please refer to Documentation/leds/leds-class-multicolor.rst. - The value is between 0 and - /sys/class/leds/<led>/max_brightness. + The value is between 0 and /sys/class/leds/<led>/max_brightness + and is represented by as a decimal. Writing 0 to this file clears active trigger. diff --git a/Documentation/ABI/testing/sysfs-class-led-multicolor b/Documentation/ABI/testing/sysfs-class-led-multicolor index 16fc827b10cb..62ce58f393d6 100644 --- a/Documentation/ABI/testing/sysfs-class-led-multicolor +++ b/Documentation/ABI/testing/sysfs-class-led-multicolor @@ -16,9 +16,22 @@ Date: March 2020 KernelVersion: 5.9 Contact: Dan Murphy <dmurphy@ti.com> Description: read/write - This file contains array of integers. Order of components is - described by the multi_index array. The maximum intensity should - not exceed /sys/class/leds/<led>/max_brightness. + This file contains an array of integers. The order of components + is described by the multi_index array. The maximum intensity value + supported by each color component is described by the multi_max_intensity + file. Writing intensity values larger than the maximum value of a + given color component will result in those values being clamped. + + For additional details please refer to + Documentation/leds/leds-class-multicolor.rst. + +What: /sys/class/leds/<led>/multi_max_intensity +Date: May 2026 +KernelVersion: 7.2 +Contact: Armin Wolf <W_Armin@gmx.de> +Description: read + This file contains an array of integers describing the maximum + intensity value for each intensity component. For additional details please refer to Documentation/leds/leds-class-multicolor.rst. diff --git a/Documentation/devicetree/bindings/leds/irled/ir-spi-led.yaml b/Documentation/devicetree/bindings/leds/irled/ir-spi-led.yaml index 72cadebf6e3e..0297bfbb2750 100644 --- a/Documentation/devicetree/bindings/leds/irled/ir-spi-led.yaml +++ b/Documentation/devicetree/bindings/leds/irled/ir-spi-led.yaml @@ -25,7 +25,7 @@ properties: duty-cycle: $ref: /schemas/types.yaml#/definitions/uint8 - enum: [50, 60, 70, 75, 80, 90] + enum: [30, 50, 60, 70, 75, 80, 90] description: Percentage of one period in which the signal is active. diff --git a/Documentation/devicetree/bindings/leds/samsung,s2mu005-flash.yaml b/Documentation/devicetree/bindings/leds/samsung,s2mu005-flash.yaml new file mode 100644 index 000000000000..36051ab20509 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/samsung,s2mu005-flash.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/samsung,s2mu005-flash.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Flash and Torch LED Controller for Samsung S2M series PMICs + +maintainers: + - Kaustabh Chakraborty <kauschluss@disroot.org> + +description: | + The Samsung S2M series PMIC flash LED has two led channels (typically + as back and front camera flashes), with support for both torch and + flash modes. + + This is a part of device tree bindings for S2M and S5M family of Power + Management IC (PMIC). + + See also Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml for + additional information and example. + +properties: + compatible: + enum: + - samsung,s2mu005-flash + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^led@[0-1]$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + enum: [0, 1] + + required: + - reg + +required: + - compatible + - "#address-cells" + - "#size-cells" + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/leds/ti,lm3560.yaml b/Documentation/devicetree/bindings/leds/ti,lm3560.yaml new file mode 100644 index 000000000000..6cf8cf91ab2e --- /dev/null +++ b/Documentation/devicetree/bindings/leds/ti,lm3560.yaml @@ -0,0 +1,163 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/ti,lm3560.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI LM3560 Synchronous Boost Flash Driver + +maintainers: + - Svyatoslav Ryhel <clamor95@gmail.com> + +description: + The LM3560 is a 2-MHz fixed frequency synchronous boost converter with two + 1000-mA constant current drivers for high-current white LEDs. The dual high- + side current sources allow for grounded cathode LED operation and can be + tied together for providing flash currents at up to 2 A through a single LED. + An adaptive regulation method ensures the current for each LED remains in + regulation and maximizes efficiency. + +properties: + compatible: + enum: + - ti,lm3559 + - ti,lm3560 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + enable-gpios: + description: GPIO connected to the HWEN pin. + maxItems: 1 + + vin-supply: + description: Supply connected to the IN line. + + flash-max-timeout-us: + minimum: 32000 + maximum: 1024000 + default: 32000 + + ti,peak-current-microamp: + description: + The LM3560 features 4 selectable current limits 1.6A, 2.3A, 3A, and 3.6A + (in case of LM3559 - 1.4A, 2.1A, 2.7A, and 3.2A). When the current limit + is reached, the LM3559/LM3560 stops switching for the remainder of the + switching cycle. + +patternProperties: + '^led@[01]$': + type: object + $ref: /schemas/leds/common.yaml# + description: LED control bank nodes. + unevaluatedProperties: false + + properties: + reg: + description: Control bank selection (0 = bank A, 1 = bank B). + maximum: 1 + + required: + - reg + - flash-max-microamp + - led-max-microamp + +allOf: + - $ref: /schemas/leds/common.yaml# + - if: + properties: + compatible: + contains: + const: ti,lm3559 + then: + properties: + ti,peak-current-microamp: + enum: [1400000, 2100000, 2700000, 3200000] + default: 1400000 + patternProperties: + '^led@[01]$': + properties: + flash-max-microamp: + minimum: 56250 + maximum: 900000 + led-max-microamp: + minimum: 28125 + maximum: 225000 + + - if: + properties: + compatible: + contains: + const: ti,lm3560 + then: + properties: + ti,peak-current-microamp: + enum: [1600000, 2300000, 3000000, 3600000] + default: 1600000 + patternProperties: + '^led@[01]$': + properties: + flash-max-microamp: + minimum: 62500 + maximum: 1000000 + led-max-microamp: + minimum: 31250 + maximum: 250000 + +required: + - compatible + - reg + - '#address-cells' + - '#size-cells' + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/leds/common.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@53 { + compatible = "ti,lm3560"; + reg = <0x53>; + + enable-gpios = <&gpio 28 GPIO_ACTIVE_HIGH>; + vin-supply = <&vdd_3v3_sys>; + + flash-max-timeout-us = <1024000>; + ti,peak-current-microamp = <1600000>; + + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + + function = LED_FUNCTION_FLASH; + color = <LED_COLOR_ID_WHITE>; + + flash-max-microamp = <562500>; + led-max-microamp = <156250>; + }; + + led@1 { + reg = <1>; + + function = LED_FUNCTION_FLASH; + color = <LED_COLOR_ID_YELLOW>; + + flash-max-microamp = <562500>; + led-max-microamp = <156250>; + }; + }; + }; diff --git a/Documentation/leds/index.rst b/Documentation/leds/index.rst index bebf44004278..23fa9ff7aaf4 100644 --- a/Documentation/leds/index.rst +++ b/Documentation/leds/index.rst @@ -28,6 +28,7 @@ LEDs leds-lp5812 leds-mlxcpld leds-mt6370-rgb + leds-s2m-rgb leds-sc27xx leds-st1202 leds-qcom-lpg diff --git a/Documentation/leds/leds-class-multicolor.rst b/Documentation/leds/leds-class-multicolor.rst index c6b47b4093c4..68340644f80b 100644 --- a/Documentation/leds/leds-class-multicolor.rst +++ b/Documentation/leds/leds-class-multicolor.rst @@ -25,10 +25,14 @@ color name to indexed value. The ``multi_index`` file is an array that contains the string list of the colors as they are defined in each ``multi_*`` array file. -The ``multi_intensity`` is an array that can be read or written to for the +The ``multi_intensity`` file is an array that can be read or written to for the individual color intensities. All elements within this array must be written in order for the color LED intensities to be updated. +The ``multi_max_intensity`` file is an array that contains the maximum intensity +value supported by each color intensity. Intensity values above this will be +automatically clamped into the supported range. + Directory Layout Example ======================== .. code-block:: console @@ -38,6 +42,7 @@ Directory Layout Example -r--r--r-- 1 root root 4096 Oct 19 16:16 max_brightness -r--r--r-- 1 root root 4096 Oct 19 16:16 multi_index -rw-r--r-- 1 root root 4096 Oct 19 16:16 multi_intensity + -r--r--r-- 1 root root 4096 Oct 19 16:16 multi_max_intensity .. @@ -104,3 +109,17 @@ the color LED group. 128 .. + +Writing intensity values larger than the maximum specified in ``multi_max_intensity`` +will result in those values being clamped into the supported range. + +.. code-block:: console + + # cat /sys/class/leds/multicolor:status/multi_max_intensity + 255 255 255 + + # echo 512 512 512 > /sys/class/leds/multicolor:status/multi_intensity + # cat /sys/class/leds/multicolor:status/multi_intensity + 255 255 255 + +.. diff --git a/Documentation/leds/leds-class.rst b/Documentation/leds/leds-class.rst index 5db620ed27aa..3913966cfdac 100644 --- a/Documentation/leds/leds-class.rst +++ b/Documentation/leds/leds-class.rst @@ -116,6 +116,69 @@ above leaves scope for further attributes should they be needed. If sections of the name don't apply, just leave that section blank. +Keyboard backlight control LED Device Naming +============================================ + +For backlit keyboards with a single brightness / color settings a single +(multicolor) LED class device should be used to allow userspace to change +the backlight brightness (and if possible the color). This LED class device +must use "kbd_backlight" for the function part of the LED class device name. +IOW the name must end with ":kbd_backlight". + +For backlit keyboards with multiple control zones, one (multicolor) LED class +device should be used per zone. These LED class devices' name must follow: + + "<devicename>:<color>:kbd_zoned_backlight-<zone_name>" + +and <devicename> must be the same for all zones of the same keyboard. + +<zone_name> should be descriptive of which part of the keyboard backlight +the zone covers and should be suitable for userspace to show to an end user +in an UI for controlling the zones. + +Where possible <zone_name> should be a value already used by other +zoned keyboards with a similar or identical zone layout, e.g.: + +<devicename>:<color>:kbd_zoned_backlight-right +<devicename>:<color>:kbd_zoned_backlight-middle +<devicename>:<color>:kbd_zoned_backlight-left +<devicename>:<color>:kbd_zoned_backlight-corners +<devicename>:<color>:kbd_zoned_backlight-wasd + +or: + +<devicename>:<color>:kbd_zoned_backlight-main +<devicename>:<color>:kbd_zoned_backlight-cursor +<devicename>:<color>:kbd_zoned_backlight-numpad +<devicename>:<color>:kbd_zoned_backlight-corners +<devicename>:<color>:kbd_zoned_backlight-wasd + +Note that this is intended for keyboards with a limited number of zones, +keyboards with per key addressable backlighting must not use LED class devices +since the sysfs API is not suitable for rapidly change multiple LEDs in one +"commit" as is necessary to do animations / special effects on such keyboards. + +An exception to the rule that all zones must follow: + + "<devicename>:<color>:kbd_zoned_backlight-<zone_name>" + +is made for the special case where there is a single big zone which controls +the backlighting of almost all of the keyboard and there are some small areas +with separate control, like just the 4 cursor keys, or the WASD keys. In this +case the main zone should use 'kbd_backlight' for the function part of the name +for compatibility with (older) userspace code which is not aware of +the "kbd_zoned_backlight-<zone_name>" function naming scheme. + +While the smaller zones should use the new zoned naming scheme. Such a setup +would result in e.g.: + +<devicename>:<color>:kbd_backlight +<devicename>:<color>:kbd_zoned_backlight-wasd + +"kbd_zoned_backlight-<zone_name>" aware userspace should be aware of this +exception and check for a main zone with a "kbd_backlight" function-name. + + Brightness setting API ====================== diff --git a/Documentation/leds/leds-lp5812.rst b/Documentation/leds/leds-lp5812.rst index c2a6368d5149..12e757d45c3a 100644 --- a/Documentation/leds/leds-lp5812.rst +++ b/Documentation/leds/leds-lp5812.rst @@ -20,7 +20,7 @@ Sysfs Interface =============== This driver uses the standard multicolor LED class interfaces defined -in Documentation/ABI/testing/sysfs-class-led-multicolor.rst. +in Documentation/ABI/testing/sysfs-class-led-multicolor. Each LP5812 LED output appears under ``/sys/class/leds/`` with its assigned label (for example ``LED_A``). diff --git a/Documentation/leds/leds-s2m-rgb.rst b/Documentation/leds/leds-s2m-rgb.rst new file mode 100644 index 000000000000..4f89a8c89ea8 --- /dev/null +++ b/Documentation/leds/leds-s2m-rgb.rst @@ -0,0 +1,60 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====================================== +Samsung S2M Series PMIC RGB LED Driver +====================================== + +Description +----------- + +The RGB LED on the S2M series PMIC hardware features a three-channel LED that +is grouped together as a single device. Furthermore, it supports 8-bit +brightness control for each channel. This LED is typically used as a status +indicator in mobile devices. It also supports various parameters for hardware +patterns. + +The hardware pattern can be programmed using the "pattern" trigger, using the +hw_pattern attribute. + +/sys/class/leds/<led>/repeat +---------------------------- + +The hardware supports only indefinitely repeating patterns. The repeat +attribute must be set to -1 for hardware patterns to function. + +/sys/class/leds/<led>/hw_pattern +-------------------------------- + +Specify a hardware pattern for the RGB LEDs. + +The pattern is a series of brightness levels and durations in milliseconds. +There should be only one non-zero brightness level. Unlike the results +described in leds-trigger-pattern, the transitions between on and off states +are smoothed out by the hardware. + +Simple pattern:: + + "255 3000 0 1000" + + 255 -+ ''''''-. .-'''''''-. + | '. .' '. + | \ / \ + | '. .' '. + | '-.......-' '- + 0 -+-------+-------+-------+-------+-------+-------+--> time (s) + 0 1 2 3 4 5 6 + +As described in leds-trigger-pattern, it is also possible to use zero-length +entries to disable the ramping mechanism. + +On-Off pattern:: + + "255 1000 255 0 0 1000 0 0" + + 255 -+ ------+ +-------+ +-------+ + | | | | | | + | | | | | | + | | | | | | + | +-------+ +-------+ +------- + 0 -+-------+-------+-------+-------+-------+-------+--> time (s) + 0 1 2 3 4 5 6 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); diff --git a/include/dt-bindings/leds/common.h b/include/dt-bindings/leds/common.h index 4f017bea0123..b7bafbaf7df3 100644 --- a/include/dt-bindings/leds/common.h +++ b/include/dt-bindings/leds/common.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */ /* * This header provides macros for the common LEDs device tree bindings. * diff --git a/include/linux/led-class-multicolor.h b/include/linux/led-class-multicolor.h index db9f34c6736e..8a05836cae25 100644 --- a/include/linux/led-class-multicolor.h +++ b/include/linux/led-class-multicolor.h @@ -9,10 +9,31 @@ #include <linux/leds.h> #include <dt-bindings/leds/common.h> +/** + * struct mc_subled - Color component description. + * @color_index: Color ID. + * @brightness: Scaled intensity. + * @intensity: Current intensity. + * @max_intensity: Maximum supported intensity value. + * @channel: Channel index. + * + * Describes a color component of a multicolor LED. Many multicolor LEDs + * do not support global brightness control in hardware, so they use + * the brightness field in connection with led_mc_calc_color_components() + * to perform the intensity scaling in software. + * Such drivers should set max_intensity to 0 to signal the multicolor LED core + * that the maximum global brightness of the LED class device should be used for + * limiting incoming intensity values. + * + * Multicolor LEDs that do support global brightness control in hardware + * should instead set max_intensity to the maximum intensity value supported + * by the hardware for a given color component. + */ struct mc_subled { unsigned int color_index; unsigned int brightness; unsigned int intensity; + unsigned int max_intensity; unsigned int channel; }; @@ -53,7 +74,14 @@ int led_classdev_multicolor_register_ext(struct device *parent, */ void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev); -/* Calculate brightness for the monochrome LED cluster */ +/** + * led_mc_calc_color_components() - Calculates component brightness values of a LED cluster. + * @mcled_cdev - Multicolor LED class device of the LED cluster. + * @brightness - Global brightness of the LED cluster. + * + * Calculates the brightness values for each color component of a monochrome LED cluster, + * see Documentation/leds/leds-class-multicolor.rst for details. + */ int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, enum led_brightness brightness); |
