LEDs for v7.4

- New Support & Features
   - Core: Add `led_trigger_notify_hw_control_changed()` to allow drivers to notify the
     core of autonomous hardware control transitions
   - Core: Add the `hw_offloaded()` trigger callback and `trigger_may_offload_to_hw` sysfs
     attribute to expose hardware-offloaded triggers
   - Netdev Trigger: Implement the `hw_offloaded()` callback to report hardware control
     state to the LED core
   - Analog Devices LTC3208: Add support for the LTC3208 7-channel multi-display I2C LED
     driver
   - ChromeOS EC: Implement the `hw_offloaded()` callback for the `chromeos-auto` trigger
   - CZ.NIC Turris Omnia: Implement the `hw_offloaded()` callback and declare `omnia-mcu`
     as a hardware control trigger
   - STMicroelectronics ST1202: Add hardware-accelerated blink support via `blink_set()`
 
 - Improvements & Fixes
   - Class: Always hold the `led_access` mutex in `brightness_show()` to avoid exposing
     transient registration states
   - Class: Deactivate active hardware control triggers when writing to the `brightness`
     sysfs attribute
   - Core: Enforce strict validation in `led_trigger_is_hw_controlled()` and warn on legacy
     fallback paths
   - Flash Class: Initialize the fault variable in `flash_fault_show()` to prevent
     undefined behavior when callbacks perform bitwise ORs
   - Kinetic KTD2692: Pass the driver context directly to `regulator_disable_action()` to
     prevent a NULL pointer dereference during error unwinding
   - Maxim MAX77705: Use `fwnode_for_each_child_node_scoped()` in `max77705_add_led()` to
     prevent child node reference leaks on error paths
   - NXP PCA9532: Check the return value of `i2c_smbus_read_byte_data()` in
     `pca9532_getled()` to avoid integer underflow on read errors
   - Qualcomm WLED: Fix a NULL pointer dereference in `wled_remove()` by setting platform
     driver data in `wled_probe()`
   - Qualcomm WLED: Fix the WLED3 brightness register stride to prevent consecutive string
     writes from overlapping
   - Qualcomm WLED: Read back the hardware-programmed brightness at probe, expose it via
     `get_brightness()`, and prevent unbalanced OVP IRQ enabling
   - STMicroelectronics ST1202: Clear unused PWM pattern slots in `pattern_set()` and
     `blink_set()` to prevent unintended output when sharing the global sequencer
   - STMicroelectronics ST1202: Program and cap the channel current according to
     `max_brightness` across brightness, blink, and hardware pattern modes, and scale
     pattern brightness to the 12-bit PWM range
   - STMicroelectronics ST1202: Retain a reference to each child LED firmware node for the
     lifetime of the device
   - STMicroelectronics ST1202: Use only the blocking `brightness_set_blocking()` callback
     to avoid sleeping in atomic context during I2C transfers
   - TI LM3692x: Check the final register write in `lm3692x_leds_enable()` to prevent
     regulator/GPIO leaks and an inconsistent enabled state
   - TI LP8860: Fix a `device_node` reference leak in `lp8860_probe()` using
     `__free(device_node)`
 
 - Cleanups & Refactoring
   - Core: Move `led_trigger_is_hw_controlled()` and `led_trigger_group` from `led-class.c`
     into `led-triggers.c`
   - Core: Replace conditional `set_bit()`/`clear_bit()` patterns with `assign_bit()` and
     `__assign_bit()`
   - Flash Class: Simplify `flash_fault_show()` using `sysfs_emit_at()` and omit the
     trailing space
   - Maintainers: Add Griffin Kroah-Hartman as an LED subsystem reviewer
   - Intel SS4200: Fix a spelling typo in a comment
   - ISSI IS31FL32xx: Convert DT-specific parsing helpers to generic device and fwnode
     property APIs
   - NXP PCA955x: Drop redundant intermediate platform data and allocate the LED array
     using a flexible array member
   - Qualcomm LPG: Convert LED and channel parsing to fwnode APIs and avoid leaving
     `default_trigger` uninitialized
   - Skyworks AAT1290: Use `ARRAY_SIZE()` when allocating `mm_current_scale` to match the
     target pointer type for type-aware allocators
   - STMicroelectronics ST1202: Introduce a `st1202_iled_max()` helper to unify channel
     current ceiling calculations
   - STMicroelectronics ST1202: Standardize variable and macro naming, fix helper spelling,
     and use `u8` consistently instead of `uint8_t`
   - Syscon: Convert from OF-specific property helpers to the generic device property and
     fwnode APIs
 
 - Device Tree Binding Updates
   - Core: Allow PHY RX triggers (`phy[0-9]+rx`) in `linux,default-trigger`
   - Core: Standardize example indentation across multiple LED and backlight binding
     schemas
   - Analog Devices LTC3208: Document the LTC3208 multi-display LED driver
   - LaCie netxbig: Convert LED bindings to DT schema
   - Motorola CPCAP: Convert LED bindings to DT schemaLEDs for v7.4
 
 - New Support & Features
   - Core: Add `led_trigger_notify_hw_control_changed()` to allow drivers to notify the
     core of autonomous hardware control transitions
   - Core: Add the `hw_offloaded()` trigger callback and `trigger_may_offload_to_hw` sysfs
     attribute to expose hardware-offloaded triggers
   - Netdev Trigger: Implement the `hw_offloaded()` callback to report hardware control
     state to the LED core
   - Analog Devices LTC3208: Add support for the LTC3208 7-channel multi-display I2C LED
     driver
   - ChromeOS EC: Implement the `hw_offloaded()` callback for the `chromeos-auto` trigger
   - CZ.NIC Turris Omnia: Implement the `hw_offloaded()` callback and declare `omnia-mcu`
     as a hardware control trigger
   - STMicroelectronics ST1202: Add hardware-accelerated blink support via `blink_set()`
 
 - Improvements & Fixes
   - Class: Always hold the `led_access` mutex in `brightness_show()` to avoid exposing
     transient registration states
   - Class: Deactivate active hardware control triggers when writing to the `brightness`
     sysfs attribute
   - Core: Enforce strict validation in `led_trigger_is_hw_controlled()` and warn on legacy
     fallback paths
   - Flash Class: Initialize the fault variable in `flash_fault_show()` to prevent
     undefined behavior when callbacks perform bitwise ORs
   - Kinetic KTD2692: Pass the driver context directly to `regulator_disable_action()` to
     prevent a NULL pointer dereference during error unwinding
   - Maxim MAX77705: Use `fwnode_for_each_child_node_scoped()` in `max77705_add_led()` to
     prevent child node reference leaks on error paths
   - NXP PCA9532: Check the return value of `i2c_smbus_read_byte_data()` in
     `pca9532_getled()` to avoid integer underflow on read errors
   - Qualcomm WLED: Fix a NULL pointer dereference in `wled_remove()` by setting platform
     driver data in `wled_probe()`
   - Qualcomm WLED: Fix the WLED3 brightness register stride to prevent consecutive string
     writes from overlapping
   - Qualcomm WLED: Read back the hardware-programmed brightness at probe, expose it via
     `get_brightness()`, and prevent unbalanced OVP IRQ enabling
   - STMicroelectronics ST1202: Clear unused PWM pattern slots in `pattern_set()` and
     `blink_set()` to prevent unintended output when sharing the global sequencer
   - STMicroelectronics ST1202: Program and cap the channel current according to
     `max_brightness` across brightness, blink, and hardware pattern modes, and scale
     pattern brightness to the 12-bit PWM range
   - STMicroelectronics ST1202: Retain a reference to each child LED firmware node for the
     lifetime of the device
   - STMicroelectronics ST1202: Use only the blocking `brightness_set_blocking()` callback
     to avoid sleeping in atomic context during I2C transfers
   - TI LM3692x: Check the final register write in `lm3692x_leds_enable()` to prevent
     regulator/GPIO leaks and an inconsistent enabled state
   - TI LP8860: Fix a `device_node` reference leak in `lp8860_probe()` using
     `__free(device_node)`
 
 - Cleanups & Refactoring
   - Core: Move `led_trigger_is_hw_controlled()` and `led_trigger_group` from `led-class.c`
     into `led-triggers.c`
   - Core: Replace conditional `set_bit()`/`clear_bit()` patterns with `assign_bit()` and
     `__assign_bit()`
   - Flash Class: Simplify `flash_fault_show()` using `sysfs_emit_at()` and omit the
     trailing space
   - Maintainers: Add Griffin Kroah-Hartman as an LED subsystem reviewer
   - Intel SS4200: Fix a spelling typo in a comment
   - ISSI IS31FL32xx: Convert DT-specific parsing helpers to generic device and fwnode
     property APIs
   - NXP PCA955x: Drop redundant intermediate platform data and allocate the LED array
     using a flexible array member
   - Qualcomm LPG: Convert LED and channel parsing to fwnode APIs and avoid leaving
     `default_trigger` uninitialized
   - Skyworks AAT1290: Use `ARRAY_SIZE()` when allocating `mm_current_scale` to match the
     target pointer type for type-aware allocators
   - STMicroelectronics ST1202: Introduce a `st1202_iled_max()` helper to unify channel
     current ceiling calculations
   - STMicroelectronics ST1202: Standardize variable and macro naming, fix helper spelling,
     and use `u8` consistently instead of `uint8_t`
   - Syscon: Convert from OF-specific property helpers to the generic device property and
     fwnode APIs
 
 - Device Tree Binding Updates
   - Core: Allow PHY RX triggers (`phy[0-9]+rx`) in `linux,default-trigger`
   - Core: Standardize example indentation across multiple LED and backlight binding
     schemas
   - Analog Devices LTC3208: Document the LTC3208 multi-display LED driver
   - LaCie netxbig: Convert LED bindings to DT schema
   - Motorola CPCAP: Convert LED bindings to DT schema
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmrfbg4ACgkQUa+KL4f8
 d2EYqg/+IH3k8Jm0Et7WGte+Ej4cmQJ3/xJNNu+ezGeaZu1WcxT4xXLCGhE89GyC
 4hqlyHM7mSDFBwVoiEqznu/rMQ9gCxQZbDPZkBJFrnAIvWYd2veMAuzC5l3vnvUD
 DuOyN+i++KzoX5F5dNd802CElKmhhB03V+FbU4QUyHT6I6zjUtR/6WXYv6Wyvca4
 gQSYKcyHem7VQaVmk85kvvH9Z8wIvAlzaMHOz7vyfZNarb5K+RXgvCjZfncR/c6P
 FYGh4RRl0zJV/x+8FZ7Ehwck4YjOJf0hySmRQ9DeABV2CFDYFrSEck5UfMPDO+c0
 tpdHHQp66lrLjM1MZns1zP7SnucXt1G6y9b5DmMd/bneMlDa5enBLIFbS106hdIh
 NDu9rKQz/AW1V/fPV+db+AgjL7Sr7oKTNbRuclLj57Zj7TEbtgeUOXh/FKRxPKfu
 mzLPpjdggiuXbTCSNxyW+jKm9a7wthlk2dSsNOWgVea6d3Et/Y1oYOpbOX+s+CqF
 WShv55jkpyqpazorpzdNdv8mdsXRItf13UZy8izmwufve0JrnAlbqTt5TEzs7zgj
 cuDYreg90RNckLWNv/Tdgjxw1aIIWAC3Z3gVVJt0sbdPGS56r6aHu3Rt4Usurxx5
 GHcgIOZcmK5bPk/2gH/chvQj3MOsxrr38Q1Gazv55yVTOnp/39Q=
 =x9D5
 -----END PGP SIGNATURE-----

Merge tag 'leds-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds

