diff --git a/Documentation/devicetree/bindings/leds/adi,ltc3220.yaml b/Documentation/devicetree/bindings/leds/adi,ltc3220.yaml new file mode 100644 index 000000000000..48215ad82d9a --- /dev/null +++ b/Documentation/devicetree/bindings/leds/adi,ltc3220.yaml @@ -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 + +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 + #include + + 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 + + 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; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/leds/backlight/ti,tps65217.yaml b/Documentation/devicetree/bindings/leds/backlight/ti,tps65217.yaml new file mode 100644 index 000000000000..4d2f4dbd2d52 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/backlight/ti,tps65217.yaml @@ -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 + +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; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/leds/backlight/tps65217-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/tps65217-backlight.txt deleted file mode 100644 index 5fb9279ac287..000000000000 --- a/Documentation/devicetree/bindings/leds/backlight/tps65217-backlight.txt +++ /dev/null @@ -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>; - }; - }; - diff --git a/Documentation/devicetree/bindings/leds/brcm,bcm6358-leds.yaml b/Documentation/devicetree/bindings/leds/brcm,bcm6358-leds.yaml new file mode 100644 index 000000000000..95fa311fddb5 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/brcm,bcm6358-leds.yaml @@ -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 + +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 + 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"; + }; + }; +... diff --git a/Documentation/devicetree/bindings/leds/common.yaml b/Documentation/devicetree/bindings/leds/common.yaml index f4e44b33f56d..a0ef2f2f0724 100644 --- a/Documentation/devicetree/bindings/leds/common.yaml +++ b/Documentation/devicetree/bindings/leds/common.yaml @@ -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 diff --git a/Documentation/devicetree/bindings/leds/lacie,ns2-leds.yaml b/Documentation/devicetree/bindings/leds/lacie,ns2-leds.yaml new file mode 100644 index 000000000000..2138c75c0413 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/lacie,ns2-leds.yaml @@ -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 + +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 + + led-controller { + compatible = "lacie,ns2-leds"; + + led-0 { + label = "ns2:blue:sata"; + slow-gpio = <&gpio0 29 0>; + cmd-gpio = <&gpio0 30 0>; + modes-map = ; + }; + }; +... diff --git a/Documentation/devicetree/bindings/leds/leds-bcm6358.txt b/Documentation/devicetree/bindings/leds/leds-bcm6358.txt deleted file mode 100644 index 211ffc3c4a20..000000000000 --- a/Documentation/devicetree/bindings/leds/leds-bcm6358.txt +++ /dev/null @@ -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"; - }; - }; diff --git a/Documentation/devicetree/bindings/leds/leds-ns2.txt b/Documentation/devicetree/bindings/leds/leds-ns2.txt deleted file mode 100644 index 9f81258a5b6e..000000000000 --- a/Documentation/devicetree/bindings/leds/leds-ns2.txt +++ /dev/null @@ -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 - -ns2-leds { - compatible = "lacie,ns2-leds"; - - blue-sata { - label = "ns2:blue:sata"; - slow-gpio = <&gpio0 29 0>; - cmd-gpio = <&gpio0 30 0>; - modes-map = ; - }; -}; diff --git a/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml b/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml index a31a202afe5c..7bf687c89411 100644 --- a/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml +++ b/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml @@ -45,6 +45,8 @@ properties: color: true + default-intensity: true + required: - pwms - color @@ -63,12 +65,14 @@ examples: multi-led { color = ; + linux,default-trigger = "default-on"; function = LED_FUNCTION_INDICATOR; max-brightness = <65535>; led-red { pwms = <&pwm1 0 1000000>; color = ; + default-intensity = <65535>; }; led-green { diff --git a/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml b/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml index 938d0e48fe51..a16ca11f630b 100644 --- a/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml +++ b/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml @@ -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 + + 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 = ; + function = LED_FUNCTION_STATUS; + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + color = ; + }; + + led@1 { + reg = <1>; + color = ; + }; + + led@2 { + reg = <2>; + color = ; + }; + }; + + /* Remaining channel used as a plain white LED */ + led@3 { + reg = <3>; + color = ; + function = LED_FUNCTION_STATUS; + }; + }; + }; + ... diff --git a/Documentation/devicetree/bindings/regulator/tps65217.txt b/Documentation/devicetree/bindings/regulator/tps65217.txt deleted file mode 100644 index 4f05d208c95c..000000000000 --- a/Documentation/devicetree/bindings/regulator/tps65217.txt +++ /dev/null @@ -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; - }; - }; - }; diff --git a/Documentation/leds/leds-st1202.rst b/Documentation/leds/leds-st1202.rst index 1a09fbfcedcf..ed32eb3a27d4 100644 --- a/Documentation/leds/leds-st1202.rst +++ b/Documentation/leds/leds-st1202.rst @@ -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. diff --git a/MAINTAINERS b/MAINTAINERS index 4cd6ffc27c4a..8091f2ef524e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 +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 L: linux-hwmon@vger.kernel.org diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index f4a0a3c8c870..d0d3182236be 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -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 diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 7db3768912ca..a68244bd50fb 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -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 diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c index 45c0662df933..12b70ee31b9a 100644 --- a/drivers/leds/blink/leds-bcm63138.c +++ b/drivers/leds/blink/leds-bcm63138.c @@ -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); diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 8ae71c2e91e0..dbb84949c3c2 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -9,8 +9,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -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; } diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c index 5206082b7722..50ebbc271107 100644 --- a/drivers/leds/leds-is31fl319x.c +++ b/drivers/leds/leds-is31fl319x.c @@ -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; } diff --git a/drivers/leds/leds-ltc3220.c b/drivers/leds/leds-ltc3220.c new file mode 100644 index 000000000000..5060d5042fe1 --- /dev/null +++ b/drivers/leds/leds-ltc3220.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LTC3220 18-Channel LED Driver + * + * Copyright 2026 Analog Devices Inc. + * + * Author: Edelweise Escala + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +MODULE_DESCRIPTION("LED driver for LTC3220 controllers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index f3bf59495b68..b2e081c8f139 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -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; diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index e3a81c60ee27..605cf7497e4c 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -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; } diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 7f68d956f694..168df5ecf27b 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -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; } diff --git a/drivers/leds/rgb/leds-lp5860-core.c b/drivers/leds/rgb/leds-lp5860-core.c index fd0e2f6e6e0f..e21d5f2302be 100644 --- a/drivers/leds/rgb/leds-lp5860-core.c +++ b/drivers/leds/rgb/leds-lp5860-core.c @@ -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; } diff --git a/drivers/leds/rgb/leds-lp5860-spi.c b/drivers/leds/rgb/leds-lp5860-spi.c index 5e0c44854a68..6bf6a625c28a 100644 --- a/drivers/leds/rgb/leds-lp5860-spi.c +++ b/drivers/leds/rgb/leds-lp5860-spi.c @@ -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); } diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c index d5b303aab5d6..6365459ec27f 100644 --- a/drivers/leds/rgb/leds-pwm-multicolor.c +++ b/drivers/leds/rgb/leds-pwm-multicolor.c @@ -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++; } diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index 64c078e997f2..5b4e92c14dbb 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -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( diff --git a/include/linux/leds.h b/include/linux/leds.h index b16b803cc1ac..a515f075c29a 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -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;