mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 08:15:07 -04:00
Merge tag 'leds-next-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds
Pull LED updates from Lee Jones:
"New Support & Features:
- Core: Extend netdev trigger speeds up to 100G
- PWM Multicolor: Introduce default-intensity property
- Analog Devices LTC3220: Add support for 18 channel LED driver
- NXP PCA963x: Add multicolor LED class support
Improvements & Fixes:
- GPIO: Clear error pointers for skipped LEDs
- Broadcom BCM63138: Use %pe to print pinctrl error instead of %ld
- ISSI IS31FL319x: Modernize device registration by using fwnode APIs
- NXP PCA9532: Fix inverted GPIO output polarity
- NXP PCA9532: Fix phantom device registration on missing hardware
- STMicroelectronics ST1202: Correct and extend hw_pattern
documentation
- STMicroelectronics ST1202: Fix channel disable logic on zero
brightness and ensure brightness changes are applied in active mode
- STMicroelectronics ST1202: Fix hardware pattern sequence
programming, validate inputs, and correct pattern duration
calculations
- STMicroelectronics ST1202: Validate LED reg property against
channel count
- TI LP5860: Fix a potential double-unlock during device
initialization and fix error handling path by using
devm_mutex_init()
Cleanups & Refactoring:
- GPIO: Make legacy gpiolib interface optional
Device Tree Binding Updates:
- Core: Add default-intensity property
- Core: Document "gpio" trigger
- Analog Devices LTC3220: Add DT binding for LTC3220 18 channel LED
driver
- Broadcom BCM6358: Convert to DT schema
- LaCie NS2: Convert to DT schema
- NXP PCA963x: Add multicolor LED support
- NXP PCA963x: Fix reg maximum for pca9635
- TI TPS65217: Convert backlight bindings to DT schema"
* tag 'leds-next-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (29 commits)
leds: is31fl319x: Modernize registration
dt-bindings: leds: lacie,ns2-leds: Convert to DT schema
leds: pca963x: Add multicolor LED class support
dt-bindings: leds: nxp,pca963x: Add multicolor LED support
dt-bindings: leds: nxp,pca963x: Fix reg maximum for pca9635
leds: gpio: Clear error pointers for skipped LEDs
dt-bindings: leds: backlight: Convert TPS65217 to DT schema
leds: pca9532: Fix phantom device registration on missing hardware
leds: gpio: Make legacy gpiolib interface optional
leds: bcm63138: Use %pe to print pinctrl error instead of %ld
dt-bindings: leds: Add default-intensity property
leds: ltc3220: Add Support for LTC3220 18 channel LED Driver
dt-bindings: leds: Add LTC3220 18 channel LED Driver
dt-bindings: leds: bcm6358: Convert to DT schema
dt-bindings: leds: Document "gpio" trigger
leds: st1202: Correct and extend hw_pattern documentation
leds: st1202: Validate LED reg property against channel count
leds: st1202: Disable channel when brightness is set to zero
leds: st1202: Fix brightness having no effect while pattern mode is active
leds: st1202: Fix spurious pattern sequence start in setup
...
This commit is contained in:
133
Documentation/devicetree/bindings/leds/adi,ltc3220.yaml
Normal file
133
Documentation/devicetree/bindings/leds/adi,ltc3220.yaml
Normal file
@@ -0,0 +1,133 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/leds/adi,ltc3220.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices LTC3220 LED Driver
|
||||
|
||||
maintainers:
|
||||
- Edelweise Escala <edelweise.escala@analog.com>
|
||||
|
||||
description: >
|
||||
The LTC3220 is a multi-display LED driver, which contains a high-efficiency,
|
||||
low-noise charge pump to provide power to up to 18 LED current sources.
|
||||
The LEDs are individually configurable to 64-step linear brightness control,
|
||||
blinking and gradation control via 2-wire I2C interface.
|
||||
|
||||
For more product information please see the link below
|
||||
https://www.analog.com/en/products/ltc3220.html
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: adi,ltc3220
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
'#address-cells':
|
||||
const: 1
|
||||
|
||||
'#size-cells':
|
||||
const: 0
|
||||
|
||||
reset-gpios:
|
||||
maxItems: 1
|
||||
|
||||
patternProperties:
|
||||
'^led@([1-9a-f]|1[0-2])$':
|
||||
type: object
|
||||
$ref: /schemas/leds/common.yaml#
|
||||
unevaluatedProperties: false
|
||||
properties:
|
||||
reg:
|
||||
description:
|
||||
Output channel for the LED (1-18 maps to LED outputs D1-D18).
|
||||
Unit-address must be in hexadecimal (1-12 hex = 1-18 decimal).
|
||||
For aggregated LED control, define only one LED node with reg = <0x1>
|
||||
and use led-sources to list all controlled outputs. Only reg 1 should
|
||||
be present when using led-sources.
|
||||
items:
|
||||
- minimum: 1
|
||||
maximum: 18
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
if:
|
||||
required:
|
||||
- led-sources
|
||||
then:
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
- const: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- '#address-cells'
|
||||
- '#size-cells'
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
// Independent LEDs
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/leds/common.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led-controller@1c {
|
||||
compatible = "adi,ltc3220";
|
||||
reg = <0x1c>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reset-gpios = <&gpio 17 GPIO_ACTIVE_LOW>;
|
||||
|
||||
led@1 {
|
||||
reg = <0x1>;
|
||||
function = LED_FUNCTION_INDICATOR;
|
||||
function-enumerator = <1>;
|
||||
};
|
||||
|
||||
led@2 {
|
||||
reg = <0x2>;
|
||||
function = LED_FUNCTION_INDICATOR;
|
||||
function-enumerator = <2>;
|
||||
};
|
||||
|
||||
led@3 {
|
||||
reg = <0x3>;
|
||||
function = LED_FUNCTION_INDICATOR;
|
||||
function-enumerator = <3>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
// Aggregated LED
|
||||
#include <dt-bindings/leds/common.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led-controller@1c {
|
||||
compatible = "adi,ltc3220";
|
||||
reg = <0x1c>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led@1 {
|
||||
reg = <0x1>;
|
||||
led-sources = <0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf 0x10 0x11 0x12>;
|
||||
function = LED_FUNCTION_BACKLIGHT;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
@@ -0,0 +1,170 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/leds/backlight/ti,tps65217.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TPS65217 family of regulators
|
||||
|
||||
maintainers:
|
||||
- Eduard Bostina <egbostina@gmail.com>
|
||||
|
||||
description:
|
||||
The TPS65217 chip contains a boost converter and current sinks which can be
|
||||
used to drive LEDs for use as backlights.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ti,tps65217
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
description: I2C slave address
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
interrupt-controller: true
|
||||
|
||||
"#interrupt-cells":
|
||||
const: 1
|
||||
|
||||
ti,pmic-shutdown-controller:
|
||||
type: boolean
|
||||
description:
|
||||
Set the PMIC to shutdown on PWR_EN toggle.
|
||||
|
||||
backlight:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
description:
|
||||
Node for specifying WLED1 and WLED2 lines in TPS65217.
|
||||
|
||||
properties:
|
||||
isel:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [1, 2]
|
||||
description: |
|
||||
Selection bit. Valid values:
|
||||
1 - ISEL1 (low-level)
|
||||
2 - ISEL2 (high-level)
|
||||
|
||||
fdim:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [100, 200, 500, 1000]
|
||||
description:
|
||||
PWM dimming frequency in Hz.
|
||||
|
||||
default-brightness:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
|
||||
required:
|
||||
- isel
|
||||
- fdim
|
||||
- default-brightness
|
||||
|
||||
regulators:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
description:
|
||||
List of child nodes that specify the regulator initialization data.
|
||||
Not all regulators for the given device need to be present.
|
||||
|
||||
patternProperties:
|
||||
"^(dcdc[1-3]|ldo[1-4])$":
|
||||
type: object
|
||||
$ref: /schemas/regulator/regulator.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
charger:
|
||||
$ref: /schemas/power/supply/tps65217-charger.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
pwrbutton:
|
||||
$ref: /schemas/input/ti,tps65217-pwrbutton.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pmic@24 {
|
||||
compatible = "ti,tps65217";
|
||||
reg = <0x24>;
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <1>;
|
||||
ti,pmic-shutdown-controller;
|
||||
|
||||
backlight {
|
||||
isel = <1>; /* 1 - ISET1, 2 ISET2 */
|
||||
fdim = <100>; /* TPS65217_BL_FDIM_100HZ */
|
||||
default-brightness = <50>;
|
||||
};
|
||||
|
||||
charger {
|
||||
compatible = "ti,tps65217-charger";
|
||||
interrupts = <0>, <1>;
|
||||
interrupt-names = "USB", "AC";
|
||||
};
|
||||
|
||||
pwrbutton {
|
||||
compatible = "ti,tps65217-pwrbutton";
|
||||
interrupts = <2>;
|
||||
};
|
||||
|
||||
regulators {
|
||||
dcdc1 {
|
||||
regulator-name = "vdds_dpr";
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
dcdc2 {
|
||||
regulator-name = "vdd_mpu";
|
||||
regulator-min-microvolt = <925000>;
|
||||
regulator-max-microvolt = <1351500>;
|
||||
regulator-boot-on;
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
dcdc3 {
|
||||
regulator-name = "vdd_core";
|
||||
regulator-min-microvolt = <925000>;
|
||||
regulator-max-microvolt = <1150000>;
|
||||
regulator-boot-on;
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
ldo1 {
|
||||
regulator-name = "vio,vrtc,vdds";
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
ldo2 {
|
||||
regulator-name = "vdd_3v3aux";
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
ldo3 {
|
||||
regulator-name = "vdd_1v8";
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <1800000>;
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
ldo4 {
|
||||
regulator-name = "vdd_3v3a";
|
||||
regulator-always-on;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
TPS65217 family of regulators
|
||||
|
||||
The TPS65217 chip contains a boost converter and current sinks which can be
|
||||
used to drive LEDs for use as backlights.
|
||||
|
||||
Required properties:
|
||||
- compatible: "ti,tps65217"
|
||||
- reg: I2C slave address
|
||||
- backlight: node for specifying WLED1 and WLED2 lines in TPS65217
|
||||
- isel: selection bit, valid values: 1 for ISEL1 (low-level) and 2 for ISEL2 (high-level)
|
||||
- fdim: PWM dimming frequency, valid values: 100, 200, 500, 1000
|
||||
- default-brightness: valid values: 0-100
|
||||
|
||||
Each regulator is defined using the standard binding for regulators.
|
||||
|
||||
Example:
|
||||
|
||||
tps: tps@24 {
|
||||
reg = <0x24>;
|
||||
compatible = "ti,tps65217";
|
||||
backlight {
|
||||
isel = <1>; /* 1 - ISET1, 2 ISET2 */
|
||||
fdim = <100>; /* TPS65217_BL_FDIM_100HZ */
|
||||
default-brightness = <50>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/leds/brcm,bcm6358-leds.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: LEDs connected to Broadcom BCM6358 controller
|
||||
|
||||
description: |
|
||||
This controller is present on BCM6358 and BCM6368.
|
||||
In these SoCs there are Serial LEDs (LEDs connected to a 74x164 controller),
|
||||
which can either be controlled by software (exporting the 74x164 as spi-gpio.
|
||||
See Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml), or
|
||||
by hardware using this driver.
|
||||
|
||||
maintainers:
|
||||
- Álvaro Fernández Rojas <noltari@gmail.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: brcm,bcm6358-leds
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
brcm,clk-div:
|
||||
description: SCK signal divider.
|
||||
default: 1
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [1, 2, 4, 8]
|
||||
|
||||
brcm,clk-dat-low:
|
||||
description: Makes clock and data signals active low.
|
||||
type: boolean
|
||||
|
||||
patternProperties:
|
||||
"^led@1?[0-9a-f]$":
|
||||
type: object
|
||||
$ref: common.yaml#
|
||||
unevaluatedProperties: false
|
||||
description: Each LED is represented as a sub-node of
|
||||
this device.
|
||||
|
||||
properties:
|
||||
reg:
|
||||
description: LED pin number (0 to 31).
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/leds/common.h>
|
||||
led-controller@fffe00d0 {
|
||||
compatible = "brcm,bcm6358-leds";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0xfffe00d0 0x8>;
|
||||
|
||||
led@0 {
|
||||
reg = <0>;
|
||||
active-low;
|
||||
label = "white:alarm";
|
||||
};
|
||||
led@2 {
|
||||
reg = <2>;
|
||||
active-low;
|
||||
label = "white:tv";
|
||||
};
|
||||
led@3 {
|
||||
reg = <3>;
|
||||
active-low;
|
||||
label = "white:tel";
|
||||
};
|
||||
led@4 {
|
||||
reg = <4>;
|
||||
active-low;
|
||||
label = "white:adsl";
|
||||
};
|
||||
};
|
||||
...
|
||||
@@ -73,6 +73,16 @@ properties:
|
||||
- keep
|
||||
default: off
|
||||
|
||||
default-intensity:
|
||||
description:
|
||||
The initial intensity of the LED color component. As the intensity of
|
||||
each sub-LED is multiplied with the overall brightness, without this
|
||||
property on a sub-LED, it may effectively be initialized at minimum
|
||||
brightness regardless of its linux,default-trigger and default-brightness
|
||||
properties.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
default: 0
|
||||
|
||||
linux,default-trigger:
|
||||
description:
|
||||
This parameter, if present, is a string defining the trigger assigned to
|
||||
@@ -106,6 +116,8 @@ properties:
|
||||
- bluetooth-power
|
||||
# LED indicates camera flash state
|
||||
- flash
|
||||
# LED indicates level of GPIO input referenced by trigger-sources
|
||||
- gpio
|
||||
# LED indicated keyboard capslock
|
||||
- kbd-capslock
|
||||
# LED indicates MTD memory activity
|
||||
|
||||
85
Documentation/devicetree/bindings/leds/lacie,ns2-leds.yaml
Normal file
85
Documentation/devicetree/bindings/leds/lacie,ns2-leds.yaml
Normal file
@@ -0,0 +1,85 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/leds/lacie,ns2-leds.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Dual-GPIO LEDs found on Network Space v2 (and parents)
|
||||
|
||||
maintainers:
|
||||
- Simon Guinot <simon.guinot@sequanux.org>
|
||||
|
||||
description:
|
||||
The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED
|
||||
modes are available, off, on and SATA activity blinking. The LED modes are
|
||||
controlled through two GPIOs (command and slow), each combination of values
|
||||
for the command/slow GPIOs corresponds to a LED mode.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: lacie,ns2-leds
|
||||
|
||||
additionalProperties:
|
||||
type: object
|
||||
$ref: common.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
description: Each child node represents a single LED
|
||||
|
||||
properties:
|
||||
cmd-gpio:
|
||||
maxItems: 1
|
||||
description: GPIO connected to the command LED output
|
||||
|
||||
slow-gpio:
|
||||
maxItems: 1
|
||||
description: GPIO connected to the slow LED output
|
||||
|
||||
num-modes:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
Number of entries in modes-map.
|
||||
|
||||
modes-map:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32-matrix
|
||||
description:
|
||||
A mapping between LED modes (off, on or SATA activity blinking) and
|
||||
the corresponding cmd-gpio/slow-gpio values. All the GPIO values
|
||||
combinations should be given in order to avoid having an unknown
|
||||
mode at driver probe time.
|
||||
|
||||
items:
|
||||
items:
|
||||
- description: LED mode
|
||||
enum: [0, 1, 2]
|
||||
- description: Command GPIO level
|
||||
enum: [0, 1]
|
||||
- description: Slow GPIO level
|
||||
enum: [0, 1]
|
||||
|
||||
required:
|
||||
- cmd-gpio
|
||||
- slow-gpio
|
||||
- modes-map
|
||||
|
||||
required:
|
||||
- compatible
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/leds/leds-ns2.h>
|
||||
|
||||
led-controller {
|
||||
compatible = "lacie,ns2-leds";
|
||||
|
||||
led-0 {
|
||||
label = "ns2:blue:sata";
|
||||
slow-gpio = <&gpio0 29 0>;
|
||||
cmd-gpio = <&gpio0 30 0>;
|
||||
modes-map = <NS_V2_LED_OFF 0 1
|
||||
NS_V2_LED_ON 1 0
|
||||
NS_V2_LED_ON 0 0
|
||||
NS_V2_LED_SATA 1 1>;
|
||||
};
|
||||
};
|
||||
...
|
||||
@@ -1,143 +0,0 @@
|
||||
LEDs connected to Broadcom BCM6358 controller
|
||||
|
||||
This controller is present on BCM6358 and BCM6368.
|
||||
In these SoCs there are Serial LEDs (LEDs connected to a 74x164 controller),
|
||||
which can either be controlled by software (exporting the 74x164 as spi-gpio.
|
||||
See Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml), or
|
||||
by hardware using this driver.
|
||||
|
||||
Required properties:
|
||||
- compatible : should be "brcm,bcm6358-leds".
|
||||
- #address-cells : must be 1.
|
||||
- #size-cells : must be 0.
|
||||
- reg : BCM6358 LED controller address and size.
|
||||
|
||||
Optional properties:
|
||||
- brcm,clk-div : SCK signal divider. Possible values are 1, 2, 4 and 8.
|
||||
Default : 1
|
||||
- brcm,clk-dat-low : Boolean, makes clock and data signals active low.
|
||||
Default : false
|
||||
|
||||
Each LED is represented as a sub-node of the brcm,bcm6358-leds device.
|
||||
|
||||
LED sub-node required properties:
|
||||
- reg : LED pin number (only LEDs 0 to 31 are valid).
|
||||
|
||||
LED sub-node optional properties:
|
||||
- label : see Documentation/devicetree/bindings/leds/common.txt
|
||||
- default-state : see
|
||||
Documentation/devicetree/bindings/leds/common.txt
|
||||
- linux,default-trigger : see
|
||||
Documentation/devicetree/bindings/leds/common.txt
|
||||
|
||||
Examples:
|
||||
Scenario 1 : BCM6358
|
||||
leds0: led-controller@fffe00d0 {
|
||||
compatible = "brcm,bcm6358-leds";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0xfffe00d0 0x8>;
|
||||
|
||||
alarm_white {
|
||||
reg = <0>;
|
||||
active-low;
|
||||
label = "white:alarm";
|
||||
};
|
||||
tv_white {
|
||||
reg = <2>;
|
||||
active-low;
|
||||
label = "white:tv";
|
||||
};
|
||||
tel_white {
|
||||
reg = <3>;
|
||||
active-low;
|
||||
label = "white:tel";
|
||||
};
|
||||
adsl_white {
|
||||
reg = <4>;
|
||||
active-low;
|
||||
label = "white:adsl";
|
||||
};
|
||||
};
|
||||
|
||||
Scenario 2 : BCM6368
|
||||
leds0: led-controller@100000d0 {
|
||||
compatible = "brcm,bcm6358-leds";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0x100000d0 0x8>;
|
||||
brcm,pol-low;
|
||||
brcm,clk-div = <4>;
|
||||
|
||||
power_red {
|
||||
reg = <0>;
|
||||
active-low;
|
||||
label = "red:power";
|
||||
};
|
||||
power_green {
|
||||
reg = <1>;
|
||||
active-low;
|
||||
label = "green:power";
|
||||
default-state = "on";
|
||||
};
|
||||
power_blue {
|
||||
reg = <2>;
|
||||
label = "blue:power";
|
||||
};
|
||||
broadband_red {
|
||||
reg = <3>;
|
||||
active-low;
|
||||
label = "red:broadband";
|
||||
};
|
||||
broadband_green {
|
||||
reg = <4>;
|
||||
label = "green:broadband";
|
||||
};
|
||||
broadband_blue {
|
||||
reg = <5>;
|
||||
active-low;
|
||||
label = "blue:broadband";
|
||||
};
|
||||
wireless_red {
|
||||
reg = <6>;
|
||||
active-low;
|
||||
label = "red:wireless";
|
||||
};
|
||||
wireless_green {
|
||||
reg = <7>;
|
||||
active-low;
|
||||
label = "green:wireless";
|
||||
};
|
||||
wireless_blue {
|
||||
reg = <8>;
|
||||
label = "blue:wireless";
|
||||
};
|
||||
phone_red {
|
||||
reg = <9>;
|
||||
active-low;
|
||||
label = "red:phone";
|
||||
};
|
||||
phone_green {
|
||||
reg = <10>;
|
||||
active-low;
|
||||
label = "green:phone";
|
||||
};
|
||||
phone_blue {
|
||||
reg = <11>;
|
||||
label = "blue:phone";
|
||||
};
|
||||
upgrading_red {
|
||||
reg = <12>;
|
||||
active-low;
|
||||
label = "red:upgrading";
|
||||
};
|
||||
upgrading_green {
|
||||
reg = <13>;
|
||||
active-low;
|
||||
label = "green:upgrading";
|
||||
};
|
||||
upgrading_blue {
|
||||
reg = <14>;
|
||||
label = "blue:upgrading";
|
||||
};
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
Binding for dual-GPIO LED found on Network Space v2 (and parents).
|
||||
|
||||
Required properties:
|
||||
- compatible: "lacie,ns2-leds".
|
||||
|
||||
Each LED is represented as a sub-node of the ns2-leds device.
|
||||
|
||||
Required sub-node properties:
|
||||
- cmd-gpio: Command LED GPIO. See OF device-tree GPIO specification.
|
||||
- slow-gpio: Slow LED GPIO. See OF device-tree GPIO specification.
|
||||
- modes-map: A mapping between LED modes (off, on or SATA activity blinking) and
|
||||
the corresponding cmd-gpio/slow-gpio values. All the GPIO values combinations
|
||||
should be given in order to avoid having an unknown mode at driver probe time.
|
||||
|
||||
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:
|
||||
|
||||
#include <dt-bindings/leds/leds-ns2.h>
|
||||
|
||||
ns2-leds {
|
||||
compatible = "lacie,ns2-leds";
|
||||
|
||||
blue-sata {
|
||||
label = "ns2:blue:sata";
|
||||
slow-gpio = <&gpio0 29 0>;
|
||||
cmd-gpio = <&gpio0 30 0>;
|
||||
modes-map = <NS_V2_LED_OFF 0 1
|
||||
NS_V2_LED_ON 1 0
|
||||
NS_V2_LED_ON 0 0
|
||||
NS_V2_LED_SATA 1 1>;
|
||||
};
|
||||
};
|
||||
@@ -45,6 +45,8 @@ properties:
|
||||
|
||||
color: true
|
||||
|
||||
default-intensity: true
|
||||
|
||||
required:
|
||||
- pwms
|
||||
- color
|
||||
@@ -63,12 +65,14 @@ examples:
|
||||
|
||||
multi-led {
|
||||
color = <LED_COLOR_ID_RGB>;
|
||||
linux,default-trigger = "default-on";
|
||||
function = LED_FUNCTION_INDICATOR;
|
||||
max-brightness = <65535>;
|
||||
|
||||
led-red {
|
||||
pwms = <&pwm1 0 1000000>;
|
||||
color = <LED_COLOR_ID_RED>;
|
||||
default-intensity = <65535>;
|
||||
};
|
||||
|
||||
led-green {
|
||||
|
||||
@@ -62,18 +62,52 @@ properties:
|
||||
open-drain, newer chips to totem pole).
|
||||
|
||||
patternProperties:
|
||||
"^led@[0-9a-f]+$":
|
||||
"^led@[0-9a-f]$":
|
||||
type: object
|
||||
$ref: common.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
reg:
|
||||
minimum: 0
|
||||
maximum: 15
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
"^multi-led@[0-9a-f]$":
|
||||
type: object
|
||||
$ref: leds-class-multicolor.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
reg:
|
||||
maximum: 15
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
patternProperties:
|
||||
"^led@[0-9a-f]$":
|
||||
type: object
|
||||
$ref: common.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
reg:
|
||||
maximum: 15
|
||||
|
||||
required:
|
||||
- reg
|
||||
- color
|
||||
|
||||
required:
|
||||
- reg
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
@@ -84,16 +118,34 @@ allOf:
|
||||
- nxp,pca9633
|
||||
then:
|
||||
patternProperties:
|
||||
"^led@[0-9a-f]+$":
|
||||
"^.*led@[0-9a-f]$":
|
||||
properties:
|
||||
reg:
|
||||
maximum: 3
|
||||
else:
|
||||
"^multi-led@[0-9a-f]$":
|
||||
patternProperties:
|
||||
"^led@[0-9a-f]$":
|
||||
properties:
|
||||
reg:
|
||||
maximum: 3
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- nxp,pca9634
|
||||
then:
|
||||
patternProperties:
|
||||
"^led@[0-9a-f]+$":
|
||||
"^.*led@[0-9a-f]$":
|
||||
properties:
|
||||
reg:
|
||||
maximum: 7
|
||||
"^multi-led@[0-9a-f]$":
|
||||
patternProperties:
|
||||
"^led@[0-9a-f]$":
|
||||
properties:
|
||||
reg:
|
||||
maximum: 7
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
@@ -137,4 +189,50 @@ examples:
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
#include <dt-bindings/leds/common.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led-controller@62 {
|
||||
compatible = "nxp,pca9633";
|
||||
reg = <0x62>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
/* Three channels controlling one RGB LED */
|
||||
multi-led@0 {
|
||||
reg = <0>;
|
||||
color = <LED_COLOR_ID_RGB>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led@0 {
|
||||
reg = <0>;
|
||||
color = <LED_COLOR_ID_RED>;
|
||||
};
|
||||
|
||||
led@1 {
|
||||
reg = <1>;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
};
|
||||
|
||||
led@2 {
|
||||
reg = <2>;
|
||||
color = <LED_COLOR_ID_BLUE>;
|
||||
};
|
||||
};
|
||||
|
||||
/* Remaining channel used as a plain white LED */
|
||||
led@3 {
|
||||
reg = <3>;
|
||||
color = <LED_COLOR_ID_WHITE>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
TPS65217 family of regulators
|
||||
|
||||
Required properties:
|
||||
- compatible: "ti,tps65217"
|
||||
- reg: I2C slave address
|
||||
- regulators: list of regulators provided by this controller, must be named
|
||||
after their hardware counterparts: dcdc[1-3] and ldo[1-4]
|
||||
- regulators: This is the list of child nodes that specify the regulator
|
||||
initialization data for defined regulators. Not all regulators for the given
|
||||
device need to be present. The definition for each of these nodes is defined
|
||||
using the standard binding for regulators found at
|
||||
Documentation/devicetree/bindings/regulator/regulator.txt.
|
||||
|
||||
Optional properties:
|
||||
- ti,pmic-shutdown-controller: Telling the PMIC to shutdown on PWR_EN toggle.
|
||||
|
||||
The valid names for regulators are:
|
||||
tps65217: dcdc1, dcdc2, dcdc3, ldo1, ldo2, ldo3 and ldo4
|
||||
|
||||
Each regulator is defined using the standard binding for regulators.
|
||||
|
||||
Example:
|
||||
|
||||
tps: tps@24 {
|
||||
compatible = "ti,tps65217";
|
||||
ti,pmic-shutdown-controller;
|
||||
|
||||
regulators {
|
||||
dcdc1_reg: dcdc1 {
|
||||
regulator-min-microvolt = <900000>;
|
||||
regulator-max-microvolt = <1800000>;
|
||||
regulator-boot-on;
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
dcdc2_reg: dcdc2 {
|
||||
regulator-min-microvolt = <900000>;
|
||||
regulator-max-microvolt = <3300000>;
|
||||
regulator-boot-on;
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
dcdc3_reg: dcc3 {
|
||||
regulator-min-microvolt = <900000>;
|
||||
regulator-max-microvolt = <1500000>;
|
||||
regulator-boot-on;
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
ldo1_reg: ldo1 {
|
||||
regulator-min-microvolt = <1000000>;
|
||||
regulator-max-microvolt = <3300000>;
|
||||
regulator-boot-on;
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
ldo2_reg: ldo2 {
|
||||
regulator-min-microvolt = <900000>;
|
||||
regulator-max-microvolt = <3300000>;
|
||||
regulator-boot-on;
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
ldo3_reg: ldo3 {
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <3300000>;
|
||||
regulator-boot-on;
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
ldo4_reg: ldo4 {
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <3300000>;
|
||||
regulator-boot-on;
|
||||
regulator-always-on;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -16,8 +16,9 @@ in terms of PWM duty-cycle and duration (ms).
|
||||
To be compatible with the hardware pattern format, maximum 8 tuples of
|
||||
brightness (PWM) and duration must be written to hw_pattern.
|
||||
|
||||
- Brightness range: 0-255
|
||||
- Min pattern duration: 22 ms
|
||||
- Max pattern duration: 5660 ms
|
||||
- Max pattern duration: 5610 ms
|
||||
|
||||
The format of the hardware pattern values should be:
|
||||
"brightness duration brightness duration ..."
|
||||
@@ -26,9 +27,7 @@ The format of the hardware pattern values should be:
|
||||
----------------------------
|
||||
|
||||
Specify a pattern repeat number, which is common for all channels.
|
||||
Default is 1; negative numbers and 0 are invalid.
|
||||
Default is 1. Writing 0 is invalid. Writing -1 or 255 repeats the
|
||||
pattern indefinitely.
|
||||
|
||||
This file will always return the originally written repeat number.
|
||||
|
||||
When the 255 value is written to it, all patterns will repeat
|
||||
indefinitely.
|
||||
|
||||
@@ -15527,6 +15527,14 @@ W: https://ez.analog.com/linux-software-drivers
|
||||
F: Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml
|
||||
F: drivers/iio/temperature/ltc2983.c
|
||||
|
||||
LTC3220 LED DRIVER
|
||||
M: Edelweise Escala <edelweise.escala@analog.com>
|
||||
L: linux-leds@vger.kernel.org
|
||||
S: Maintained
|
||||
W: https://ez.analog.com/linux-software-drivers
|
||||
F: Documentation/devicetree/bindings/leds/adi,ltc3220.yaml
|
||||
F: drivers/leds/leds-ltc3220.c
|
||||
|
||||
LTC4282 HARDWARE MONITOR DRIVER
|
||||
M: Nuno Sa <nuno.sa@analog.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
|
||||
@@ -596,6 +596,7 @@ config LEDS_PCA963X
|
||||
tristate "LED support for PCA963x I2C chip"
|
||||
depends on LEDS_CLASS
|
||||
depends on I2C
|
||||
select LEDS_CLASS_MULTICOLOR
|
||||
help
|
||||
This option enables support for LEDs connected to the PCA963x
|
||||
LED driver chip accessed via the I2C bus. Supported
|
||||
@@ -1000,6 +1001,19 @@ config LEDS_ST1202
|
||||
Say Y to enable support for LEDs connected to LED1202
|
||||
LED driver chips accessed via the I2C bus.
|
||||
|
||||
config LEDS_LTC3220
|
||||
tristate "LED Driver for Analog Devices Inc. LTC3220"
|
||||
depends on I2C && LEDS_CLASS
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y to enable support for the Analog Devices LTC3220
|
||||
18-channel LED controller with I2C interface.
|
||||
The driver supports individual LED brightness control (64 steps),
|
||||
hardware-assisted blinking and gradation effects.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called leds-ltc3220.
|
||||
|
||||
config LEDS_TPS6105X
|
||||
tristate "LED support for TI TPS6105X"
|
||||
depends on LEDS_CLASS
|
||||
|
||||
@@ -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_LTC3220) += leds-ltc3220.o
|
||||
obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o
|
||||
obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o
|
||||
obj-$(CONFIG_LEDS_MAX77705) += leds-max77705.o
|
||||
|
||||
@@ -236,8 +236,8 @@ static void bcm63138_leds_create_led(struct bcm63138_leds *leds,
|
||||
|
||||
pinctrl = devm_pinctrl_get_select_default(led->cdev.dev);
|
||||
if (IS_ERR(pinctrl) && PTR_ERR(pinctrl) != -ENODEV) {
|
||||
dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %ld\n",
|
||||
np, PTR_ERR(pinctrl));
|
||||
dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %pe\n",
|
||||
np, pinctrl);
|
||||
}
|
||||
|
||||
bit = BIT(led->pin);
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/legacy.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/overflow.h>
|
||||
@@ -211,7 +211,6 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
|
||||
const struct gpio_led *template)
|
||||
{
|
||||
struct gpio_desc *gpiod;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* This means the LED does not come from the device tree
|
||||
@@ -222,16 +221,29 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
|
||||
gpiod = devm_gpiod_get_index_optional(dev, NULL, idx, GPIOD_OUT_LOW);
|
||||
if (IS_ERR(gpiod))
|
||||
return gpiod;
|
||||
if (gpiod) {
|
||||
gpiod_set_consumer_name(gpiod, template->name);
|
||||
return gpiod;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the legacy code path for platform code that
|
||||
* still uses GPIO numbers. Ultimately we would like to get
|
||||
* rid of this block completely.
|
||||
*/
|
||||
gpiod_set_consumer_name(gpiod, template->name);
|
||||
return gpiod;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GPIOLIB_LEGACY
|
||||
/*
|
||||
* This is the legacy code path for platform code that still uses
|
||||
* GPIO numbers, mainly MIPS and SuperH board files.
|
||||
* Ultimately we would like to get rid of this block completely.
|
||||
*
|
||||
* ppc44x-warp sets the template->gpiod directly instead of
|
||||
* adding a lookup table or device properties. This is not
|
||||
* much better.
|
||||
*/
|
||||
static struct gpio_desc *gpio_led_get_legacy_gpiod(struct device *dev, int idx,
|
||||
const struct gpio_led *template)
|
||||
{
|
||||
struct gpio_desc *gpiod;
|
||||
int ret;
|
||||
|
||||
if (template->gpiod)
|
||||
return template->gpiod;
|
||||
|
||||
/* skip leds that aren't available */
|
||||
if (!gpio_is_valid(template->gpio))
|
||||
@@ -251,6 +263,13 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
|
||||
|
||||
return gpiod;
|
||||
}
|
||||
#else
|
||||
static struct gpio_desc *gpio_led_get_legacy_gpiod(struct device *dev, int idx,
|
||||
const struct gpio_led *template)
|
||||
{
|
||||
return template->gpiod ?: ERR_PTR(-ENOENT);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int gpio_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
@@ -269,14 +288,14 @@ static int gpio_led_probe(struct platform_device *pdev)
|
||||
const struct gpio_led *template = &pdata->leds[i];
|
||||
struct gpio_led_data *led_dat = &priv->leds[i];
|
||||
|
||||
if (template->gpiod)
|
||||
led_dat->gpiod = template->gpiod;
|
||||
else
|
||||
led_dat->gpiod = gpio_led_get_gpiod(dev, i, template);
|
||||
if (!led_dat->gpiod)
|
||||
led_dat->gpiod =
|
||||
gpio_led_get_gpiod(dev, i, template);
|
||||
gpio_led_get_legacy_gpiod(dev, i, template);
|
||||
if (IS_ERR(led_dat->gpiod)) {
|
||||
dev_info(dev, "Skipping unavailable LED gpio %d (%s)\n",
|
||||
template->gpio, template->name);
|
||||
dev_info(dev, "Skipping unavailable LED gpio %s\n",
|
||||
template->name);
|
||||
led_dat->gpiod = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ struct is31fl319x_chip {
|
||||
struct is31fl319x_chip *chip;
|
||||
struct led_classdev cdev;
|
||||
u32 max_microamp;
|
||||
bool configured;
|
||||
struct fwnode_handle *fwnode;
|
||||
} leds[IS31FL319X_MAX_LEDS];
|
||||
};
|
||||
|
||||
@@ -362,31 +362,17 @@ static const struct of_device_id of_is31fl319x_match[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_is31fl319x_match);
|
||||
|
||||
static int is31fl319x_parse_child_fw(const struct device *dev,
|
||||
const struct fwnode_handle *child,
|
||||
struct is31fl319x_led *led,
|
||||
struct is31fl319x_chip *is31)
|
||||
static void is31_free_fwnode(void *data)
|
||||
{
|
||||
struct led_classdev *cdev = &led->cdev;
|
||||
int ret;
|
||||
struct is31fl319x_chip *is31 = data;
|
||||
int i;
|
||||
|
||||
if (fwnode_property_read_string(child, "label", &cdev->name))
|
||||
cdev->name = fwnode_get_name(child);
|
||||
for (i = 0; i < is31->cdef->num_leds; i++) {
|
||||
if (is31->leds[i].fwnode)
|
||||
fwnode_handle_put(is31->leds[i].fwnode);
|
||||
|
||||
ret = fwnode_property_read_string(child, "linux,default-trigger", &cdev->default_trigger);
|
||||
if (ret < 0 && ret != -EINVAL) /* is optional */
|
||||
return ret;
|
||||
|
||||
led->max_microamp = is31->cdef->current_default;
|
||||
ret = fwnode_property_read_u32(child, "led-max-microamp", &led->max_microamp);
|
||||
if (!ret) {
|
||||
if (led->max_microamp < is31->cdef->current_min)
|
||||
return -EINVAL; /* not supported */
|
||||
led->max_microamp = min(led->max_microamp,
|
||||
is31->cdef->current_max);
|
||||
is31->leds[i].fwnode = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31)
|
||||
@@ -401,6 +387,12 @@ static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31)
|
||||
"Failed to get shutdown gpio\n");
|
||||
|
||||
is31->cdef = device_get_match_data(dev);
|
||||
if (!is31->cdef)
|
||||
return -ENODEV;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, is31_free_fwnode, is31);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
count = 0;
|
||||
device_for_each_child_node_scoped(dev, child)
|
||||
@@ -426,14 +418,20 @@ static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31)
|
||||
|
||||
led = &is31->leds[reg - 1];
|
||||
|
||||
if (led->configured)
|
||||
if (led->fwnode)
|
||||
return dev_err_probe(dev, -EINVAL, "led %u is already configured\n", reg);
|
||||
|
||||
ret = is31fl319x_parse_child_fw(dev, child, led, is31);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "led %u DT parsing failed\n", reg);
|
||||
led->max_microamp = is31->cdef->current_default;
|
||||
ret = fwnode_property_read_u32(child, "led-max-microamp", &led->max_microamp);
|
||||
if (!ret) {
|
||||
if (led->max_microamp < is31->cdef->current_min)
|
||||
return dev_err_probe(dev, -EINVAL, "invalid maximum current\n");
|
||||
|
||||
led->configured = true;
|
||||
led->max_microamp = min(led->max_microamp,
|
||||
is31->cdef->current_max);
|
||||
}
|
||||
|
||||
led->fwnode = fwnode_handle_get(child);
|
||||
}
|
||||
|
||||
is31->audio_gain_db = 0;
|
||||
@@ -530,7 +528,7 @@ static int is31fl319x_probe(struct i2c_client *client)
|
||||
*/
|
||||
aggregated_led_microamp = is31->cdef->current_max;
|
||||
for (i = 0; i < is31->cdef->num_leds; i++)
|
||||
if (is31->leds[i].configured &&
|
||||
if (is31->leds[i].fwnode &&
|
||||
is31->leds[i].max_microamp < aggregated_led_microamp)
|
||||
aggregated_led_microamp = is31->leds[i].max_microamp;
|
||||
|
||||
@@ -544,14 +542,17 @@ static int is31fl319x_probe(struct i2c_client *client)
|
||||
|
||||
for (i = 0; i < is31->cdef->num_leds; i++) {
|
||||
struct is31fl319x_led *led = &is31->leds[i];
|
||||
struct led_init_data init_data = {};
|
||||
|
||||
if (!led->configured)
|
||||
if (!led->fwnode)
|
||||
continue;
|
||||
|
||||
init_data.fwnode = led->fwnode;
|
||||
|
||||
led->chip = is31;
|
||||
led->cdev.brightness_set_blocking = is31->cdef->brightness_set;
|
||||
|
||||
err = devm_led_classdev_register(&client->dev, &led->cdev);
|
||||
err = devm_led_classdev_register_ext(&client->dev, &led->cdev, &init_data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
586
drivers/leds/leds-ltc3220.c
Normal file
586
drivers/leds/leds-ltc3220.c
Normal file
@@ -0,0 +1,586 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* LTC3220 18-Channel LED Driver
|
||||
*
|
||||
* Copyright 2026 Analog Devices Inc.
|
||||
*
|
||||
* Author: Edelweise Escala <edelweise.escala@analog.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio/consumer.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>
|
||||
|
||||
/* LTC3220 Registers */
|
||||
#define LTC3220_COMMAND_REG 0x00
|
||||
#define LTC3220_QUICK_WRITE_MASK BIT(0)
|
||||
#define LTC3220_SHUTDOWN_MASK BIT(3)
|
||||
|
||||
#define LTC3220_ULED_REG(x) (0x01 + (x))
|
||||
#define LTC3220_LED_CURRENT_MASK GENMASK(5, 0)
|
||||
#define LTC3220_LED_MODE_MASK GENMASK(7, 6)
|
||||
|
||||
#define LTC3220_GRAD_BLINK_REG 0x13
|
||||
#define LTC3220_GRADATION_MASK GENMASK(2, 0)
|
||||
#define LTC3220_GRADATION_DIRECTION_MASK BIT(0)
|
||||
#define LTC3220_GRADATION_PERIOD_MASK GENMASK(2, 1)
|
||||
#define LTC3220_BLINK_MASK GENMASK(4, 3)
|
||||
|
||||
#define LTC3220_NUM_LEDS 18
|
||||
#define LTC3220_MAX_BRIGHTNESS 63
|
||||
|
||||
#define LTC3220_GRADATION_RAMP_TIME_240MS 240
|
||||
#define LTC3220_GRADATION_RAMP_TIME_480MS 480
|
||||
|
||||
#define LTC3220_BLINK_ON_156MS 156
|
||||
#define LTC3220_BLINK_ON_625MS 625
|
||||
#define LTC3220_BLINK_PERIOD_1250MS 1250
|
||||
#define LTC3220_BLINK_PERIOD_2500MS 2500
|
||||
|
||||
#define LTC3220_BLINK_SHORT_ON_TIME BIT(0)
|
||||
#define LTC3220_BLINK_LONG_PERIOD BIT(1)
|
||||
|
||||
enum ltc3220_led_mode {
|
||||
LTC3220_NORMAL_MODE,
|
||||
LTC3220_BLINK_MODE,
|
||||
LTC3220_GRADATION_MODE,
|
||||
};
|
||||
|
||||
enum ltc3220_blink_mode {
|
||||
LTC3220_BLINK_MODE_625MS_1250MS,
|
||||
LTC3220_BLINK_MODE_156MS_1250MS,
|
||||
LTC3220_BLINK_MODE_625MS_2500MS,
|
||||
LTC3220_BLINK_MODE_156MS_2500MS
|
||||
};
|
||||
|
||||
enum ltc3220_gradation_mode {
|
||||
LTC3220_GRADATION_MODE_DISABLED,
|
||||
LTC3220_GRADATION_MODE_240MS_RAMP_TIME,
|
||||
LTC3220_GRADATION_MODE_480MS_RAMP_TIME,
|
||||
LTC3220_GRADATION_MODE_960MS_RAMP_TIME
|
||||
};
|
||||
|
||||
static const struct regmap_config ltc3220_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = LTC3220_GRAD_BLINK_REG,
|
||||
.cache_type = REGCACHE_FLAT_S,
|
||||
};
|
||||
|
||||
struct ltc3220_uled_cfg {
|
||||
struct led_classdev led_cdev;
|
||||
u8 reg_value;
|
||||
u8 led_index;
|
||||
bool registered;
|
||||
};
|
||||
|
||||
struct ltc3220 {
|
||||
struct ltc3220_uled_cfg uled_cfg[LTC3220_NUM_LEDS];
|
||||
struct regmap *regmap;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
/*
|
||||
* Set LED brightness. Hardware supports 0-63 brightness levels.
|
||||
* Mode switching (blink/gradation) is handled through dedicated callbacks.
|
||||
*
|
||||
* In aggregated mode only a single LED (reg = 1) is registered and the
|
||||
* hardware quick-write feature propagates the write to all 18 channels, so
|
||||
* there is no need to update the other registers explicitly.
|
||||
*/
|
||||
static int __ltc3220_set_led_data(struct ltc3220 *ltc3220,
|
||||
struct ltc3220_uled_cfg *uled_cfg,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
int ret;
|
||||
|
||||
brightness &= LTC3220_LED_CURRENT_MASK;
|
||||
|
||||
ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index),
|
||||
brightness);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
uled_cfg->reg_value = brightness;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ltc3220_set_led_data(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg,
|
||||
led_cdev);
|
||||
struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220,
|
||||
uled_cfg[0]);
|
||||
int ret;
|
||||
|
||||
mutex_lock(<c3220->lock);
|
||||
ret = __ltc3220_set_led_data(ltc3220, uled_cfg, brightness);
|
||||
mutex_unlock(<c3220->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum led_brightness ltc3220_get_led_data(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg,
|
||||
led_cdev);
|
||||
|
||||
return uled_cfg->reg_value;
|
||||
}
|
||||
|
||||
/*
|
||||
* LTC3220 pattern support for hardware-assisted breathing/gradation.
|
||||
* The hardware supports 3 gradation ramp times (240ms, 480ms, 960ms)
|
||||
* and can ramp up or down. The gradation period and direction are chip-global
|
||||
* registers (LTC3220_GRAD_BLINK_REG), affecting all 18 channels simultaneously.
|
||||
* This is a hardware limitation, not a driver bug.
|
||||
*
|
||||
* Pattern array interpretation:
|
||||
* pattern[0].brightness = start brightness (0-63)
|
||||
* pattern[0].delta_t = ramp time in milliseconds
|
||||
* pattern[1].brightness = end brightness (0-63)
|
||||
* pattern[1].delta_t = (optional, can be 0 or same as pattern[0].delta_t)
|
||||
*/
|
||||
static int ltc3220_pattern_set(struct led_classdev *led_cdev,
|
||||
struct led_pattern *pattern,
|
||||
u32 len, int repeat)
|
||||
{
|
||||
struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg,
|
||||
led_cdev);
|
||||
struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220,
|
||||
uled_cfg[0]);
|
||||
u8 gradation_period;
|
||||
u8 start_brightness;
|
||||
u8 end_brightness;
|
||||
u8 gradation_val;
|
||||
u8 led_mode;
|
||||
bool is_increasing;
|
||||
int ret;
|
||||
|
||||
if (len != 2)
|
||||
return -EINVAL;
|
||||
|
||||
start_brightness = clamp_val(pattern[0].brightness, 0, LTC3220_LED_CURRENT_MASK);
|
||||
end_brightness = clamp_val(pattern[1].brightness, 0, LTC3220_LED_CURRENT_MASK);
|
||||
|
||||
is_increasing = end_brightness > start_brightness;
|
||||
|
||||
if (pattern[0].delta_t == 0)
|
||||
gradation_period = LTC3220_GRADATION_MODE_DISABLED;
|
||||
else if (pattern[0].delta_t <= LTC3220_GRADATION_RAMP_TIME_240MS)
|
||||
gradation_period = LTC3220_GRADATION_MODE_240MS_RAMP_TIME;
|
||||
else if (pattern[0].delta_t <= LTC3220_GRADATION_RAMP_TIME_480MS)
|
||||
gradation_period = LTC3220_GRADATION_MODE_480MS_RAMP_TIME;
|
||||
else
|
||||
gradation_period = LTC3220_GRADATION_MODE_960MS_RAMP_TIME;
|
||||
|
||||
gradation_val = FIELD_PREP(LTC3220_GRADATION_PERIOD_MASK, gradation_period);
|
||||
gradation_val |= FIELD_PREP(LTC3220_GRADATION_DIRECTION_MASK, is_increasing);
|
||||
|
||||
/*
|
||||
* With the ramp disabled (delta_t == 0) there is no gradation to run,
|
||||
* so apply the end brightness directly in NORMAL mode instead of
|
||||
* leaving the channel in gradation mode with a disabled ramp.
|
||||
*/
|
||||
led_mode = gradation_period == LTC3220_GRADATION_MODE_DISABLED ?
|
||||
LTC3220_NORMAL_MODE : LTC3220_GRADATION_MODE;
|
||||
|
||||
mutex_lock(<c3220->lock);
|
||||
|
||||
ret = regmap_update_bits(ltc3220->regmap, LTC3220_GRAD_BLINK_REG,
|
||||
LTC3220_GRADATION_MASK, gradation_val);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
if (led_mode == LTC3220_GRADATION_MODE) {
|
||||
ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index),
|
||||
start_brightness & LTC3220_LED_CURRENT_MASK);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index),
|
||||
FIELD_PREP(LTC3220_LED_MODE_MASK, led_mode) |
|
||||
(end_brightness & LTC3220_LED_CURRENT_MASK));
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
uled_cfg->reg_value = end_brightness;
|
||||
} else {
|
||||
ret = __ltc3220_set_led_data(ltc3220, uled_cfg, end_brightness);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
unlock:
|
||||
mutex_unlock(<c3220->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltc3220_pattern_clear(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg,
|
||||
led_cdev);
|
||||
struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220,
|
||||
uled_cfg[0]);
|
||||
int ret;
|
||||
|
||||
mutex_lock(<c3220->lock);
|
||||
|
||||
ret = regmap_update_bits(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index),
|
||||
LTC3220_LED_MODE_MASK, LTC3220_NORMAL_MODE);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
ret = __ltc3220_set_led_data(ltc3220, uled_cfg, LED_OFF);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(<c3220->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* LTC3220 has a global blink configuration that affects all LEDs.
|
||||
* This implementation allows per-LED blink requests via sysfs, but setting
|
||||
* blink on any LED reprograms the timing for all 18 channels simultaneously.
|
||||
* The delay values are mapped to the hardware's discrete blink rates.
|
||||
*
|
||||
* HARDWARE LIMITATION: This is not a driver bug. Per-LED blink timing control
|
||||
* is not possible with this hardware due to the global blink register.
|
||||
*/
|
||||
static int ltc3220_blink_set(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg,
|
||||
led_cdev);
|
||||
struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220,
|
||||
uled_cfg[0]);
|
||||
u8 blink_brightness;
|
||||
u8 blink_mode = 0;
|
||||
int ret;
|
||||
|
||||
if (*delay_on <= LTC3220_BLINK_ON_156MS)
|
||||
blink_mode = LTC3220_BLINK_SHORT_ON_TIME;
|
||||
|
||||
if (*delay_on + *delay_off > LTC3220_BLINK_PERIOD_1250MS)
|
||||
blink_mode |= LTC3220_BLINK_LONG_PERIOD;
|
||||
|
||||
switch (blink_mode) {
|
||||
case LTC3220_BLINK_MODE_625MS_1250MS:
|
||||
*delay_on = LTC3220_BLINK_ON_625MS;
|
||||
*delay_off = LTC3220_BLINK_PERIOD_1250MS - LTC3220_BLINK_ON_625MS;
|
||||
break;
|
||||
case LTC3220_BLINK_MODE_156MS_1250MS:
|
||||
*delay_on = LTC3220_BLINK_ON_156MS;
|
||||
*delay_off = LTC3220_BLINK_PERIOD_1250MS - LTC3220_BLINK_ON_156MS;
|
||||
break;
|
||||
case LTC3220_BLINK_MODE_625MS_2500MS:
|
||||
*delay_on = LTC3220_BLINK_ON_625MS;
|
||||
*delay_off = LTC3220_BLINK_PERIOD_2500MS - LTC3220_BLINK_ON_625MS;
|
||||
break;
|
||||
case LTC3220_BLINK_MODE_156MS_2500MS:
|
||||
*delay_on = LTC3220_BLINK_ON_156MS;
|
||||
*delay_off = LTC3220_BLINK_PERIOD_2500MS - LTC3220_BLINK_ON_156MS;
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_lock(<c3220->lock);
|
||||
|
||||
ret = regmap_update_bits(ltc3220->regmap, LTC3220_GRAD_BLINK_REG,
|
||||
LTC3220_BLINK_MASK, FIELD_PREP(LTC3220_BLINK_MASK, blink_mode));
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
blink_brightness = uled_cfg->reg_value ? : led_cdev->max_brightness;
|
||||
|
||||
ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index),
|
||||
FIELD_PREP(LTC3220_LED_MODE_MASK, LTC3220_BLINK_MODE) |
|
||||
(blink_brightness & LTC3220_LED_CURRENT_MASK));
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
uled_cfg->reg_value = blink_brightness;
|
||||
|
||||
unlock:
|
||||
mutex_unlock(<c3220->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ltc3220_reset_gpio_action(void *data)
|
||||
{
|
||||
struct gpio_desc *reset_gpio = data;
|
||||
|
||||
gpiod_set_value_cansleep(reset_gpio, 1);
|
||||
}
|
||||
|
||||
static int ltc3220_reset(struct ltc3220 *ltc3220, struct i2c_client *client)
|
||||
{
|
||||
struct gpio_desc *reset_gpio;
|
||||
int ret;
|
||||
|
||||
reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(reset_gpio))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(reset_gpio), "Failed on reset GPIO\n");
|
||||
|
||||
if (reset_gpio) {
|
||||
usleep_range(10000, 12000);
|
||||
gpiod_set_value_cansleep(reset_gpio, 0);
|
||||
usleep_range(10000, 12000);
|
||||
|
||||
ret = devm_add_action_or_reset(&client->dev, ltc3220_reset_gpio_action,
|
||||
reset_gpio);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_write(ltc3220->regmap, LTC3220_COMMAND_REG, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (int i = 0; i < LTC3220_NUM_LEDS; i++) {
|
||||
ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(i), 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return regmap_write(ltc3220->regmap, LTC3220_GRAD_BLINK_REG, 0);
|
||||
}
|
||||
|
||||
static int ltc3220_suspend(struct device *dev)
|
||||
{
|
||||
struct ltc3220 *ltc3220 = i2c_get_clientdata(to_i2c_client(dev));
|
||||
int ret;
|
||||
|
||||
ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG,
|
||||
LTC3220_SHUTDOWN_MASK, LTC3220_SHUTDOWN_MASK);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
regcache_mark_dirty(ltc3220->regmap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ltc3220_resume(struct device *dev)
|
||||
{
|
||||
struct ltc3220 *ltc3220 = i2c_get_clientdata(to_i2c_client(dev));
|
||||
bool quick_write_enabled;
|
||||
unsigned int command_reg;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(ltc3220->regmap, LTC3220_COMMAND_REG, &command_reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
quick_write_enabled = command_reg & LTC3220_QUICK_WRITE_MASK;
|
||||
|
||||
if (quick_write_enabled) {
|
||||
ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG,
|
||||
LTC3220_QUICK_WRITE_MASK, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG,
|
||||
LTC3220_SHUTDOWN_MASK, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
usleep_range(10000, 12000);
|
||||
|
||||
ret = regcache_sync(ltc3220->regmap);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (quick_write_enabled) {
|
||||
ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG,
|
||||
LTC3220_QUICK_WRITE_MASK,
|
||||
LTC3220_QUICK_WRITE_MASK);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(ltc3220_pm_ops, ltc3220_suspend, ltc3220_resume);
|
||||
|
||||
static int ltc3220_probe(struct i2c_client *client)
|
||||
{
|
||||
struct ltc3220 *ltc3220;
|
||||
bool aggregated_led_found = false;
|
||||
int num_leds = 0;
|
||||
u8 led_index = 0;
|
||||
int ret;
|
||||
|
||||
ltc3220 = devm_kzalloc(&client->dev, sizeof(*ltc3220), GFP_KERNEL);
|
||||
if (!ltc3220)
|
||||
return -ENOMEM;
|
||||
|
||||
ltc3220->regmap = devm_regmap_init_i2c(client, <c3220_regmap_config);
|
||||
if (IS_ERR(ltc3220->regmap))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(ltc3220->regmap),
|
||||
"Failed to initialize regmap\n");
|
||||
|
||||
ret = devm_mutex_init(&client->dev, <c3220->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i2c_set_clientdata(client, ltc3220);
|
||||
|
||||
ret = ltc3220_reset(ltc3220, client);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret, "Failed to reset device\n");
|
||||
|
||||
/* First pass: validate configuration and set up LED structures */
|
||||
device_for_each_child_node_scoped(&client->dev, child) {
|
||||
struct ltc3220_uled_cfg *led;
|
||||
u32 source;
|
||||
|
||||
ret = fwnode_property_read_u32(child, "reg", &source);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret, "Couldn't read LED address\n");
|
||||
|
||||
if (!source || source > LTC3220_NUM_LEDS)
|
||||
return dev_err_probe(&client->dev, -EINVAL, "LED address out of range\n");
|
||||
|
||||
if (fwnode_property_present(child, "led-sources")) {
|
||||
u32 led_sources[LTC3220_NUM_LEDS];
|
||||
int count;
|
||||
|
||||
if (source != 1)
|
||||
return dev_err_probe(&client->dev, -EINVAL,
|
||||
"Aggregated LED out of range\n");
|
||||
|
||||
if (aggregated_led_found)
|
||||
return dev_err_probe(&client->dev, -EINVAL,
|
||||
"One Aggregated LED only\n");
|
||||
|
||||
count = fwnode_property_count_u32(child, "led-sources");
|
||||
if (count != LTC3220_NUM_LEDS)
|
||||
return dev_err_probe(&client->dev, -EINVAL,
|
||||
"Aggregated mode requires all %d outputs in led-sources, got %d\n",
|
||||
LTC3220_NUM_LEDS, count);
|
||||
|
||||
ret = fwnode_property_read_u32_array(child, "led-sources",
|
||||
led_sources, LTC3220_NUM_LEDS);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to read led-sources array\n");
|
||||
|
||||
/*
|
||||
* Validate array contents for DT correctness. The hardware
|
||||
* quick-write broadcasts to all 18 channels regardless of
|
||||
* array contents, but checking helps catch DT mistakes.
|
||||
*/
|
||||
for (int i = 0; i < LTC3220_NUM_LEDS; i++) {
|
||||
if (led_sources[i] < 1 || led_sources[i] > LTC3220_NUM_LEDS)
|
||||
return dev_err_probe(&client->dev, -EINVAL,
|
||||
"Invalid output %u in led-sources\n",
|
||||
led_sources[i]);
|
||||
}
|
||||
|
||||
aggregated_led_found = true;
|
||||
}
|
||||
|
||||
num_leds++;
|
||||
|
||||
/* LED node reg/index/address goes from 1 to 18 */
|
||||
led_index = source - 1;
|
||||
led = <c3220->uled_cfg[led_index];
|
||||
|
||||
if (led->registered)
|
||||
return dev_err_probe(&client->dev, -EINVAL,
|
||||
"Duplicate LED reg %u found\n", source);
|
||||
|
||||
led->registered = true;
|
||||
led->led_index = led_index;
|
||||
led->reg_value = 0;
|
||||
led->led_cdev.brightness_set_blocking = ltc3220_set_led_data;
|
||||
led->led_cdev.brightness_get = ltc3220_get_led_data;
|
||||
led->led_cdev.max_brightness = LTC3220_MAX_BRIGHTNESS;
|
||||
led->led_cdev.blink_set = ltc3220_blink_set;
|
||||
led->led_cdev.pattern_set = ltc3220_pattern_set;
|
||||
led->led_cdev.pattern_clear = ltc3220_pattern_clear;
|
||||
}
|
||||
|
||||
/*
|
||||
* Aggregated LED mode uses hardware quick-write to control all 18 LEDs
|
||||
* simultaneously. This is mutually exclusive with individual LED control.
|
||||
* See Documentation/devicetree/bindings/leds/adi,ltc3220.yaml for details
|
||||
* on how to configure aggregated LED mode.
|
||||
*/
|
||||
if (aggregated_led_found && num_leds > 1)
|
||||
return dev_err_probe(&client->dev, -EINVAL,
|
||||
"Aggregated LED must be the only LED node\n");
|
||||
|
||||
if (num_leds == 0)
|
||||
return dev_err_probe(&client->dev, -EINVAL,
|
||||
"No LED nodes found in device tree\n");
|
||||
|
||||
if (aggregated_led_found) {
|
||||
ret = regmap_update_bits(ltc3220->regmap,
|
||||
LTC3220_COMMAND_REG,
|
||||
LTC3220_QUICK_WRITE_MASK,
|
||||
LTC3220_QUICK_WRITE_MASK);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to set quick write mode\n");
|
||||
}
|
||||
|
||||
/* Second pass: register LEDs after validation */
|
||||
device_for_each_child_node_scoped(&client->dev, child) {
|
||||
struct led_init_data init_data = {};
|
||||
struct ltc3220_uled_cfg *led;
|
||||
u32 source;
|
||||
|
||||
ret = fwnode_property_read_u32(child, "reg", &source);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!source || source > LTC3220_NUM_LEDS)
|
||||
return dev_err_probe(&client->dev, -EINVAL,
|
||||
"LED address out of range in second pass\n");
|
||||
|
||||
init_data.fwnode = child;
|
||||
init_data.devicename = "ltc3220";
|
||||
|
||||
led_index = source - 1;
|
||||
led = <c3220->uled_cfg[led_index];
|
||||
|
||||
ret = devm_led_classdev_register_ext(&client->dev, &led->led_cdev, &init_data);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret, "Failed to register LED class\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ltc3220_of_match[] = {
|
||||
{ .compatible = "adi,ltc3220" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ltc3220_of_match);
|
||||
|
||||
static struct i2c_driver ltc3220_led_driver = {
|
||||
.driver = {
|
||||
.name = "ltc3220",
|
||||
.of_match_table = ltc3220_of_match,
|
||||
.pm = pm_sleep_ptr(<c3220_pm_ops),
|
||||
},
|
||||
.probe = ltc3220_probe,
|
||||
};
|
||||
module_i2c_driver(ltc3220_led_driver);
|
||||
|
||||
MODULE_AUTHOR("Edelweise Escala <edelweise.escala@analog.com>");
|
||||
MODULE_DESCRIPTION("LED driver for LTC3220 controllers");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -327,9 +327,9 @@ static int pca9532_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
|
||||
struct pca9532_led *led = &data->leds[offset];
|
||||
|
||||
if (val)
|
||||
led->state = PCA9532_ON;
|
||||
else
|
||||
led->state = PCA9532_OFF;
|
||||
else
|
||||
led->state = PCA9532_ON;
|
||||
|
||||
pca9532_setled(led);
|
||||
|
||||
@@ -349,7 +349,7 @@ static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset)
|
||||
static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
|
||||
{
|
||||
/* To use as input ensure pin is not driven */
|
||||
pca9532_gpio_set_value(gc, offset, 0);
|
||||
pca9532_gpio_set_value(gc, offset, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -397,10 +397,14 @@ static int pca9532_configure(struct i2c_client *client,
|
||||
for (i = 0; i < 2; i++) {
|
||||
data->pwm[i] = pdata->pwm[i];
|
||||
data->psc[i] = pdata->psc[i];
|
||||
i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, i),
|
||||
data->pwm[i]);
|
||||
i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i),
|
||||
data->psc[i]);
|
||||
err = i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, i),
|
||||
data->pwm[i]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i),
|
||||
data->psc[i]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
data->hw_blink = true;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <linux/string.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/led-class-multicolor.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/property.h>
|
||||
@@ -101,8 +102,11 @@ struct pca963x;
|
||||
struct pca963x_led {
|
||||
struct pca963x *chip;
|
||||
struct led_classdev led_cdev;
|
||||
struct led_classdev_mc mc_cdev;
|
||||
struct mc_subled subleds[4];
|
||||
int led_num; /* 0 .. 15 potentially */
|
||||
bool blinking;
|
||||
bool is_mc;
|
||||
u8 gdc;
|
||||
u8 gfrq;
|
||||
};
|
||||
@@ -115,7 +119,7 @@ struct pca963x {
|
||||
struct pca963x_led leds[];
|
||||
};
|
||||
|
||||
static int pca963x_brightness(struct pca963x_led *led,
|
||||
static int pca963x_brightness(struct pca963x_led *led, unsigned int led_num,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct i2c_client *client = led->chip->client;
|
||||
@@ -124,8 +128,8 @@ static int pca963x_brightness(struct pca963x_led *led,
|
||||
int shift;
|
||||
int ret;
|
||||
|
||||
ledout_addr = chipdef->ledout_base + (led->led_num / 4);
|
||||
shift = 2 * (led->led_num % 4);
|
||||
ledout_addr = chipdef->ledout_base + (led_num / 4);
|
||||
shift = 2 * (led_num % 4);
|
||||
mask = 0x3 << shift;
|
||||
ledout = i2c_smbus_read_byte_data(client, ledout_addr);
|
||||
|
||||
@@ -135,7 +139,7 @@ static int pca963x_brightness(struct pca963x_led *led,
|
||||
val = (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift);
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
PCA963X_PWM_BASE +
|
||||
led->led_num,
|
||||
led_num,
|
||||
LED_FULL);
|
||||
} else {
|
||||
val = (ledout & ~mask) | (PCA963X_LED_ON << shift);
|
||||
@@ -150,7 +154,7 @@ static int pca963x_brightness(struct pca963x_led *led,
|
||||
default:
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
PCA963X_PWM_BASE +
|
||||
led->led_num,
|
||||
led_num,
|
||||
brightness);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -199,20 +203,24 @@ static void pca963x_blink(struct pca963x_led *led)
|
||||
led->blinking = true;
|
||||
}
|
||||
|
||||
static int pca963x_power_state(struct pca963x_led *led)
|
||||
static void pca963x_track_power_state(struct pca963x_led *led, unsigned int led_num,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
unsigned long *leds_on = &led->chip->leds_on;
|
||||
|
||||
if (brightness)
|
||||
set_bit(led_num, leds_on);
|
||||
else
|
||||
clear_bit(led_num, leds_on);
|
||||
}
|
||||
|
||||
static int pca963x_sync_power_state(struct pca963x_led *led, unsigned long cached_leds)
|
||||
{
|
||||
struct i2c_client *client = led->chip->client;
|
||||
unsigned long *leds_on = &led->chip->leds_on;
|
||||
unsigned long cached_leds = *leds_on;
|
||||
|
||||
if (led->led_cdev.brightness)
|
||||
set_bit(led->led_num, leds_on);
|
||||
else
|
||||
clear_bit(led->led_num, leds_on);
|
||||
|
||||
if (!(*leds_on) != !cached_leds)
|
||||
if (!led->chip->leds_on != !cached_leds)
|
||||
return i2c_smbus_write_byte_data(client, PCA963X_MODE1,
|
||||
*leds_on ? 0 : BIT(4));
|
||||
led->chip->leds_on ? 0 : BIT(4));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -221,22 +229,60 @@ static int pca963x_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct pca963x_led *led;
|
||||
unsigned long cached_leds;
|
||||
int ret;
|
||||
|
||||
led = container_of(led_cdev, struct pca963x_led, led_cdev);
|
||||
|
||||
mutex_lock(&led->chip->mutex);
|
||||
|
||||
ret = pca963x_brightness(led, value);
|
||||
if (ret < 0)
|
||||
cached_leds = led->chip->leds_on;
|
||||
ret = pca963x_brightness(led, led->led_num, value);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
ret = pca963x_power_state(led);
|
||||
|
||||
pca963x_track_power_state(led, led->led_num, value);
|
||||
ret = pca963x_sync_power_state(led, cached_leds);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&led->chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pca963x_led_mc_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
|
||||
struct pca963x_led *led = container_of(mc_cdev, struct pca963x_led, mc_cdev);
|
||||
unsigned long cached_leds;
|
||||
int ret = 0, sync_ret;
|
||||
|
||||
led_mc_calc_color_components(mc_cdev, value);
|
||||
|
||||
guard(mutex)(&led->chip->mutex);
|
||||
|
||||
cached_leds = led->chip->leds_on;
|
||||
for (unsigned int i = 0; i < mc_cdev->num_colors; i++) {
|
||||
unsigned int channel = mc_cdev->subled_info[i].channel;
|
||||
|
||||
ret = pca963x_brightness(led, channel,
|
||||
mc_cdev->subled_info[i].brightness);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
pca963x_track_power_state(led, channel,
|
||||
mc_cdev->subled_info[i].brightness);
|
||||
}
|
||||
|
||||
/*
|
||||
* Some channels may already have been updated before the error, so
|
||||
* still sync the global on/off state to reflect what actually changed.
|
||||
*/
|
||||
sync_ret = pca963x_sync_power_state(led, cached_leds);
|
||||
|
||||
return ret ? : sync_ret;
|
||||
}
|
||||
|
||||
static unsigned int pca963x_period_scale(struct pca963x_led *led,
|
||||
unsigned int val)
|
||||
{
|
||||
@@ -300,6 +346,81 @@ static int pca963x_blink_set(struct led_classdev *led_cdev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pca963x_parse_mc_subleds(struct device *dev, struct pca963x_led *led,
|
||||
struct fwnode_handle *fwnode,
|
||||
const struct pca963x_chipdef *chipdef)
|
||||
{
|
||||
unsigned int num_colors = 0;
|
||||
int ret;
|
||||
|
||||
fwnode_for_each_child_node_scoped(fwnode, sub) {
|
||||
u32 color, subreg;
|
||||
|
||||
if (num_colors >= ARRAY_SIZE(led->subleds))
|
||||
return dev_err_probe(dev, -EINVAL, "Too many LEDs for node %pfw\n", fwnode);
|
||||
|
||||
ret = fwnode_property_read_u32(sub, "reg", &subreg);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Missing 'reg' for sub-LED %pfw\n", sub);
|
||||
if (subreg >= chipdef->n_leds)
|
||||
return dev_err_probe(dev, -EINVAL, "Invalid 'reg' for sub-LED %pfw\n", sub);
|
||||
|
||||
ret = fwnode_property_read_u32(sub, "color", &color);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Missing 'color' for sub-LED %pfw\n", sub);
|
||||
|
||||
led->subleds[num_colors].channel = subreg;
|
||||
led->subleds[num_colors].color_index = color;
|
||||
led->subleds[num_colors].intensity = LED_FULL;
|
||||
num_colors++;
|
||||
}
|
||||
|
||||
led->mc_cdev.subled_info = led->subleds;
|
||||
led->mc_cdev.num_colors = num_colors;
|
||||
led->mc_cdev.led_cdev.max_brightness = LED_FULL;
|
||||
led->mc_cdev.led_cdev.brightness_set_blocking = pca963x_led_mc_set;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pca963x_register_led(struct device *dev, struct pca963x_led *led,
|
||||
u32 reg, struct fwnode_handle *fwnode,
|
||||
const struct pca963x_chipdef *chipdef,
|
||||
bool hw_blink)
|
||||
{
|
||||
struct i2c_client *client = led->chip->client;
|
||||
struct led_init_data init_data = {};
|
||||
char label[32];
|
||||
int ret;
|
||||
|
||||
led->led_num = reg;
|
||||
|
||||
/* A node with sub-children groups several channels into a multicolor LED. */
|
||||
led->is_mc = fwnode_get_child_node_count(fwnode) > 0;
|
||||
|
||||
if (led->is_mc) {
|
||||
ret = pca963x_parse_mc_subleds(dev, led, fwnode, chipdef);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
led->led_cdev.brightness_set_blocking = pca963x_led_set;
|
||||
if (hw_blink)
|
||||
led->led_cdev.blink_set = pca963x_blink_set;
|
||||
}
|
||||
|
||||
init_data.fwnode = fwnode;
|
||||
/* Keep the legacy device name to preserve existing sysfs LED names. */
|
||||
init_data.devicename = "pca963x";
|
||||
snprintf(label, sizeof(label), "%d:%.2x:%u", client->adapter->nr, client->addr, reg);
|
||||
init_data.default_label = label;
|
||||
|
||||
if (led->is_mc)
|
||||
return devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev,
|
||||
&init_data);
|
||||
|
||||
return devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data);
|
||||
}
|
||||
|
||||
static int pca963x_register_leds(struct i2c_client *client,
|
||||
struct pca963x *chip)
|
||||
{
|
||||
@@ -338,37 +459,21 @@ static int pca963x_register_leds(struct i2c_client *client,
|
||||
return ret;
|
||||
|
||||
device_for_each_child_node_scoped(dev, child) {
|
||||
struct led_init_data init_data = {};
|
||||
char default_label[32];
|
||||
|
||||
ret = fwnode_property_read_u32(child, "reg", ®);
|
||||
if (ret || reg >= chipdef->n_leds) {
|
||||
dev_err(dev, "Invalid 'reg' property for node %pfw\n",
|
||||
child);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Missing 'reg' property for node %pfw\n", child);
|
||||
if (reg >= chipdef->n_leds)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"Invalid 'reg' property for node %pfw\n", child);
|
||||
|
||||
led->led_num = reg;
|
||||
led->chip = chip;
|
||||
led->led_cdev.brightness_set_blocking = pca963x_led_set;
|
||||
if (hw_blink)
|
||||
led->led_cdev.blink_set = pca963x_blink_set;
|
||||
led->blinking = false;
|
||||
|
||||
init_data.fwnode = child;
|
||||
/* for backwards compatibility */
|
||||
init_data.devicename = "pca963x";
|
||||
snprintf(default_label, sizeof(default_label), "%d:%.2x:%u",
|
||||
client->adapter->nr, client->addr, reg);
|
||||
init_data.default_label = default_label;
|
||||
|
||||
ret = devm_led_classdev_register_ext(dev, &led->led_cdev,
|
||||
&init_data);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register LED for node %pfw\n",
|
||||
child);
|
||||
return ret;
|
||||
}
|
||||
ret = pca963x_register_led(dev, led, reg, child, chipdef, hw_blink);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to register LED for node %pfw\n",
|
||||
child);
|
||||
|
||||
++led;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,11 @@
|
||||
#define ST1202_ILED_REG0 0x09
|
||||
#define ST1202_MAX_LEDS 12
|
||||
#define ST1202_MAX_PATTERNS 8
|
||||
#define ST1202_MILLIS_PATTERN_DUR_MAX 5660
|
||||
#define ST1202_MILLIS_PATTERN_DUR_MAX (ST1202_MILLIS_PATTERN_DUR_MIN * U8_MAX)
|
||||
#define ST1202_MILLIS_PATTERN_DUR_MIN 22
|
||||
#define ST1202_PATTERN_DUR 0x16
|
||||
#define ST1202_PATTERN_PWM 0x1E
|
||||
#define ST1202_PATTERN_PWM_FULL 0x0FFF
|
||||
#define ST1202_PATTERN_REP 0x15
|
||||
|
||||
struct st1202_led {
|
||||
@@ -85,7 +86,7 @@ static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val)
|
||||
|
||||
static uint8_t st1202_prescalar_to_miliseconds(unsigned int value)
|
||||
{
|
||||
return value / ST1202_MILLIS_PATTERN_DUR_MIN - 1;
|
||||
return value / ST1202_MILLIS_PATTERN_DUR_MIN;
|
||||
}
|
||||
|
||||
static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num,
|
||||
@@ -127,37 +128,11 @@ static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern,
|
||||
st1202_prescalar_to_miliseconds(value));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
st1202_write_reg(chip, ST1202_ILED_REG0 + 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);
|
||||
struct st1202_chip *chip = led->chip;
|
||||
u8 value = 0;
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active)
|
||||
static int __st1202_channel_set(struct st1202_chip *chip, int led_num, bool active)
|
||||
{
|
||||
u8 chan_low, chan_high;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
if (led_num <= 7) {
|
||||
ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_LOW, &chan_low);
|
||||
if (ret < 0)
|
||||
@@ -185,6 +160,40 @@ static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active
|
||||
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);
|
||||
struct st1202_chip *chip = led->chip;
|
||||
u8 value = 0;
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value)
|
||||
{
|
||||
struct st1202_led *led = cdev_to_st1202_led(ldev);
|
||||
@@ -200,12 +209,16 @@ 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);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) {
|
||||
ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF);
|
||||
ret = st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = st1202_duration_pattern_write(chip, patt, ST1202_MILLIS_PATTERN_DUR_MIN);
|
||||
ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + patt, 0);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
@@ -224,13 +237,19 @@ static int st1202_led_pattern_set(struct led_classdev *ldev,
|
||||
if (len > ST1202_MAX_PATTERNS)
|
||||
return -EINVAL;
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
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)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
|
||||
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);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
@@ -244,6 +263,10 @@ static int st1202_led_pattern_set(struct led_classdev *ldev,
|
||||
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));
|
||||
if (ret != 0)
|
||||
@@ -256,13 +279,19 @@ static int st1202_dt_init(struct st1202_chip *chip)
|
||||
{
|
||||
struct device *dev = &chip->client->dev;
|
||||
struct st1202_led *led;
|
||||
int err, reg;
|
||||
int err;
|
||||
u32 reg;
|
||||
|
||||
for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
|
||||
err = of_property_read_u32(child, "reg", ®);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "Invalid register\n");
|
||||
|
||||
if (reg >= ST1202_MAX_LEDS)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"LED reg %u out of range [0, %d]\n",
|
||||
reg, ST1202_MAX_LEDS - 1);
|
||||
|
||||
led = &chip->leds[reg];
|
||||
led->is_active = true;
|
||||
led->fwnode = of_fwnode_handle(child);
|
||||
@@ -322,11 +351,6 @@ static int st1202_setup(struct st1202_chip *chip)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = st1202_write_reg(chip, ST1202_CONFIG_REG,
|
||||
ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_PATSR);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -204,9 +204,9 @@ int lp5860_device_init(struct device *dev)
|
||||
mutex_lock(&lp->lock);
|
||||
ret = regmap_update_bits(lp->regmap, LP5860_REG_DEV_INITIAL, LP5860_MODE_MASK,
|
||||
LP5860_MODE_1 << LP5860_MODE_SHIFT);
|
||||
mutex_unlock(&lp->lock);
|
||||
if (ret)
|
||||
goto err_disable;
|
||||
mutex_unlock(&lp->lock);
|
||||
|
||||
ret = lp5860_init_dt(lp);
|
||||
if (ret)
|
||||
@@ -215,7 +215,6 @@ int lp5860_device_init(struct device *dev)
|
||||
return 0;
|
||||
|
||||
err_disable:
|
||||
mutex_unlock(&lp->lock);
|
||||
lp5860_chip_enable(lp, LP5860_CHIP_DISABLE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ static int lp5860_probe(struct spi_device *spi)
|
||||
struct device *dev = &spi->dev;
|
||||
struct lp5860 *lp5860;
|
||||
unsigned int multi_leds;
|
||||
int ret;
|
||||
|
||||
multi_leds = device_get_child_node_count(dev);
|
||||
if (!multi_leds) {
|
||||
@@ -61,7 +62,10 @@ static int lp5860_probe(struct spi_device *spi)
|
||||
"Failed to initialise Regmap.\n");
|
||||
|
||||
lp5860->dev = dev;
|
||||
mutex_init(&lp5860->lock);
|
||||
|
||||
ret = devm_mutex_init(dev, &lp5860->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spi_set_drvdata(spi, lp5860);
|
||||
|
||||
@@ -70,10 +74,6 @@ static int lp5860_probe(struct spi_device *spi)
|
||||
|
||||
static void lp5860_remove(struct spi_device *spi)
|
||||
{
|
||||
struct lp5860 *lp5860 = spi_get_drvdata(spi);
|
||||
|
||||
mutex_destroy(&lp5860->lock);
|
||||
|
||||
lp5860_device_remove(&spi->dev);
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,8 @@ static int iterate_subleds(struct device *dev, struct pwm_mc_led *priv,
|
||||
}
|
||||
|
||||
subled[priv->mc_cdev.num_colors].color_index = color;
|
||||
fwnode_property_read_u32(fwnode, "default-intensity",
|
||||
&subled[priv->mc_cdev.num_colors].intensity);
|
||||
priv->mc_cdev.num_colors++;
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,22 @@ static void set_baseline_state(struct led_netdev_data *trigger_data)
|
||||
trigger_data->link_speed == SPEED_10000)
|
||||
blink_on = true;
|
||||
|
||||
if (test_bit(TRIGGER_NETDEV_LINK_25000, &trigger_data->mode) &&
|
||||
trigger_data->link_speed == SPEED_25000)
|
||||
blink_on = true;
|
||||
|
||||
if (test_bit(TRIGGER_NETDEV_LINK_40000, &trigger_data->mode) &&
|
||||
trigger_data->link_speed == SPEED_40000)
|
||||
blink_on = true;
|
||||
|
||||
if (test_bit(TRIGGER_NETDEV_LINK_50000, &trigger_data->mode) &&
|
||||
trigger_data->link_speed == SPEED_50000)
|
||||
blink_on = true;
|
||||
|
||||
if (test_bit(TRIGGER_NETDEV_LINK_100000, &trigger_data->mode) &&
|
||||
trigger_data->link_speed == SPEED_100000)
|
||||
blink_on = true;
|
||||
|
||||
if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) &&
|
||||
trigger_data->duplex == DUPLEX_HALF)
|
||||
blink_on = true;
|
||||
@@ -342,6 +358,10 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
|
||||
case TRIGGER_NETDEV_LINK_2500:
|
||||
case TRIGGER_NETDEV_LINK_5000:
|
||||
case TRIGGER_NETDEV_LINK_10000:
|
||||
case TRIGGER_NETDEV_LINK_25000:
|
||||
case TRIGGER_NETDEV_LINK_40000:
|
||||
case TRIGGER_NETDEV_LINK_50000:
|
||||
case TRIGGER_NETDEV_LINK_100000:
|
||||
case TRIGGER_NETDEV_HALF_DUPLEX:
|
||||
case TRIGGER_NETDEV_FULL_DUPLEX:
|
||||
case TRIGGER_NETDEV_TX:
|
||||
@@ -378,6 +398,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
|
||||
case TRIGGER_NETDEV_LINK_2500:
|
||||
case TRIGGER_NETDEV_LINK_5000:
|
||||
case TRIGGER_NETDEV_LINK_10000:
|
||||
case TRIGGER_NETDEV_LINK_25000:
|
||||
case TRIGGER_NETDEV_LINK_40000:
|
||||
case TRIGGER_NETDEV_LINK_50000:
|
||||
case TRIGGER_NETDEV_LINK_100000:
|
||||
case TRIGGER_NETDEV_HALF_DUPLEX:
|
||||
case TRIGGER_NETDEV_FULL_DUPLEX:
|
||||
case TRIGGER_NETDEV_TX:
|
||||
@@ -401,7 +425,11 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
|
||||
test_bit(TRIGGER_NETDEV_LINK_1000, &mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_2500, &mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_5000, &mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_10000, &mode)))
|
||||
test_bit(TRIGGER_NETDEV_LINK_10000, &mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_25000, &mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_40000, &mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_50000, &mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_100000, &mode)))
|
||||
return -EINVAL;
|
||||
|
||||
cancel_delayed_work_sync(&trigger_data->work);
|
||||
@@ -438,6 +466,10 @@ DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
|
||||
DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500);
|
||||
DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000);
|
||||
DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000);
|
||||
DEFINE_NETDEV_TRIGGER(link_25000, TRIGGER_NETDEV_LINK_25000);
|
||||
DEFINE_NETDEV_TRIGGER(link_40000, TRIGGER_NETDEV_LINK_40000);
|
||||
DEFINE_NETDEV_TRIGGER(link_50000, TRIGGER_NETDEV_LINK_50000);
|
||||
DEFINE_NETDEV_TRIGGER(link_100000, TRIGGER_NETDEV_LINK_100000);
|
||||
DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX);
|
||||
DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX);
|
||||
DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
|
||||
@@ -526,6 +558,10 @@ static umode_t netdev_trig_link_speed_visible(struct kobject *kobj,
|
||||
CHECK_LINK_MODE_ATTR(2500);
|
||||
CHECK_LINK_MODE_ATTR(5000);
|
||||
CHECK_LINK_MODE_ATTR(10000);
|
||||
CHECK_LINK_MODE_ATTR(25000);
|
||||
CHECK_LINK_MODE_ATTR(40000);
|
||||
CHECK_LINK_MODE_ATTR(50000);
|
||||
CHECK_LINK_MODE_ATTR(100000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -538,6 +574,10 @@ static struct attribute *netdev_trig_link_speed_attrs[] = {
|
||||
&dev_attr_link_2500.attr,
|
||||
&dev_attr_link_5000.attr,
|
||||
&dev_attr_link_10000.attr,
|
||||
&dev_attr_link_25000.attr,
|
||||
&dev_attr_link_40000.attr,
|
||||
&dev_attr_link_50000.attr,
|
||||
&dev_attr_link_100000.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -673,6 +713,10 @@ static void netdev_trig_work(struct work_struct *work)
|
||||
test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_25000, &trigger_data->mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_40000, &trigger_data->mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_50000, &trigger_data->mode) ||
|
||||
test_bit(TRIGGER_NETDEV_LINK_100000, &trigger_data->mode) ||
|
||||
test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) ||
|
||||
test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode);
|
||||
interval = jiffies_to_msecs(
|
||||
|
||||
@@ -607,6 +607,10 @@ enum led_trigger_netdev_modes {
|
||||
TRIGGER_NETDEV_LINK_2500,
|
||||
TRIGGER_NETDEV_LINK_5000,
|
||||
TRIGGER_NETDEV_LINK_10000,
|
||||
TRIGGER_NETDEV_LINK_25000,
|
||||
TRIGGER_NETDEV_LINK_40000,
|
||||
TRIGGER_NETDEV_LINK_50000,
|
||||
TRIGGER_NETDEV_LINK_100000,
|
||||
TRIGGER_NETDEV_HALF_DUPLEX,
|
||||
TRIGGER_NETDEV_FULL_DUPLEX,
|
||||
TRIGGER_NETDEV_TX,
|
||||
@@ -676,8 +680,10 @@ typedef int (*gpio_blink_set_t)(struct gpio_desc *desc, int state,
|
||||
struct gpio_led {
|
||||
const char *name;
|
||||
const char *default_trigger;
|
||||
#ifdef CONFIG_GPIOLIB_LEGACY
|
||||
unsigned gpio;
|
||||
unsigned active_low : 1;
|
||||
#endif
|
||||
unsigned retain_state_suspended : 1;
|
||||
unsigned panic_indicator : 1;
|
||||
unsigned default_state : 2;
|
||||
|
||||
Reference in New Issue
Block a user