Pull LED updates from Lee Jones:
 "New Support & Features:
   - Core: Add `led_trigger_notify_hw_control_changed()` to allow
     drivers to notify the core of autonomous hardware control
     transitions
   - Core: Add the `hw_offloaded()` trigger callback and
     `trigger_may_offload_to_hw` sysfs attribute to expose
     hardware-offloaded triggers
   - Netdev Trigger: Implement the `hw_offloaded()` callback to report
     hardware control state to the LED core
   - Analog Devices LTC3208: Add support for the LTC3208 7-channel
     multi-display I2C LED driver
   - ChromeOS EC: Implement the `hw_offloaded()` callback for the
     `chromeos-auto` trigger
   - CZ.NIC Turris Omnia: Implement the `hw_offloaded()` callback and
     declare `omnia-mcu` as a hardware control trigger
   - STMicroelectronics ST1202: Add hardware-accelerated blink support
     via `blink_set()`

  Improvements & Fixes:
   - Class: Always hold the `led_access` mutex in `brightness_show()` to
     avoid exposing transient registration states
   - Class: Deactivate active hardware control triggers when writing to
     the `brightness` sysfs attribute
   - Core: Enforce strict validation in `led_trigger_is_hw_controlled()`
     and warn on legacy fallback paths
   - Flash Class: Initialize the fault variable in `flash_fault_show()`
     to prevent undefined behavior when callbacks perform bitwise ORs
   - Kinetic KTD2692: Pass the driver context directly to
     `regulator_disable_action()` to prevent a NULL pointer dereference
     during error unwinding
   - Maxim MAX77705: Use `fwnode_for_each_child_node_scoped()` in
     `max77705_add_led()` to prevent child node reference leaks on error
     paths
   - NXP PCA9532: Check the return value of `i2c_smbus_read_byte_data()`
     in `pca9532_getled()` to avoid integer underflow on read errors
   - Qualcomm WLED: Fix a NULL pointer dereference in `wled_remove()` by
     setting platform driver data in `wled_probe()`
   - Qualcomm WLED: Fix the WLED3 brightness register stride to prevent
     consecutive string writes from overlapping
   - Qualcomm WLED: Read back the hardware-programmed brightness at
     probe, expose it via `get_brightness()`, and prevent unbalanced OVP
     IRQ enabling
   - STMicroelectronics ST1202: Clear unused PWM pattern slots in
     `pattern_set()` and `blink_set()` to prevent unintended output when
     sharing the global sequencer
   - STMicroelectronics ST1202: Program and cap the channel current
     according to `max_brightness` across brightness, blink, and
     hardware pattern modes, and scale pattern brightness to the 12-bit
     PWM range
   - STMicroelectronics ST1202: Retain a reference to each child LED
     firmware node for the lifetime of the device
   - STMicroelectronics ST1202: Use only the blocking
     `brightness_set_blocking()` callback to avoid sleeping in atomic
     context during I2C transfers
   - TI LM3692x: Check the final register write in
     `lm3692x_leds_enable()` to prevent regulator/GPIO leaks and an
     inconsistent enabled state
   - TI LP8860: Fix a `device_node` reference leak in `lp8860_probe()`
     using `__free(device_node)`

  Cleanups & Refactoring:
   - Core: Move `led_trigger_is_hw_controlled()` and `led_trigger_group`
     from `led-class.c` into `led-triggers.c`
   - Core: Replace conditional `set_bit()`/`clear_bit()` patterns with
     `assign_bit()` and `__assign_bit()`
   - Flash Class: Simplify `flash_fault_show()` using `sysfs_emit_at()`
     and omit the trailing space
   - Maintainers: Add Griffin Kroah-Hartman as an LED subsystem reviewer
   - Intel SS4200: Fix a spelling typo in a comment
   - ISSI IS31FL32xx: Convert DT-specific parsing helpers to generic
     device and fwnode property APIs
   - NXP PCA955x: Drop redundant intermediate platform data and allocate
     the LED array using a flexible array member
   - Qualcomm LPG: Convert LED and channel parsing to fwnode APIs and
     avoid leaving `default_trigger` uninitialized
   - Skyworks AAT1290: Use `ARRAY_SIZE()` when allocating
     `mm_current_scale` to match the target pointer type for type-aware
     allocators
   - STMicroelectronics ST1202: Introduce a `st1202_iled_max()` helper
     to unify channel current ceiling calculations
   - STMicroelectronics ST1202: Standardize variable and macro naming,
     fix helper spelling, and use `u8` consistently instead of `uint8_t`
   - Syscon: Convert from OF-specific property helpers to the generic
     device property and fwnode APIs

  Device Tree Binding Updates:
   - Core: Allow PHY RX triggers (`phy[0-9]+rx`) in
     `linux,default-trigger`
   - Core: Standardize example indentation across multiple LED and
     backlight binding schemas
   - Analog Devices LTC3208: Document the LTC3208 multi-display LED
     driver
   - LaCie netxbig: Convert LED bindings to DT schema
   - Motorola CPCAP: Convert LED bindings to DT schemaLEDs for v7.4"

* tag 'leds-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (49 commits)
  dt-bindings: leds: lacie,netxbig-leds: Convert to DT schema
  leds: pca955x: Drop platform data and allocate LEDs with the device
  dt-bindings: leds: Use consistent indentation in the example
  backlight: qcom-wled: Read back the programmed brightness at probe
  backlight: qcom-wled: Fix unbalanced OVP IRQ enable at probe
  backlight: qcom-wled: Fix WLED3 brightness register stride
  backlight: qcom-wled: Fix NULL pointer dereference in wled_remove()
  dt-bindings: leds: Allow PHY RX triggers
  MAINTAINERS: Add myself as a LED SUBSYSTEM reviewer
  leds: trigger: Add led_trigger_notify_hw_control_changed() interface
  leds: trigger: Enforce strict checks in led_trigger_is_hw_controlled()
  leds: trigger: netdev: Implement hw_offloaded() callback
  leds: turris-omnia: Implement hw_offloaded() trigger callback and declare hw_control_trigger
  leds: cros_ec: Implement hw_offloaded() trigger callback
  leds: trigger: Add hw_offloaded() callback and provide trigger_may_offload_to_hw attribute
  leds: trigger: Move led_trigger_group to the right place
  leds: class: Remove hardware control trigger when writing brightness
  leds: trigger: Move led_trigger_is_hw_controlled() to the right place
  leds: class: Always protect brightness_show() with led_access
  dt-bindings: leds: leds-cpcap: Convert to DT schema
  ...
This commit is contained in:
Linus Torvalds 2026-10-26 08:55:14 -10:00
commit 5b93dc1c0c
45 changed files with 1401 additions and 431 deletions

View File

@ -28,7 +28,9 @@ Description:
Writing 0 to this file clears active trigger.
Writing non-zero to this file while trigger is active changes the
top brightness trigger is going to use.
top brightness trigger is going to use, unless the current
trigger is offloaded to hardware (in which case writing any
value will deactivate the trigger).
@ -78,6 +80,30 @@ Description:
(which would often be configured in the device tree for the
hardware).
What: /sys/class/leds/<led>/trigger_may_offload_to_hw
Date: September 2026
KernelVersion: 7.4
Contact: linux-leds@vger.kernel.org
Description:
Names and states of triggers that may be offloaded to hardware.
Such triggers are also called "hardware control trigger" in some
context.
Only exists when the LED supports trigger offload.
Reading this file returns a list of triggers that are capable to
be offloaded. The optional brackets around the trigger name
indicate the state of the current trigger:
- `foo_trigger`: the trigger is not selected, or selected but
falls back to software blink for some reasons, e.g.,
incompatible trigger parameters.
- `[foo_trigger]`: the trigger is selected and offloaded to
hardware.
The "netdev" trigger also provides a custom attribute to
indicate its state, see `/sys/class/leds/<led>/offloaded`.
What: /sys/class/leds/<led>/inverted
Date: January 2011
KernelVersion: 2.6.38

View File

@ -75,6 +75,9 @@ Description:
If 1, the LED blinking in requested mode is offloaded to
hardware.
LED trigger core also provides a generic attribute for this
purpose, see `/sys/class/leds/<led>/trigger_may_offload_to_hw`.
What: /sys/class/leds/<led>/link_10
Date: Jun 2023
KernelVersion: 6.5

View File

@ -0,0 +1,183 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
# Copyright (c) 2026 Analog Devices, Inc.
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/adi,ltc3208.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: LTC3208 Multidisplay LED Controller from Linear Technologies (Now Analog Devices)
maintainers:
- Jan Carlo Roleda <jancarlo.roleda@analog.com>
description:
The LTC3208 is a multidisplay LED controller that can support up to 1A to all
connected LEDs.
The datasheet for this device can be found in
https://www.analog.com/en/products/ltc3208.html
properties:
compatible:
const: adi,ltc3208
reg:
maxItems: 1
"#address-cells":
const: 1
"#size-cells":
const: 0
adi,disable-camhl-pin:
type: boolean
description:
Configures whether the external CAMHL pin is disabled.
If disabled then the output pins associated with CAM will always select
the CAM register's high half-byte brightness.
adi,cfg-enrgbs-pin:
type: boolean
description:
Configures which channel the ENRGBS pin toggles when it receives a signal.
ENRGBS pin controls the SUB channel's output pins if this is set,
or RGB channel's output pins if this is unset.
adi,disable-rgb-aux4-dropout:
type: boolean
description:
Configures the RGB and AUX4 dropout signals to be disabled.
adi,aux1-channel:
$ref: /schemas/types.yaml#/definitions/string
description:
LED Channel that the AUX1 output pin mirrors its brightness level from.
enum: [aux, main, sub, cam]
default: aux
adi,aux2-channel:
$ref: /schemas/types.yaml#/definitions/string
description:
LED Channel that the AUX2 output pin mirrors its brightness level from.
enum: [aux, main, sub, cam]
default: aux
adi,aux3-channel:
$ref: /schemas/types.yaml#/definitions/string
description:
LED Channel that the AUX3 output pin mirrors its brightness level from.
enum: [aux, main, sub, cam]
default: aux
adi,aux4-channel:
$ref: /schemas/types.yaml#/definitions/string
description:
LED Channel that the AUX4 output pin mirrors its brightness level from.
enum: [aux, main, sub, cam]
default: aux
patternProperties:
"^led@[0-7]$":
type: object
$ref: /schemas/leds/common.yaml#
unevaluatedProperties: false
properties:
reg:
description:
LED Channel Number. each channel maps to a specific channel group used
to configure the brightness level of the output pins corresponding to
the channel.
enum:
- 0 # Main Channel (8-bit brightness)
- 1 # Sub Channel (8-bit brightness)
- 2 # AUX Channel (4-bit brightness)
- 3 # Camera Channel, Low-side byte (4-bit brightness)
- 4 # Camera Channel, High-side byte (4-bit brightness)
- 5 # Red Channel (4-bit brightness)
- 6 # Blue Channel (4-bit brightness)
- 7 # Green Channel (4-bit brightness)
required:
- reg
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
additionalProperties: false
examples:
- |
#include <dt-bindings/leds/common.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
led-controller@1b {
compatible = "adi,ltc3208";
reg = <0x1b>;
#address-cells = <1>;
#size-cells = <0>;
adi,disable-camhl-pin;
adi,cfg-enrgbs-pin;
adi,disable-rgb-aux4-dropout;
/* MAIN */
led@0 {
reg = <0>;
function = LED_FUNCTION_ACTIVITY;
color = <LED_COLOR_ID_WHITE>;
};
/* SUB */
led@1 {
reg = <1>;
function = LED_FUNCTION_ACTIVITY;
color = <LED_COLOR_ID_WHITE>;
};
/* AUX */
led@2 {
reg = <2>;
function = LED_FUNCTION_ACTIVITY;
color = <LED_COLOR_ID_WHITE>;
};
/* CAMLO */
led@3 {
reg = <3>;
function = LED_FUNCTION_FLASH;
color = <LED_COLOR_ID_WHITE>;
};
/* CAMHI */
led@4 {
reg = <4>;
function = LED_FUNCTION_FLASH;
color = <LED_COLOR_ID_WHITE>;
};
/* RED */
led@5 {
reg = <5>;
function = LED_FUNCTION_INDICATOR;
color = <LED_COLOR_ID_RED>;
};
/* BLUE */
led@6 {
reg = <6>;
function = LED_FUNCTION_INDICATOR;
color = <LED_COLOR_ID_BLUE>;
};
/* GREEN */
led@7 {
reg = <7>;
function = LED_FUNCTION_INDICATOR;
color = <LED_COLOR_ID_GREEN>;
};
};
};

View File

@ -99,20 +99,20 @@ examples:
#size-cells = <0>;
led-controller@38 {
compatible = "ti,lm3630a";
reg = <0x38>;
enable-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>;
compatible = "ti,lm3630a";
reg = <0x38>;
enable-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
led-sources = <0 1>;
label = "lcd-backlight";
default-brightness = <200>;
max-brightness = <255>;
};
led@0 {
reg = <0>;
led-sources = <0 1>;
label = "lcd-backlight";
default-brightness = <200>;
max-brightness = <255>;
};
};
};
- |
@ -121,22 +121,22 @@ examples:
#size-cells = <0>;
led-controller@38 {
compatible = "ti,lm3630a";
reg = <0x38>;
compatible = "ti,lm3630a";
reg = <0x38>;
#address-cells = <1>;
#size-cells = <0>;
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
default-brightness = <150>;
ti,linear-mapping-mode;
};
led@0 {
reg = <0>;
default-brightness = <150>;
ti,linear-mapping-mode;
};
led@1 {
reg = <1>;
default-brightness = <225>;
ti,linear-mapping-mode;
};
led@1 {
reg = <1>;
default-brightness = <225>;
ti,linear-mapping-mode;
};
};
};

View File

@ -95,20 +95,20 @@ examples:
/* 4V OV, 4 output LED0 string enabled */
rom-14h {
rom-addr = /bits/ 8 <0x14>;
rom-val = /bits/ 8 <0xcf>;
rom-addr = /bits/ 8 <0x14>;
rom-val = /bits/ 8 <0xcf>;
};
/* Heavy smoothing, 24ms ramp time step */
rom-15h {
rom-addr = /bits/ 8 <0x15>;
rom-val = /bits/ 8 <0xc7>;
rom-addr = /bits/ 8 <0x15>;
rom-val = /bits/ 8 <0xc7>;
};
/* 4 output LED1 string enabled */
rom-19h {
rom-addr = /bits/ 8 <0x19>;
rom-val = /bits/ 8 <0x0f>;
rom-addr = /bits/ 8 <0x19>;
rom-val = /bits/ 8 <0x0f>;
};
};
};
@ -125,7 +125,7 @@ examples:
dev-ctrl = /bits/ 8 <0x85>;
init-brt = /bits/ 8 <0x10>;
};
};
};
- |
i2c {
#address-cells = <1>;
@ -142,8 +142,8 @@ examples:
/* 4V OV, 4 output LED string enabled */
rom-14h {
rom-addr = /bits/ 8 <0x14>;
rom-val = /bits/ 8 <0xcf>;
rom-addr = /bits/ 8 <0x14>;
rom-val = /bits/ 8 <0xcf>;
};
};
};

