mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 01:32:21 +02:00
LEDs for v7.2
- 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
- Texas Instruments 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
- Core: Fix a race condition in the software blink logic when stopping blinking and
setting brightness simultaneously
- Core: Introduce the `multi_max_intensity` sysfs attribute for multicolor LEDs to
support hardware-based global brightness control
- Core: Replace OF-based device lookup with firmware node equivalents to support ACPI
and software nodes
- Core: Return `ENODATA` when reading brightness from hardware-controlled LEDs
- Core: 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
- TI 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
- Userspace LEDs (uLEDs): 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 Core: 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
- Samsung S2M series: Document the pattern behavior for Samsung S2M series PMIC RGB
LEDs
- Samsung 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
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmoylZ0ACgkQUa+KL4f8
d2GgaQ//QrL6Ap+Sp4JrG4m0FDvMB72QkqtTBtfMtUUpvliFrggGr5tAbPVOnITn
N5vFNJy++ffGA25xvi7XvMUDAi5dXY71razR7Rw+QAp7KytNnNNu5GxcYw4rGXGR
yndd9YQBhowlLF+jGjk866txj59vBKf/MpHXt+VrdJGK/se3nzeg4rOsCx/ZP1p5
nhBcyunGKereeVUHj041N22PVxicR8TsleDf9ho69UdiKTh3ZGiRmzdvn0sgA+wi
b8wZCQXNmnpnmWm2xtwoH7dCdTFMshbs1rmFl1GxctkRqwx5yRU5sREqJKxeW6g9
Q/24gwMI6UD350Yx5ir748aKgIeQxmGXAzKvqostqVNxt4uqKIcBat4Q3BHjzSgX
HzmpF1OZHZOcyPrN1mNgc5fpiv+5FyzQirddg+Au9P2FKe+leOH08u70b0eaZHX0
RgzSHayGq8DR2evAsUl1pIaJfSuh7W+YopSFaB2VnU83ShTEqLqE3DvdjwwQsjZ8
Fb1XkPfFqFShQr1gZ8SG3F60yDAbs77ZBzxH6vEC5kjMM7ewsg5rg06o4siR81S1
6rWyWTQyDXsywapra/vFyBUxum3aE/tj50YieSoovb7pKnwsoeiGU9Tzn2PRdlTX
u6LxZnnebsIvlKE2UWRWs82raKzDjTRa9u3HwQ42E8xiI0WYVDs=
=zlOu
-----END PGP SIGNATURE-----
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
...
This commit is contained in:
commit
6e717507bf
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
163
Documentation/devicetree/bindings/leds/ti,lm3560.yaml
Normal file
163
Documentation/devicetree/bindings/leds/ti,lm3560.yaml
Normal file
|
|
@ -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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -28,6 +28,7 @@ LEDs
|
|||
leds-lp5812
|
||||
leds-mlxcpld
|
||||
leds-mt6370-rgb
|
||||
leds-s2m-rgb
|
||||
leds-sc27xx
|
||||
leds-st1202
|
||||
leds-qcom-lpg
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
..
|
||||
|
|
|
|||
|
|
@ -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
|
||||
======================
|
||||
|
||||
|
|
|
|||
|
|
@ -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``).
|
||||
|
|
|
|||
60
Documentation/leds/leds-s2m-rgb.rst
Normal file
60
Documentation/leds/leds-s2m-rgb.rst
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
350
drivers/leds/flash/leds-s2m-flash.c
Normal file
350
drivers/leds/flash/leds-s2m-flash.c
Normal file
|
|
@ -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");
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 software blink disable is pending, also queue brightness setting.
|
||||
* If software blink is active, delay brightness setting
|
||||
* until the next timer tick.
|
||||
*/
|
||||
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);
|
||||
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.
|
||||
*/
|
||||
if (!brightness) {
|
||||
set_bit(LED_BLINK_DISABLE, &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);
|
||||
led_cdev->new_blink_brightness = brightness;
|
||||
}
|
||||
set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
|
||||
queue_work(led_cdev->wq, &led_cdev->set_brightness_work);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ struct tca6507_chip {
|
|||
};
|
||||
|
||||
static const struct i2c_device_id tca6507_id[] = {
|
||||
{ "tca6507" },
|
||||
{ .name = "tca6507" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tca6507_id);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
234
drivers/leds/rgb/leds-lp5860-core.c
Normal file
234
drivers/leds/rgb/leds-lp5860-core.c
Normal file
|
|
@ -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");
|
||||
98
drivers/leds/rgb/leds-lp5860-spi.c
Normal file
98
drivers/leds/rgb/leds-lp5860-spi.c
Normal file
|
|
@ -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");
|
||||
268
drivers/leds/rgb/leds-lp5860.h
Normal file
268
drivers/leds/rgb/leds-lp5860.h
Normal file
|
|
@ -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 */
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,58 +80,8 @@
|
|||
#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
|
||||
|
|
@ -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;
|
||||
|
|
@ -216,6 +166,55 @@ struct lpg_led {
|
|||
struct lpg_channel *channels[] __counted_by(num_channels);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
426
drivers/leds/rgb/leds-s2m-rgb.c
Normal file
426
drivers/leds/rgb/leds-s2m-rgb.c
Normal file
|
|
@ -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");
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user