diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led index d4c918cc11a1..ea113fed10ef 100644 --- a/Documentation/ABI/testing/sysfs-class-led +++ b/Documentation/ABI/testing/sysfs-class-led @@ -28,7 +28,9 @@ Description: Writing 0 to this file clears active trigger. Writing non-zero to this file while trigger is active changes the - top brightness trigger is going to use. + top brightness trigger is going to use, unless the current + trigger is offloaded to hardware (in which case writing any + value will deactivate the trigger). @@ -78,6 +80,30 @@ Description: (which would often be configured in the device tree for the hardware). +What: /sys/class/leds//trigger_may_offload_to_hw +Date: September 2026 +KernelVersion: 7.4 +Contact: linux-leds@vger.kernel.org +Description: + Names and states of triggers that may be offloaded to hardware. + Such triggers are also called "hardware control trigger" in some + context. + + Only exists when the LED supports trigger offload. + + Reading this file returns a list of triggers that are capable to + be offloaded. The optional brackets around the trigger name + indicate the state of the current trigger: + + - `foo_trigger`: the trigger is not selected, or selected but + falls back to software blink for some reasons, e.g., + incompatible trigger parameters. + - `[foo_trigger]`: the trigger is selected and offloaded to + hardware. + + The "netdev" trigger also provides a custom attribute to + indicate its state, see `/sys/class/leds//offloaded`. + What: /sys/class/leds//inverted Date: January 2011 KernelVersion: 2.6.38 diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-netdev b/Documentation/ABI/testing/sysfs-class-led-trigger-netdev index ed46b37ab8a2..203ea58396ed 100644 --- a/Documentation/ABI/testing/sysfs-class-led-trigger-netdev +++ b/Documentation/ABI/testing/sysfs-class-led-trigger-netdev @@ -75,6 +75,9 @@ Description: If 1, the LED blinking in requested mode is offloaded to hardware. + LED trigger core also provides a generic attribute for this + purpose, see `/sys/class/leds//trigger_may_offload_to_hw`. + What: /sys/class/leds//link_10 Date: Jun 2023 KernelVersion: 6.5 diff --git a/Documentation/devicetree/bindings/leds/adi,ltc3208.yaml b/Documentation/devicetree/bindings/leds/adi,ltc3208.yaml new file mode 100644 index 000000000000..c6697816b7d9 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/adi,ltc3208.yaml @@ -0,0 +1,183 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2026 Analog Devices, Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/adi,ltc3208.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LTC3208 Multidisplay LED Controller from Linear Technologies (Now Analog Devices) + +maintainers: + - Jan Carlo Roleda + +description: + The LTC3208 is a multidisplay LED controller that can support up to 1A to all + connected LEDs. + + The datasheet for this device can be found in + https://www.analog.com/en/products/ltc3208.html + +properties: + compatible: + const: adi,ltc3208 + + reg: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + adi,disable-camhl-pin: + type: boolean + description: + Configures whether the external CAMHL pin is disabled. + If disabled then the output pins associated with CAM will always select + the CAM register's high half-byte brightness. + + adi,cfg-enrgbs-pin: + type: boolean + description: + Configures which channel the ENRGBS pin toggles when it receives a signal. + ENRGBS pin controls the SUB channel's output pins if this is set, + or RGB channel's output pins if this is unset. + + adi,disable-rgb-aux4-dropout: + type: boolean + description: + Configures the RGB and AUX4 dropout signals to be disabled. + + adi,aux1-channel: + $ref: /schemas/types.yaml#/definitions/string + description: + LED Channel that the AUX1 output pin mirrors its brightness level from. + enum: [aux, main, sub, cam] + default: aux + + adi,aux2-channel: + $ref: /schemas/types.yaml#/definitions/string + description: + LED Channel that the AUX2 output pin mirrors its brightness level from. + enum: [aux, main, sub, cam] + default: aux + + adi,aux3-channel: + $ref: /schemas/types.yaml#/definitions/string + description: + LED Channel that the AUX3 output pin mirrors its brightness level from. + enum: [aux, main, sub, cam] + default: aux + + adi,aux4-channel: + $ref: /schemas/types.yaml#/definitions/string + description: + LED Channel that the AUX4 output pin mirrors its brightness level from. + enum: [aux, main, sub, cam] + default: aux + +patternProperties: + "^led@[0-7]$": + type: object + $ref: /schemas/leds/common.yaml# + unevaluatedProperties: false + properties: + reg: + description: + LED Channel Number. each channel maps to a specific channel group used + to configure the brightness level of the output pins corresponding to + the channel. + enum: + - 0 # Main Channel (8-bit brightness) + - 1 # Sub Channel (8-bit brightness) + - 2 # AUX Channel (4-bit brightness) + - 3 # Camera Channel, Low-side byte (4-bit brightness) + - 4 # Camera Channel, High-side byte (4-bit brightness) + - 5 # Red Channel (4-bit brightness) + - 6 # Blue Channel (4-bit brightness) + - 7 # Green Channel (4-bit brightness) + required: + - reg + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@1b { + compatible = "adi,ltc3208"; + reg = <0x1b>; + #address-cells = <1>; + #size-cells = <0>; + adi,disable-camhl-pin; + adi,cfg-enrgbs-pin; + adi,disable-rgb-aux4-dropout; + + /* MAIN */ + led@0 { + reg = <0>; + function = LED_FUNCTION_ACTIVITY; + color = ; + }; + + /* SUB */ + led@1 { + reg = <1>; + function = LED_FUNCTION_ACTIVITY; + color = ; + }; + + /* AUX */ + led@2 { + reg = <2>; + function = LED_FUNCTION_ACTIVITY; + color = ; + }; + + /* CAMLO */ + led@3 { + reg = <3>; + function = LED_FUNCTION_FLASH; + color = ; + }; + + /* CAMHI */ + led@4 { + reg = <4>; + function = LED_FUNCTION_FLASH; + color = ; + }; + + /* RED */ + led@5 { + reg = <5>; + function = LED_FUNCTION_INDICATOR; + color = ; + }; + + /* BLUE */ + led@6 { + reg = <6>; + function = LED_FUNCTION_INDICATOR; + color = ; + }; + + /* GREEN */ + led@7 { + reg = <7>; + function = LED_FUNCTION_INDICATOR; + color = ; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml b/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml index 3c9b4054ed9a..6393cbf3afdf 100644 --- a/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml +++ b/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml @@ -99,20 +99,20 @@ examples: #size-cells = <0>; led-controller@38 { - compatible = "ti,lm3630a"; - reg = <0x38>; - enable-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>; + compatible = "ti,lm3630a"; + reg = <0x38>; + enable-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>; - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - led@0 { - reg = <0>; - led-sources = <0 1>; - label = "lcd-backlight"; - default-brightness = <200>; - max-brightness = <255>; - }; + led@0 { + reg = <0>; + led-sources = <0 1>; + label = "lcd-backlight"; + default-brightness = <200>; + max-brightness = <255>; + }; }; }; - | @@ -121,22 +121,22 @@ examples: #size-cells = <0>; led-controller@38 { - compatible = "ti,lm3630a"; - reg = <0x38>; + compatible = "ti,lm3630a"; + reg = <0x38>; - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - led@0 { - reg = <0>; - default-brightness = <150>; - ti,linear-mapping-mode; - }; + led@0 { + reg = <0>; + default-brightness = <150>; + ti,linear-mapping-mode; + }; - led@1 { - reg = <1>; - default-brightness = <225>; - ti,linear-mapping-mode; - }; + led@1 { + reg = <1>; + default-brightness = <225>; + ti,linear-mapping-mode; + }; }; }; diff --git a/Documentation/devicetree/bindings/leds/backlight/lp855x-backlight.yaml b/Documentation/devicetree/bindings/leds/backlight/lp855x-backlight.yaml index 9416e1bfab92..2215ee1b2e5b 100644 --- a/Documentation/devicetree/bindings/leds/backlight/lp855x-backlight.yaml +++ b/Documentation/devicetree/bindings/leds/backlight/lp855x-backlight.yaml @@ -95,20 +95,20 @@ examples: /* 4V OV, 4 output LED0 string enabled */ rom-14h { - rom-addr = /bits/ 8 <0x14>; - rom-val = /bits/ 8 <0xcf>; + rom-addr = /bits/ 8 <0x14>; + rom-val = /bits/ 8 <0xcf>; }; /* Heavy smoothing, 24ms ramp time step */ rom-15h { - rom-addr = /bits/ 8 <0x15>; - rom-val = /bits/ 8 <0xc7>; + rom-addr = /bits/ 8 <0x15>; + rom-val = /bits/ 8 <0xc7>; }; /* 4 output LED1 string enabled */ rom-19h { - rom-addr = /bits/ 8 <0x19>; - rom-val = /bits/ 8 <0x0f>; + rom-addr = /bits/ 8 <0x19>; + rom-val = /bits/ 8 <0x0f>; }; }; }; @@ -125,7 +125,7 @@ examples: dev-ctrl = /bits/ 8 <0x85>; init-brt = /bits/ 8 <0x10>; }; - }; + }; - | i2c { #address-cells = <1>; @@ -142,8 +142,8 @@ examples: /* 4V OV, 4 output LED string enabled */ rom-14h { - rom-addr = /bits/ 8 <0x14>; - rom-val = /bits/ 8 <0xcf>; + rom-addr = /bits/ 8 <0x14>; + rom-val = /bits/ 8 <0xcf>; }; }; }; diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-wled.yaml b/Documentation/devicetree/bindings/leds/backlight/qcom-wled.yaml index a54448cfdb38..bca00b774bb0 100644 --- a/Documentation/devicetree/bindings/leds/backlight/qcom-wled.yaml +++ b/Documentation/devicetree/bindings/leds/backlight/qcom-wled.yaml @@ -280,4 +280,4 @@ examples: qcom,ovp = <29>; qcom,num-strings = <2>; qcom,enabled-strings = <0 1>; - }; + }; diff --git a/Documentation/devicetree/bindings/leds/common.yaml b/Documentation/devicetree/bindings/leds/common.yaml index a0ef2f2f0724..2502e6f221f8 100644 --- a/Documentation/devicetree/bindings/leds/common.yaml +++ b/Documentation/devicetree/bindings/leds/common.yaml @@ -147,7 +147,7 @@ properties: # LED is triggered by SD/MMC activity - pattern: "^mmc[0-9]+$" # LED is triggered by WLAN activity - - pattern: "^phy[0-9]+tx$" + - pattern: "^phy[0-9]+(rx|tx)$" led-pattern: description: | diff --git a/Documentation/devicetree/bindings/leds/lacie,netxbig-leds.yaml b/Documentation/devicetree/bindings/leds/lacie,netxbig-leds.yaml new file mode 100644 index 000000000000..fe47c12044da --- /dev/null +++ b/Documentation/devicetree/bindings/leds/lacie,netxbig-leds.yaml @@ -0,0 +1,136 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/lacie,netxbig-leds.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LaCie/Seagate netxbig CPLD LEDs + +maintainers: + - Simon Guinot + +description: | + Binding for the CPLD LEDs (GPIO extension bus) found on some LaCie/Seagate + boards (Example: 2Big/5Big Network v2, 2Big NAS). + +properties: + compatible: + const: lacie,netxbig-leds + + gpio-ext: + description: Phandle for the gpio-ext bus. + $ref: /schemas/types.yaml#/definitions/phandle + + timers: + description: + Timer array. Each timer entry is represented by three integers - + Mode (gpio-ext bus), delay_on and delay_off. + $ref: /schemas/types.yaml#/definitions/uint32-array + +patternProperties: + "^led-[a-z0-9]+$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + mode-addr: + description: Mode register address on gpio-ext bus. + $ref: /schemas/types.yaml#/definitions/uint32 + + mode-val: + description: + Mode to value mapping. Each entry is represented by two integers - + a mode and the corresponding value on the gpio-ext bus. + $ref: /schemas/types.yaml#/definitions/uint32-array + + bright-addr: + description: Brightness register address on gpio-ext bus. + $ref: /schemas/types.yaml#/definitions/uint32 + + max-brightness: + description: Maximum brightness value. + $ref: /schemas/types.yaml#/definitions/uint32 + + required: + - mode-addr + - mode-val + - bright-addr + - max-brightness + +required: + - compatible + - gpio-ext + +additionalProperties: false + +examples: + - | + #include + + leds { + compatible = "lacie,netxbig-leds"; + + gpio-ext = <&netxbig_gpio_ext>; + + timers = ; + + led-0 { + label = "netxbig:blue:power"; + mode-addr = <0>; + mode-val = ; + bright-addr = <1>; + max-brightness = <7>; + }; + led-1 { + label = "netxbig:red:power"; + mode-addr = <0>; + mode-val = ; + bright-addr = <1>; + max-brightness = <7>; + }; + led-2 { + label = "netxbig:blue:sata0"; + mode-addr = <3>; + mode-val = ; + bright-addr = <2>; + max-brightness = <7>; + }; + led-3 { + label = "netxbig:red:sata0"; + mode-addr = <3>; + mode-val = ; + bright-addr = <2>; + max-brightness = <7>; + }; + led-4 { + label = "netxbig:blue:sata1"; + mode-addr = <4>; + mode-val = ; + bright-addr = <2>; + max-brightness = <7>; + }; + led-5 { + label = "netxbig:red:sata1"; + mode-addr = <4>; + mode-val = ; + bright-addr = <2>; + max-brightness = <7>; + }; + }; diff --git a/Documentation/devicetree/bindings/leds/leds-consumer.yaml b/Documentation/devicetree/bindings/leds/leds-consumer.yaml index fe6a0faa1d3b..b4065c9733a5 100644 --- a/Documentation/devicetree/bindings/leds/leds-consumer.yaml +++ b/Documentation/devicetree/bindings/leds/leds-consumer.yaml @@ -53,15 +53,15 @@ examples: }; i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - v4l2_node: camera@36 { - reg = <0x36>; + v4l2_node: camera@36 { + reg = <0x36>; - leds = <&privacy_led>; - led-names = "privacy"; - }; + leds = <&privacy_led>; + led-names = "privacy"; + }; }; ... diff --git a/Documentation/devicetree/bindings/leds/leds-cpcap.txt b/Documentation/devicetree/bindings/leds/leds-cpcap.txt deleted file mode 100644 index ebf7cdc7f70c..000000000000 --- a/Documentation/devicetree/bindings/leds/leds-cpcap.txt +++ /dev/null @@ -1,29 +0,0 @@ -Motorola CPCAP PMIC LEDs ------------------------- - -This module is part of the CPCAP. For more details about the whole -chip see Documentation/devicetree/bindings/mfd/motorola-cpcap.txt. - -Requires node properties: -- compatible: should be one of - * "motorola,cpcap-led-mdl" (Main Display Lighting) - * "motorola,cpcap-led-kl" (Keyboard Lighting) - * "motorola,cpcap-led-adl" (Aux Display Lighting) - * "motorola,cpcap-led-red" (Red Triode) - * "motorola,cpcap-led-green" (Green Triode) - * "motorola,cpcap-led-blue" (Blue Triode) - * "motorola,cpcap-led-cf" (Camera Flash) - * "motorola,cpcap-led-bt" (Bluetooth) - * "motorola,cpcap-led-cp" (Camera Privacy LED) -- label: see Documentation/devicetree/bindings/leds/common.txt -- vdd-supply: A phandle to the regulator powering the LED - -Example: - -&cpcap { - cpcap_led_red: red-led { - compatible = "motorola,cpcap-led-red"; - label = "cpcap:red"; - vdd-supply = <&sw5>; - }; -}; diff --git a/Documentation/devicetree/bindings/leds/leds-lp50xx.yaml b/Documentation/devicetree/bindings/leds/leds-lp50xx.yaml index 23f809906ba7..85182ad7ad09 100644 --- a/Documentation/devicetree/bindings/leds/leds-lp50xx.yaml +++ b/Documentation/devicetree/bindings/leds/leds-lp50xx.yaml @@ -121,18 +121,18 @@ examples: function = LED_FUNCTION_CHARGING; led@0 { - reg = <0x0>; - color = ; + reg = <0x0>; + color = ; }; led@1 { - reg = <0x1>; - color = ; + reg = <0x1>; + color = ; }; led@2 { - reg = <0x2>; - color = ; + reg = <0x2>; + color = ; }; }; diff --git a/Documentation/devicetree/bindings/leds/leds-lp5860.yaml b/Documentation/devicetree/bindings/leds/leds-lp5860.yaml index 0e88c71c2d39..710fec3738ed 100644 --- a/Documentation/devicetree/bindings/leds/leds-lp5860.yaml +++ b/Documentation/devicetree/bindings/leds/leds-lp5860.yaml @@ -100,18 +100,18 @@ examples: color = ; led@0 { - reg = <0x0>; - color = ; + reg = <0x0>; + color = ; }; led@1 { - reg = <0x1>; - color = ; + reg = <0x1>; + color = ; }; led@2 { - reg = <0x2>; - color = ; + reg = <0x2>; + color = ; }; }; }; diff --git a/Documentation/devicetree/bindings/leds/leds-netxbig.txt b/Documentation/devicetree/bindings/leds/leds-netxbig.txt deleted file mode 100644 index 5ef92a26d768..000000000000 --- a/Documentation/devicetree/bindings/leds/leds-netxbig.txt +++ /dev/null @@ -1,92 +0,0 @@ -Binding for the CPLD LEDs (GPIO extension bus) found on some LaCie/Seagate -boards (Example: 2Big/5Big Network v2, 2Big NAS). - -Required properties: -- compatible: "lacie,netxbig-leds". -- gpio-ext: Phandle for the gpio-ext bus. - -Optional properties: -- timers: Timer array. Each timer entry is represented by three integers: - Mode (gpio-ext bus), delay_on and delay_off. - -Each LED is represented as a sub-node of the netxbig-leds device. - -Required sub-node properties: -- mode-addr: Mode register address on gpio-ext bus. -- mode-val: Mode to value mapping. Each entry is represented by two integers: - A mode and the corresponding value on the gpio-ext bus. -- bright-addr: Brightness register address on gpio-ext bus. -- max-brightness: Maximum brightness value. - -Optional sub-node properties: -- label: Name for this LED. If omitted, the label is taken from the node name. -- linux,default-trigger: Trigger assigned to the LED. - -Example: - -netxbig-leds { - compatible = "lacie,netxbig-leds"; - - gpio-ext = &gpio_ext; - - timers = ; - - blue-power { - label = "netxbig:blue:power"; - mode-addr = <0>; - mode-val = ; - bright-addr = <1>; - max-brightness = <7>; - }; - red-power { - label = "netxbig:red:power"; - mode-addr = <0>; - mode-val = ; - bright-addr = <1>; - max-brightness = <7>; - }; - blue-sata0 { - label = "netxbig:blue:sata0"; - mode-addr = <3>; - mode-val = ; - bright-addr = <2>; - max-brightness = <7>; - }; - red-sata0 { - label = "netxbig:red:sata0"; - mode-addr = <3>; - mode-val = ; - bright-addr = <2>; - max-brightness = <7>; - }; - blue-sata1 { - label = "netxbig:blue:sata1"; - mode-addr = <4>; - mode-val = ; - bright-addr = <2>; - max-brightness = <7>; - }; - red-sata1 { - label = "netxbig:red:sata1"; - mode-addr = <4>; - mode-val = ; - bright-addr = <2>; - max-brightness = <7>; - }; -}; diff --git a/Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml b/Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml new file mode 100644 index 000000000000..c8e7b88a05cc --- /dev/null +++ b/Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/motorola,cpcap-leds.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Motorola CPCAP PMIC LEDs + +maintainers: + - Svyatoslav Ryhel + +description: + This module is part of the Motorola CPCAP MFD device. For more details + see Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml. LEDs are + represented as sub-nodes of the PMIC node on the device tree. + +allOf: + - $ref: /schemas/leds/common.yaml# + +properties: + compatible: + enum: + - motorola,cpcap-led-adl # Display Lighting + - motorola,cpcap-led-blue # Blue Triode + - motorola,cpcap-led-bt # Bluetooth + - motorola,cpcap-led-cf # Camera Flash + - motorola,cpcap-led-cp # Camera Privacy LED + - motorola,cpcap-led-green # Green Triode + - motorola,cpcap-led-kl # Keyboard Lighting + - motorola,cpcap-led-mdl # Main Display Lighting + - motorola,cpcap-led-red # Red Triode + + vdd-supply: true + +required: + - compatible + - label + - vdd-supply + +unevaluatedProperties: false + +... diff --git a/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml b/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml index a16ca11f630b..dac316741b9b 100644 --- a/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml +++ b/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml @@ -164,27 +164,27 @@ examples: #size-cells = <0>; led@0 { - reg = <0>; - color = ; - function = LED_FUNCTION_STATUS; + reg = <0>; + color = ; + function = LED_FUNCTION_STATUS; }; led@1 { - reg = <1>; - color = ; - function = LED_FUNCTION_STATUS; + reg = <1>; + color = ; + function = LED_FUNCTION_STATUS; }; led@2 { - reg = <2>; - color = ; - function = LED_FUNCTION_STATUS; + reg = <2>; + color = ; + function = LED_FUNCTION_STATUS; }; led@3 { - reg = <3>; - color = ; - function = LED_FUNCTION_STATUS; + reg = <3>; + color = ; + function = LED_FUNCTION_STATUS; }; }; }; diff --git a/Documentation/leds/leds-class.rst b/Documentation/leds/leds-class.rst index 3913966cfdac..464e54cabf52 100644 --- a/Documentation/leds/leds-class.rst +++ b/Documentation/leds/leds-class.rst @@ -242,6 +242,9 @@ ops and needs to declare specific support for the supported triggers. With hw control we refer to the LED driven by hardware. +A sysfs attribute `trigger_may_offload_to_hw` is provided for userspace +to query supported triggers and their states. + LED driver must define the following value to support hw control: - hw_control_trigger: @@ -298,6 +301,15 @@ LED driver must implement the following API to support hw control: Returns a pointer to a struct device or NULL if nothing is currently attached. +LED trigger should implement the following API to indicate hw control: + - hw_offloaded: + return a boolean indicating if the trigger is currently + offloaded to hardware. + + If a trigger doesn't implement this callback, the default + value will be true for private triggers and false for generic + ones. + LED driver can activate additional modes by default to workaround the impossibility of supporting each different mode on the supported trigger. Examples are hardcoding the blink speed to a set interval, enable special @@ -311,9 +323,69 @@ the end use hw_control_set to activate hw control. A trigger can use hw_control_get to check if a LED is already in hw control and init their flags. +Alternatively, a private trigger can be implemented along with the LED driver if +the LED's hardware control doesn't fit any generic trigger. To associate the +private trigger with the LED classdev, their `trigger_type` must be the same. To +declare that the private trigger provides hardware control for the associated +LED classdev, set the `hw_control_trigger` string to the trigger's name. Since +both the LED classdev and the private trigger are in the same LED driver, it's +not necessary for them to coordinate via `hw_control_*` callbacks. + When the LED is in hw control, no software blink is possible and doing so will effectively disable hw control. +Hardware-initiated trigger transition +===================================== + +Some hardware can autonomously activate/deactivate hardware control. After that, +the LED hardware notifies the LED driver. + +If the driver can detect such transitions and thus wants to notify the LED core +to update the current trigger then the `LED_TRIG_HW_CHANGED` flag must be set in +flags before registering. To update the current trigger accordingly, call +`led_trigger_notify_hw_control_changed` on the LED classdev. + +This capability is restricted to the LED device's private trigger. The private +trigger must have been properly registered (see above) and named after +`hw_control_trigger`. + +Only two transitions are defined: + +- "none" => private trigger: + This happens when the hardware autonomously activates hardware control + and when "none" (i.e., no trigger) is currently active. If the private + trigger is already active when the method is called, this is essentially + a no-op. + + The activation sequence for the private trigger will be executed as + normal. + + The LED driver and its private trigger must be able to handle the + activation sequence even if the hardware is currently in hardware + control. + + If error occurs in the activation sequence, the LED Trigger core reverts + the effective trigger to "none". + +- private trigger => "none" + This happens when the hardware autonomously deactivates hardware control + and when the private trigger is currently active. If "none" (i.e., no + trigger) is active when the method is called, this is essentially a + no-op. + + The deactivation sequence for the private trigger will be executed as + normal, except that the current LED brightness is retained. The reason + for keeping the brightness unchanged is that some hardware may choose a + specific brightness instead of simply turning off the LED after + autonomously deactivating hardware control. + + The LED driver and its private trigger must be able to handle the + deactivation sequence even if the hardware is not currently in hardware + control. + +If the current trigger is neither the private trigger nor "none", no transition +will be made. + Known Issues ============ diff --git a/Documentation/leds/leds-st1202.rst b/Documentation/leds/leds-st1202.rst index ed32eb3a27d4..28479a112389 100644 --- a/Documentation/leds/leds-st1202.rst +++ b/Documentation/leds/leds-st1202.rst @@ -20,6 +20,11 @@ brightness (PWM) and duration must be written to hw_pattern. - Min pattern duration: 22 ms - Max pattern duration: 5610 ms +If the device tree sets max-brightness, that value replaces 255 as the +upper limit, and a step equal to it drives the channel at full scale. +The hardware cannot go above 255, so with a larger max-brightness every +step from 255 upwards is full scale. + The format of the hardware pattern values should be: "brightness duration brightness duration ..." diff --git a/MAINTAINERS b/MAINTAINERS index ec0cc5e755ab..03649f08c7c7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15077,6 +15077,7 @@ F: drivers/scsi/leapraid/ LED SUBSYSTEM M: Lee Jones M: Pavel Machek +R: Griffin Kroah-Hartman L: linux-leds@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds.git @@ -15821,6 +15822,14 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml F: drivers/iio/temperature/ltc2983.c +LTC3208 LED DRIVER +M: Jan Carlo Roleda +L: linux-leds@vger.kernel.org +S: Maintained +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/leds/adi,ltc3208.yaml +F: drivers/leds/leds-ltc3208.c + LTC3220 LED DRIVER M: Edelweise Escala L: linux-leds@vger.kernel.org diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index d0d3182236be..7ec2c8d78542 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -1042,6 +1042,18 @@ config LEDS_ACER_A500 This option enables support for the Power Button LED of Acer Iconia Tab A500. +config LEDS_LTC3208 + tristate "LED Driver for Analog Devices LTC3208" + depends on LEDS_CLASS && I2C + select REGMAP_I2C + help + Say Y to enable the LTC3208 LED driver. + This enables the LED device LTC3208, a 7-channel, 17-current source + multidisplay high-current LED driver, configured via I2C. + + To compile this driver as a module, choose M here: the module will + be called ltc3208. + source "drivers/leds/blink/Kconfig" comment "Flash and Torch LED drivers" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index a68244bd50fb..4d4b089156e2 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_LTC3208) += leds-ltc3208.o obj-$(CONFIG_LEDS_LTC3220) += leds-ltc3220.o obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o diff --git a/drivers/leds/flash/leds-aat1290.c b/drivers/leds/flash/leds-aat1290.c index 49251cfd3350..4bde0b07fe78 100644 --- a/drivers/leds/flash/leds-aat1290.c +++ b/drivers/leds/flash/leds-aat1290.c @@ -311,8 +311,9 @@ static int init_mm_current_scale(struct aat1290_led *led, int i, max_mm_current = AAT1290_MAX_MM_CURRENT(cfg->max_flash_current); - led->mm_current_scale = devm_kzalloc(&led->pdev->dev, - sizeof(max_mm_current_percent), + led->mm_current_scale = devm_kcalloc(&led->pdev->dev, + ARRAY_SIZE(max_mm_current_percent), + sizeof(*led->mm_current_scale), GFP_KERNEL); if (!led->mm_current_scale) return -ENOMEM; diff --git a/drivers/leds/flash/leds-ktd2692.c b/drivers/leds/flash/leds-ktd2692.c index 22fbfccd4873..43d4d55503ab 100644 --- a/drivers/leds/flash/leds-ktd2692.c +++ b/drivers/leds/flash/leds-ktd2692.c @@ -69,6 +69,8 @@ static const struct expresswire_timing ktd2692_timing = { }; struct ktd2692_context { + struct device *dev; + /* Common ExpressWire properties (ctrl GPIO and timing) */ struct expresswire_common_props props; @@ -196,13 +198,12 @@ static void ktd2692_setup(struct ktd2692_context *led) static void regulator_disable_action(void *_data) { - struct device *dev = _data; - struct ktd2692_context *led = dev_get_drvdata(dev); + struct ktd2692_context *led = _data; int ret; ret = regulator_disable(led->regulator); if (ret) - dev_err(dev, "Failed to disable supply: %d\n", ret); + dev_err(led->dev, "Failed to disable supply: %d\n", ret); } static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev, @@ -233,7 +234,7 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev, dev_err(dev, "Failed to enable supply: %d\n", ret); } else { ret = devm_add_action_or_reset(dev, - regulator_disable_action, dev); + regulator_disable_action, led); if (ret) return ret; } @@ -290,6 +291,7 @@ static int ktd2692_probe(struct platform_device *pdev) if (!led) return -ENOMEM; + led->dev = &pdev->dev; fled_cdev = &led->fled_cdev; led_cdev = &fled_cdev->led_cdev; led->props.timing = ktd2692_timing; diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c index 165035a8826c..181dfd49c6ad 100644 --- a/drivers/leds/led-class-flash.c +++ b/drivers/leds/led-class-flash.c @@ -187,26 +187,23 @@ static ssize_t flash_fault_show(struct device *dev, { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); - u32 fault, mask = 0x1; - char *pbuf = buf; - int i, ret, buf_len; + u32 fault = 0, mask = 0x1; + int i, ret; ret = led_get_flash_fault(fled_cdev, &fault); if (ret < 0) return -EINVAL; - *buf = '\0'; - - for (i = 0; i < LED_NUM_FLASH_FAULTS; ++i) { - if (fault & mask) { - buf_len = sprintf(pbuf, "%s ", - led_flash_fault_names[i]); - pbuf += buf_len; - } + for (ret = 0, i = 0; i < LED_NUM_FLASH_FAULTS; ++i) { + if (fault & mask) + ret += sysfs_emit_at(buf, ret, "%s%s", + (ret ? " " : ""), + led_flash_fault_names[i]); mask <<= 1; } - return strlen(strcat(buf, "\n")); + ret += sysfs_emit_at(buf, ret, "\n"); + return ret; } static DEVICE_ATTR_RO(flash_fault); diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index a51b0ed53886..00be3cb96c00 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -27,29 +27,19 @@ static LIST_HEAD(leds_lookup_list); static struct workqueue_struct *leds_wq; -static bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev) -{ -#ifdef CONFIG_LEDS_TRIGGERS - guard(rwsem_read)(&led_cdev->trigger_lock); - return led_cdev->trigger && led_cdev->trigger->trigger_type; -#else - return false; -#endif -} - static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); unsigned int brightness; - if (led_trigger_is_hw_controlled(led_cdev)) - return -ENODATA; + scoped_guard(mutex, &led_cdev->led_access) { + if (led_trigger_is_hw_controlled(led_cdev)) + return -ENODATA; - mutex_lock(&led_cdev->led_access); - led_update_brightness(led_cdev); - brightness = led_cdev->brightness; - mutex_unlock(&led_cdev->led_access); + led_update_brightness(led_cdev); + brightness = led_cdev->brightness; + } return sysfs_emit(buf, "%u\n", brightness); } @@ -74,6 +64,9 @@ static ssize_t brightness_store(struct device *dev, if (state == LED_OFF) led_trigger_remove(led_cdev); + else + led_trigger_remove_hw_control(led_cdev); + led_set_brightness(led_cdev, state); ret = size; @@ -97,17 +90,6 @@ static ssize_t max_brightness_show(struct device *dev, } static DEVICE_ATTR_RO(max_brightness); -#ifdef CONFIG_LEDS_TRIGGERS -static const BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0); -static const struct bin_attribute *const led_trigger_bin_attrs[] = { - &bin_attr_trigger, - NULL, -}; -static const struct attribute_group led_trigger_group = { - .bin_attrs = led_trigger_bin_attrs, -}; -#endif - static struct attribute *led_class_attrs[] = { &dev_attr_brightness.attr, &dev_attr_max_brightness.attr, @@ -577,6 +559,11 @@ int led_classdev_register_ext(struct device *parent, #endif #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED led_cdev->brightness_hw_changed = -1; +#endif +#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED + if (led_cdev->flags & LED_TRIG_HW_CHANGED) + INIT_WORK(&led_cdev->trigger_hw_changed_work, + led_trigger_hw_control_changed_worker); #endif if (!led_cdev->max_brightness) led_cdev->max_brightness = LED_FULL; @@ -616,6 +603,11 @@ void led_classdev_unregister(struct led_classdev *led_cdev) if (IS_ERR_OR_NULL(led_cdev->dev)) return; +#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED + if (led_cdev->flags & LED_TRIG_HW_CHANGED) + disable_work_sync(&led_cdev->trigger_hw_changed_work); +#endif + #ifdef CONFIG_LEDS_TRIGGERS down_write(&led_cdev->trigger_lock); if (led_cdev->trigger) diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index 073c547068cc..80e281dce195 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -267,10 +267,7 @@ void led_blink_set_oneshot(struct led_classdev *led_cdev, set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags); clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags); - if (invert) - set_bit(LED_BLINK_INVERT, &led_cdev->work_flags); - else - clear_bit(LED_BLINK_INVERT, &led_cdev->work_flags); + assign_bit(LED_BLINK_INVERT, &led_cdev->work_flags, invert); led_blink_setup(led_cdev, delay_on, delay_off); } diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index b1223218bda1..f16603b5ff44 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -7,9 +7,13 @@ * Author: Richard Purdie */ +#include +#include +#include #include #include #include +#include #include #include #include @@ -33,9 +37,38 @@ trigger_relevant(struct led_classdev *led_cdev, struct led_trigger *trig) return !trig->trigger_type || trig->trigger_type == led_cdev->trigger_type; } -ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, - const struct bin_attribute *bin_attr, char *buf, - loff_t pos, size_t count) +static bool __led_trigger_is_hw_controlled(struct led_classdev *led_cdev) +{ + lockdep_assert_held(&led_cdev->trigger_lock); + + if (!led_cdev->trigger) + return false; + + if (!led_cdev->hw_control_trigger || + strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name)) + return false; + + if (led_cdev->trigger->hw_offloaded) + return led_cdev->trigger->hw_offloaded(led_cdev); + + dev_warn_once(led_cdev->dev, + "Hardware control trigger %s doesn't provide offloaded state\n", + led_cdev->trigger->name); + + /* Otherwise assume private triggers are always offloaded. */ + return led_cdev->trigger->trigger_type; +} + +bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev) +{ + guard(rwsem_read)(&led_cdev->trigger_lock); + return __led_trigger_is_hw_controlled(led_cdev); +} +EXPORT_SYMBOL_GPL(led_trigger_is_hw_controlled); + +static ssize_t trigger_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) { struct device *dev = kobj_to_dev(kobj); struct led_classdev *led_cdev = dev_get_drvdata(dev); @@ -78,7 +111,6 @@ ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, mutex_unlock(&led_cdev->led_access); return ret; } -EXPORT_SYMBOL_GPL(led_trigger_write); __printf(3, 4) static int led_trigger_snprintf(char *buf, ssize_t size, const char *fmt, ...) @@ -130,9 +162,9 @@ static int led_trigger_format(char *buf, size_t size, * attribute, which is not limited by length. This is _not_ good design, do not * copy it. */ -ssize_t led_trigger_read(struct file *filp, struct kobject *kobj, - const struct bin_attribute *attr, char *buf, - loff_t pos, size_t count) +static ssize_t trigger_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t pos, size_t count) { struct device *dev = kobj_to_dev(kobj); struct led_classdev *led_cdev = dev_get_drvdata(dev); @@ -160,10 +192,53 @@ ssize_t led_trigger_read(struct file *filp, struct kobject *kobj, return len; } -EXPORT_SYMBOL_GPL(led_trigger_read); +static const BIN_ATTR_RW(trigger, 0); + +static const struct bin_attribute *const led_trigger_bin_attrs[] = { + &bin_attr_trigger, + NULL +}; + +static ssize_t trigger_may_offload_to_hw_show(struct device *dev, + const struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + bool offloaded = led_trigger_is_hw_controlled(led_cdev); + + return sysfs_emit(buf, "%s%s%s\n", + offloaded ? "[" : "", + led_cdev->hw_control_trigger, + offloaded ? "]" : ""); +} +static const DEVICE_ATTR_RO(trigger_may_offload_to_hw); + +static const struct attribute *const led_trigger_attrs[] = { + &dev_attr_trigger_may_offload_to_hw.attr, + NULL +}; + +static umode_t led_trigger_is_visible(struct kobject *kobj, + const struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + if (attr == &dev_attr_trigger_may_offload_to_hw.attr) + return led_cdev->hw_control_trigger ? attr->mode : 0; + + return attr->mode; +} + +const struct attribute_group led_trigger_group = { + .bin_attrs = led_trigger_bin_attrs, + .attrs_const = led_trigger_attrs, + .is_visible_const = led_trigger_is_visible, +}; +EXPORT_SYMBOL_GPL(led_trigger_group); /* Caller must ensure led_cdev->trigger_lock held */ -int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) +static int __led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig, + bool hw_triggered) { char *event = NULL; char *envp[2]; @@ -194,7 +269,21 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) led_cdev->trigger_data = NULL; led_cdev->activated = false; led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER; - led_set_brightness(led_cdev, LED_OFF); + + /* + * Hardware may have selected a new brightness level during its + * hardware control transition, so only reset brightness if we + * are switching to another trigger or if the switching is not + * hardware triggered. + * + * Note that this does not apply to the error path, as running + * into the error path implies a none => private trigger + * transition. This hints that the LED driver and its private + * trigger must have some fundamental bugs, so the error path + * always turns off the LED to reset it to a certain state. + */ + if (trig || !hw_triggered) + led_set_brightness(led_cdev, LED_OFF); } if (trig) { spin_lock(&trig->leddev_list_lock); @@ -258,6 +347,11 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) return ret; } + +int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) +{ + return __led_trigger_set(led_cdev, trig, false); +} EXPORT_SYMBOL_GPL(led_trigger_set); void led_trigger_remove(struct led_classdev *led_cdev) @@ -268,6 +362,15 @@ void led_trigger_remove(struct led_classdev *led_cdev) } EXPORT_SYMBOL_GPL(led_trigger_remove); +void led_trigger_remove_hw_control(struct led_classdev *led_cdev) +{ + guard(rwsem_write)(&led_cdev->trigger_lock); + + if (__led_trigger_is_hw_controlled(led_cdev)) + led_trigger_set(led_cdev, NULL); +} +EXPORT_SYMBOL_GPL(led_trigger_remove_hw_control); + static bool led_match_default_trigger(struct led_classdev *led_cdev, struct led_trigger *trig) { @@ -403,6 +506,60 @@ int devm_led_trigger_register(struct device *dev, } EXPORT_SYMBOL_GPL(devm_led_trigger_register); +#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED + +static void led_trigger_do_hw_control_transition(struct led_classdev *led_cdev, bool activate, + struct led_trigger *hc_trig) +{ + if (activate && !led_cdev->trigger) /* "none" => private trigger. */ + __led_trigger_set(led_cdev, hc_trig, true); + else if (!activate && led_cdev->trigger == hc_trig) /* private trigger => "none". */ + __led_trigger_set(led_cdev, NULL, true); + + /* Already in the desired state, or another trigger is active, ignore. */ +} + +void led_trigger_hw_control_changed_worker(struct work_struct *work) +{ + struct led_classdev *led_cdev = + container_of(work, struct led_classdev, trigger_hw_changed_work); + bool activate = READ_ONCE(led_cdev->trigger_hw_changed); + + scoped_guard(rwsem_read, &triggers_list_lock) { + struct led_trigger *trig; + + list_for_each_entry(trig, &trigger_list, next_trig) { + if (trig->trigger_type == led_cdev->trigger_type && + !strcmp(trig->name, led_cdev->hw_control_trigger)) { + guard(rwsem_write)(&led_cdev->trigger_lock); + + led_trigger_do_hw_control_transition(led_cdev, activate, trig); + return; + } + } + } + + dev_warn(led_cdev->dev, + "Private trigger %s is not registered, can't toggle hardware control\n", + led_cdev->hw_control_trigger); +} +EXPORT_SYMBOL_GPL(led_trigger_hw_control_changed_worker); + +void led_trigger_notify_hw_control_changed(struct led_classdev *led_cdev, bool activate) +{ + /* Restricted to private triggers. */ + if (WARN_ON(!(led_cdev->flags & LED_TRIG_HW_CHANGED) || + !led_cdev->hw_control_trigger || !led_cdev->trigger_type)) + return; + + WRITE_ONCE(led_cdev->trigger_hw_changed, activate); + + schedule_work(&led_cdev->trigger_hw_changed_work); +} +EXPORT_SYMBOL_GPL(led_trigger_notify_hw_control_changed); + +#endif /* CONFIG_LEDS_TRIGGERS_HW_CHANGED */ + /* Simple LED Trigger Interface */ void led_trigger_event(struct led_trigger *trig, diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c index 1844d0cd5f52..0e93aa28a8b1 100644 --- a/drivers/leds/leds-cros_ec.c +++ b/drivers/leds/leds-cros_ec.c @@ -85,12 +85,18 @@ static int cros_ec_led_trigger_activate(struct led_classdev *led_cdev) return cros_ec_led_send_cmd(priv->cros_ec, &arg); } +static bool cros_ec_led_trigger_hw_offloaded(struct led_classdev *led_cdev) +{ + return true; +} + static struct led_hw_trigger_type cros_ec_led_trigger_type; static struct led_trigger cros_ec_led_trigger = { .name = "chromeos-auto", .trigger_type = &cros_ec_led_trigger_type, .activate = cros_ec_led_trigger_activate, + .hw_offloaded = cros_ec_led_trigger_hw_offloaded, }; static int cros_ec_led_brightness_set_blocking(struct led_classdev *led_cdev, diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c index 6c8d6b833260..285085d27453 100644 --- a/drivers/leds/leds-is31fl32xx.c +++ b/drivers/leds/leds-is31fl32xx.c @@ -378,23 +378,23 @@ static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv) } static int is31fl32xx_parse_child_dt(const struct device *dev, - const struct device_node *child, + const struct fwnode_handle *child, struct is31fl32xx_led_data *led_data) { struct led_classdev *cdev = &led_data->cdev; int ret = 0; u32 reg; - ret = of_property_read_u32(child, "reg", ®); + ret = fwnode_property_read_u32(child, "reg", ®); if (ret || reg < 1 || reg > led_data->priv->cdef->channels) { dev_err(dev, - "Child node %pOF does not have a valid reg property\n", + "Child node %pfwP does not have a valid reg property\n", child); return -EINVAL; } led_data->channel = reg; - of_property_read_u32(child, "led-max-microamp", &led_data->max_microamp); + fwnode_property_read_u32(child, "led-max-microamp", &led_data->max_microamp); cdev->brightness_set_blocking = is31fl32xx_brightness_set; @@ -422,7 +422,7 @@ static int is31fl32xx_parse_dt(struct device *dev, int ret = 0; if ((cdef->output_frequency_setting_reg != IS31FL32XX_REG_NONE) && - of_property_read_bool(dev_of_node(dev), "issi,22khz-pwm")) { + device_property_read_bool(dev, "issi,22khz-pwm")) { ret = is31fl32xx_write(priv, cdef->output_frequency_setting_reg, IS31FL32XX_PWM_FREQUENCY_22KHZ); @@ -433,7 +433,7 @@ static int is31fl32xx_parse_dt(struct device *dev, } } - for_each_available_child_of_node_scoped(dev_of_node(dev), child) { + device_for_each_child_node_scoped(dev, child) { struct led_init_data init_data = {}; struct is31fl32xx_led_data *led_data = &priv->leds[priv->num_leds]; @@ -451,17 +451,17 @@ static int is31fl32xx_parse_dt(struct device *dev, led_data->channel); if (other_led_data) { dev_err(dev, - "Node %pOF 'reg' conflicts with another LED\n", + "Node %pfwP 'reg' conflicts with another LED\n", child); return -EINVAL; } - init_data.fwnode = of_fwnode_handle(child); + init_data.fwnode = child; ret = devm_led_classdev_register_ext(dev, &led_data->cdev, &init_data); if (ret) { - dev_err(dev, "Failed to register LED for %pOF: %d\n", + dev_err(dev, "Failed to register LED for %pfwP: %d\n", child, ret); return ret; } @@ -576,7 +576,7 @@ static int is31fl32xx_probe(struct i2c_client *client) cdef = device_get_match_data(dev); - count = of_get_available_child_count(dev_of_node(dev)); + count = device_get_child_node_count(dev); if (!count) return -EINVAL; diff --git a/drivers/leds/leds-lm3692x.c b/drivers/leds/leds-lm3692x.c index 8d2678dc9e4f..9c58d54c5783 100644 --- a/drivers/leds/leds-lm3692x.c +++ b/drivers/leds/leds-lm3692x.c @@ -276,9 +276,11 @@ static int lm3692x_leds_enable(struct lm3692x_led *led) ret = regmap_update_bits(led->regmap, LM3692X_EN, LM3692X_ENABLE_MASK, enable_state | LM3692X_DEVICE_EN); + if (ret) + goto out; led->enabled = true; - return ret; + return 0; out: dev_err(&led->client->dev, "Fail writing initialization values\n"); diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c index 69f064781f69..f6e4227de903 100644 --- a/drivers/leds/leds-lp8860.c +++ b/drivers/leds/leds-lp8860.c @@ -7,6 +7,7 @@ * Author: Dan Murphy */ +#include #include #include #include @@ -274,7 +275,6 @@ static int lp8860_probe(struct i2c_client *client) int ret; struct lp8860_led *led; struct device_node *np = dev_of_node(&client->dev); - struct device_node *child_node; struct led_init_data init_data = {}; struct gpio_desc *enable_gpio; @@ -282,7 +282,8 @@ static int lp8860_probe(struct i2c_client *client) if (!led) return -ENOMEM; - child_node = of_get_next_available_child(np, NULL); + struct device_node *child_node __free(device_node) = + of_get_next_available_child(np, NULL); if (!child_node) return -EINVAL; diff --git a/drivers/leds/leds-ltc3208.c b/drivers/leds/leds-ltc3208.c new file mode 100644 index 000000000000..64091f29f18a --- /dev/null +++ b/drivers/leds/leds-ltc3208.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LED driver for Analog Devices LTC3208 Multi-Display Driver + * + * Copyright 2026 Analog Devices Inc. + * + * Author: Jan Carlo Roleda + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Registers */ +#define LTC3208_REG_A_GRNRED_CURRENT 0x1 /* Green and Red current DAC */ +#define LTC3208_REG_B_AUXBLU_CURRENT 0x2 /* AUX and Blue current DAC */ +#define LTC3208_REG_C_MAIN_CURRENT 0x3 /* Main current DAC */ +#define LTC3208_REG_D_SUB_CURRENT 0x4 /* Sub current DAC */ +#define LTC3208_REG_E_AUX_SELECT 0x5 /* AUX DAC Select */ +#define LTC3208_AUX1_MASK GENMASK(1, 0) +#define LTC3208_AUX2_MASK GENMASK(3, 2) +#define LTC3208_AUX3_MASK GENMASK(5, 4) +#define LTC3208_AUX4_MASK GENMASK(7, 6) +#define LTC3208_REG_F_CAM_CURRENT 0x6 /* CAM (High and Low) current DAC */ +#define LTC3208_REG_G_OPT 0x7 /* Device Options */ +#define LTC3208_OPT_CPO_MASK GENMASK(7, 6) +#define LTC3208_OPT_DIS_RGBDROP BIT(3) +#define LTC3208_OPT_DIS_CAMHILO BIT(2) +#define LTC3208_OPT_EN_RGBS BIT(1) + +#define LTC3208_MAX_BRIGHTNESS_4BIT 0xF +#define LTC3208_MAX_BRIGHTNESS_8BIT 0xFF + +#define LTC3208_NUM_AUX_LEDS 4 /* Number of configurable aux channels */ +#define LTC3208_NUM_REGS 7 /* Number of available registers to access */ + +enum ltc3208_aux_channel { + LTC3208_AUX_CHAN_AUX = 0, + LTC3208_AUX_CHAN_MAIN, + LTC3208_AUX_CHAN_SUB, + LTC3208_AUX_CHAN_CAM +}; + +enum ltc3208_channel { + LTC3208_CHAN_MAIN = 0, + LTC3208_CHAN_SUB, + LTC3208_CHAN_AUX, + LTC3208_CHAN_CAML, + LTC3208_CHAN_CAMH, + LTC3208_CHAN_RED, + LTC3208_CHAN_BLUE, + LTC3208_CHAN_GREEN, + LTC3208_CHAN_N_COUNT, +}; + +static const char *const ltc3208_dt_aux_channels[] = { + "adi,aux1-channel", + "adi,aux2-channel", + "adi,aux3-channel", + "adi,aux4-channel" +}; + +static const char *const ltc3208_aux_opt[] = { "aux", "main", "sub", "cam" }; + +struct ltc3208_led { + struct led_classdev cdev; + struct regmap_field *rfield; +}; + +static const struct reg_default ltc3208_reg_defaults[] = { + { LTC3208_REG_A_GRNRED_CURRENT, 0 }, + { LTC3208_REG_B_AUXBLU_CURRENT, 0 }, + { LTC3208_REG_C_MAIN_CURRENT, 0 }, + { LTC3208_REG_D_SUB_CURRENT, 0 }, + { LTC3208_REG_E_AUX_SELECT, 0 }, + { LTC3208_REG_F_CAM_CURRENT, 0 }, + { LTC3208_REG_G_OPT, 0 } +}; + +static const struct regmap_config ltc3208_regmap_cfg = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LTC3208_REG_G_OPT, + .cache_type = REGCACHE_FLAT_S, + .reg_defaults = ltc3208_reg_defaults, + .num_reg_defaults = LTC3208_NUM_REGS, +}; + +static const struct reg_field ltc3208_led_reg_field[LTC3208_CHAN_N_COUNT] = { + [LTC3208_CHAN_MAIN] = REG_FIELD(LTC3208_REG_C_MAIN_CURRENT, 0, 7), + [LTC3208_CHAN_SUB] = REG_FIELD(LTC3208_REG_D_SUB_CURRENT, 0, 7), + [LTC3208_CHAN_BLUE] = REG_FIELD(LTC3208_REG_B_AUXBLU_CURRENT, 0, 3), + [LTC3208_CHAN_AUX] = REG_FIELD(LTC3208_REG_B_AUXBLU_CURRENT, 4, 7), + [LTC3208_CHAN_CAML] = REG_FIELD(LTC3208_REG_F_CAM_CURRENT, 0, 3), + [LTC3208_CHAN_CAMH] = REG_FIELD(LTC3208_REG_F_CAM_CURRENT, 4, 7), + [LTC3208_CHAN_RED] = REG_FIELD(LTC3208_REG_A_GRNRED_CURRENT, 0, 3), + [LTC3208_CHAN_GREEN] = REG_FIELD(LTC3208_REG_A_GRNRED_CURRENT, 4, 7), +}; + +static int ltc3208_led_set_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) +{ + struct ltc3208_led *led = container_of(led_cdev, struct ltc3208_led, cdev); + u8 current_level = brightness; + + return regmap_field_write(led->rfield, current_level); +} + +static int ltc3208_probe(struct i2c_client *client) +{ + enum ltc3208_aux_channel aux_channels[LTC3208_NUM_AUX_LEDS]; + DECLARE_BITMAP(registered_chans, LTC3208_CHAN_N_COUNT) = { }; + struct regmap *regmap; + bool disable_rgb_aux4_dropout_signal; + bool disable_camhl_pin; + bool set_sub_control_pin; + int ret; + u8 dev_options; + + regmap = devm_regmap_init_i2c(client, <c3208_regmap_cfg); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), "Failed to initialize regmap"); + + regcache_mark_dirty(regmap); + ret = regcache_sync(regmap); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to sync register cache"); + + disable_camhl_pin = device_property_read_bool(&client->dev, "adi,disable-camhl-pin"); + + set_sub_control_pin = device_property_read_bool(&client->dev, "adi,cfg-enrgbs-pin"); + + disable_rgb_aux4_dropout_signal = + device_property_read_bool(&client->dev, "adi,disable-rgb-aux4-dropout"); + + dev_options = FIELD_PREP(LTC3208_OPT_EN_RGBS, set_sub_control_pin) | + FIELD_PREP(LTC3208_OPT_DIS_CAMHILO, disable_camhl_pin) | + FIELD_PREP(LTC3208_OPT_CPO_MASK, 0) | + FIELD_PREP(LTC3208_OPT_DIS_RGBDROP, disable_rgb_aux4_dropout_signal); + + ret = regmap_write(regmap, LTC3208_REG_G_OPT, dev_options); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to set device options register"); + + /* Initialize AUX channel configurations */ + for (int i = 0; i < LTC3208_NUM_AUX_LEDS; i++) { + /* Default value (AUX) if property is not present in DT */ + if (!device_property_present(&client->dev, ltc3208_dt_aux_channels[i])) { + aux_channels[i] = LTC3208_AUX_CHAN_AUX; + continue; + } + + ret = device_property_match_property_string(&client->dev, + ltc3208_dt_aux_channels[i], + ltc3208_aux_opt, LTC3208_NUM_AUX_LEDS); + if (ret < 0) + return dev_err_probe(&client->dev, ret, "Error reading AUX Channel %d", i); + + aux_channels[i] = ret; + } + + dev_options = FIELD_PREP(LTC3208_AUX1_MASK, aux_channels[0]) | + FIELD_PREP(LTC3208_AUX2_MASK, aux_channels[1]) | + FIELD_PREP(LTC3208_AUX3_MASK, aux_channels[2]) | + FIELD_PREP(LTC3208_AUX4_MASK, aux_channels[3]); + + ret = regmap_write(regmap, LTC3208_REG_E_AUX_SELECT, dev_options); + if (ret) + return dev_err_probe(&client->dev, ret, "Error writing to aux channel register"); + + device_for_each_child_node_scoped(&client->dev, child) { + struct ltc3208_led *led; + struct led_init_data init_data = { }; + u32 chan; + + ret = fwnode_property_read_u32(child, "reg", &chan); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to get register value of LED"); + if (chan >= LTC3208_CHAN_N_COUNT) + return dev_err_probe(&client->dev, -EINVAL, "%u is an invalid LED ID", + chan); + if (test_and_set_bit(chan, registered_chans)) + return dev_err_probe(&client->dev, -EINVAL, "%u is already registered", + chan); + + led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->rfield = devm_regmap_field_alloc(&client->dev, regmap, + ltc3208_led_reg_field[chan]); + if (IS_ERR(led->rfield)) + return dev_err_probe(&client->dev, PTR_ERR(led->rfield), + "Cannot allocate regmap field"); + led->cdev.brightness_set_blocking = ltc3208_led_set_brightness; + led->cdev.max_brightness = LTC3208_MAX_BRIGHTNESS_4BIT; + + if (chan == LTC3208_CHAN_MAIN || chan == LTC3208_CHAN_SUB) + led->cdev.max_brightness = LTC3208_MAX_BRIGHTNESS_8BIT; + + init_data.fwnode = child; + + ret = devm_led_classdev_register_ext(&client->dev, &led->cdev, &init_data); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to register LED %u", chan); + } + + return 0; +} + +static const struct of_device_id ltc3208_match_table[] = { + { .compatible = "adi,ltc3208" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc3208_match_table); + +static struct i2c_driver ltc3208_driver = { + .driver = { + .name = "ltc3208", + .of_match_table = ltc3208_match_table, + }, + .probe = ltc3208_probe, +}; +module_i2c_driver(ltc3208_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jan Carlo Roleda "); +MODULE_DESCRIPTION("LTC3208 LED Driver"); diff --git a/drivers/leds/leds-max77705.c b/drivers/leds/leds-max77705.c index 1e2054c1bf80..4fd803c95989 100644 --- a/drivers/leds/leds-max77705.c +++ b/drivers/leds/leds-max77705.c @@ -160,7 +160,6 @@ static int max77705_add_led(struct device *dev, struct regmap *regmap, struct fw struct max77705_led *led; struct led_classdev *cdev; struct mc_subled *info; - struct fwnode_handle *child; struct led_init_data init_data = {}; led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); @@ -191,7 +190,7 @@ static int max77705_add_led(struct device *dev, struct regmap *regmap, struct fw cdev->brightness_set_blocking = max77705_led_brightness_set_multi; cdev->blink_set = max77705_rgb_blink; - fwnode_for_each_child_node(np, child) { + fwnode_for_each_child_node_scoped(np, child) { ret = max77705_parse_subled(dev, child, &info[i]); if (ret < 0) return ret; diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index b2e081c8f139..1133fa348e24 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -298,12 +298,15 @@ static enum pca9532_state pca9532_getled(struct pca9532_led *led) struct i2c_client *client = led->client; struct pca9532_data *data = i2c_get_clientdata(client); u8 maxleds = data->chip_info->num_leds; - char reg; - enum pca9532_state ret; + int reg; + enum pca9532_state ret = PCA9532_OFF; mutex_lock(&data->update_lock); reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id)); - ret = (reg & LED_MASK(led->id)) >> LED_SHIFT(led->id); + if (reg >= 0) + ret = (reg & LED_MASK(led->id)) >> LED_SHIFT(led->id); + else + dev_warn(&client->dev, "failed to read LED register: %d\n", reg); mutex_unlock(&data->update_lock); return ret; } diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index 273383351ba0..e4af7a3b639f 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -79,6 +79,17 @@ struct pca955x_chipdef { int blink_div; /* PSC divider */ }; +struct pca955x_led { + struct pca955x *pca955x; + struct led_classdev led_cdev; + int led_num; /* 0 .. 15 potentially */ + u32 type; + enum led_default_state default_state; + struct fwnode_handle *fwnode; +}; + +#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev) + static const struct pca955x_chipdef pca955x_chipdefs[] = { [pca9550] = { .bits = 2, @@ -114,7 +125,6 @@ static const struct pca955x_chipdef pca955x_chipdefs[] = { struct pca955x { struct mutex lock; - struct pca955x_led *leds; const struct pca955x_chipdef *chipdef; struct i2c_client *client; unsigned long active_blink; @@ -123,22 +133,7 @@ struct pca955x { #ifdef CONFIG_LEDS_PCA955X_GPIO struct gpio_chip gpio; #endif -}; - -struct pca955x_led { - struct pca955x *pca955x; - struct led_classdev led_cdev; - int led_num; /* 0 .. 15 potentially */ - u32 type; - enum led_default_state default_state; - struct fwnode_handle *fwnode; -}; - -#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev) - -struct pca955x_platform_data { - struct pca955x_led *leds; - int num_leds; + struct pca955x_led leds[]; }; /* 8 bits per input register */ @@ -530,37 +525,27 @@ static int pca955x_gpio_direction_output(struct gpio_chip *gc, } #endif /* CONFIG_LEDS_PCA955X_GPIO */ -static struct pca955x_platform_data * -pca955x_get_pdata(struct i2c_client *client, const struct pca955x_chipdef *chip) +static int pca955x_parse_fwnode(struct pca955x *pca955x) { - struct pca955x_platform_data *pdata; + struct device *dev = &pca955x->client->dev; + u8 bits = pca955x->chipdef->bits; struct pca955x_led *led; struct fwnode_handle *child; int count; - count = device_get_child_node_count(&client->dev); - if (count > chip->bits) - return ERR_PTR(-ENODEV); + count = device_get_child_node_count(dev); + if (count > bits) + return -ENODEV; - pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return ERR_PTR(-ENOMEM); - - pdata->leds = devm_kcalloc(&client->dev, - chip->bits, sizeof(struct pca955x_led), - GFP_KERNEL); - if (!pdata->leds) - return ERR_PTR(-ENOMEM); - - device_for_each_child_node(&client->dev, child) { + device_for_each_child_node(dev, child) { u32 reg; int res; res = fwnode_property_read_u32(child, "reg", ®); - if ((res != 0) || (reg >= chip->bits)) + if ((res != 0) || (reg >= bits)) continue; - led = &pdata->leds[reg]; + led = &pca955x->leds[reg]; led->type = PCA955X_TYPE_LED; led->fwnode = child; led->default_state = led_init_default_state_get(child); @@ -568,9 +553,7 @@ pca955x_get_pdata(struct i2c_client *client, const struct pca955x_chipdef *chip) fwnode_property_read_u32(child, "type", &led->type); } - pdata->num_leds = chip->bits; - - return pdata; + return 0; } static int pca955x_probe(struct i2c_client *client) @@ -584,7 +567,6 @@ static int pca955x_probe(struct i2c_client *client) u8 i, nls, psc0; u8 ls1[4]; u8 ls2[4]; - struct pca955x_platform_data *pdata; bool keep_psc0 = false; bool set_default_label = false; char default_label[4]; @@ -595,12 +577,6 @@ static int pca955x_probe(struct i2c_client *client) return dev_err_probe(&client->dev, -ENODEV, "unknown chip\n"); adapter = client->adapter; - pdata = dev_get_platdata(&client->dev); - if (!pdata) { - pdata = pca955x_get_pdata(client, chip); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - } /* Make sure the slave address / chip type combo given is possible */ if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) != @@ -616,22 +592,11 @@ static int pca955x_probe(struct i2c_client *client) if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -EIO; - if (pdata->num_leds != chip->bits) { - dev_err(&client->dev, - "board info claims %d LEDs on a %u-bit chip\n", - pdata->num_leds, chip->bits); - return -ENODEV; - } - - pca955x = devm_kzalloc(&client->dev, sizeof(*pca955x), GFP_KERNEL); + pca955x = devm_kzalloc(&client->dev, + struct_size(pca955x, leds, chip->bits), GFP_KERNEL); if (!pca955x) return -ENOMEM; - pca955x->leds = devm_kcalloc(&client->dev, chip->bits, - sizeof(*pca955x_led), GFP_KERNEL); - if (!pca955x->leds) - return -ENOMEM; - i2c_set_clientdata(client, pca955x); mutex_init(&pca955x->lock); @@ -639,6 +604,10 @@ static int pca955x_probe(struct i2c_client *client) pca955x->chipdef = chip; pca955x->blink_period = PCA955X_BLINK_DEFAULT_MS; + err = pca955x_parse_fwnode(pca955x); + if (err) + return err; + init_data.devname_mandatory = false; init_data.devicename = "pca955x"; @@ -657,7 +626,6 @@ static int pca955x_probe(struct i2c_client *client) pca955x_led = &pca955x->leds[i]; pca955x_led->led_num = i; pca955x_led->pca955x = pca955x; - pca955x_led->type = pdata->leds[i].type; switch (pca955x_led->type) { case PCA955X_TYPE_NONE: @@ -671,16 +639,16 @@ static int pca955x_probe(struct i2c_client *client) led->brightness_get = pca955x_led_get; led->blink_set = pca955x_led_blink; - if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF) + if (pca955x_led->default_state == LEDS_DEFSTATE_OFF) ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_OFF); - else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON) + else if (pca955x_led->default_state == LEDS_DEFSTATE_ON) ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_ON); else if (pca955x_ledstate(ls2[reg], bit) == PCA955X_LS_BLINK0) { keep_psc0 = true; set_bit(i, &pca955x->active_blink); } - init_data.fwnode = pdata->leds[i].fwnode; + init_data.fwnode = pca955x_led->fwnode; if (is_of_node(init_data.fwnode)) { if (to_of_node(init_data.fwnode)->name[0] == diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 605cf7497e4c..5363afbe93ea 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -208,10 +208,7 @@ static void pca963x_track_power_state(struct pca963x_led *led, unsigned int led_ { unsigned long *leds_on = &led->chip->leds_on; - if (brightness) - set_bit(led_num, leds_on); - else - clear_bit(led_num, leds_on); + assign_bit(led_num, leds_on, brightness); } static int pca963x_sync_power_state(struct pca963x_led *led, unsigned long cached_leds) diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c index f24ca75c7cb1..c782339b1330 100644 --- a/drivers/leds/leds-ss4200.c +++ b/drivers/leds/leds-ss4200.c @@ -339,7 +339,7 @@ static void ich7_lpc_cleanup(struct device *dev) /* * The OS has determined that the LPC of the Intel ICH7 Southbridge is present - * so we can retrive the required operational information and prepare the GPIO. + * so we can retrieve the required operational information and prepare the GPIO. */ static struct pci_dev *nas_gpio_pci_dev; static int ich7_lpc_probe(struct pci_dev *dev, diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 168df5ecf27b..8c2e1d58ba0c 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -12,9 +12,11 @@ #include #include #include +#include #include #include +#define ST1202_BLINK_DEFAULT_DELAY 500 #define ST1202_CHAN_DISABLE_ALL 0x00 #define ST1202_CHAN_ENABLE_HIGH 0x03 #define ST1202_CHAN_ENABLE_LOW 0x02 @@ -23,7 +25,8 @@ #define ST1202_CONFIG_REG_PATS BIT(7) /* PATSR: Pattern sequence runs (self-clear when sequence is finished) */ #define ST1202_CONFIG_REG_PATSR BIT(6) -#define ST1202_CONFIG_REG_SHFT BIT(3) +/* SHFT: Phase-shift delay enable */ +#define ST1202_CONFIG_REG_PHASE_SHIFT BIT(3) #define ST1202_DEV_ENABLE 0x01 #define ST1202_DEV_ENABLE_ON BIT(0) #define ST1202_DEV_ENABLE_RESET BIT(7) @@ -57,7 +60,7 @@ static struct st1202_led *cdev_to_st1202_led(struct led_classdev *cdev) return container_of(cdev, struct st1202_led, led_cdev); } -static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val) +static int st1202_read_reg(struct st1202_chip *chip, int reg, u8 *val) { struct device *dev = &chip->client->dev; int ret; @@ -68,11 +71,11 @@ static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val) return ret; } - *val = (uint8_t)ret; + *val = (u8)ret; return 0; } -static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val) +static int st1202_write_reg(struct st1202_chip *chip, int reg, u8 val) { struct device *dev = &chip->client->dev; int ret; @@ -84,11 +87,27 @@ static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val) return ret; } -static uint8_t st1202_prescalar_to_miliseconds(unsigned int value) +static u8 st1202_milliseconds_to_prescaler(unsigned int value) { return value / ST1202_MILLIS_PATTERN_DUR_MIN; } +/* The channel current register is 8 bits wide, whatever max-brightness says */ +static u8 st1202_iled_max(struct led_classdev *led_cdev) +{ + return min_t(unsigned int, led_cdev->max_brightness, U8_MAX); +} + +static u16 st1202_brightness_to_pwm(int brightness, unsigned int max_brightness) +{ + if (!max_brightness) + return 0; + + brightness = clamp_t(int, brightness, LED_OFF, max_brightness); + + return brightness * ST1202_PATTERN_PWM_FULL / max_brightness; +} + static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num, int pattern, unsigned int value) { @@ -125,7 +144,7 @@ static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern, unsigned int value) { return st1202_write_reg(chip, (ST1202_PATTERN_DUR + pattern), - st1202_prescalar_to_miliseconds(value)); + st1202_milliseconds_to_prescaler(value)); } static int __st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) @@ -160,27 +179,6 @@ static int __st1202_channel_set(struct st1202_chip *chip, int led_num, bool acti return 0; } -static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) -{ - guard(mutex)(&chip->lock); - - return __st1202_channel_set(chip, led_num, active); -} - -static void st1202_brightness_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - struct st1202_led *led = cdev_to_st1202_led(led_cdev); - struct st1202_chip *chip = led->chip; - - guard(mutex)(&chip->lock); - - for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) - st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL); - st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value); - __st1202_channel_set(chip, led->led_num, !!value); -} - static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev) { struct st1202_led *led = cdev_to_st1202_led(led_cdev); @@ -197,8 +195,30 @@ static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev) static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value) { struct st1202_led *led = cdev_to_st1202_led(ldev); + struct st1202_chip *chip = led->chip; + int ret; - return st1202_channel_set(led->chip, led->led_num, !!value); + guard(mutex)(&chip->lock); + + /* + * The output of a channel is ILED x Pattern_PWM / 4095. Setting every + * PWM slot to full scale makes it equal ILED whatever the state of the + * sequencer, so the brightness takes effect without stopping the + * sequencer, which is global and would disturb the other channels. + */ + for (int pattern = 0; pattern < ST1202_MAX_PATTERNS; pattern++) { + ret = st1202_pwm_pattern_write(chip, led->led_num, pattern, + ST1202_PATTERN_PWM_FULL); + if (ret) + return ret; + } + + ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, + min_t(unsigned int, value, st1202_iled_max(ldev))); + if (ret) + return ret; + + return __st1202_channel_set(chip, led->led_num, !!value); } static int st1202_led_pattern_clear(struct led_classdev *ldev) @@ -209,16 +229,25 @@ static int st1202_led_pattern_clear(struct led_classdev *ldev) guard(mutex)(&chip->lock); - ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); + ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, LED_OFF); if (ret != 0) return ret; - for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) { - ret = st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL); + ret = __st1202_channel_set(chip, led->led_num, false); + if (ret != 0) + return ret; + + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT); + if (ret != 0) + return ret; + + for (int pattern = 0; pattern < ST1202_MAX_PATTERNS; pattern++) { + ret = st1202_pwm_pattern_write(chip, led->led_num, pattern, + ST1202_PATTERN_PWM_FULL); if (ret != 0) return ret; - ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + patt, 0); + ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + pattern, 0); if (ret != 0) return ret; } @@ -227,34 +256,45 @@ static int st1202_led_pattern_clear(struct led_classdev *ldev) } static int st1202_led_pattern_set(struct led_classdev *ldev, - struct led_pattern *pattern, + struct led_pattern *patterns, u32 len, int repeat) { struct st1202_led *led = cdev_to_st1202_led(ldev); struct st1202_chip *chip = led->chip; + unsigned int max_brightness; int ret; + max_brightness = st1202_iled_max(ldev); + if (len > ST1202_MAX_PATTERNS) return -EINVAL; - for (int patt = 0; patt < len; patt++) { - if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN || - pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX) + for (int pattern = 0; pattern < len; pattern++) { + if (patterns[pattern].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN || + patterns[pattern].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX) return -EINVAL; } guard(mutex)(&chip->lock); - ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT); if (ret != 0) return ret; - for (int patt = 0; patt < len; patt++) { - ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness); + for (int pattern = 0; pattern < len; pattern++) { + ret = st1202_pwm_pattern_write(chip, led->led_num, pattern, + st1202_brightness_to_pwm(patterns[pattern].brightness, + max_brightness)); if (ret != 0) return ret; - ret = st1202_duration_pattern_write(chip, patt, pattern[patt].delta_t); + ret = st1202_duration_pattern_write(chip, pattern, patterns[pattern].delta_t); + if (ret != 0) + return ret; + } + + for (int pattern = len; pattern < ST1202_MAX_PATTERNS; pattern++) { + ret = st1202_pwm_pattern_write(chip, led->led_num, pattern, LED_OFF); if (ret != 0) return ret; } @@ -263,18 +303,117 @@ static int st1202_led_pattern_set(struct led_classdev *ldev, if (ret != 0) return ret; + ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, max_brightness); + if (ret != 0) + return ret; + ret = __st1202_channel_set(chip, led->led_num, true); if (ret != 0) return ret; - ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR | - ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT)); + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, + ST1202_CONFIG_REG_PATSR | ST1202_CONFIG_REG_PATS | + ST1202_CONFIG_REG_PHASE_SHIFT); if (ret != 0) return ret; return 0; } +static int st1202_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct st1202_led *led = cdev_to_st1202_led(led_cdev); + struct st1202_chip *chip = led->chip; + unsigned long on, off; + int ret; + + if (!*delay_on) + *delay_on = ST1202_BLINK_DEFAULT_DELAY; + if (!*delay_off) + *delay_off = ST1202_BLINK_DEFAULT_DELAY; + + on = *delay_on; + off = *delay_off; + + on = clamp_val(on, ST1202_MILLIS_PATTERN_DUR_MIN, ST1202_MILLIS_PATTERN_DUR_MAX); + off = clamp_val(off, ST1202_MILLIS_PATTERN_DUR_MIN, ST1202_MILLIS_PATTERN_DUR_MAX); + on = roundup(on, ST1202_MILLIS_PATTERN_DUR_MIN); + off = roundup(off, ST1202_MILLIS_PATTERN_DUR_MIN); + + guard(mutex)(&chip->lock); + + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT); + if (ret) + return ret; + + /* Zero out PWM for all other active channels to prevent them from blinking */ + for (int chan = 0; chan < ST1202_MAX_LEDS; chan++) { + if (!chip->leds[chan].is_active || chan == led->led_num) + continue; + + ret = st1202_pwm_pattern_write(chip, chan, 0, LED_OFF); + if (ret) + return ret; + + ret = st1202_pwm_pattern_write(chip, chan, 1, LED_OFF); + if (ret) + return ret; + } + + ret = st1202_pwm_pattern_write(chip, led->led_num, 0, ST1202_PATTERN_PWM_FULL); + if (ret) + return ret; + + for (int pattern = 1; pattern < ST1202_MAX_PATTERNS; pattern++) { + ret = st1202_pwm_pattern_write(chip, led->led_num, pattern, LED_OFF); + if (ret) + return ret; + } + + ret = st1202_duration_pattern_write(chip, 0, on); + if (ret) + return ret; + + ret = st1202_duration_pattern_write(chip, 1, off); + if (ret) + return ret; + + for (int pattern = 2; pattern < ST1202_MAX_PATTERNS; pattern++) { + ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + pattern, 0); + if (ret) + return ret; + } + + ret = st1202_write_reg(chip, ST1202_PATTERN_REP, U8_MAX); + if (ret) + return ret; + + ret = st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, st1202_iled_max(led_cdev)); + if (ret) + return ret; + + ret = __st1202_channel_set(chip, led->led_num, true); + if (ret) + return ret; + + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, + ST1202_CONFIG_REG_PATSR | ST1202_CONFIG_REG_PATS | + ST1202_CONFIG_REG_PHASE_SHIFT); + if (ret) + return ret; + + *delay_on = on; + *delay_off = off; + + return 0; +} + +static void st1202_fwnode_put(void *data) +{ + fwnode_handle_put(data); +} + static int st1202_dt_init(struct st1202_chip *chip) { struct device *dev = &chip->client->dev; @@ -294,14 +433,18 @@ static int st1202_dt_init(struct st1202_chip *chip) led = &chip->leds[reg]; led->is_active = true; - led->fwnode = of_fwnode_handle(child); + led->fwnode = fwnode_handle_get(of_fwnode_handle(child)); + + err = devm_add_action_or_reset(dev, st1202_fwnode_put, led->fwnode); + if (err) + return err; led->led_cdev.max_brightness = U8_MAX; led->led_cdev.brightness_set_blocking = st1202_led_set; led->led_cdev.pattern_set = st1202_led_pattern_set; led->led_cdev.pattern_clear = st1202_led_pattern_clear; led->led_cdev.default_trigger = "pattern"; - led->led_cdev.brightness_set = st1202_brightness_set; + led->led_cdev.blink_set = st1202_blink_set; led->led_cdev.brightness_get = st1202_brightness_get; } @@ -330,7 +473,7 @@ static int st1202_setup(struct st1202_chip *chip) return ret; /* Enable phase-shift delay feature */ - ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_PHASE_SHIFT); if (ret < 0) return ret; @@ -342,7 +485,7 @@ static int st1202_setup(struct st1202_chip *chip) /* Duration of initialization */ usleep_range(6500, 10000); - /* Deactivate all LEDS (channels) and activate only the ones found in Device Tree */ + /* Deactivate all LEDs (channels); each is enabled when it is lit */ ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, ST1202_CHAN_DISABLE_ALL); if (ret < 0) return ret; @@ -389,11 +532,6 @@ static int st1202_probe(struct i2c_client *client) if (!led->is_active) continue; - ret = st1202_channel_set(led->chip, led->led_num, true); - if (ret < 0) - return dev_err_probe(&client->dev, ret, - "Failed to activate LED channel\n"); - ret = st1202_led_pattern_clear(&led->led_cdev); if (ret < 0) return dev_err_probe(&client->dev, ret, diff --git a/drivers/leds/leds-syscon.c b/drivers/leds/leds-syscon.c index d633ad519d0c..741850f35f19 100644 --- a/drivers/leds/leds-syscon.c +++ b/drivers/leds/leds-syscon.c @@ -56,7 +56,6 @@ static int syscon_led_probe(struct platform_device *pdev) { struct led_init_data init_data = {}; struct device *dev = &pdev->dev; - struct device_node *np = dev_of_node(dev); struct device *parent; struct regmap *map; struct syscon_led *sled; @@ -81,13 +80,13 @@ static int syscon_led_probe(struct platform_device *pdev) sled->map = map; - if (of_property_read_u32(np, "reg", &sled->offset) && - of_property_read_u32(np, "offset", &sled->offset)) + if (device_property_read_u32(dev, "reg", &sled->offset) && + device_property_read_u32(dev, "offset", &sled->offset)) return -EINVAL; - if (of_property_read_u32(np, "mask", &sled->mask)) + if (device_property_read_u32(dev, "mask", &sled->mask)) return -EINVAL; - init_data.fwnode = of_fwnode_handle(np); + init_data.fwnode = dev_fwnode(dev); state = led_init_default_state_get(init_data.fwnode); switch (state) { diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index ed6a47bbb44f..57128ae7e8fd 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -195,10 +195,16 @@ static void omnia_hwtrig_deactivate(struct led_classdev *cdev) err); } +static bool omnia_hwtrig_hw_offloaded(struct led_classdev *cdev) +{ + return true; +} + static struct led_trigger omnia_hw_trigger = { .name = "omnia-mcu", .activate = omnia_hwtrig_activate, .deactivate = omnia_hwtrig_deactivate, + .hw_offloaded = omnia_hwtrig_hw_offloaded, .trigger_type = &omnia_hw_trigger_type, }; @@ -251,6 +257,7 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, * by LED class from the linux,default-trigger property. */ cdev->default_trigger = omnia_hw_trigger.name; + cdev->hw_control_trigger = omnia_hw_trigger.name; /* Put the LED into software mode */ ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(led->reg) | diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h index bee46651e068..6d00e6f44126 100644 --- a/drivers/leds/leds.h +++ b/drivers/leds/leds.h @@ -21,14 +21,10 @@ void led_init_core(struct led_classdev *led_cdev); void led_stop_software_blink(struct led_classdev *led_cdev); void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value); void led_set_brightness_nosleep(struct led_classdev *led_cdev, unsigned int value); -ssize_t led_trigger_read(struct file *filp, struct kobject *kobj, - const struct bin_attribute *attr, char *buf, - loff_t pos, size_t count); -ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, - const struct bin_attribute *bin_attr, char *buf, - loff_t pos, size_t count); +void led_trigger_hw_control_changed_worker(struct work_struct *work); extern struct rw_semaphore leds_list_lock; extern struct list_head leds_list; +extern const struct attribute_group led_trigger_group; #endif /* __LEDS_H_INCLUDED */ diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index d7d6518de30f..24ffe079f221 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -1342,7 +1343,7 @@ static int lpg_add_pwm(struct lpg *lpg) return ret; } -static int lpg_parse_channel(struct lpg *lpg, struct device_node *np, +static int lpg_parse_channel(struct lpg *lpg, struct fwnode_handle *fw, struct lpg_channel **channel) { struct lpg_channel *chan; @@ -1350,17 +1351,18 @@ static int lpg_parse_channel(struct lpg *lpg, struct device_node *np, u32 reg; int ret; - ret = of_property_read_u32(np, "reg", ®); + ret = fwnode_property_read_u32(fw, "reg", ®); if (ret || !reg || reg > lpg->num_channels) - return dev_err_probe(lpg->dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np); + return dev_err_probe(lpg->dev, -EINVAL, + "invalid \"reg\" of %s\n", fwnode_get_name(fw)); chan = &lpg->channels[reg - 1]; chan->in_use = true; - ret = of_property_read_u32(np, "color", &color); + ret = fwnode_property_read_u32(fw, "color", &color); if (ret < 0 && ret != -EINVAL) return dev_err_probe(lpg->dev, ret, - "failed to parse \"color\" of %pOF\n", np); + "failed to parse \"color\" of %s\n", fwnode_get_name(fw)); chan->color = color; @@ -1369,25 +1371,26 @@ static int lpg_parse_channel(struct lpg *lpg, struct device_node *np, return 0; } -static int lpg_add_led(struct lpg *lpg, struct device_node *np) +static int lpg_add_led(struct lpg *lpg, struct fwnode_handle *fw) { struct led_init_data init_data = {}; struct led_classdev *cdev; struct mc_subled *info; struct lpg_led *led; + const char *trigger; const char *state; int num_channels; u32 color = 0; int ret; int i; - ret = of_property_read_u32(np, "color", &color); + ret = fwnode_property_read_u32(fw, "color", &color); if (ret < 0 && ret != -EINVAL) return dev_err_probe(lpg->dev, ret, - "failed to parse \"color\" of %pOF\n", np); + "failed to parse \"color\" of %s\n", fwnode_get_name(fw)); if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI) - num_channels = of_get_available_child_count(np); + num_channels = fwnode_get_child_node_count(fw); else num_channels = 1; @@ -1403,7 +1406,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) if (!info) return -ENOMEM; i = 0; - for_each_available_child_of_node_scoped(np, child) { + fwnode_for_each_available_child_node_scoped(fw, child) { ret = lpg_parse_channel(lpg, child, &led->channels[i]); if (ret < 0) return ret; @@ -1414,7 +1417,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) } led->mcdev.subled_info = info; - led->mcdev.num_colors = num_channels; + led->mcdev.num_colors = i; cdev = &led->mcdev.led_cdev; cdev->brightness_set_blocking = lpg_brightness_mc_set; @@ -1426,7 +1429,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) cdev->pattern_clear = lpg_pattern_mc_clear; } } else { - ret = lpg_parse_channel(lpg, np, &led->channels[0]); + ret = lpg_parse_channel(lpg, fw, &led->channels[0]); if (ret < 0) return ret; @@ -1441,14 +1444,15 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) } } - cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL); + if (!fwnode_property_read_string(fw, "linux,default-trigger", &trigger)) + cdev->default_trigger = trigger; if (lpg->lpg_chan_sdam) cdev->max_brightness = PPG_MAX_LED_BRIGHTNESS; else cdev->max_brightness = LPG_RESOLUTION_9BIT - 1; - if (!of_property_read_string(np, "default-state", &state) && + if (!fwnode_property_read_string(fw, "default-state", &state) && !strcmp(state, "on")) cdev->brightness = cdev->max_brightness; else @@ -1456,7 +1460,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) cdev->brightness_set_blocking(cdev, cdev->brightness); - init_data.fwnode = of_fwnode_handle(np); + init_data.fwnode = fw; if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI) ret = devm_led_classdev_multicolor_register_ext(lpg->dev, &led->mcdev, &init_data); @@ -1638,8 +1642,8 @@ static int lpg_probe(struct platform_device *pdev) if (ret < 0) return ret; - for_each_available_child_of_node_scoped(pdev->dev.of_node, np) { - ret = lpg_add_led(lpg, np); + device_for_each_child_node_scoped(&pdev->dev, child) { + ret = lpg_add_led(lpg, child); if (ret) return ret; } diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index c11282a74b5a..a11d04ce4ab2 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -9,6 +9,16 @@ menuconfig LEDS_TRIGGERS if LEDS_TRIGGERS +config LEDS_TRIGGERS_HW_CHANGED + bool "LED hardware-initiated trigger transition support" + default LEDS_BRIGHTNESS_HW_CHANGED + help + This option enables support for hardware initiated hardware control + transitions, where the LED hardware autonomously switches between + "none" (i.e., no trigger) and its private trigger. + + See Documentation/leds/leds-class.rst for details. + config LEDS_TRIGGER_TIMER tristate "LED Timer Trigger" help diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index 5b4e92c14dbb..5b0132484594 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -414,10 +414,7 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, return -EINVAL; } - if (state) - set_bit(bit, &mode); - else - clear_bit(bit, &mode); + assign_bit(bit, &mode, state); if (test_bit(TRIGGER_NETDEV_LINK, &mode) && (test_bit(TRIGGER_NETDEV_LINK_10, &mode) || @@ -798,10 +795,18 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev) kfree(trigger_data); } +static bool netdev_trig_hw_offloaded(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data = led_get_trigger_data(led_cdev); + + return trigger_data->hw_control; +} + static struct led_trigger netdev_led_trigger = { .name = "netdev", .activate = netdev_trig_activate, .deactivate = netdev_trig_deactivate, + .hw_offloaded = netdev_trig_hw_offloaded, .groups = netdev_trig_groups, }; diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c index 650dd95f06ef..ca350f320e17 100644 --- a/drivers/video/backlight/qcom-wled.c +++ b/drivers/video/backlight/qcom-wled.c @@ -59,7 +59,7 @@ #define WLED3_SINK_REG_CURR_SINK_SHFT 5 /* WLED3 specific per-'string' registers below */ -#define WLED3_SINK_REG_BRIGHT(n) (0x40 + n) +#define WLED3_SINK_REG_BRIGHT(n) (0x40 + (n * 0x2)) #define WLED3_SINK_REG_STR_MOD_EN(n) (0x60 + (n * 0x10)) #define WLED3_SINK_REG_STR_MOD_MASK BIT(7) @@ -290,6 +290,50 @@ static int wled5_set_brightness(struct wled *wled, u16 brightness) return rc; } +static int wled_read_brightness(struct wled *wled) +{ + u16 addr, mask; + __le16 v; + u32 val; + int rc; + + rc = regmap_read(wled->regmap, wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN, + &val); + if (rc < 0) + return rc; + + if (!(val & WLED3_CTRL_REG_MOD_EN_MASK)) + return 0; + + switch (wled->version) { + case 3: + addr = wled->sink_addr + + WLED3_SINK_REG_BRIGHT(wled->cfg.enabled_strings[0]); + mask = WLED3_SINK_REG_BRIGHT_MAX; + break; + case 4: + addr = wled->sink_addr + + WLED4_SINK_REG_BRIGHT(wled->cfg.enabled_strings[0]); + mask = WLED3_SINK_REG_BRIGHT_MAX; + break; + case 5: + addr = wled->sink_addr + (wled->cfg.mod_sel == MOD_A ? + WLED5_SINK_REG_MOD_A_BRIGHTNESS_LSB : + WLED5_SINK_REG_MOD_B_BRIGHTNESS_LSB); + mask = WLED5_SINK_REG_BRIGHT_MAX_15B; + break; + default: + dev_err(wled->dev, "Invalid WLED version\n"); + return -EINVAL; + } + + rc = regmap_bulk_read(wled->regmap, addr, &v, sizeof(v)); + if (rc < 0) + return rc; + + return min_t(u32, le16_to_cpu(v) & mask, wled->max_brightness); +} + static void wled_ovp_work(struct work_struct *work) { struct wled *wled = container_of(work, @@ -480,6 +524,18 @@ static int wled_update_status(struct backlight_device *bl) return rc; } +static int wled_get_brightness(struct backlight_device *bl) +{ + struct wled *wled = bl_get_data(bl); + int rc; + + mutex_lock(&wled->lock); + rc = wled_read_brightness(wled); + mutex_unlock(&wled->lock); + + return rc; +} + static int wled4_cabc_config(struct wled *wled, bool enable) { int i, j, rc; @@ -1627,7 +1683,6 @@ static int wled_configure_ovp_irq(struct wled *wled, struct platform_device *pdev) { int rc; - u32 val; wled->ovp_irq = platform_get_irq_byname(pdev, "ovp"); if (wled->ovp_irq < 0) { @@ -1643,13 +1698,8 @@ static int wled_configure_ovp_irq(struct wled *wled, return 0; } - rc = regmap_read(wled->regmap, wled->ctrl_addr + - WLED3_CTRL_REG_MOD_EN, &val); - if (rc < 0) - return rc; - - /* Keep OVP irq disabled until module is enabled */ - if (!(val & WLED3_CTRL_REG_MOD_EN_MASK)) + /* Keep the OVP irq disabled until the module is enabled */ + if (!wled->brightness) disable_irq(wled->ovp_irq); return 0; @@ -1657,6 +1707,7 @@ static int wled_configure_ovp_irq(struct wled *wled, static const struct backlight_ops wled_ops = { .update_status = wled_update_status, + .get_brightness = wled_get_brightness, }; static int wled_probe(struct platform_device *pdev) @@ -1680,6 +1731,7 @@ static int wled_probe(struct platform_device *pdev) wled->regmap = regmap; wled->dev = &pdev->dev; + platform_set_drvdata(pdev, wled); wled->version = (uintptr_t)of_device_get_match_data(&pdev->dev); if (!wled->version) { @@ -1734,6 +1786,20 @@ static int wled_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&wled->ovp_work, wled_ovp_work); + val = WLED_DEFAULT_BRIGHTNESS; + of_property_read_u32(pdev->dev.of_node, "default-brightness", &val); + + /* + * The module may already be lit, either by the bootloader or by the + * setup above. Start from what the hardware is driving, so that the + * first brightness update does not step the brightness. + */ + rc = wled_read_brightness(wled); + if (rc < 0) + return rc; + + wled->brightness = rc; + rc = wled_configure_short_irq(wled, pdev); if (rc < 0) return rc; @@ -1742,12 +1808,9 @@ static int wled_probe(struct platform_device *pdev) if (rc < 0) return rc; - val = WLED_DEFAULT_BRIGHTNESS; - of_property_read_u32(pdev->dev.of_node, "default-brightness", &val); - memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_RAW; - props.brightness = val; + props.brightness = wled->brightness ?: val; props.max_brightness = wled->max_brightness; bl = devm_backlight_device_register(&pdev->dev, wled->name, &pdev->dev, wled, diff --git a/include/linux/leds.h b/include/linux/leds.h index a515f075c29a..beaf23993063 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -109,6 +109,7 @@ struct led_classdev { #define LED_INIT_DEFAULT_TRIGGER BIT(23) #define LED_REJECT_NAME_CONFLICT BIT(24) #define LED_MULTI_COLOR BIT(25) +#define LED_TRIG_HW_CHANGED BIT(26) /* set_brightness_work / blink_timer flags, atomic, private. */ unsigned long work_flags; @@ -239,6 +240,11 @@ struct led_classdev { struct kernfs_node *brightness_hw_changed_kn; #endif +#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED + bool trigger_hw_changed; + struct work_struct trigger_hw_changed_work; +#endif + /* Ensures consistent access to the LED class device */ struct mutex led_access; }; @@ -485,6 +491,7 @@ struct led_trigger { const char *name; int (*activate)(struct led_classdev *led_cdev); void (*deactivate)(struct led_classdev *led_cdev); + bool (*hw_offloaded)(struct led_classdev *led_cdev); /* Brightness set by led_trigger_event */ enum led_brightness brightness; @@ -533,6 +540,9 @@ void led_trigger_blink_oneshot(struct led_trigger *trigger, void led_trigger_set_default(struct led_classdev *led_cdev); int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger); void led_trigger_remove(struct led_classdev *led_cdev); +void led_trigger_remove_hw_control(struct led_classdev *led_cdev); + +bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev); static inline void led_set_trigger_data(struct led_classdev *led_cdev, void *trigger_data) @@ -584,6 +594,13 @@ static inline int led_trigger_set(struct led_classdev *led_cdev, } static inline void led_trigger_remove(struct led_classdev *led_cdev) {} +static inline void led_trigger_remove_hw_control(struct led_classdev *led_cdev) {} + +static inline bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev) +{ + return false; +} + static inline void led_set_trigger_data(struct led_classdev *led_cdev) {} static inline void *led_get_trigger_data(struct led_classdev *led_cdev) { @@ -598,6 +615,13 @@ led_trigger_get_brightness(const struct led_trigger *trigger) #endif /* CONFIG_LEDS_TRIGGERS */ +#ifdef CONFIG_LEDS_TRIGGERS_HW_CHANGED +void led_trigger_notify_hw_control_changed(struct led_classdev *led_cdev, bool activate); +#else +static inline void led_trigger_notify_hw_control_changed(struct led_classdev *led_cdev, + bool activate) {} +#endif + /* Trigger specific enum */ enum led_trigger_netdev_modes { TRIGGER_NETDEV_LINK = 0,