View File

@ -280,4 +280,4 @@ examples:
qcom,ovp = <29>;
qcom,num-strings = <2>;
qcom,enabled-strings = <0 1>;
};
};

View File

@ -147,7 +147,7 @@ properties:
# LED is triggered by SD/MMC activity
- pattern: "^mmc[0-9]+$"
# LED is triggered by WLAN activity
- pattern: "^phy[0-9]+tx$"
- pattern: "^phy[0-9]+(rx|tx)$"
led-pattern:
description: |

View File

@ -0,0 +1,136 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/lacie,netxbig-leds.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: LaCie/Seagate netxbig CPLD LEDs
maintainers:
- Simon Guinot <simon.guinot@sequanux.org>
description: |
Binding for the CPLD LEDs (GPIO extension bus) found on some LaCie/Seagate
boards (Example: 2Big/5Big Network v2, 2Big NAS).
properties:
compatible:
const: lacie,netxbig-leds
gpio-ext:
description: Phandle for the gpio-ext bus.
$ref: /schemas/types.yaml#/definitions/phandle
timers:
description:
Timer array. Each timer entry is represented by three integers -
Mode (gpio-ext bus), delay_on and delay_off.
$ref: /schemas/types.yaml#/definitions/uint32-array
patternProperties:
"^led-[a-z0-9]+$":
type: object
$ref: common.yaml#
unevaluatedProperties: false
properties:
mode-addr:
description: Mode register address on gpio-ext bus.
$ref: /schemas/types.yaml#/definitions/uint32
mode-val:
description:
Mode to value mapping. Each entry is represented by two integers -
a mode and the corresponding value on the gpio-ext bus.
$ref: /schemas/types.yaml#/definitions/uint32-array
bright-addr:
description: Brightness register address on gpio-ext bus.
$ref: /schemas/types.yaml#/definitions/uint32
max-brightness:
description: Maximum brightness value.
$ref: /schemas/types.yaml#/definitions/uint32
required:
- mode-addr
- mode-val
- bright-addr
- max-brightness
required:
- compatible
- gpio-ext
additionalProperties: false
examples:
- |
#include <dt-bindings/leds/leds-netxbig.h>
leds {
compatible = "lacie,netxbig-leds";
gpio-ext = <&netxbig_gpio_ext>;
timers = <NETXBIG_LED_TIMER1 500 500
NETXBIG_LED_TIMER2 500 1000>;
led-0 {
label = "netxbig:blue:power";
mode-addr = <0>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 1
NETXBIG_LED_TIMER1 3
NETXBIG_LED_TIMER2 7>;
bright-addr = <1>;
max-brightness = <7>;
};
led-1 {
label = "netxbig:red:power";
mode-addr = <0>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 2
NETXBIG_LED_TIMER1 4>;
bright-addr = <1>;
max-brightness = <7>;
};
led-2 {
label = "netxbig:blue:sata0";
mode-addr = <3>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 7
NETXBIG_LED_SATA 1
NETXBIG_LED_TIMER1 3>;
bright-addr = <2>;
max-brightness = <7>;
};
led-3 {
label = "netxbig:red:sata0";
mode-addr = <3>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 2
NETXBIG_LED_TIMER1 4>;
bright-addr = <2>;
max-brightness = <7>;
};
led-4 {
label = "netxbig:blue:sata1";
mode-addr = <4>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 7
NETXBIG_LED_SATA 1
NETXBIG_LED_TIMER1 3>;
bright-addr = <2>;
max-brightness = <7>;
};
led-5 {
label = "netxbig:red:sata1";
mode-addr = <4>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 2
NETXBIG_LED_TIMER1 4>;
bright-addr = <2>;
max-brightness = <7>;
};
};

View File

@ -53,15 +53,15 @@ examples:
};
i2c {
#address-cells = <1>;
#size-cells = <0>;
#address-cells = <1>;
#size-cells = <0>;
v4l2_node: camera@36 {
reg = <0x36>;
v4l2_node: camera@36 {
reg = <0x36>;
leds = <&privacy_led>;
led-names = "privacy";
};
leds = <&privacy_led>;
led-names = "privacy";
};
};
...

View File

@ -1,29 +0,0 @@
Motorola CPCAP PMIC LEDs
------------------------
This module is part of the CPCAP. For more details about the whole
chip see Documentation/devicetree/bindings/mfd/motorola-cpcap.txt.
Requires node properties:
- compatible: should be one of
* "motorola,cpcap-led-mdl" (Main Display Lighting)
* "motorola,cpcap-led-kl" (Keyboard Lighting)
* "motorola,cpcap-led-adl" (Aux Display Lighting)
* "motorola,cpcap-led-red" (Red Triode)
* "motorola,cpcap-led-green" (Green Triode)
* "motorola,cpcap-led-blue" (Blue Triode)
* "motorola,cpcap-led-cf" (Camera Flash)
* "motorola,cpcap-led-bt" (Bluetooth)
* "motorola,cpcap-led-cp" (Camera Privacy LED)
- label: see Documentation/devicetree/bindings/leds/common.txt
- vdd-supply: A phandle to the regulator powering the LED
Example:
&cpcap {
cpcap_led_red: red-led {
compatible = "motorola,cpcap-led-red";
label = "cpcap:red";
vdd-supply = <&sw5>;
};
};

View File

@ -121,18 +121,18 @@ examples:
function = LED_FUNCTION_CHARGING;
led@0 {
reg = <0x0>;
color = <LED_COLOR_ID_RED>;
reg = <0x0>;
color = <LED_COLOR_ID_RED>;
};
led@1 {
reg = <0x1>;
color = <LED_COLOR_ID_GREEN>;
reg = <0x1>;
color = <LED_COLOR_ID_GREEN>;
};
led@2 {
reg = <0x2>;
color = <LED_COLOR_ID_BLUE>;
reg = <0x2>;
color = <LED_COLOR_ID_BLUE>;
};
};

View File

@ -100,18 +100,18 @@ examples:
color = <LED_COLOR_ID_RGB>;
led@0 {
reg = <0x0>;
color = <LED_COLOR_ID_RED>;
reg = <0x0>;
color = <LED_COLOR_ID_RED>;
};
led@1 {
reg = <0x1>;
color = <LED_COLOR_ID_GREEN>;
reg = <0x1>;
color = <LED_COLOR_ID_GREEN>;
};
led@2 {
reg = <0x2>;
color = <LED_COLOR_ID_BLUE>;
reg = <0x2>;
color = <LED_COLOR_ID_BLUE>;
};
};
};

View File

@ -1,92 +0,0 @@
Binding for the CPLD LEDs (GPIO extension bus) found on some LaCie/Seagate
boards (Example: 2Big/5Big Network v2, 2Big NAS).
Required properties:
- compatible: "lacie,netxbig-leds".
- gpio-ext: Phandle for the gpio-ext bus.
Optional properties:
- timers: Timer array. Each timer entry is represented by three integers:
Mode (gpio-ext bus), delay_on and delay_off.
Each LED is represented as a sub-node of the netxbig-leds device.
Required sub-node properties:
- mode-addr: Mode register address on gpio-ext bus.
- mode-val: Mode to value mapping. Each entry is represented by two integers:
A mode and the corresponding value on the gpio-ext bus.
- bright-addr: Brightness register address on gpio-ext bus.
- max-brightness: Maximum brightness value.
Optional sub-node properties:
- label: Name for this LED. If omitted, the label is taken from the node name.
- linux,default-trigger: Trigger assigned to the LED.
Example:
netxbig-leds {
compatible = "lacie,netxbig-leds";
gpio-ext = &gpio_ext;
timers = <NETXBIG_LED_TIMER1 500 500
NETXBIG_LED_TIMER2 500 1000>;
blue-power {
label = "netxbig:blue:power";
mode-addr = <0>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 1
NETXBIG_LED_TIMER1 3
NETXBIG_LED_TIMER2 7>;
bright-addr = <1>;
max-brightness = <7>;
};
red-power {
label = "netxbig:red:power";
mode-addr = <0>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 2
NETXBIG_LED_TIMER1 4>;
bright-addr = <1>;
max-brightness = <7>;
};
blue-sata0 {
label = "netxbig:blue:sata0";
mode-addr = <3>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 7
NETXBIG_LED_SATA 1
NETXBIG_LED_TIMER1 3>;
bright-addr = <2>;
max-brightness = <7>;
};
red-sata0 {
label = "netxbig:red:sata0";
mode-addr = <3>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 2
NETXBIG_LED_TIMER1 4>;
bright-addr = <2>;
max-brightness = <7>;
};
blue-sata1 {
label = "netxbig:blue:sata1";
mode-addr = <4>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 7
NETXBIG_LED_SATA 1
NETXBIG_LED_TIMER1 3>;
bright-addr = <2>;
max-brightness = <7>;
};
red-sata1 {
label = "netxbig:red:sata1";
mode-addr = <4>;
mode-val = <NETXBIG_LED_OFF 0
NETXBIG_LED_ON 2
NETXBIG_LED_TIMER1 4>;
bright-addr = <2>;
max-brightness = <7>;
};
};

View File

@ -0,0 +1,42 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/motorola,cpcap-leds.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Motorola CPCAP PMIC LEDs
maintainers:
- Svyatoslav Ryhel <clamor95@gmail.com>
description:
This module is part of the Motorola CPCAP MFD device. For more details
see Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml. LEDs are
represented as sub-nodes of the PMIC node on the device tree.
allOf:
- $ref: /schemas/leds/common.yaml#
properties:
compatible:
enum:
- motorola,cpcap-led-adl # Display Lighting
- motorola,cpcap-led-blue # Blue Triode
- motorola,cpcap-led-bt # Bluetooth
- motorola,cpcap-led-cf # Camera Flash
- motorola,cpcap-led-cp # Camera Privacy LED
- motorola,cpcap-led-green # Green Triode
- motorola,cpcap-led-kl # Keyboard Lighting
- motorola,cpcap-led-mdl # Main Display Lighting
- motorola,cpcap-led-red # Red Triode
vdd-supply: true
required:
- compatible
- label
- vdd-supply
unevaluatedProperties: false
...

View File

@ -164,27 +164,27 @@ examples:
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_RED>;
function = LED_FUNCTION_STATUS;
reg = <0>;
color = <LED_COLOR_ID_RED>;
function = LED_FUNCTION_STATUS;
};
led@1 {
reg = <1>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_STATUS;
reg = <1>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_STATUS;
};
led@2 {
reg = <2>;
color = <LED_COLOR_ID_BLUE>;
function = LED_FUNCTION_STATUS;
reg = <2>;
color = <LED_COLOR_ID_BLUE>;
function = LED_FUNCTION_STATUS;
};
led@3 {
reg = <3>;
color = <LED_COLOR_ID_WHITE>;
function = LED_FUNCTION_STATUS;
reg = <3>;
color = <LED_COLOR_ID_WHITE>;
function = LED_FUNCTION_STATUS;
};
};
};

View File

@ -242,6 +242,9 @@ ops and needs to declare specific support for the supported triggers.
With hw control we refer to the LED driven by hardware.
A sysfs attribute `trigger_may_offload_to_hw` is provided for userspace
to query supported triggers and their states.
LED driver must define the following value to support hw control:
- hw_control_trigger:
@ -298,6 +301,15 @@ LED driver must implement the following API to support hw control:
Returns a pointer to a struct device or NULL if nothing
is currently attached.
LED trigger should implement the following API to indicate hw control:
- hw_offloaded:
return a boolean indicating if the trigger is currently
offloaded to hardware.
If a trigger doesn't implement this callback, the default
value will be true for private triggers and false for generic
ones.
LED driver can activate additional modes by default to workaround the
impossibility of supporting each different mode on the supported trigger.
Examples are hardcoding the blink speed to a set interval, enable special
@ -311,9 +323,69 @@ the end use hw_control_set to activate hw control.
A trigger can use hw_control_get to check if a LED is already in hw control
and init their flags.
Alternatively, a private trigger can be implemented along with the LED driver if
the LED's hardware control doesn't fit any generic trigger. To associate the
private trigger with the LED classdev, their `trigger_type` must be the same. To
declare that the private trigger provides hardware control for the associated
LED classdev, set the `hw_control_trigger` string to the trigger's name. Since
both the LED classdev and the private trigger are in the same LED driver, it's
not necessary for them to coordinate via `hw_control_*` callbacks.
When the LED is in hw control, no software blink is possible and doing so
will effectively disable hw control.
Hardware-initiated trigger transition
=====================================
Some hardware can autonomously activate/deactivate hardware control. After that,
the LED hardware notifies the LED driver.
If the driver can detect such transitions and thus wants to notify the LED core
to update the current trigger then the `LED_TRIG_HW_CHANGED` flag must be set in
flags before registering. To update the current trigger accordingly, call
`led_trigger_notify_hw_control_changed` on the LED classdev.
This capability is restricted to the LED device's private trigger. The private
trigger must have been properly registered (see above) and named after
`hw_control_trigger`.
Only two transitions are defined:
- "none" => private trigger:
This happens when the hardware autonomously activates hardware control
and when "none" (i.e., no trigger) is currently active. If the private
trigger is already active when the method is called, this is essentially
a no-op.
The activation sequence for the private trigger will be executed as
normal.
The LED driver and its private trigger must be able to handle the
activation sequence even if the hardware is currently in hardware
control.
If error occurs in the activation sequence, the LED Trigger core reverts
the effective trigger to "none".
- private trigger => "none"
This happens when the hardware autonomously deactivates hardware control
and when the private trigger is currently active. If "none" (i.e., no
trigger) is active when the method is called, this is essentially a
no-op.
The deactivation sequence for the private trigger will be executed as
normal, except that the current LED brightness is retained. The reason
for keeping the brightness unchanged is that some hardware may choose a
specific brightness instead of simply turning off the LED after
autonomously deactivating hardware control.
The LED driver and its private trigger must be able to handle the
deactivation sequence even if the hardware is not currently in hardware
control.
If the current trigger is neither the private trigger nor "none", no transition
will be made.
Known Issues
============

View File

@ -20,6 +20,11 @@ brightness (PWM) and duration must be written to hw_pattern.
- Min pattern duration: 22 ms
- Max pattern duration: 5610 ms
If the device tree sets max-brightness, that value replaces 255 as the
upper limit, and a step equal to it drives the channel at full scale.
The hardware cannot go above 255, so with a larger max-brightness every
step from 255 upwards is full scale.
The format of the hardware pattern values should be:
"brightness duration brightness duration ..."

View File

@ -15077,6 +15077,7 @@ F: drivers/scsi/leapraid/
LED SUBSYSTEM
M: Lee Jones <lee@kernel.org>
M: Pavel Machek <pavel@kernel.org>
R: Griffin Kroah-Hartman <griffin@kroah.com>
L: linux-leds@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds.git
@ -15821,6 +15822,14 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml
F: drivers/iio/temperature/ltc2983.c
LTC3208 LED DRIVER
M: Jan Carlo Roleda <jancarlo.roleda@analog.com>
L: linux-leds@vger.kernel.org
S: Maintained
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/leds/adi,ltc3208.yaml
F: drivers/leds/leds-ltc3208.c
LTC3220 LED DRIVER
M: Edelweise Escala <edelweise.escala@analog.com>
L: linux-leds@vger.kernel.org

View File

@ -1042,6 +1042,18 @@ config LEDS_ACER_A500
This option enables support for the Power Button LED of
Acer Iconia Tab A500.
config LEDS_LTC3208
tristate "LED Driver for Analog Devices LTC3208"
depends on LEDS_CLASS && I2C
select REGMAP_I2C
help
Say Y to enable the LTC3208 LED driver.
This enables the LED device LTC3208, a 7-channel, 17-current source
multidisplay high-current LED driver, configured via I2C.
To compile this driver as a module, choose M here: the module will
be called ltc3208.
source "drivers/leds/blink/Kconfig"
comment "Flash and Torch LED drivers"

View File

@ -61,6 +61,7 @@ obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o
obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o
obj-$(CONFIG_LEDS_LP8864) += leds-lp8864.o
obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o
obj-$(CONFIG_LEDS_LTC3208) += leds-ltc3208.o
obj-$(CONFIG_LEDS_LTC3220) += leds-ltc3220.o
obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o
obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o

View File

@ -311,8 +311,9 @@ static int init_mm_current_scale(struct aat1290_led *led,
int i, max_mm_current =
AAT1290_MAX_MM_CURRENT(cfg->max_flash_current);
led->mm_current_scale = devm_kzalloc(&led->pdev->dev,
sizeof(max_mm_current_percent),
led->mm_current_scale = devm_kcalloc(&led->pdev->dev,
ARRAY_SIZE(max_mm_current_percent),
sizeof(*led->mm_current_scale),
GFP_KERNEL);
if (!led->mm_current_scale)
return -ENOMEM;

View File

@ -69,6 +69,8 @@ static const struct expresswire_timing ktd2692_timing = {
};
struct ktd2692_context {
struct device *dev;
/* Common ExpressWire properties (ctrl GPIO and timing) */
struct expresswire_common_props props;
@ -196,13 +198,12 @@ static void ktd2692_setup(struct ktd2692_context *led)
static void regulator_disable_action(void *_data)
{
struct device *dev = _data;
struct ktd2692_context *led = dev_get_drvdata(dev);
struct ktd2692_context *led = _data;
int ret;
ret = regulator_disable(led->regulator);
if (ret)
dev_err(dev, "Failed to disable supply: %d\n", ret);
dev_err(led->dev, "Failed to disable supply: %d\n", ret);
}
static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
@ -233,7 +234,7 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
dev_err(dev, "Failed to enable supply: %d\n", ret);
} else {
ret = devm_add_action_or_reset(dev,
regulator_disable_action, dev);
regulator_disable_action, led);
if (ret)
return ret;
}
@ -290,6 +291,7 @@ static int ktd2692_probe(struct platform_device *pdev)
if (!led)
return -ENOMEM;
led->dev = &pdev->dev;
fled_cdev = &led->fled_cdev;
led_cdev = &fled_cdev->led_cdev;
led->props.timing = ktd2692_timing;

View File

@ -187,26 +187,23 @@ static ssize_t flash_fault_show(struct device *dev,
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
u32 fault, mask = 0x1;
char *pbuf = buf;
int i, ret, buf_len;
u32 fault = 0, mask = 0x1;
int i, ret;
ret = led_get_flash_fault(fled_cdev, &fault);
if (ret < 0)
return -EINVAL;
*buf = '\0';
for (i = 0; i < LED_NUM_FLASH_FAULTS; ++i) {
if (fault & mask) {
buf_len = sprintf(pbuf, "%s ",
led_flash_fault_names[i]);
pbuf += buf_len;
}
for (ret = 0, i = 0; i < LED_NUM_FLASH_FAULTS; ++i) {
if (fault & mask)
ret += sysfs_emit_at(buf, ret, "%s%s",
(ret ? " " : ""),
led_flash_fault_names[i]);
mask <<= 1;
}
return strlen(strcat(buf, "\n"));
ret += sysfs_emit_at(buf, ret, "\n");
return ret;
}
static DEVICE_ATTR_RO(flash_fault);

View File

@ -27,29 +27,19 @@ 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;
scoped_guard(mutex, &led_cdev->led_access) {
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;
mutex_unlock(&led_cdev->led_access);
led_update_brightness(led_cdev);
brightness = led_cdev->brightness;
}
return sysfs_emit(buf, "%u\n", brightness);
}
@ -74,6 +64,9 @@ static ssize_t brightness_store(struct device *dev,
if (state == LED_OFF)
led_trigger_remove(led_cdev);
else
led_trigger_remove_hw_control(led_cdev);
led_set_brightness(led_cdev, state);
ret = size;
@ -97,17 +90,6 @@ static ssize_t max_brightness_show(struct device *dev,
}
static DEVICE_ATTR_RO(max_brightness);
#ifdef CONFIG_LEDS_TRIGGERS
static const BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0);
static const struct bin_attribute *const led_trigger_bin_attrs[] = {
&bin_attr_trigger,
NULL,
};
static const struct attribute_group led_trigger_group = {
.bin_attrs = led_trigger_bin_attrs,
};
#endif
static struct attribute *led_class_attrs[] = {
&dev_attr_brightness.attr,
&dev_attr_max_brightness.attr,
@ -577,6 +559,11 @@ int led_classdev_register_ext(struct device *parent,
#endif
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
led_cdev->brightness_hw_changed = -1;
#endif
#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED
if (led_cdev->flags & LED_TRIG_HW_CHANGED)
INIT_WORK(&led_cdev->trigger_hw_changed_work,
led_trigger_hw_control_changed_worker);
#endif
if (!led_cdev->max_brightness)
led_cdev->max_brightness = LED_FULL;
@ -616,6 +603,11 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
if (IS_ERR_OR_NULL(led_cdev->dev))
return;
#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED
if (led_cdev->flags & LED_TRIG_HW_CHANGED)
disable_work_sync(&led_cdev->trigger_hw_changed_work);
#endif
#ifdef CONFIG_LEDS_TRIGGERS
down_write(&led_cdev->trigger_lock);
if (led_cdev->trigger)

View File

@ -267,10 +267,7 @@ void led_blink_set_oneshot(struct led_classdev *led_cdev,
set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
if (invert)
set_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
else
clear_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
assign_bit(LED_BLINK_INVERT, &led_cdev->work_flags, invert);
led_blink_setup(led_cdev, delay_on, delay_off);
}

View File

@ -7,9 +7,13 @@
* Author: Richard Purdie <rpurdie@openedhand.com>
*/
#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/compiler.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/lockdep.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/timer.h>
@ -33,9 +37,38 @@ trigger_relevant(struct led_classdev *led_cdev, struct led_trigger *trig)
return !trig->trigger_type || trig->trigger_type == led_cdev->trigger_type;
}
ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count)
static bool __led_trigger_is_hw_controlled(struct led_classdev *led_cdev)
{
lockdep_assert_held(&led_cdev->trigger_lock);
if (!led_cdev->trigger)
return false;
if (!led_cdev->hw_control_trigger ||
strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name))
return false;
if (led_cdev->trigger->hw_offloaded)
return led_cdev->trigger->hw_offloaded(led_cdev);
dev_warn_once(led_cdev->dev,
"Hardware control trigger %s doesn't provide offloaded state\n",
led_cdev->trigger->name);
/* Otherwise assume private triggers are always offloaded. */
return led_cdev->trigger->trigger_type;
}
bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev)
{
guard(rwsem_read)(&led_cdev->trigger_lock);
return __led_trigger_is_hw_controlled(led_cdev);
}
EXPORT_SYMBOL_GPL(led_trigger_is_hw_controlled);
static ssize_t trigger_write(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct led_classdev *led_cdev = dev_get_drvdata(dev);
@ -78,7 +111,6 @@ ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
mutex_unlock(&led_cdev->led_access);
return ret;
}
EXPORT_SYMBOL_GPL(led_trigger_write);
__printf(3, 4)
static int led_trigger_snprintf(char *buf, ssize_t size, const char *fmt, ...)
@ -130,9 +162,9 @@ static int led_trigger_format(char *buf, size_t size,
* attribute, which is not limited by length. This is _not_ good design, do not
* copy it.
*/
ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *attr, char *buf,
loff_t pos, size_t count)
static ssize_t trigger_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *attr, char *buf,
loff_t pos, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct led_classdev *led_cdev = dev_get_drvdata(dev);
@ -160,10 +192,53 @@ ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
return len;
}
EXPORT_SYMBOL_GPL(led_trigger_read);
static const BIN_ATTR_RW(trigger, 0);
static const struct bin_attribute *const led_trigger_bin_attrs[] = {
&bin_attr_trigger,
NULL
};
static ssize_t trigger_may_offload_to_hw_show(struct device *dev,
const struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
bool offloaded = led_trigger_is_hw_controlled(led_cdev);
return sysfs_emit(buf, "%s%s%s\n",
offloaded ? "[" : "",
led_cdev->hw_control_trigger,
offloaded ? "]" : "");
}
static const DEVICE_ATTR_RO(trigger_may_offload_to_hw);
static const struct attribute *const led_trigger_attrs[] = {
&dev_attr_trigger_may_offload_to_hw.attr,
NULL
};
static umode_t led_trigger_is_visible(struct kobject *kobj,
const struct attribute *attr, int idx)
{
struct device *dev = kobj_to_dev(kobj);
struct led_classdev *led_cdev = dev_get_drvdata(dev);
if (attr == &dev_attr_trigger_may_offload_to_hw.attr)
return led_cdev->hw_control_trigger ? attr->mode : 0;
return attr->mode;
}
const struct attribute_group led_trigger_group = {
.bin_attrs = led_trigger_bin_attrs,
.attrs_const = led_trigger_attrs,
.is_visible_const = led_trigger_is_visible,
};
EXPORT_SYMBOL_GPL(led_trigger_group);
/* Caller must ensure led_cdev->trigger_lock held */
int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
static int __led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig,
bool hw_triggered)
{
char *event = NULL;
char *envp[2];
@ -194,7 +269,21 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
led_cdev->trigger_data = NULL;
led_cdev->activated = false;
led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
led_set_brightness(led_cdev, LED_OFF);
/*
* Hardware may have selected a new brightness level during its
* hardware control transition, so only reset brightness if we
* are switching to another trigger or if the switching is not
* hardware triggered.
*
* Note that this does not apply to the error path, as running
* into the error path implies a none => private trigger
* transition. This hints that the LED driver and its private
* trigger must have some fundamental bugs, so the error path
* always turns off the LED to reset it to a certain state.
*/
if (trig || !hw_triggered)
led_set_brightness(led_cdev, LED_OFF);
}
if (trig) {
spin_lock(&trig->leddev_list_lock);
@ -258,6 +347,11 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
return ret;
}
int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
{
return __led_trigger_set(led_cdev, trig, false);
}
EXPORT_SYMBOL_GPL(led_trigger_set);
void led_trigger_remove(struct led_classdev *led_cdev)
@ -268,6 +362,15 @@ void led_trigger_remove(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_trigger_remove);
void led_trigger_remove_hw_control(struct led_classdev *led_cdev)
{
guard(rwsem_write)(&led_cdev->trigger_lock);
if (__led_trigger_is_hw_controlled(led_cdev))
led_trigger_set(led_cdev, NULL);
}
EXPORT_SYMBOL_GPL(led_trigger_remove_hw_control);
static bool led_match_default_trigger(struct led_classdev *led_cdev,
struct led_trigger *trig)
{
@ -403,6 +506,60 @@ int devm_led_trigger_register(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_led_trigger_register);
#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED
static void led_trigger_do_hw_control_transition(struct led_classdev *led_cdev, bool activate,
struct led_trigger *hc_trig)
{
if (activate && !led_cdev->trigger) /* "none" => private trigger. */
__led_trigger_set(led_cdev, hc_trig, true);
else if (!activate && led_cdev->trigger == hc_trig) /* private trigger => "none". */
__led_trigger_set(led_cdev, NULL, true);
/* Already in the desired state, or another trigger is active, ignore. */
}
void led_trigger_hw_control_changed_worker(struct work_struct *work)
{
struct led_classdev *led_cdev =
container_of(work, struct led_classdev, trigger_hw_changed_work);
bool activate = READ_ONCE(led_cdev->trigger_hw_changed);
scoped_guard(rwsem_read, &triggers_list_lock) {
struct led_trigger *trig;
list_for_each_entry(trig, &trigger_list, next_trig) {
if (trig->trigger_type == led_cdev->trigger_type &&
!strcmp(trig->name, led_cdev->hw_control_trigger)) {
guard(rwsem_write)(&led_cdev->trigger_lock);
led_trigger_do_hw_control_transition(led_cdev, activate, trig);
return;
}
}
}
dev_warn(led_cdev->dev,
"Private trigger %s is not registered, can't toggle hardware control\n",
led_cdev->hw_control_trigger);
}
EXPORT_SYMBOL_GPL(led_trigger_hw_control_changed_worker);
void led_trigger_notify_hw_control_changed(struct led_classdev *led_cdev, bool activate)
{
/* Restricted to private triggers. */
if (WARN_ON(!(led_cdev->flags & LED_TRIG_HW_CHANGED) ||
!led_cdev->hw_control_trigger || !led_cdev->trigger_type))
return;
WRITE_ONCE(led_cdev->trigger_hw_changed, activate);
schedule_work(&led_cdev->trigger_hw_changed_work);
}
EXPORT_SYMBOL_GPL(led_trigger_notify_hw_control_changed);
#endif /* CONFIG_LEDS_TRIGGERS_HW_CHANGED */
/* Simple LED Trigger Interface */
void led_trigger_event(struct led_trigger *trig,

View File

@ -85,12 +85,18 @@ static int cros_ec_led_trigger_activate(struct led_classdev *led_cdev)
return cros_ec_led_send_cmd(priv->cros_ec, &arg);
}
static bool cros_ec_led_trigger_hw_offloaded(struct led_classdev *led_cdev)
{
return true;
}
static struct led_hw_trigger_type cros_ec_led_trigger_type;
static struct led_trigger cros_ec_led_trigger = {
.name = "chromeos-auto",
.trigger_type = &cros_ec_led_trigger_type,
.activate = cros_ec_led_trigger_activate,
.hw_offloaded = cros_ec_led_trigger_hw_offloaded,
};
static int cros_ec_led_brightness_set_blocking(struct led_classdev *led_cdev,

View File

@ -378,23 +378,23 @@ static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
}
static int is31fl32xx_parse_child_dt(const struct device *dev,
const struct device_node *child,
const struct fwnode_handle *child,
struct is31fl32xx_led_data *led_data)
{
struct led_classdev *cdev = &led_data->cdev;
int ret = 0;
u32 reg;
ret = of_property_read_u32(child, "reg", &reg);
ret = fwnode_property_read_u32(child, "reg", &reg);
if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
dev_err(dev,
"Child node %pOF does not have a valid reg property\n",
"Child node %pfwP does not have a valid reg property\n",
child);
return -EINVAL;
}
led_data->channel = reg;
of_property_read_u32(child, "led-max-microamp", &led_data->max_microamp);
fwnode_property_read_u32(child, "led-max-microamp", &led_data->max_microamp);
cdev->brightness_set_blocking = is31fl32xx_brightness_set;
@ -422,7 +422,7 @@ static int is31fl32xx_parse_dt(struct device *dev,
int ret = 0;
if ((cdef->output_frequency_setting_reg != IS31FL32XX_REG_NONE) &&
of_property_read_bool(dev_of_node(dev), "issi,22khz-pwm")) {
device_property_read_bool(dev, "issi,22khz-pwm")) {
ret = is31fl32xx_write(priv, cdef->output_frequency_setting_reg,
IS31FL32XX_PWM_FREQUENCY_22KHZ);
@ -433,7 +433,7 @@ static int is31fl32xx_parse_dt(struct device *dev,
}
}
for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
device_for_each_child_node_scoped(dev, child) {
struct led_init_data init_data = {};
struct is31fl32xx_led_data *led_data =
&priv->leds[priv->num_leds];
@ -451,17 +451,17 @@ static int is31fl32xx_parse_dt(struct device *dev,
led_data->channel);
if (other_led_data) {
dev_err(dev,
"Node %pOF 'reg' conflicts with another LED\n",
"Node %pfwP 'reg' conflicts with another LED\n",
child);
return -EINVAL;
}
init_data.fwnode = of_fwnode_handle(child);
init_data.fwnode = child;
ret = devm_led_classdev_register_ext(dev, &led_data->cdev,
&init_data);
if (ret) {
dev_err(dev, "Failed to register LED for %pOF: %d\n",
dev_err(dev, "Failed to register LED for %pfwP: %d\n",
child, ret);
return ret;
}
@ -576,7 +576,7 @@ static int is31fl32xx_probe(struct i2c_client *client)
cdef = device_get_match_data(dev);
count = of_get_available_child_count(dev_of_node(dev));
count = device_get_child_node_count(dev);
if (!count)
return -EINVAL;

View File

@ -276,9 +276,11 @@ static int lm3692x_leds_enable(struct lm3692x_led *led)
ret = regmap_update_bits(led->regmap, LM3692X_EN, LM3692X_ENABLE_MASK,
enable_state | LM3692X_DEVICE_EN);
if (ret)
goto out;
led->enabled = true;
return ret;
return 0;
out:
dev_err(&led->client->dev, "Fail writing initialization values\n");

View File

@ -7,6 +7,7 @@
* Author: Dan Murphy <dmurphy@ti.com>
*/
#include <linux/cleanup.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/leds.h>
@ -274,7 +275,6 @@ static int lp8860_probe(struct i2c_client *client)
int ret;
struct lp8860_led *led;
struct device_node *np = dev_of_node(&client->dev);
struct device_node *child_node;
struct led_init_data init_data = {};
struct gpio_desc *enable_gpio;
@ -282,7 +282,8 @@ static int lp8860_probe(struct i2c_client *client)
if (!led)
return -ENOMEM;
child_node = of_get_next_available_child(np, NULL);
struct device_node *child_node __free(device_node) =
of_get_next_available_child(np, NULL);
if (!child_node)
return -EINVAL;

234
drivers/leds/leds-ltc3208.c Normal file
View File

@ -0,0 +1,234 @@
// SPDX-License-Identifier: GPL-2.0
/*
* LED driver for Analog Devices LTC3208 Multi-Display Driver
*
* Copyright 2026 Analog Devices Inc.
*
* Author: Jan Carlo Roleda <jancarlo.roleda@analog.com>
*
*/
#include <linux/bitfield.h>
#include <linux/bitmap.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/types.h>
/* Registers */
#define LTC3208_REG_A_GRNRED_CURRENT 0x1 /* Green and Red current DAC */
#define LTC3208_REG_B_AUXBLU_CURRENT 0x2 /* AUX and Blue current DAC */
#define LTC3208_REG_C_MAIN_CURRENT 0x3 /* Main current DAC */
#define LTC3208_REG_D_SUB_CURRENT 0x4 /* Sub current DAC */
#define LTC3208_REG_E_AUX_SELECT 0x5 /* AUX DAC Select */
#define LTC3208_AUX1_MASK GENMASK(1, 0)
#define LTC3208_AUX2_MASK GENMASK(3, 2)
#define LTC3208_AUX3_MASK GENMASK(5, 4)
#define LTC3208_AUX4_MASK GENMASK(7, 6)
#define LTC3208_REG_F_CAM_CURRENT 0x6 /* CAM (High and Low) current DAC */
#define LTC3208_REG_G_OPT 0x7 /* Device Options */
#define LTC3208_OPT_CPO_MASK GENMASK(7, 6)
#define LTC3208_OPT_DIS_RGBDROP BIT(3)
#define LTC3208_OPT_DIS_CAMHILO BIT(2)
#define LTC3208_OPT_EN_RGBS BIT(1)
#define LTC3208_MAX_BRIGHTNESS_4BIT 0xF
#define LTC3208_MAX_BRIGHTNESS_8BIT 0xFF
#define LTC3208_NUM_AUX_LEDS 4 /* Number of configurable aux channels */
#define LTC3208_NUM_REGS 7 /* Number of available registers to access */
enum ltc3208_aux_channel {
LTC3208_AUX_CHAN_AUX = 0,
LTC3208_AUX_CHAN_MAIN,
LTC3208_AUX_CHAN_SUB,
LTC3208_AUX_CHAN_CAM
};
enum ltc3208_channel {
LTC3208_CHAN_MAIN = 0,
LTC3208_CHAN_SUB,
LTC3208_CHAN_AUX,
LTC3208_CHAN_CAML,
LTC3208_CHAN_CAMH,
LTC3208_CHAN_RED,
LTC3208_CHAN_BLUE,
LTC3208_CHAN_GREEN,
LTC3208_CHAN_N_COUNT,
};
static const char *const ltc3208_dt_aux_channels[] = {
"adi,aux1-channel",
"adi,aux2-channel",
"adi,aux3-channel",
"adi,aux4-channel"
};
static const char *const ltc3208_aux_opt[] = { "aux", "main", "sub", "cam" };
struct ltc3208_led {
struct led_classdev cdev;
struct regmap_field *rfield;
};
static const struct reg_default ltc3208_reg_defaults[] = {
{ LTC3208_REG_A_GRNRED_CURRENT, 0 },
{ LTC3208_REG_B_AUXBLU_CURRENT, 0 },
{ LTC3208_REG_C_MAIN_CURRENT, 0 },
{ LTC3208_REG_D_SUB_CURRENT, 0 },
{ LTC3208_REG_E_AUX_SELECT, 0 },
{ LTC3208_REG_F_CAM_CURRENT, 0 },
{ LTC3208_REG_G_OPT, 0 }
};
static const struct regmap_config ltc3208_regmap_cfg = {
.reg_bits = 8,
.val_bits = 8,
.max_register = LTC3208_REG_G_OPT,
.cache_type = REGCACHE_FLAT_S,
.reg_defaults = ltc3208_reg_defaults,
.num_reg_defaults = LTC3208_NUM_REGS,
};
static const struct reg_field ltc3208_led_reg_field[LTC3208_CHAN_N_COUNT] = {
[LTC3208_CHAN_MAIN] = REG_FIELD(LTC3208_REG_C_MAIN_CURRENT, 0, 7),
[LTC3208_CHAN_SUB] = REG_FIELD(LTC3208_REG_D_SUB_CURRENT, 0, 7),
[LTC3208_CHAN_BLUE] = REG_FIELD(LTC3208_REG_B_AUXBLU_CURRENT, 0, 3),
[LTC3208_CHAN_AUX] = REG_FIELD(LTC3208_REG_B_AUXBLU_CURRENT, 4, 7),
[LTC3208_CHAN_CAML] = REG_FIELD(LTC3208_REG_F_CAM_CURRENT, 0, 3),
[LTC3208_CHAN_CAMH] = REG_FIELD(LTC3208_REG_F_CAM_CURRENT, 4, 7),
[LTC3208_CHAN_RED] = REG_FIELD(LTC3208_REG_A_GRNRED_CURRENT, 0, 3),
[LTC3208_CHAN_GREEN] = REG_FIELD(LTC3208_REG_A_GRNRED_CURRENT, 4, 7),
};
static int ltc3208_led_set_brightness(struct led_classdev *led_cdev, enum led_brightness brightness)
{
struct ltc3208_led *led = container_of(led_cdev, struct ltc3208_led, cdev);
u8 current_level = brightness;
return regmap_field_write(led->rfield, current_level);
}
static int ltc3208_probe(struct i2c_client *client)
{
enum ltc3208_aux_channel aux_channels[LTC3208_NUM_AUX_LEDS];
DECLARE_BITMAP(registered_chans, LTC3208_CHAN_N_COUNT) = { };
struct regmap *regmap;
bool disable_rgb_aux4_dropout_signal;
bool disable_camhl_pin;
bool set_sub_control_pin;
int ret;
u8 dev_options;
regmap = devm_regmap_init_i2c(client, &ltc3208_regmap_cfg);
if (IS_ERR(regmap))
return dev_err_probe(&client->dev, PTR_ERR(regmap), "Failed to initialize regmap");
regcache_mark_dirty(regmap);
ret = regcache_sync(regmap);
if (ret)
return dev_err_probe(&client->dev, ret, "Failed to sync register cache");
disable_camhl_pin = device_property_read_bool(&client->dev, "adi,disable-camhl-pin");
set_sub_control_pin = device_property_read_bool(&client->dev, "adi,cfg-enrgbs-pin");
disable_rgb_aux4_dropout_signal =
device_property_read_bool(&client->dev, "adi,disable-rgb-aux4-dropout");
dev_options = FIELD_PREP(LTC3208_OPT_EN_RGBS, set_sub_control_pin) |
FIELD_PREP(LTC3208_OPT_DIS_CAMHILO, disable_camhl_pin) |
FIELD_PREP(LTC3208_OPT_CPO_MASK, 0) |
FIELD_PREP(LTC3208_OPT_DIS_RGBDROP, disable_rgb_aux4_dropout_signal);
ret = regmap_write(regmap, LTC3208_REG_G_OPT, dev_options);
if (ret)
return dev_err_probe(&client->dev, ret, "Failed to set device options register");
/* Initialize AUX channel configurations */
for (int i = 0; i < LTC3208_NUM_AUX_LEDS; i++) {
/* Default value (AUX) if property is not present in DT */
if (!device_property_present(&client->dev, ltc3208_dt_aux_channels[i])) {
aux_channels[i] = LTC3208_AUX_CHAN_AUX;
continue;
}
ret = device_property_match_property_string(&client->dev,
ltc3208_dt_aux_channels[i],
ltc3208_aux_opt, LTC3208_NUM_AUX_LEDS);
if (ret < 0)
return dev_err_probe(&client->dev, ret, "Error reading AUX Channel %d", i);
aux_channels[i] = ret;
}
dev_options = FIELD_PREP(LTC3208_AUX1_MASK, aux_channels[0]) |
FIELD_PREP(LTC3208_AUX2_MASK, aux_channels[1]) |
FIELD_PREP(LTC3208_AUX3_MASK, aux_channels[2]) |
FIELD_PREP(LTC3208_AUX4_MASK, aux_channels[3]);
ret = regmap_write(regmap, LTC3208_REG_E_AUX_SELECT, dev_options);
if (ret)
return dev_err_probe(&client->dev, ret, "Error writing to aux channel register");
device_for_each_child_node_scoped(&client->dev, child) {
struct ltc3208_led *led;
struct led_init_data init_data = { };
u32 chan;
ret = fwnode_property_read_u32(child, "reg", &chan);
if (ret)
return dev_err_probe(&client->dev, ret,
"Failed to get register value of LED");
if (chan >= LTC3208_CHAN_N_COUNT)
return dev_err_probe(&client->dev, -EINVAL, "%u is an invalid LED ID",
chan);
if (test_and_set_bit(chan, registered_chans))
return dev_err_probe(&client->dev, -EINVAL, "%u is already registered",
chan);
led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
led->rfield = devm_regmap_field_alloc(&client->dev, regmap,
ltc3208_led_reg_field[chan]);
if (IS_ERR(led->rfield))
return dev_err_probe(&client->dev, PTR_ERR(led->rfield),
"Cannot allocate regmap field");
led->cdev.brightness_set_blocking = ltc3208_led_set_brightness;
led->cdev.max_brightness = LTC3208_MAX_BRIGHTNESS_4BIT;
if (chan == LTC3208_CHAN_MAIN || chan == LTC3208_CHAN_SUB)
led->cdev.max_brightness = LTC3208_MAX_BRIGHTNESS_8BIT;
init_data.fwnode = child;
ret = devm_led_classdev_register_ext(&client->dev, &led->cdev, &init_data);
if (ret)
return dev_err_probe(&client->dev, ret, "Failed to register LED %u", chan);
}
return 0;
}
static const struct of_device_id ltc3208_match_table[] = {
{ .compatible = "adi,ltc3208" },
{ }
};
MODULE_DEVICE_TABLE(of, ltc3208_match_table);
static struct i2c_driver ltc3208_driver = {
.driver = {
.name = "ltc3208",
.of_match_table = ltc3208_match_table,
},
.probe = ltc3208_probe,
};
module_i2c_driver(ltc3208_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jan Carlo Roleda <jancarlo.roleda@analog.com>");
MODULE_DESCRIPTION("LTC3208 LED Driver");

View File

@ -160,7 +160,6 @@ static int max77705_add_led(struct device *dev, struct regmap *regmap, struct fw
struct max77705_led *led;
struct led_classdev *cdev;
struct mc_subled *info;
struct fwnode_handle *child;
struct led_init_data init_data = {};
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
@ -191,7 +190,7 @@ static int max77705_add_led(struct device *dev, struct regmap *regmap, struct fw
cdev->brightness_set_blocking = max77705_led_brightness_set_multi;
cdev->blink_set = max77705_rgb_blink;
fwnode_for_each_child_node(np, child) {
fwnode_for_each_child_node_scoped(np, child) {
ret = max77705_parse_subled(dev, child, &info[i]);
if (ret < 0)
return ret;

View File

@ -298,12 +298,15 @@ static enum pca9532_state pca9532_getled(struct pca9532_led *led)
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
char reg;
enum pca9532_state ret;
int reg;
enum pca9532_state ret = PCA9532_OFF;
mutex_lock(&data->update_lock);
reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
ret = (reg & LED_MASK(led->id)) >> LED_SHIFT(led->id);
if (reg >= 0)
ret = (reg & LED_MASK(led->id)) >> LED_SHIFT(led->id);
else
dev_warn(&client->dev, "failed to read LED register: %d\n", reg);
mutex_unlock(&data->update_lock);
return ret;
}

View File

@ -79,6 +79,17 @@ struct pca955x_chipdef {
int blink_div; /* PSC divider */
};
struct pca955x_led {
struct pca955x *pca955x;
struct led_classdev led_cdev;
int led_num; /* 0 .. 15 potentially */
u32 type;
enum led_default_state default_state;
struct fwnode_handle *fwnode;
};
#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev)
static const struct pca955x_chipdef pca955x_chipdefs[] = {
[pca9550] = {
.bits = 2,
@ -114,7 +125,6 @@ static const struct pca955x_chipdef pca955x_chipdefs[] = {
struct pca955x {
struct mutex lock;
struct pca955x_led *leds;
const struct pca955x_chipdef *chipdef;
struct i2c_client *client;
unsigned long active_blink;
@ -123,22 +133,7 @@ struct pca955x {
#ifdef CONFIG_LEDS_PCA955X_GPIO
struct gpio_chip gpio;
#endif
};
struct pca955x_led {
struct pca955x *pca955x;
struct led_classdev led_cdev;
int led_num; /* 0 .. 15 potentially */
u32 type;
enum led_default_state default_state;
struct fwnode_handle *fwnode;
};
#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev)
struct pca955x_platform_data {
struct pca955x_led *leds;
int num_leds;
struct pca955x_led leds[];
};
/* 8 bits per input register */
@ -530,37 +525,27 @@ static int pca955x_gpio_direction_output(struct gpio_chip *gc,
}
#endif /* CONFIG_LEDS_PCA955X_GPIO */
static struct pca955x_platform_data *
pca955x_get_pdata(struct i2c_client *client, const struct pca955x_chipdef *chip)
static int pca955x_parse_fwnode(struct pca955x *pca955x)
{
struct pca955x_platform_data *pdata;
struct device *dev = &pca955x->client->dev;
u8 bits = pca955x->chipdef->bits;
struct pca955x_led *led;
struct fwnode_handle *child;
int count;
count = device_get_child_node_count(&client->dev);
if (count > chip->bits)
return ERR_PTR(-ENODEV);
count = device_get_child_node_count(dev);
if (count > bits)
return -ENODEV;
pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->leds = devm_kcalloc(&client->dev,
chip->bits, sizeof(struct pca955x_led),
GFP_KERNEL);
if (!pdata->leds)
return ERR_PTR(-ENOMEM);
device_for_each_child_node(&client->dev, child) {
device_for_each_child_node(dev, child) {
u32 reg;
int res;
res = fwnode_property_read_u32(child, "reg", &reg);
if ((res != 0) || (reg >= chip->bits))
if ((res != 0) || (reg >= bits))
continue;
led = &pdata->leds[reg];
led = &pca955x->leds[reg];
led->type = PCA955X_TYPE_LED;
led->fwnode = child;
led->default_state = led_init_default_state_get(child);
@ -568,9 +553,7 @@ pca955x_get_pdata(struct i2c_client *client, const struct pca955x_chipdef *chip)
fwnode_property_read_u32(child, "type", &led->type);
}
pdata->num_leds = chip->bits;
return pdata;
return 0;
}
static int pca955x_probe(struct i2c_client *client)
@ -584,7 +567,6 @@ static int pca955x_probe(struct i2c_client *client)
u8 i, nls, psc0;
u8 ls1[4];
u8 ls2[4];
struct pca955x_platform_data *pdata;
bool keep_psc0 = false;
bool set_default_label = false;
char default_label[4];
@ -595,12 +577,6 @@ static int pca955x_probe(struct i2c_client *client)
return dev_err_probe(&client->dev, -ENODEV, "unknown chip\n");
adapter = client->adapter;
pdata = dev_get_platdata(&client->dev);
if (!pdata) {
pdata = pca955x_get_pdata(client, chip);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
/* Make sure the slave address / chip type combo given is possible */
if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) !=
@ -616,22 +592,11 @@ static int pca955x_probe(struct i2c_client *client)
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
if (pdata->num_leds != chip->bits) {
dev_err(&client->dev,
"board info claims %d LEDs on a %u-bit chip\n",
pdata->num_leds, chip->bits);
return -ENODEV;
}
pca955x = devm_kzalloc(&client->dev, sizeof(*pca955x), GFP_KERNEL);
pca955x = devm_kzalloc(&client->dev,
struct_size(pca955x, leds, chip->bits), GFP_KERNEL);
if (!pca955x)
return -ENOMEM;
pca955x->leds = devm_kcalloc(&client->dev, chip->bits,
sizeof(*pca955x_led), GFP_KERNEL);
if (!pca955x->leds)
return -ENOMEM;
i2c_set_clientdata(client, pca955x);
mutex_init(&pca955x->lock);
@ -639,6 +604,10 @@ static int pca955x_probe(struct i2c_client *client)
pca955x->chipdef = chip;
pca955x->blink_period = PCA955X_BLINK_DEFAULT_MS;
err = pca955x_parse_fwnode(pca955x);
if (err)
return err;
init_data.devname_mandatory = false;
init_data.devicename = "pca955x";
@ -657,7 +626,6 @@ static int pca955x_probe(struct i2c_client *client)
pca955x_led = &pca955x->leds[i];
pca955x_led->led_num = i;
pca955x_led->pca955x = pca955x;
pca955x_led->type = pdata->leds[i].type;
switch (pca955x_led->type) {
case PCA955X_TYPE_NONE:
@ -671,16 +639,16 @@ static int pca955x_probe(struct i2c_client *client)
led->brightness_get = pca955x_led_get;
led->blink_set = pca955x_led_blink;
if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF)
if (pca955x_led->default_state == LEDS_DEFSTATE_OFF)
ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_OFF);
else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON)
else if (pca955x_led->default_state == LEDS_DEFSTATE_ON)
ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_ON);
else if (pca955x_ledstate(ls2[reg], bit) == PCA955X_LS_BLINK0) {
keep_psc0 = true;
set_bit(i, &pca955x->active_blink);
}
init_data.fwnode = pdata->leds[i].fwnode;
init_data.fwnode = pca955x_led->fwnode;
if (is_of_node(init_data.fwnode)) {
if (to_of_node(init_data.fwnode)->name[0] ==

View File

@ -208,10 +208,7 @@ static void pca963x_track_power_state(struct pca963x_led *led, unsigned int led_
{
unsigned long *leds_on = &led->chip->leds_on;
if (brightness)
set_bit(led_num, leds_on);
else
clear_bit(led_num, leds_on);
assign_bit(led_num, leds_on, brightness);
}
static int pca963x_sync_power_state(struct pca963x_led *led, unsigned long cached_leds)

View File

@ -339,7 +339,7 @@ static void ich7_lpc_cleanup(struct device *dev)
/*
* The OS has determined that the LPC of the Intel ICH7 Southbridge is present
* so we can retrive the required operational information and prepare the GPIO.
* so we can retrieve the required operational information and prepare the GPIO.
*/
static struct pci_dev *nas_gpio_pci_dev;
static int ich7_lpc_probe(struct pci_dev *dev,

View File

@ -12,9 +12,11 @@
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/string.h>
#define ST1202_BLINK_DEFAULT_DELAY 500
#define ST1202_CHAN_DISABLE_ALL 0x00
#define ST1202_CHAN_ENABLE_HIGH 0x03
#define ST1202_CHAN_ENABLE_LOW 0x02
@ -23,7 +25,8 @@
#define ST1202_CONFIG_REG_PATS BIT(7)
/* PATSR: Pattern sequence runs (self-clear when sequence is finished) */
#define ST1202_CONFIG_REG_PATSR BIT(6)
#define ST1202_CONFIG_REG_SHFT BIT(3)
/* SHFT: Phase-shift delay enable */
#define ST1202_CONFIG_REG_PHASE_SHIFT BIT(3)
#define ST1202_DEV_ENABLE 0x01
#define ST1202_DEV_ENABLE_ON BIT(0)
#define ST1202_DEV_ENABLE_RESET BIT(7)
@ -57,7 +60,7 @@ static struct st1202_led *cdev_to_st1202_led(struct led_classdev *cdev)
return container_of(cdev, struct st1202_led, led_cdev);
}
static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val)
static int st1202_read_reg(struct st1202_chip *chip, int reg, u8 *val)
{
struct device *dev = &chip->client->dev;
int ret;
@ -68,11 +71,11 @@ static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val)
return ret;
}
*val = (uint8_t)ret;
*val = (u8)ret;
return 0;
}
static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val)
static int st1202_write_reg(struct st1202_chip *chip, int reg, u8 val)
{
struct device *dev = &chip->client->dev;
int ret;
@ -84,11 +87,27 @@ static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val)
return ret;
}
static uint8_t st1202_prescalar_to_miliseconds(unsigned int value)
static u8 st1202_milliseconds_to_prescaler(unsigned int value)
{
return value / ST1202_MILLIS_PATTERN_DUR_MIN;
}
/* The channel current register is 8 bits wide, whatever max-brightness says */
static u8 st1202_iled_max(struct led_classdev *led_cdev)
{
return min_t(unsigned int, led_cdev->max_brightness, U8_MAX);
}
static u16 st1202_brightness_to_pwm(int brightness, unsigned int max_brightness)
{
if (!max_brightness)
return 0;
brightness = clamp_t(int, brightness, LED_OFF, max_brightness);
return brightness * ST1202_PATTERN_PWM_FULL / max_brightness;
}
static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num,
int pattern, unsigned int value)
{
@ -125,7 +144,7 @@ static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern,
unsigned int value)
{
return st1202_write_reg(chip, (ST1202_PATTERN_DUR + pattern),
st1202_prescalar_to_miliseconds(value));
st1202_milliseconds_to_prescaler(value));
}
static int __st1202_channel_set(struct st1202_chip *chip, int led_num, bool active)
@ -160,27 +179,6 @@ static int __st1202_channel_set(struct st1202_chip *chip, int led_num, bool acti
return 0;
}
static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active)
{
guard(mutex)(&chip->lock);
return __st1202_channel_set(chip, led_num, active);
}
static void st1202_brightness_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct st1202_led *led = cdev_to_st1202_led(led_cdev);
struct st1202_chip *chip = led->chip;
guard(mutex)(&chip->lock);
for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++)
st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL);
st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value);
__st1202_channel_set(chip, led->led_num, !!value);
}
static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev)
{
struct st1202_led *led = cdev_to_st1202_led(led_cdev);
@ -197,8 +195,30 @@ static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev)
static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value)
{
struct st1202_led *led = cdev_to_st1202_led(ldev);
struct st1202_chip *chip = led->chip;
int ret;
return st1202_channel_set(led->chip, led->led_num, !!value);
guard(mutex)(&chip->lock);
/*
* The output of a channel is ILED x Pattern_PWM / 4095. Setting every
* PWM slot to full scale makes it equal ILED whatever the state of the
* sequencer, so the brightness takes effect without stopping the
* sequencer, which is global and would disturb the other channels.
*/
for (int pattern = 0; pattern < ST1202_MAX_PATTERNS; pattern++) {
ret = st1202_pwm_pattern_write(chip, led->led_num, pattern,
ST1202_PATTERN_PWM_FULL);
if (ret)
return ret;
}
ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num,
min_t(unsigned int, value, st1202_iled_max(ldev)));
if (ret)
return ret;
return __st1202_channel_set(chip, led->led_num, !!value);
}
static int st1202_led_pattern_clear(struct led_classdev *ldev)
@ -209,16 +229,25 @@ static int st1202_led_pattern_clear(struct led_classdev *ldev)
guard(mutex)(&chip->lock);
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, LED_OFF);
if (ret != 0)
return ret;
for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) {
ret = st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL);
ret = __st1202_channel_set(chip, led->led_num, false);
if (ret != 0)
return ret;
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT);
if (ret != 0)
return ret;
for (int pattern = 0; pattern < ST1202_MAX_PATTERNS; pattern++) {
ret = st1202_pwm_pattern_write(chip, led->led_num, pattern,
ST1202_PATTERN_PWM_FULL);
if (ret != 0)
return ret;
ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + patt, 0);
ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + pattern, 0);
if (ret != 0)
return ret;
}
@ -227,34 +256,45 @@ static int st1202_led_pattern_clear(struct led_classdev *ldev)
}
static int st1202_led_pattern_set(struct led_classdev *ldev,
struct led_pattern *pattern,
struct led_pattern *patterns,
u32 len, int repeat)
{
struct st1202_led *led = cdev_to_st1202_led(ldev);
struct st1202_chip *chip = led->chip;
unsigned int max_brightness;
int ret;
max_brightness = st1202_iled_max(ldev);
if (len > ST1202_MAX_PATTERNS)
return -EINVAL;
for (int patt = 0; patt < len; patt++) {
if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN ||
pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX)
for (int pattern = 0; pattern < len; pattern++) {
if (patterns[pattern].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN ||
patterns[pattern].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX)
return -EINVAL;
}
guard(mutex)(&chip->lock);
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT);
if (ret != 0)
return ret;
for (int patt = 0; patt < len; patt++) {
ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness);
for (int pattern = 0; pattern < len; pattern++) {
ret = st1202_pwm_pattern_write(chip, led->led_num, pattern,
st1202_brightness_to_pwm(patterns[pattern].brightness,
max_brightness));
if (ret != 0)
return ret;
ret = st1202_duration_pattern_write(chip, patt, pattern[patt].delta_t);
ret = st1202_duration_pattern_write(chip, pattern, patterns[pattern].delta_t);
if (ret != 0)
return ret;
}
for (int pattern = len; pattern < ST1202_MAX_PATTERNS; pattern++) {
ret = st1202_pwm_pattern_write(chip, led->led_num, pattern, LED_OFF);
if (ret != 0)
return ret;
}
@ -263,18 +303,117 @@ static int st1202_led_pattern_set(struct led_classdev *ldev,
if (ret != 0)
return ret;
ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, max_brightness);
if (ret != 0)
return ret;
ret = __st1202_channel_set(chip, led->led_num, true);
if (ret != 0)
return ret;
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR |
ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT));
ret = st1202_write_reg(chip, ST1202_CONFIG_REG,
ST1202_CONFIG_REG_PATSR | ST1202_CONFIG_REG_PATS |
ST1202_CONFIG_REG_PHASE_SHIFT);
if (ret != 0)
return ret;
return 0;
}
static int st1202_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_off)
{
struct st1202_led *led = cdev_to_st1202_led(led_cdev);
struct st1202_chip *chip = led->chip;
unsigned long on, off;
int ret;
if (!*delay_on)
*delay_on = ST1202_BLINK_DEFAULT_DELAY;
if (!*delay_off)
*delay_off = ST1202_BLINK_DEFAULT_DELAY;
on = *delay_on;
off = *delay_off;
on = clamp_val(on, ST1202_MILLIS_PATTERN_DUR_MIN, ST1202_MILLIS_PATTERN_DUR_MAX);
off = clamp_val(off, ST1202_MILLIS_PATTERN_DUR_MIN, ST1202_MILLIS_PATTERN_DUR_MAX);
on = roundup(on, ST1202_MILLIS_PATTERN_DUR_MIN);
off = roundup(off, ST1202_MILLIS_PATTERN_DUR_MIN);
guard(mutex)(&chip->lock);
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT);
if (ret)
return ret;
/* Zero out PWM for all other active channels to prevent them from blinking */
for (int chan = 0; chan < ST1202_MAX_LEDS; chan++) {
if (!chip->leds[chan].is_active || chan == led->led_num)
continue;
ret = st1202_pwm_pattern_write(chip, chan, 0, LED_OFF);
if (ret)
return ret;
ret = st1202_pwm_pattern_write(chip, chan, 1, LED_OFF);
if (ret)
return ret;
}
ret = st1202_pwm_pattern_write(chip, led->led_num, 0, ST1202_PATTERN_PWM_FULL);
if (ret)
return ret;
for (int pattern = 1; pattern < ST1202_MAX_PATTERNS; pattern++) {
ret = st1202_pwm_pattern_write(chip, led->led_num, pattern, LED_OFF);
if (ret)
return ret;
}
ret = st1202_duration_pattern_write(chip, 0, on);
if (ret)
return ret;
ret = st1202_duration_pattern_write(chip, 1, off);
if (ret)
return ret;
for (int pattern = 2; pattern < ST1202_MAX_PATTERNS; pattern++) {
ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + pattern, 0);
if (ret)
return ret;
}
ret = st1202_write_reg(chip, ST1202_PATTERN_REP, U8_MAX);
if (ret)
return ret;
ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, st1202_iled_max(led_cdev));
if (ret)
return ret;
ret = __st1202_channel_set(chip, led->led_num, true);
if (ret)
return ret;
ret = st1202_write_reg(chip, ST1202_CONFIG_REG,
ST1202_CONFIG_REG_PATSR | ST1202_CONFIG_REG_PATS |
ST1202_CONFIG_REG_PHASE_SHIFT);
if (ret)
return ret;
*delay_on = on;
*delay_off = off;
return 0;
}
static void st1202_fwnode_put(void *data)
{
fwnode_handle_put(data);
}
static int st1202_dt_init(struct st1202_chip *chip)
{
struct device *dev = &chip->client->dev;
@ -294,14 +433,18 @@ static int st1202_dt_init(struct st1202_chip *chip)
led = &chip->leds[reg];
led->is_active = true;
led->fwnode = of_fwnode_handle(child);
led->fwnode = fwnode_handle_get(of_fwnode_handle(child));
err = devm_add_action_or_reset(dev, st1202_fwnode_put, led->fwnode);
if (err)
return err;
led->led_cdev.max_brightness = U8_MAX;
led->led_cdev.brightness_set_blocking = st1202_led_set;
led->led_cdev.pattern_set = st1202_led_pattern_set;
led->led_cdev.pattern_clear = st1202_led_pattern_clear;
led->led_cdev.default_trigger = "pattern";
led->led_cdev.brightness_set = st1202_brightness_set;
led->led_cdev.blink_set = st1202_blink_set;
led->led_cdev.brightness_get = st1202_brightness_get;
}
@ -330,7 +473,7 @@ static int st1202_setup(struct st1202_chip *chip)
return ret;
/* Enable phase-shift delay feature */
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT);
if (ret < 0)
return ret;
@ -342,7 +485,7 @@ static int st1202_setup(struct st1202_chip *chip)
/* Duration of initialization */
usleep_range(6500, 10000);
/* Deactivate all LEDS (channels) and activate only the ones found in Device Tree */
/* Deactivate all LEDs (channels); each is enabled when it is lit */
ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, ST1202_CHAN_DISABLE_ALL);
if (ret < 0)
return ret;
@ -389,11 +532,6 @@ static int st1202_probe(struct i2c_client *client)
if (!led->is_active)
continue;
ret = st1202_channel_set(led->chip, led->led_num, true);
if (ret < 0)
return dev_err_probe(&client->dev, ret,
"Failed to activate LED channel\n");
ret = st1202_led_pattern_clear(&led->led_cdev);
if (ret < 0)
return dev_err_probe(&client->dev, ret,

View File

@ -56,7 +56,6 @@ static int syscon_led_probe(struct platform_device *pdev)
{
struct led_init_data init_data = {};
struct device *dev = &pdev->dev;
struct device_node *np = dev_of_node(dev);
struct device *parent;
struct regmap *map;
struct syscon_led *sled;
@ -81,13 +80,13 @@ static int syscon_led_probe(struct platform_device *pdev)
sled->map = map;
if (of_property_read_u32(np, "reg", &sled->offset) &&
of_property_read_u32(np, "offset", &sled->offset))
if (device_property_read_u32(dev, "reg", &sled->offset) &&
device_property_read_u32(dev, "offset", &sled->offset))
return -EINVAL;
if (of_property_read_u32(np, "mask", &sled->mask))
if (device_property_read_u32(dev, "mask", &sled->mask))
return -EINVAL;
init_data.fwnode = of_fwnode_handle(np);
init_data.fwnode = dev_fwnode(dev);
state = led_init_default_state_get(init_data.fwnode);
switch (state) {

View File

@ -195,10 +195,16 @@ static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
err);
}
static bool omnia_hwtrig_hw_offloaded(struct led_classdev *cdev)
{
return true;
}
static struct led_trigger omnia_hw_trigger = {
.name = "omnia-mcu",
.activate = omnia_hwtrig_activate,
.deactivate = omnia_hwtrig_deactivate,
.hw_offloaded = omnia_hwtrig_hw_offloaded,
.trigger_type = &omnia_hw_trigger_type,
};
@ -251,6 +257,7 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
* by LED class from the linux,default-trigger property.
*/
cdev->default_trigger = omnia_hw_trigger.name;
cdev->hw_control_trigger = omnia_hw_trigger.name;
/* Put the LED into software mode */
ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(led->reg) |

View File

@ -21,14 +21,10 @@ void led_init_core(struct led_classdev *led_cdev);
void led_stop_software_blink(struct led_classdev *led_cdev);
void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value);
void led_set_brightness_nosleep(struct led_classdev *led_cdev, unsigned int value);
ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *attr, char *buf,
loff_t pos, size_t count);
ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count);
void led_trigger_hw_control_changed_worker(struct work_struct *work);
extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list;
extern const struct attribute_group led_trigger_group;
#endif /* __LEDS_H_INCLUDED */

View File

@ -11,6 +11,7 @@
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
@ -1342,7 +1343,7 @@ static int lpg_add_pwm(struct lpg *lpg)
return ret;
}
static int lpg_parse_channel(struct lpg *lpg, struct device_node *np,
static int lpg_parse_channel(struct lpg *lpg, struct fwnode_handle *fw,
struct lpg_channel **channel)
{
struct lpg_channel *chan;
@ -1350,17 +1351,18 @@ static int lpg_parse_channel(struct lpg *lpg, struct device_node *np,
u32 reg;
int ret;
ret = of_property_read_u32(np, "reg", &reg);
ret = fwnode_property_read_u32(fw, "reg", &reg);
if (ret || !reg || reg > lpg->num_channels)
return dev_err_probe(lpg->dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np);
return dev_err_probe(lpg->dev, -EINVAL,
"invalid \"reg\" of %s\n", fwnode_get_name(fw));
chan = &lpg->channels[reg - 1];
chan->in_use = true;
ret = of_property_read_u32(np, "color", &color);
ret = fwnode_property_read_u32(fw, "color", &color);
if (ret < 0 && ret != -EINVAL)
return dev_err_probe(lpg->dev, ret,
"failed to parse \"color\" of %pOF\n", np);
"failed to parse \"color\" of %s\n", fwnode_get_name(fw));
chan->color = color;
@ -1369,25 +1371,26 @@ static int lpg_parse_channel(struct lpg *lpg, struct device_node *np,
return 0;
}
static int lpg_add_led(struct lpg *lpg, struct device_node *np)
static int lpg_add_led(struct lpg *lpg, struct fwnode_handle *fw)
{
struct led_init_data init_data = {};
struct led_classdev *cdev;
struct mc_subled *info;
struct lpg_led *led;
const char *trigger;
const char *state;
int num_channels;
u32 color = 0;
int ret;
int i;
ret = of_property_read_u32(np, "color", &color);
ret = fwnode_property_read_u32(fw, "color", &color);
if (ret < 0 && ret != -EINVAL)
return dev_err_probe(lpg->dev, ret,
"failed to parse \"color\" of %pOF\n", np);
"failed to parse \"color\" of %s\n", fwnode_get_name(fw));
if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI)
num_channels = of_get_available_child_count(np);
num_channels = fwnode_get_child_node_count(fw);
else
num_channels = 1;
@ -1403,7 +1406,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
if (!info)
return -ENOMEM;
i = 0;
for_each_available_child_of_node_scoped(np, child) {
fwnode_for_each_available_child_node_scoped(fw, child) {
ret = lpg_parse_channel(lpg, child, &led->channels[i]);
if (ret < 0)
return ret;
@ -1414,7 +1417,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
}
led->mcdev.subled_info = info;
led->mcdev.num_colors = num_channels;
led->mcdev.num_colors = i;
cdev = &led->mcdev.led_cdev;
cdev->brightness_set_blocking = lpg_brightness_mc_set;
@ -1426,7 +1429,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
cdev->pattern_clear = lpg_pattern_mc_clear;
}
} else {
ret = lpg_parse_channel(lpg, np, &led->channels[0]);
ret = lpg_parse_channel(lpg, fw, &led->channels[0]);
if (ret < 0)
return ret;
@ -1441,14 +1444,15 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
}
}
cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL);
if (!fwnode_property_read_string(fw, "linux,default-trigger", &trigger))
cdev->default_trigger = trigger;
if (lpg->lpg_chan_sdam)
cdev->max_brightness = PPG_MAX_LED_BRIGHTNESS;
else
cdev->max_brightness = LPG_RESOLUTION_9BIT - 1;
if (!of_property_read_string(np, "default-state", &state) &&
if (!fwnode_property_read_string(fw, "default-state", &state) &&
!strcmp(state, "on"))
cdev->brightness = cdev->max_brightness;
else
@ -1456,7 +1460,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
cdev->brightness_set_blocking(cdev, cdev->brightness);
init_data.fwnode = of_fwnode_handle(np);
init_data.fwnode = fw;
if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI)
ret = devm_led_classdev_multicolor_register_ext(lpg->dev, &led->mcdev, &init_data);
@ -1638,8 +1642,8 @@ static int lpg_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
for_each_available_child_of_node_scoped(pdev->dev.of_node, np) {
ret = lpg_add_led(lpg, np);
device_for_each_child_node_scoped(&pdev->dev, child) {
ret = lpg_add_led(lpg, child);
if (ret)
return ret;
}

View File

@ -9,6 +9,16 @@ menuconfig LEDS_TRIGGERS
if LEDS_TRIGGERS
config LEDS_TRIGGERS_HW_CHANGED
bool "LED hardware-initiated trigger transition support"
default LEDS_BRIGHTNESS_HW_CHANGED
help
This option enables support for hardware initiated hardware control
transitions, where the LED hardware autonomously switches between
"none" (i.e., no trigger) and its private trigger.
See Documentation/leds/leds-class.rst for details.
config LEDS_TRIGGER_TIMER
tristate "LED Timer Trigger"
help

View File

@ -414,10 +414,7 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
return -EINVAL;
}
if (state)
set_bit(bit, &mode);
else
clear_bit(bit, &mode);
assign_bit(bit, &mode, state);
if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
(test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
@ -798,10 +795,18 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev)
kfree(trigger_data);
}
static bool netdev_trig_hw_offloaded(struct led_classdev *led_cdev)
{
struct led_netdev_data *trigger_data = led_get_trigger_data(led_cdev);
return trigger_data->hw_control;
}
static struct led_trigger netdev_led_trigger = {
.name = "netdev",
.activate = netdev_trig_activate,
.deactivate = netdev_trig_deactivate,
.hw_offloaded = netdev_trig_hw_offloaded,
.groups = netdev_trig_groups,
};

View File

@ -59,7 +59,7 @@
#define WLED3_SINK_REG_CURR_SINK_SHFT 5
/* WLED3 specific per-'string' registers below */
#define WLED3_SINK_REG_BRIGHT(n) (0x40 + n)
#define WLED3_SINK_REG_BRIGHT(n) (0x40 + (n * 0x2))
#define WLED3_SINK_REG_STR_MOD_EN(n) (0x60 + (n * 0x10))
#define WLED3_SINK_REG_STR_MOD_MASK BIT(7)
@ -290,6 +290,50 @@ static int wled5_set_brightness(struct wled *wled, u16 brightness)
return rc;
}
static int wled_read_brightness(struct wled *wled)
{
u16 addr, mask;
__le16 v;
u32 val;
int rc;
rc = regmap_read(wled->regmap, wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
&val);
if (rc < 0)
return rc;
if (!(val & WLED3_CTRL_REG_MOD_EN_MASK))
return 0;
switch (wled->version) {
case 3:
addr = wled->sink_addr +
WLED3_SINK_REG_BRIGHT(wled->cfg.enabled_strings[0]);
mask = WLED3_SINK_REG_BRIGHT_MAX;
break;
case 4:
addr = wled->sink_addr +
WLED4_SINK_REG_BRIGHT(wled->cfg.enabled_strings[0]);
mask = WLED3_SINK_REG_BRIGHT_MAX;
break;
case 5:
addr = wled->sink_addr + (wled->cfg.mod_sel == MOD_A ?
WLED5_SINK_REG_MOD_A_BRIGHTNESS_LSB :
WLED5_SINK_REG_MOD_B_BRIGHTNESS_LSB);
mask = WLED5_SINK_REG_BRIGHT_MAX_15B;
break;
default:
dev_err(wled->dev, "Invalid WLED version\n");
return -EINVAL;
}
rc = regmap_bulk_read(wled->regmap, addr, &v, sizeof(v));
if (rc < 0)
return rc;
return min_t(u32, le16_to_cpu(v) & mask, wled->max_brightness);
}
static void wled_ovp_work(struct work_struct *work)
{
struct wled *wled = container_of(work,
@ -480,6 +524,18 @@ static int wled_update_status(struct backlight_device *bl)
return rc;
}
static int wled_get_brightness(struct backlight_device *bl)
{
struct wled *wled = bl_get_data(bl);
int rc;
mutex_lock(&wled->lock);
rc = wled_read_brightness(wled);
mutex_unlock(&wled->lock);
return rc;
}
static int wled4_cabc_config(struct wled *wled, bool enable)
{
int i, j, rc;
@ -1627,7 +1683,6 @@ static int wled_configure_ovp_irq(struct wled *wled,
struct platform_device *pdev)
{
int rc;
u32 val;
wled->ovp_irq = platform_get_irq_byname(pdev, "ovp");
if (wled->ovp_irq < 0) {
@ -1643,13 +1698,8 @@ static int wled_configure_ovp_irq(struct wled *wled,
return 0;
}
rc = regmap_read(wled->regmap, wled->ctrl_addr +
WLED3_CTRL_REG_MOD_EN, &val);
if (rc < 0)
return rc;
/* Keep OVP irq disabled until module is enabled */
if (!(val & WLED3_CTRL_REG_MOD_EN_MASK))
/* Keep the OVP irq disabled until the module is enabled */
if (!wled->brightness)
disable_irq(wled->ovp_irq);
return 0;
@ -1657,6 +1707,7 @@ static int wled_configure_ovp_irq(struct wled *wled,
static const struct backlight_ops wled_ops = {
.update_status = wled_update_status,
.get_brightness = wled_get_brightness,
};
static int wled_probe(struct platform_device *pdev)
@ -1680,6 +1731,7 @@ static int wled_probe(struct platform_device *pdev)
wled->regmap = regmap;
wled->dev = &pdev->dev;
platform_set_drvdata(pdev, wled);
wled->version = (uintptr_t)of_device_get_match_data(&pdev->dev);
if (!wled->version) {
@ -1734,6 +1786,20 @@ static int wled_probe(struct platform_device *pdev)
INIT_DELAYED_WORK(&wled->ovp_work, wled_ovp_work);
val = WLED_DEFAULT_BRIGHTNESS;
of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
/*
* The module may already be lit, either by the bootloader or by the
* setup above. Start from what the hardware is driving, so that the
* first brightness update does not step the brightness.
*/
rc = wled_read_brightness(wled);
if (rc < 0)
return rc;
wled->brightness = rc;
rc = wled_configure_short_irq(wled, pdev);
if (rc < 0)
return rc;
@ -1742,12 +1808,9 @@ static int wled_probe(struct platform_device *pdev)
if (rc < 0)
return rc;
val = WLED_DEFAULT_BRIGHTNESS;
of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.brightness = val;
props.brightness = wled->brightness ?: val;
props.max_brightness = wled->max_brightness;
bl = devm_backlight_device_register(&pdev->dev, wled->name,
&pdev->dev, wled,

View File

@ -109,6 +109,7 @@ struct led_classdev {
#define LED_INIT_DEFAULT_TRIGGER BIT(23)
#define LED_REJECT_NAME_CONFLICT BIT(24)
#define LED_MULTI_COLOR BIT(25)
#define LED_TRIG_HW_CHANGED BIT(26)
/* set_brightness_work / blink_timer flags, atomic, private. */
unsigned long work_flags;
@ -239,6 +240,11 @@ struct led_classdev {
struct kernfs_node *brightness_hw_changed_kn;
#endif
#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED
bool trigger_hw_changed;
struct work_struct trigger_hw_changed_work;
#endif
/* Ensures consistent access to the LED class device */
struct mutex led_access;
};
@ -485,6 +491,7 @@ struct led_trigger {
const char *name;
int (*activate)(struct led_classdev *led_cdev);
void (*deactivate)(struct led_classdev *led_cdev);
bool (*hw_offloaded)(struct led_classdev *led_cdev);
/* Brightness set by led_trigger_event */
enum led_brightness brightness;
@ -533,6 +540,9 @@ void led_trigger_blink_oneshot(struct led_trigger *trigger,
void led_trigger_set_default(struct led_classdev *led_cdev);
int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger);
void led_trigger_remove(struct led_classdev *led_cdev);
void led_trigger_remove_hw_control(struct led_classdev *led_cdev);
bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev);
static inline void led_set_trigger_data(struct led_classdev *led_cdev,
void *trigger_data)
@ -584,6 +594,13 @@ static inline int led_trigger_set(struct led_classdev *led_cdev,
}
static inline void led_trigger_remove(struct led_classdev *led_cdev) {}
static inline void led_trigger_remove_hw_control(struct led_classdev *led_cdev) {}
static inline bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev)
{
return false;
}
static inline void led_set_trigger_data(struct led_classdev *led_cdev) {}
static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
{
@ -598,6 +615,13 @@ led_trigger_get_brightness(const struct led_trigger *trigger)
#endif /* CONFIG_LEDS_TRIGGERS */
#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED
void led_trigger_notify_hw_control_changed(struct led_classdev *led_cdev, bool activate);
#else
static inline void led_trigger_notify_hw_control_changed(struct led_classdev *led_cdev,
bool activate) {}
#endif
/* Trigger specific enum */
enum led_trigger_netdev_modes {
TRIGGER_NETDEV_LINK = 0,