Skip to contentSkip to conversation

leds: flash: LM3643 dual LED flash driver

PATCH In review Rillian Grant 7 participants 19 messages Oct 11 – Nov 4

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

v3 cover leds: flash: LM3643 dual LED flash driver

This series adds a flash LED driver for the Texas Instruments LM3643. I wrote it to support the Windows Hello NIR illuminator on my ThinkPad X9-15 Gen 1, which has a single LED connected to both current sources. The driver has also been tested by Ruslan Koreev on a ThinkPad X1 Carbon Gen 14.

On both ThinkPad models the chip is described by ACPI but has no _DSD. The original RFC asked how to supply the missing child nodes. This series includes DMI-matched software nodes for both laptop models.

1/4 DT binding
2/4 driver
3/4 DMI match table and values for the X1 Carbon
4/4 DMI entry for the X9-15 Gen 1

Changes in v3:
- dt-bindings: document the enable-gpios (HWEN), flash-gpios (STROBE) and vin-supply (IN) connections (Krzysztof Kozlowski)
- dt-bindings: drop redundant minimum: 0, use led-controller as the node name in the example and tidy formatting (Krzysztof Kozlowski)
- driver: enable the optional vin-supply and drive the optional HWEN GPIO high at probe
- driver: reject an out-of-range max-brightness rather than clamping after registration (previously LED core applied max-brightness exposing sysfs before the clamp)
- driver: allow a torch output to be switched off while the device is flashing (previously returned -EBUSY)
- driver: take the chip lock when claiming current outputs
- driver: fix indentation in lm3643_torch_get_brightness()
- 3/4, 4/4: use DMI_EXACT_MATCH for the DMI entries
- 3/4: log use of the DMI description with dev_dbg() instead of dev_info()

Changes in v2:
- Rebased onto leds/for-leds-next
- dt-bindings: make flash-max-microamp, led-max-microamp and flash-max-timeout-us required
- driver: return device to standby state when the last output is switched off (squashed fix provided by Ruslan Koreev)
- New 3/4: DMI mechanism and entry for the X1 Carbon developed by Ruslan. Plus a minor update to provide a reg property for consistency with the DT bindings.
- New 4/4: DMI entry for the X9-15 Gen 1 and update struct names to be model-specific.

v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/

Rillian Grant (3):
dt-bindings: leds: Document TI LM3643 dual LED flash driver
leds: flash: Add support for the TI LM3643 dual LED flash driver
leds: flash: lm3643: Describe the LED on the Lenovo ThinkPad X9-15 Gen

    1

Ruslan Koreev (1):
leds: flash: lm3643: Describe the LED of ACPI boards without LED nodes

 .../devicetree/bindings/leds/ti,lm3643.yaml   | 143 +++
 MAINTAINERS                                   |   7 +
 drivers/leds/flash/Kconfig                    |  12 +
 drivers/leds/flash/Makefile                   |   1 +
 drivers/leds/flash/leds-lm3643.c              | 929 ++++++++++++++++++
 5 files changed, 1092 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/ti,lm3643.yaml
 create mode 100644 drivers/leds/flash/leds-lm3643.c

base-commit: 05b4738b0078f7d6f154f68068a11c8a0635e9df

Signature · 1 line
2.56.0
#1 lore raw

PATCH v3 1/4 dt-bindings: leds: Document TI LM3643 dual LED flash driver

Document Texas Instruments LM3643 dual LED flash driver devicetree bindings.

---
 .../devicetree/bindings/leds/ti,lm3643.yaml   | 143 ++++++++++++++++++
 1 file changed, 143 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/ti,lm3643.yaml
Documentation/devicetree/bindings/leds/ti,lm3643.yaml new file +143 -0
new file mode 100644
index 000000000000..63c5f09f3bd5
@@ -0,0 +1,143 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/ti,lm3643.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments LM3643 1500mA dual LED Flash Driver
+
+maintainers:
+  - Rillian Grant <[email protected]>
+
+description: |
+  The LM3643 is a dual LED flash driver utilizing a synchronous boost converter
+  to provide power to two current sources.
+
+  The dual high-side current sources can be tied together to provide flash
+  currents up to 1.5A through a single LED.
+
+  Datasheet: https://www.ti.com/lit/ds/symlink/lm3643.pdf
+
+properties:
+  compatible:
+    enum:
+      - ti,lm3643
+
+  reg:
+    maxItems: 1
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  enable-gpios:
+    description: GPIO connected to the HWEN pin.
+    maxItems: 1
+
+  flash-gpios:
+    description: GPIO connected to the STROBE pin.
+    maxItems: 1
+
+  vin-supply:
+    description: Supply connected to the IN line.
+
+patternProperties:
+  "^led@[0-1]$":
+    type: object
+    $ref: common.yaml#
+    unevaluatedProperties: false
+
+    properties:
+      reg:
+        description: Index of the LED.
+        maximum: 1
+
+      led-sources:
+        minItems: 1
+        maxItems: 2
+        items:
+          maximum: 1
+
+      flash-max-microamp:
+        description: |
+          Maximum flash current through this LED. Each high-side current source
+          can provide up to 1.5A. The maximum current the boost can deliver is
+          1.5A across both sources.
+
+          Valid values for an LED connected to one source:
+            10900 - 1500000, step by 11725 (rounded down)
+          Valid values for an LED connected to both sources:
+            21800 - 1500000, step by 23450 (rounded down)
+        minimum: 10900
+        maximum: 1500000
+
+      led-max-microamp:
+        description: |
+          Maximum torch current through this LED. In torch mode each high-side
+          current source can provide up to 179mA.
+
+          Valid values for an LED connected to one source:
+            977 - 179000, step by 1400 (rounded down)
+          Valid values for an LED connected to both sources:
+            1954 - 358000, step by 2800 (rounded down)
+        minimum: 977
+        maximum: 358000
+
+      flash-max-timeout-us:
+        description:
+          The chip supports timeouts from 10ms to 100ms in increments of 10ms
+          and from 100ms to 400ms in increments of 50ms. The provided value
+          will be rounded down to a supported value.
+        minimum: 10000
+        maximum: 400000
+
+    required:
+      - reg
+      - led-sources
+      - flash-max-microamp
+      - led-max-microamp
+      - flash-max-timeout-us
+
+required:
+  - compatible
+  - reg
+  - "#address-cells"
+  - "#size-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/leds/common.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        led-controller@63 {
+            compatible = "ti,lm3643";
+            reg = <0x63>;
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            enable-gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+            flash-gpios = <&gpio 43 GPIO_ACTIVE_HIGH>;
+            vin-supply = <&vreg_flash>;
+
+            led@0 {
+                reg = <0>;
+                led-sources = <0>, <1>;
+
+                function = LED_FUNCTION_FLASH;
+                color = <LED_COLOR_ID_IR>;
+
+                flash-max-microamp = <750000>;
+                led-max-microamp = <180000>;
+                flash-max-timeout-us = <150000>;
+            };
+        };
+    };
Signature · 1 line
2.56.0
#2 lore raw

Re: [PATCH v3 1/4] dt-bindings: leds: Document TI LM3643 dual LED flash driver

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] leds: missing driver name in commit message prefix --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] leds: missing driver name in commit message prefix
--

commit 3be78a7ca4f65d71d150658aa87edd42d6d057a1
Author: Rillian Grant <[email protected]>

dt-bindings: leds: Document TI LM3643 dual LED flash driver

This commit documents the devicetree bindings for the Texas Instruments LM3643 dual LED flash driver.

Quoted 1 line

dt-bindings: leds: Document TI LM3643 dual LED flash driver

[Severity: Low]
This isn't a bug, but does the commit message subject omit the driver name in the prefix? According to the LED subsystem rules, commit messages should include the appropriate driver prefix followed by a capitalized description, such as "dt-bindings: leds: ti,lm3643: Document...". Missing the driver portion of the prefix hinders Git history categorization and violates subsystem naming conventions.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Reviewed-by on v3 1/4
#3 lore raw

Re: [PATCH v3 1/4] dt-bindings: leds: Document TI LM3643 dual LED flash driver

In reply to Rillian Grant
Quoted 8 lines · Rillian Grant

Document Texas Instruments LM3643 dual LED flash driver devicetree bindings.

---
 .../devicetree/bindings/leds/ti,lm3643.yaml   | 143 ++++++++++++++++++
 1 file changed, 143 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/ti,lm3643.yaml

Best regards,
Krzysztof

#4 lore raw

PATCH v3 2/4 leds: flash: Add support for the TI LM3643 dual LED flash driver

In reply to Rillian Grant

The LM3643 is an I2C dual LED flash driver. Its two high-side current sources share a synchronous boost converter and each supports torch and flash modes with programmable current and flash timeout alongside fault reporting.

It also supports connecting both sources together to supply one LED with current up to the boost converter's 1.5A maximum. In that case the current will be split between each source symmetrically and the sources will be enabled and disabled at the same time.

As strobing is toggled chip-wide the driver returns -EBUSY when a sibling LED is active.

Flash timeouts do not have a constant step. The driver advertises the most granular step and rounds down to the nearest supported value.

Register behavior was taken from the datasheet (SNVS967A).

---
 MAINTAINERS                      |   7 +
 drivers/leds/flash/Kconfig       |  12 +
 drivers/leds/flash/Makefile      |   1 +
 drivers/leds/flash/leds-lm3643.c | 796 +++++++++++++++++++++++++++++++
 4 files changed, 816 insertions(+)
 create mode 100644 drivers/leds/flash/leds-lm3643.c
MAINTAINERS +7 -0
index a1eb0937238d..0c186a7f2648 100644
@@ -27036,6 +27036,13 @@ F:	include/linux/dma/k3-udma-glue.h
 F:	include/linux/dma/ti-cppi5.h
 X:	drivers/dma/ti/cppi41.c
 
+TEXAS INSTRUMENTS LM3643 LED FLASH DRIVER
+M:	Rillian Grant <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	Documentation/devicetree/bindings/leds/ti,lm3643.yaml
+F:	drivers/leds/flash/leds-lm3643.c
+
 TEXAS INSTRUMENTS TPS25990 HARDWARE MONITOR DRIVER
 M:	Jerome Brunet <[email protected]>
 L:	[email protected]
drivers/leds/flash/Kconfig +12 -0
index 9b6dd3f1ffe8..0f0a60a902fc 100644
@@ -39,6 +39,18 @@ config LEDS_LM3601X
 	  This option enables support for the TI LM3601x family
 	  of flash, torch and indicator classes.
 
+config LEDS_LM3643
+	tristate "LED support for LM3643 dual LED flash driver"
+	depends on I2C
+	depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+	select REGMAP_I2C
+	help
+	  This option enables support for the Texas Instruments LM3643 dual
+	  LED flash driver.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called "leds-lm3643".
+
 config LEDS_MAX77693
 	tristate "LED support for MAX77693 Flash"
 	depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
drivers/leds/flash/Makefile +1 -0
index 44e6c1b4beb3..e2e885e02123 100644
@@ -6,6 +6,7 @@ obj-$(CONFIG_LEDS_AAT1290)	+= leds-aat1290.o
 obj-$(CONFIG_LEDS_AS3645A)	+= leds-as3645a.o
 obj-$(CONFIG_LEDS_KTD2692)	+= leds-ktd2692.o
 obj-$(CONFIG_LEDS_LM3601X)	+= leds-lm3601x.o
+obj-$(CONFIG_LEDS_LM3643)	+= leds-lm3643.o
 obj-$(CONFIG_LEDS_MAX77693)	+= leds-max77693.o
 obj-$(CONFIG_LEDS_QCOM_FLASH)	+= leds-qcom-flash.o
 obj-$(CONFIG_LEDS_RT4505)	+= leds-rt4505.o
drivers/leds/flash/leds-lm3643.c new file +796 -0

796 lines not on this page · raw message

Signature · 1 line
2.56.0
#5 lore raw

Re: [PATCH v3 2/4] leds: flash: Add support for the TI LM3643 dual LED flash driver

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] leds: missing hardware startup delay after asserting enable GPIO - [Low] leds: non-standard…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] leds: missing hardware startup delay after asserting enable GPIO
- [Low] leds: non-standard commit message prefix used
--

commit e9482e46025f29c98cd459b067571fee928be4b6
Author: Rillian Grant <[email protected]>

leds: flash: Add support for the TI LM3643 dual LED flash driver

This commit introduces support for the Texas Instruments LM3643 dual LED flash driver over I2C. The chip provides torch and flash modes with programmable current and timeouts via a shared synchronous boost converter.

Quoted 1 line

leds: flash: Add support for the TI LM3643 dual LED flash driver

[Severity: Low]
This isn't a bug, but does this commit subject violate the subsystem naming conventions?

According to the LED subsystem guidelines, embedding subsystem subdirectory names like flash into the prefix hinders Git history categorization. Embedding implementation details can also create rigid and non-reusable drivers.

Could the driver name lm3643 be used in the prefix instead, such as "leds: lm3643: "?

Quoted 3 lines
drivers/leds/flash/leds-lm3643.c new file +0 -0

[ ... ]

Quoted 2 lines
drivers/leds/flash/leds-lm3643.c new file +2 -0
+static int lm3643_probe(struct i2c_client *client)
+{

[ ... ]

Quoted 6 lines
drivers/leds/flash/leds-lm3643.c new file +6 -0
+	hwen = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH);
+	if (IS_ERR(hwen))
+		return dev_err_probe(dev, PTR_ERR(hwen), "failed to get enable GPIO\n");
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)

drivers/leds/flash/leds-lm3643.c line 727

Quoted 10 lines
drivers/leds/flash/leds-lm3643.c new file +10 -0
+		return -ENOMEM;
+	i2c_set_clientdata(client, chip);
+
+	chip->regmap = devm_regmap_init_i2c(client, &lm3643_regmap_config);
+	if (IS_ERR(chip->regmap))
+		return dev_err_probe(dev,
+				     PTR_ERR(chip->regmap),
+				     "failed to allocate register map\n");
+
+	ret = regmap_read(chip->regmap, LM3643_REG_DEV_ID, &dev_id);

[Severity: Medium]
Does this sequence require a hardware startup delay after asserting the enable GPIO?

The lm3643_probe() function drives the HWEN GPIO high and then shortly after initiates an I2C regmap_read() to fetch the device ID. Failing to provide a programmatic delay (e.g., via usleep_range) might cause the I2C transfer to fail on platforms where the CPU executes the bus transaction before the hardware state machine and internal oscillator are fully initialized.

Could a small delay be added here to ensure the chip is ready for I2C traffic before the register read is attempted?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
#6 lore raw

Re: [PATCH v3 2/4] leds: flash: Add support for the TI LM3643 dual LED flash driver

Quoted 11 lines

[Severity: Low]
This isn't a bug, but does this commit subject violate the subsystem naming conventions?

According to the LED subsystem guidelines, embedding subsystem subdirectory names like flash into the prefix hinders Git history categorization. Embedding implementation details can also create rigid and non-reusable drivers.

Could the driver name lm3643 be used in the prefix instead, such as "leds: lm3643: "?

Happy to switch to "leds: flash: lm3643:" across the series if there is another revision, or for the maintainer to adjust when applying.

Quoted 4 lines

[Severity: Medium]
Does this sequence require a hardware startup delay after asserting the enable GPIO?

Quoted 8 lines

The lm3643_probe() function drives the HWEN GPIO high and then shortly after initiates an I2C regmap_read() to fetch the device ID. Failing to provide a programmatic delay (e.g., via usleep_range) might cause the I2C transfer to fail on platforms where the CPU executes the bus transaction before the hardware state machine and internal oscillator are fully initialized.

Could a small delay be added here to ensure the chip is ready for I2C traffic before the register read is attempted?

The LM3643 datasheet specifies no delay between HWEN high and I2C ready, so I'd rather not add an arbitrary sleep.

Signature · 1 line
Rillian Grant
#7 lore raw

PATCH v3 3/4 leds: flash: lm3643: Describe the LED of ACPI boards without LED nodes

In reply to Rillian Grant

From: Ruslan Koreev <[email protected]>

Lenovo ThinkPads enumerate the LM3643 through ACPI (HID TXNW3643) without a _DSD, so the device has no LED child nodes and the driver refuses to probe. On these laptops the chip drives the Windows Hello IR flood illuminator, with both current sources feeding a single LED, and Lenovo's Windows driver applies a fixed configuration to any LM3643 it finds.

Add a DMI table that provides that description as a software node when the firmware offers none: one IR flash LED on both outputs, with torch and flash limits well below the chip's maximums. Start with the ThinkPad X1 Carbon Gen 14.

The description is keyed by DMI instead of being applied to every TXNW3643 because the wiring is a board property: which output feeds which LED and how much current the LED tolerates cannot be read from the chip, and a wrong guess overdrives an LED. The firmware cannot be fixed from the driver side, and a per-board table is what x86-android-tablets does for devices whose ACPI tables describe nothing either. Boards with a _DSD are not affected, the fallback only runs when the device has no child nodes. The limits are conservative because the LED part is not documented; the illuminator is bright enough for face authentication at a fraction of them.

Tested on that laptop: torch through /sys/class/leds/ir:flash lights the ST VD55G1 IR camera's field of view, and the resulting frames are good enough for face authentication.

[rillian: add reg property to match the DT binding]
[rillian: change DMI activation log to dev_dbg()]
[rillian: use DMI_EXACT_MATCH]
---
 drivers/leds/flash/leds-lm3643.c | 93 ++++++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)
drivers/leds/flash/leds-lm3643.c +93 -0
index cbe66e90d8e1..18ac75df72e2 100644
@@ -9,6 +9,7 @@
 #include <linux/bitfield.h>
 #include <linux/bits.h>
 #include <linux/cleanup.h>
+#include <linux/dmi.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
 #include <linux/led-class-flash.h>
@@ -691,6 +692,92 @@ static int lm3643_register_led(struct device *dev, struct lm3643 *chip,
 	return lm3643_register_v4l2(dev, led, fwnode);
 }
 
+/*
+ * Lenovo ThinkPads enumerate the chip through ACPI (HID TXNW3643) without a
+ * _DSD, so the LED is not described by firmware. On the boards below it drives
+ * the Windows Hello IR flood illuminator, with both current sources feeding a
+ * single LED. Describe that with a software node, with torch and flash limits
+ * (100 mA and 300 mA per output) well below the chip's maximums.
+ */
+static const u32 lm3643_ir_flood_sources[] = { 0, 1 };
+
+static const struct property_entry lm3643_ir_flood_props[] = {
+	PROPERTY_ENTRY_U32("reg", 0),
+	PROPERTY_ENTRY_U32_ARRAY("led-sources", lm3643_ir_flood_sources),
+	PROPERTY_ENTRY_STRING("function", LED_FUNCTION_FLASH),
+	PROPERTY_ENTRY_U32("color", LED_COLOR_ID_IR),
+	PROPERTY_ENTRY_U32("led-max-microamp", 200000),
+	PROPERTY_ENTRY_U32("flash-max-microamp", 600000),
+	PROPERTY_ENTRY_U32("flash-max-timeout-us", 100000),
+	{ }
+};
+
+static const struct software_node lm3643_ir_flood_root = {
+	.name = "lm3643",
+};
+
+static const struct software_node lm3643_ir_flood_led = {
+	.name = "led-0",
+	.parent = &lm3643_ir_flood_root,
+	.properties = lm3643_ir_flood_props,
+};
+
+static const struct software_node *lm3643_ir_flood_nodes[] = {
+	&lm3643_ir_flood_root,
+	&lm3643_ir_flood_led,
+	NULL
+};
+
+static const struct dmi_system_id lm3643_dmi_leds[] = {
+	{
+		/* Lenovo ThinkPad X1 Carbon Gen 14 */
+		.matches = {
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X1 Carbon Gen 14"),
+		},
+		.driver_data = (void *)lm3643_ir_flood_nodes,
+	},
+	{ }
+};
+
+static void lm3643_remove_dmi_leds(void *data)
+{
+	struct device *dev = data;
+	const struct dmi_system_id *id = dmi_first_match(lm3643_dmi_leds);
+
+	set_secondary_fwnode(dev, NULL);
+	if (id)
+		software_node_unregister_node_group(id->driver_data);
+}
+
+static int lm3643_add_dmi_leds(struct device *dev)
+{
+	const struct dmi_system_id *id;
+	const struct software_node **nodes;
+	int ret;
+
+	if (!has_acpi_companion(dev))
+		return 0;
+
+	id = dmi_first_match(lm3643_dmi_leds);
+	if (!id)
+		return 0;
+
+	nodes = id->driver_data;
+	/* The nodes are static: a second chip on the same board cannot reuse them */
+	if (software_node_fwnode(nodes[0]))
+		return dev_err_probe(dev, -EBUSY, "LED description already in use\n");
+
+	ret = software_node_register_node_group(nodes);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register the LED description\n");
+
+	set_secondary_fwnode(dev, software_node_fwnode(nodes[0]));
+	dev_dbg(dev, "no LED nodes in firmware, using the DMI description\n");
+
+	return devm_add_action_or_reset(dev, lm3643_remove_dmi_leds, dev);
+}
+
 static int lm3643_probe(struct i2c_client *client)
 {
 	struct device *dev = &client->dev;
@@ -701,6 +788,12 @@ static int lm3643_probe(struct i2c_client *client)
 	int ret;
 
 	count = device_get_child_node_count(dev);
+	if (!count) {
+		ret = lm3643_add_dmi_leds(dev);
+		if (ret)
+			return ret;
+		count = device_get_child_node_count(dev);
+	}
 	if (!count || count > LM3643_NUM_CHANNELS)
 		return dev_err_probe(dev, -EINVAL, "%u LED nodes found, expected 1 to %d\n",
 					 count, LM3643_NUM_CHANNELS);
Signature · 1 line
2.56.0
#8 lore raw

Re: [PATCH v3 3/4] leds: flash: lm3643: Describe the LED of ACPI boards without LED nodes

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] leds: flash: lm3643: TOCTOU race in swnode registration causes UAF --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] leds: flash: lm3643: TOCTOU race in swnode registration causes UAF --

commit a08dd419e737ce9719711220082ac93884d1bc47
Author: Ruslan Koreev <[email protected]>

leds: flash: lm3643: Describe the LED of ACPI boards without LED nodes

This commit adds a DMI table and software node descriptions for Lenovo ThinkPads that enumerate the LM3643 through ACPI without a _DSD. This fallback allows the Windows Hello IR flood illuminator to be probed and registered when the firmware provides no child nodes.

Quoted 4 lines
drivers/leds/flash/leds-lm3643.c +0 -0
index cbe66e90d8e1b..18ac75df72e2a 100644

[ ... ]

Quoted 9 lines
drivers/leds/flash/leds-lm3643.c +9 -0
+static int lm3643_add_dmi_leds(struct device *dev)
+{
+	const struct dmi_system_id *id;
+	const struct software_node **nodes;
+	int ret;
+
+	if (!has_acpi_companion(dev))
+		return 0;
+

drivers/leds/flash/leds-lm3643.c static int lm3643_register_led(struct device *dev, struct lm3643 *chip, · line 771

Quoted 10 lines
drivers/leds/flash/leds-lm3643.c +10 -0
+	id = dmi_first_match(lm3643_dmi_leds);
+	if (!id)
+		return 0;
+
+	nodes = id->driver_data;
+	/* The nodes are static: a second chip on the same board cannot reuse them */
+	if (software_node_fwnode(nodes[0]))
+		return dev_err_probe(dev, -EBUSY, "LED description already in use\n");
+
+	ret = software_node_register_node_group(nodes);

[Severity: High]
Is there a potential race condition here when multiple devices probe concurrently?

If two threads execute lm3643_add_dmi_leds() simultaneously (for example, during concurrent sysfs bind or async probe of two LM3643 devices matching the same DMI table), they both might pass the lockless software_node_fwnode(nodes[0]) check before either registers the nodes.

Thread A could then successfully register the group via software_node_register_node_group(nodes).

Thread B would subsequently attempt to register the same static nodes array, fail, and drop into the error path within software_node_register_node_group():

drivers/base/swnode.c:software_node_register_node_group() {
    ...
		ret = software_node_register(node_group[i]);
		if (ret) {
			software_node_unregister_node_group(node_group);
			return ret;
		}
    ...
}

Because software_node_unregister_node_group() unconditionally unregisters the entire array, Thread B's failure teardown will free the nodes that Thread A successfully registered.

Could this lead to a use-after-free when Thread A subsequently calls device_get_child_node_count() and accesses the freed software nodes?

Quoted 8 lines
drivers/leds/flash/leds-lm3643.c +8 -0
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register the LED description\n");
+
+	set_secondary_fwnode(dev, software_node_fwnode(nodes[0]));
+	dev_dbg(dev, "no LED nodes in firmware, using the DMI description\n");
+
+	return devm_add_action_or_reset(dev, lm3643_remove_dmi_leds, dev);
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
#9 lore raw

Re: [PATCH v3 3/4] leds: flash: lm3643: Describe the LED of ACPI boards without LED nodes

Quoted 8 lines

[Severity: High]
Is there a potential race condition here when multiple devices probe concurrently?

If two threads execute lm3643_add_dmi_leds() simultaneously (for example, during concurrent sysfs bind or async probe of two LM3643 devices matching the same DMI table), they both might pass the lockless software_node_fwnode(nodes[0]) check before either registers the nodes.

This condition is currently unreachable. It would require two LM3643 instances on the same DMI entry.

I'd be happy to add a static mutex if preferred, or alongside any future DMI entry for a board with more than one LM3643.

Signature · 1 line
Rillian Grant
maintainer
#10 lore raw

Re: [PATCH v3 3/4] leds: flash: lm3643: Describe the LED of ACPI boards without LED nodes

Quoted 9 lines · Rillian Grant
Quoted 8 lines

[Severity: High]
Is there a potential race condition here when multiple devices probe concurrently?

If two threads execute lm3643_add_dmi_leds() simultaneously (for example, during concurrent sysfs bind or async probe of two LM3643 devices matching the same DMI table), they both might pass the lockless software_node_fwnode(nodes[0]) check before either registers the nodes.

Quoted 5 lines

This condition is currently unreachable. It would require two LM3643 instances on the same DMI entry.

I'd be happy to add a static mutex if preferred, or alongside any future DMI entry for a board with more than one LM3643.

Please.

Signature · 1 line
Lee Jones
maintainer
#11 lore raw

Re: [PATCH v3 3/4] leds: flash: lm3643: Describe the LED of ACPI boards without LED nodes

In reply to Rillian Grant

Andy, et al., Please see below.

Quoted 88 lines · Rillian Grant

From: Ruslan Koreev <[email protected]>

Lenovo ThinkPads enumerate the LM3643 through ACPI (HID TXNW3643) without a _DSD, so the device has no LED child nodes and the driver refuses to probe. On these laptops the chip drives the Windows Hello IR flood illuminator, with both current sources feeding a single LED, and Lenovo's Windows driver applies a fixed configuration to any LM3643 it finds.

Add a DMI table that provides that description as a software node when the firmware offers none: one IR flash LED on both outputs, with torch and flash limits well below the chip's maximums. Start with the ThinkPad X1 Carbon Gen 14.

The description is keyed by DMI instead of being applied to every TXNW3643 because the wiring is a board property: which output feeds which LED and how much current the LED tolerates cannot be read from the chip, and a wrong guess overdrives an LED. The firmware cannot be fixed from the driver side, and a per-board table is what x86-android-tablets does for devices whose ACPI tables describe nothing either. Boards with a _DSD are not affected, the fallback only runs when the device has no child nodes. The limits are conservative because the LED part is not documented; the illuminator is bright enough for face authentication at a fraction of them.

Tested on that laptop: torch through /sys/class/leds/ir:flash lights the ST VD55G1 IR camera's field of view, and the resulting frames are good enough for face authentication.

[rillian: add reg property to match the DT binding]
[rillian: change DMI activation log to dev_dbg()]
[rillian: use DMI_EXACT_MATCH]
---
 drivers/leds/flash/leds-lm3643.c | 93 ++++++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)
drivers/leds/flash/leds-lm3643.c +35 -0
index cbe66e90d8e1..18ac75df72e2 100644
@@ -9,6 +9,7 @@
 #include <linux/bitfield.h>
 #include <linux/bits.h>
 #include <linux/cleanup.h>
+#include <linux/dmi.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
 #include <linux/led-class-flash.h>
@@ -691,6 +692,92 @@ static int lm3643_register_led(struct device *dev, struct lm3643 *chip,
 	return lm3643_register_v4l2(dev, led, fwnode);
 }
 
+/*
+ * Lenovo ThinkPads enumerate the chip through ACPI (HID TXNW3643) without a
+ * _DSD, so the LED is not described by firmware. On the boards below it drives
+ * the Windows Hello IR flood illuminator, with both current sources feeding a
+ * single LED. Describe that with a software node, with torch and flash limits
+ * (100 mA and 300 mA per output) well below the chip's maximums.
+ */
+static const u32 lm3643_ir_flood_sources[] = { 0, 1 };
+
+static const struct property_entry lm3643_ir_flood_props[] = {
+	PROPERTY_ENTRY_U32("reg", 0),
+	PROPERTY_ENTRY_U32_ARRAY("led-sources", lm3643_ir_flood_sources),
+	PROPERTY_ENTRY_STRING("function", LED_FUNCTION_FLASH),
+	PROPERTY_ENTRY_U32("color", LED_COLOR_ID_IR),
+	PROPERTY_ENTRY_U32("led-max-microamp", 200000),
+	PROPERTY_ENTRY_U32("flash-max-microamp", 600000),
+	PROPERTY_ENTRY_U32("flash-max-timeout-us", 100000),
+	{ }
+};
+
+static const struct software_node lm3643_ir_flood_root = {
+	.name = "lm3643",
+};
+
+static const struct software_node lm3643_ir_flood_led = {
+	.name = "led-0",
+	.parent = &lm3643_ir_flood_root,
+	.properties = lm3643_ir_flood_props,
+};
+
+static const struct software_node *lm3643_ir_flood_nodes[] = {
+	&lm3643_ir_flood_root,
+	&lm3643_ir_flood_led,
+	NULL

drivers/leds/flash/leds-lm3643.c static int lm3643_register_led(struct device *dev, struct lm3643 *chip, · line 738

Quoted 10 lines
drivers/leds/flash/leds-lm3643.c +10 -0
+};
+
+static const struct dmi_system_id lm3643_dmi_leds[] = {
+	{
+		/* Lenovo ThinkPad X1 Carbon Gen 14 */
+		.matches = {
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X1 Carbon Gen 14"),
+		},
+		.driver_data = (void *)lm3643_ir_flood_nodes,

Andy, et al.,

I don't usually allow data from other registration mechanisms to be passed through another (MFD data via OF for instance).

Is this okay?

Quoted 28 lines
drivers/leds/flash/leds-lm3643.c +28 -0
+	},
+	{ }
+};
+
+static void lm3643_remove_dmi_leds(void *data)
+{
+	struct device *dev = data;
+	const struct dmi_system_id *id = dmi_first_match(lm3643_dmi_leds);
+
+	set_secondary_fwnode(dev, NULL);
+	if (id)
+		software_node_unregister_node_group(id->driver_data);
+}
+
+static int lm3643_add_dmi_leds(struct device *dev)
+{
+	const struct dmi_system_id *id;
+	const struct software_node **nodes;
+	int ret;
+
+	if (!has_acpi_companion(dev))
+		return 0;
+
+	id = dmi_first_match(lm3643_dmi_leds);
+	if (!id)
+		return 0;
+
+	nodes = id->driver_data;

drivers/leds/flash/leds-lm3643.c static int lm3643_register_led(struct device *dev, struct lm3643 *chip, · line 776

Quoted 10 lines
drivers/leds/flash/leds-lm3643.c +10 -0
+	/* The nodes are static: a second chip on the same board cannot reuse them */
+	if (software_node_fwnode(nodes[0]))
+		return dev_err_probe(dev, -EBUSY, "LED description already in use\n");
+
+	ret = software_node_register_node_group(nodes);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register the LED description\n");
+
+	set_secondary_fwnode(dev, software_node_fwnode(nodes[0]));
+	dev_dbg(dev, "no LED nodes in firmware, using the DMI description\n");

Please remove temporary debug prints before submitting.

Quoted 24 lines
drivers/leds/flash/leds-lm3643.c +10 -0
+
+	return devm_add_action_or_reset(dev, lm3643_remove_dmi_leds, dev);
+}
+
 static int lm3643_probe(struct i2c_client *client)
 {
 	struct device *dev = &client->dev;
@@ -701,6 +788,12 @@ static int lm3643_probe(struct i2c_client *client)
 	int ret;
 
 	count = device_get_child_node_count(dev);
+	if (!count) {
+		ret = lm3643_add_dmi_leds(dev);
+		if (ret)
+			return ret;
+		count = device_get_child_node_count(dev);
+	}
 	if (!count || count > LM3643_NUM_CHANNELS)
 		return dev_err_probe(dev, -EINVAL, "%u LED nodes found, expected 1 to %d\n",
 					 count, LM3643_NUM_CHANNELS);
Signature · 1 line
2.56.0
Signature · 1 line
Lee Jones
#12 lore raw

Re: [PATCH v3 3/4] leds: flash: lm3643: Describe the LED of ACPI boards without LED nodes

Hi Lee, others,

Quoted 51 lines · Lee Jones
Quoted 38 lines
drivers/leds/flash/leds-lm3643.c +34 -0
@@ -691,6 +692,92 @@ static int lm3643_register_led(struct device *dev, struct lm3643 *chip,
 	return lm3643_register_v4l2(dev, led, fwnode);
 }
 
+/*
+ * Lenovo ThinkPads enumerate the chip through ACPI (HID TXNW3643) without a
+ * _DSD, so the LED is not described by firmware. On the boards below it drives
+ * the Windows Hello IR flood illuminator, with both current sources feeding a
+ * single LED. Describe that with a software node, with torch and flash limits
+ * (100 mA and 300 mA per output) well below the chip's maximums.
+ */
+static const u32 lm3643_ir_flood_sources[] = { 0, 1 };
+
+static const struct property_entry lm3643_ir_flood_props[] = {
+	PROPERTY_ENTRY_U32("reg", 0),
+	PROPERTY_ENTRY_U32_ARRAY("led-sources", lm3643_ir_flood_sources),
+	PROPERTY_ENTRY_STRING("function", LED_FUNCTION_FLASH),
+	PROPERTY_ENTRY_U32("color", LED_COLOR_ID_IR),
+	PROPERTY_ENTRY_U32("led-max-microamp", 200000),
+	PROPERTY_ENTRY_U32("flash-max-microamp", 600000),
+	PROPERTY_ENTRY_U32("flash-max-timeout-us", 100000),
+	{ }
+};
+
+static const struct software_node lm3643_ir_flood_root = {
+	.name = "lm3643",
+};
+
+static const struct software_node lm3643_ir_flood_led = {
+	.name = "led-0",
+	.parent = &lm3643_ir_flood_root,
+	.properties = lm3643_ir_flood_props,
+};
+
+static const struct software_node *lm3643_ir_flood_nodes[] = {
+	&lm3643_ir_flood_root,
+	&lm3643_ir_flood_led,
+	NULL

drivers/leds/flash/leds-lm3643.c static int lm3643_register_led(struct device *dev, struct lm3643 *chip, · line 738

Quoted 10 lines
drivers/leds/flash/leds-lm3643.c +10 -0
+};
+
+static const struct dmi_system_id lm3643_dmi_leds[] = {
+	{
+		/* Lenovo ThinkPad X1 Carbon Gen 14 */
+		.matches = {
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X1 Carbon Gen 14"),
+		},
+		.driver_data = (void *)lm3643_ir_flood_nodes,

Andy, et al.,

Quoted 4 lines

I don't usually allow data from other registration mechanisms to be passed through another (MFD data via OF for instance).

Is this okay?

In this case the ipu-bridge needs to provide the information to the lm3643 driver via software nodes, as it already does for a number of other devices. There's no point in putting the information to the LED driver.

Presumably the ACPI device node already exists so this should be straightforward. What's also missing from the sensor then is the flash-leds property, the lens-focus handling serves as an example.

Signature · 3 lines
Regards,

Sakari Ailus
#13 lore raw

Re: [PATCH v3 3/4] leds: flash: lm3643: Describe the LED of ACPI boards without LED nodes

Quoted 1 line · Sakari Ailus

On Tue, Oct 20, 2026 at 11:45:06AM +0100, Lee Jones wrote:

...

Quoted 6 lines
Quoted 6 lines

Andy, et al.,

I don't usually allow data from other registration mechanisms to be passed through another (MFD data via OF for instance).

Is this okay?

Seconding Sakari's answer, this doesn't sound like the best approach.

Quoted 7 lines

In this case the ipu-bridge needs to provide the information to the lm3643 driver via software nodes, as it already does for a number of other devices. There's no point in putting the information to the LED driver.

Presumably the ACPI device node already exists so this should be straightforward. What's also missing from the sensor then is the flash-leds property, the lens-focus handling serves as an example.

+1 to all the above.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#14 lore raw

PATCH v3 4/4 leds: flash: lm3643: Describe the LED on the Lenovo ThinkPad X9-15 Gen 1

In reply to Rillian Grant

The ThinkPad X9-15 Gen 1 enumerates the LM3643 through ACPI (HID TXNW3643) without a _DSD, like the X1 Carbon Gen 14. It drives the Windows Hello IR flood illuminator with both current sources feeding a single LED.

The flash limits were obtained from an ETW trace of the SPB class extension during a Windows Hello unlock. The Windows driver programs 304 mA per current source (code 0x19 with the LED2 override set) and a 400 ms flash timeout.

There is no vendor value for the torch limit, so use the same conservative limit as the X1 Carbon Gen 14.

Rename the X1 Carbon Gen 14 nodes after the machine now that the table has a second entry. Sources and root node remain shared.

---
 drivers/leds/flash/leds-lm3643.c | 70 +++++++++++++++++++++++++-------
 1 file changed, 55 insertions(+), 15 deletions(-)
drivers/leds/flash/leds-lm3643.c +55 -15
index 18ac75df72e2..38b1dd09a1b7 100644
@@ -692,16 +692,21 @@ static int lm3643_register_led(struct device *dev, struct lm3643 *chip,
 	return lm3643_register_v4l2(dev, led, fwnode);
 }
 
-/*
- * Lenovo ThinkPads enumerate the chip through ACPI (HID TXNW3643) without a
- * _DSD, so the LED is not described by firmware. On the boards below it drives
- * the Windows Hello IR flood illuminator, with both current sources feeding a
- * single LED. Describe that with a software node, with torch and flash limits
- * (100 mA and 300 mA per output) well below the chip's maximums.
- */
 static const u32 lm3643_ir_flood_sources[] = { 0, 1 };
 
-static const struct property_entry lm3643_ir_flood_props[] = {
+static const struct software_node lm3643_ir_flood_root = {
+	.name = "lm3643",
+};
+
+/*
+ * The Lenovo ThinkPad X1 Carbon Gen 14 enumerates the chip through ACPI
+ * (HID TXNW3643) without a _DSD, so the LED is not described by firmware.
+ * On the X1 Gen 14 it drives the Windows Hello IR flood illuminator, with
+ * both current sources feeding a single LED. Describe that with a software
+ * node, with torch and flash limits (100 mA and 300 mA per output) well below
+ * the chip's maximums.
+ */
+static const struct property_entry lm3643_x1c14_props[] = {
 	PROPERTY_ENTRY_U32("reg", 0),
 	PROPERTY_ENTRY_U32_ARRAY("led-sources", lm3643_ir_flood_sources),
 	PROPERTY_ENTRY_STRING("function", LED_FUNCTION_FLASH),
@@ -712,19 +717,46 @@ static const struct property_entry lm3643_ir_flood_props[] = {
 	{ }
 };
 
-static const struct software_node lm3643_ir_flood_root = {
-	.name = "lm3643",
+static const struct software_node lm3643_x1c14_led = {
+	.name = "led-0",
+	.parent = &lm3643_ir_flood_root,
+	.properties = lm3643_x1c14_props,
+};
+
+static const struct software_node *lm3643_x1c14_nodes[] = {
+	&lm3643_ir_flood_root,
+	&lm3643_x1c14_led,
+	NULL
 };
 
-static const struct software_node lm3643_ir_flood_led = {
+/*
+ * The Lenovo ThinkPad X9-15 Gen 1 enumerates the chip through ACPI
+ * (HID TXNW3643) without a _DSD. On the X9-15 it drives the Windows Hello IR
+ * flood illuminator, with both current sources feeding a single LED. Flash
+ * maximums were obtained from an SPB class extension ETW trace of the
+ * vendor-supplied Windows driver. The torch limit is a conservative value well
+ * below the chip's maximum.
+ */
+static const struct property_entry lm3643_x9_15_props[] = {
+	PROPERTY_ENTRY_U32("reg", 0),
+	PROPERTY_ENTRY_U32_ARRAY("led-sources", lm3643_ir_flood_sources),
+	PROPERTY_ENTRY_STRING("function", LED_FUNCTION_FLASH),
+	PROPERTY_ENTRY_U32("color", LED_COLOR_ID_IR),
+	PROPERTY_ENTRY_U32("led-max-microamp", 200000),
+	PROPERTY_ENTRY_U32("flash-max-microamp", 608050),
+	PROPERTY_ENTRY_U32("flash-max-timeout-us", 400000),
+	{ }
+};
+
+static const struct software_node lm3643_x9_15_led = {
 	.name = "led-0",
 	.parent = &lm3643_ir_flood_root,
-	.properties = lm3643_ir_flood_props,
+	.properties = lm3643_x9_15_props,
 };
 
-static const struct software_node *lm3643_ir_flood_nodes[] = {
+static const struct software_node *lm3643_x9_15_nodes[] = {
 	&lm3643_ir_flood_root,
-	&lm3643_ir_flood_led,
+	&lm3643_x9_15_led,
 	NULL
 };
 
@@ -735,7 +767,15 @@ static const struct dmi_system_id lm3643_dmi_leds[] = {
 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
 			DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X1 Carbon Gen 14"),
 		},
-		.driver_data = (void *)lm3643_ir_flood_nodes,
+		.driver_data = (void *)lm3643_x1c14_nodes,
+	},
+	{
+		/* Lenovo ThinkPad X9-15 Gen 1 */
+		.matches = {
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X9-15 Gen 1"),
+		},
+		.driver_data = (void *)lm3643_x9_15_nodes,
 	},
 	{ }
 };
Signature · 1 line
2.56.0
#15 lore raw

Re: [PATCH v3 4/4] leds: flash: lm3643: Describe the LED on the Lenovo ThinkPad X9-15 Gen 1

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 10 lines

The ThinkPad X9-15 Gen 1 enumerates the LM3643 through ACPI (HID TXNW3643) without a _DSD, like the X1 Carbon Gen 14. It drives the Windows Hello IR flood illuminator with both current sources feeding a single LED.

The flash limits were obtained from an ETW trace of the SPB class extension during a Windows Hello unlock. The Windows driver programs 304 mA per current source (code 0x19 with the LED2 override set) and a 400 ms flash timeout.

Quoted 7 lines

There is no vendor value for the torch limit, so use the same conservative limit as the X1 Carbon Gen 14.

Rename the X1 Carbon Gen 14 nodes after the machine now that the table has a second entry. Sources and root node remain shared.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
maintainer
#16 lore raw

Re: [PATCH v3 4/4] leds: flash: lm3643: Describe the LED on the Lenovo ThinkPad X9-15 Gen 1

In reply to Rillian Grant

--- checkpatch.pl: clean (0 issues) ---

Quoted 82 lines · Rillian Grant

The ThinkPad X9-15 Gen 1 enumerates the LM3643 through ACPI (HID TXNW3643) without a _DSD, like the X1 Carbon Gen 14. It drives the Windows Hello IR flood illuminator with both current sources feeding a single LED.

The flash limits were obtained from an ETW trace of the SPB class extension during a Windows Hello unlock. The Windows driver programs 304 mA per current source (code 0x19 with the LED2 override set) and a 400 ms flash timeout.

There is no vendor value for the torch limit, so use the same conservative limit as the X1 Carbon Gen 14.

Rename the X1 Carbon Gen 14 nodes after the machine now that the table has a second entry. Sources and root node remain shared.

---
 drivers/leds/flash/leds-lm3643.c | 70 +++++++++++++++++++++++++-------
 1 file changed, 55 insertions(+), 15 deletions(-)
drivers/leds/flash/leds-lm3643.c +31 -11
index 18ac75df72e2..38b1dd09a1b7 100644
@@ -692,16 +692,21 @@ static int lm3643_register_led(struct device *dev, struct lm3643 *chip,
 	return lm3643_register_v4l2(dev, led, fwnode);
 }
 
-/*
- * Lenovo ThinkPads enumerate the chip through ACPI (HID TXNW3643) without a
- * _DSD, so the LED is not described by firmware. On the boards below it drives
- * the Windows Hello IR flood illuminator, with both current sources feeding a
- * single LED. Describe that with a software node, with torch and flash limits
- * (100 mA and 300 mA per output) well below the chip's maximums.
- */
 static const u32 lm3643_ir_flood_sources[] = { 0, 1 };
 
-static const struct property_entry lm3643_ir_flood_props[] = {
+static const struct software_node lm3643_ir_flood_root = {
+	.name = "lm3643",
+};
+
+/*
+ * The Lenovo ThinkPad X1 Carbon Gen 14 enumerates the chip through ACPI
+ * (HID TXNW3643) without a _DSD, so the LED is not described by firmware.
+ * On the X1 Gen 14 it drives the Windows Hello IR flood illuminator, with
+ * both current sources feeding a single LED. Describe that with a software
+ * node, with torch and flash limits (100 mA and 300 mA per output) well below
+ * the chip's maximums.
+ */
+static const struct property_entry lm3643_x1c14_props[] = {
 	PROPERTY_ENTRY_U32("reg", 0),
 	PROPERTY_ENTRY_U32_ARRAY("led-sources", lm3643_ir_flood_sources),
 	PROPERTY_ENTRY_STRING("function", LED_FUNCTION_FLASH),
@@ -712,19 +717,46 @@ static const struct property_entry lm3643_ir_flood_props[] = {
 	{ }
 };
 
-static const struct software_node lm3643_ir_flood_root = {
-	.name = "lm3643",
+static const struct software_node lm3643_x1c14_led = {
+	.name = "led-0",
+	.parent = &lm3643_ir_flood_root,
+	.properties = lm3643_x1c14_props,
+};
+
+static const struct software_node *lm3643_x1c14_nodes[] = {
+	&lm3643_ir_flood_root,
+	&lm3643_x1c14_led,
+	NULL
 };
 
-static const struct software_node lm3643_ir_flood_led = {
+/*
+ * The Lenovo ThinkPad X9-15 Gen 1 enumerates the chip through ACPI
+ * (HID TXNW3643) without a _DSD. On the X9-15 it drives the Windows Hello IR
+ * flood illuminator, with both current sources feeding a single LED. Flash
+ * maximums were obtained from an SPB class extension ETW trace of the
+ * vendor-supplied Windows driver. The torch limit is a conservative value well
+ * below the chip's maximum.
+ */

drivers/leds/flash/leds-lm3643.c static const struct property_entry lm3643_ir_flood_props[] = { · line 749

Quoted 10 lines
drivers/leds/flash/leds-lm3643.c +10 -0
+static const struct property_entry lm3643_x9_15_props[] = {
+	PROPERTY_ENTRY_U32("reg", 0),
+	PROPERTY_ENTRY_U32_ARRAY("led-sources", lm3643_ir_flood_sources),
+	PROPERTY_ENTRY_STRING("function", LED_FUNCTION_FLASH),
+	PROPERTY_ENTRY_U32("color", LED_COLOR_ID_IR),
+	PROPERTY_ENTRY_U32("led-max-microamp", 200000),
+	PROPERTY_ENTRY_U32("flash-max-microamp", 608050),
+	PROPERTY_ENTRY_U32("flash-max-timeout-us", 400000),
+	{ }
+};

Given that Patch 3 [0] uses conservative fallback values (600000 uA and 300000 us) versus these traced Windows driver values (608050 uA and 400000 us), why don't we just use the same set of properties for both machines and save ourselves some duplication?

[0]

static const struct property_entry lm3643_ir_flood_props[] = {
       PROPERTY_ENTRY_U32("reg", 0),
       PROPERTY_ENTRY_U32_ARRAY("led-sources", lm3643_ir_flood_sources),
       PROPERTY_ENTRY_STRING("function", LED_FUNCTION_FLASH),
       PROPERTY_ENTRY_U32("color", LED_COLOR_ID_IR),
       PROPERTY_ENTRY_U32("led-max-microamp", 200000),
       PROPERTY_ENTRY_U32("flash-max-microamp", 600000),
       PROPERTY_ENTRY_U32("flash-max-timeout-us", 100000),
       { }
};
Quoted 37 lines
drivers/leds/flash/leds-lm3643.c +14 -4
+
+static const struct software_node lm3643_x9_15_led = {
 	.name = "led-0",
 	.parent = &lm3643_ir_flood_root,
-	.properties = lm3643_ir_flood_props,
+	.properties = lm3643_x9_15_props,
 };
 
-static const struct software_node *lm3643_ir_flood_nodes[] = {
+static const struct software_node *lm3643_x9_15_nodes[] = {
 	&lm3643_ir_flood_root,
-	&lm3643_ir_flood_led,
+	&lm3643_x9_15_led,
 	NULL
 };
 
@@ -735,7 +767,15 @@ static const struct dmi_system_id lm3643_dmi_leds[] = {
 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
 			DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X1 Carbon Gen 14"),
 		},
-		.driver_data = (void *)lm3643_ir_flood_nodes,
+		.driver_data = (void *)lm3643_x1c14_nodes,
+	},
+	{
+		/* Lenovo ThinkPad X9-15 Gen 1 */
+		.matches = {
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X9-15 Gen 1"),
+		},
+		.driver_data = (void *)lm3643_x9_15_nodes,
 	},
 	{ }
 };
Signature · 1 line
2.56.0
Signature · 1 line
Lee Jones
#17 lore raw

Re: [PATCH v3 4/4] leds: flash: lm3643: Describe the LED on the Lenovo ThinkPad X9-15 Gen 1

Hi Lee,

We can share the board-description code in ipu-bridge and add flash-leds, as Sakari and Andy suggested, while keeping per-board current settings.

Lenovo's packages set IRFlashLedIntensity=30 for the X1 [1] and 40 for the X9 [2]. With both outputs using the same current setting, these convert to nominal totals of 467350 uA and 608050 uA respectively. The X9 result matches Rillian's trace; the X1 result is from static analysis, with live Windows behavior still unverified.

My earlier claim that Windows applies a fixed configuration to any LM3643 was too broad: the sensor configuration affects the current request.

Both default paths configure a 400 ms controller timeout. Actual mode, pulse duration and continuous-torch limits remain unverified, so these are candidate operating settings rather than established LED limits.

Rillian, could you share the relevant SPB trace, including mode, current and timeout writes, together with the installed driver version?

[1] https://download.lenovo.com/pccbbs/mobiles/n4oce17w.html
[2] https://download.lenovo.com/pccbbs/mobiles/n4cim20w.html

Ruslan

#18 lore raw

Re: [PATCH v3 4/4] leds: flash: lm3643: Describe the LED on the Lenovo ThinkPad X9-15 Gen 1

Hi Ruslan,

Quoted 2 lines · On Wednesday, 21 October 2026 15:40:34 GMT Ruslan Koreev wrote:

Rillian, could you share the relevant SPB trace, including mode, current and timeout writes, together with the installed driver version?

Driver versions: the X9-15 Gen 1 values are from the Windows install on my machine. The X1 Carbon Gen 14 values are from the current package on Lenovo's support site.

X9-15 Gen 1:
- lm3643.inf/.sys 70.26100.2.18321 (02/20/2025)
- IR sensor: HM1092, hm1092.inf 70.26100.2.18321
- FlashLEDsIntensity = 0x3C64 (lm3643.inf)
- IRFlashLedIntensity = 40 (hm1092.inf)

X1 Carbon Gen 14:
- lm3643.inf 71.26100.10.21060 (08/05/2026) however lm3643.sys contradicts with 71.26100.23.21060
- IR sensor: VD55G1, TBE0020A1.inf 71.26100.10.21060
- FlashLEDsIntensity = 0x3C64 (lm3643.inf)
- IRFlashLedIntensity = 30 (TBE0020A1.inf)

Both lm3643.inf files bind ACPI\TXNW3643 and ACPI\INT347D. Apart from the version, they differ only in where DeviceCharacteristics/Security are written. The registry on the X9 matches the INF defaults.

Before I submitted the DMI software node patch, I had seen that the Carbon had a different driver version and a different IRFlashLedIntensity. That is why I didn't reuse its values for the X9.

I traced Microsoft-Windows-SPB-ClassExtension (analytic) during a Windows Hello face unlock on the X9. Every write to the LM3643 is preceded by a read of the same register. Register decodes are from SNVS967A.

Start of the session (once, after the controller entered D0):
R 0x0A=00 R 0x0B=00 R 0x01=03 R 0x0C=02
R 0x07=09 -> W 0x07=09 boost config, POR default (2 MHz, 2.8 A ILIM,
short detect on)
W 0x01=03 standby, LED1+LED2 enable, TX pin enable off

                           (POR is 0x80)

W 0x08=00 no torch ramp, flash timeout 10 ms

Per pulse (repeats every ~133 ms):

  W 0x01=03                standby
  W 0x08=0F                no torch ramp, flash timeout 400 ms
  W 0x03=99                bit7 = LED2 flash current override (LED2 = LED1),
                           code 0x19 -> 25 x 11.725 + 10.9 = ~304 mA per
                           LED (~608 mA total)
  W 0x01=0F                flash mode (M1,M0=11), LED1+LED2  <- LED on
  ... ~65 ms ...
  W 0x08=00                timeout set to 10 ms while the flash is running
  W 0x01=03                standby                           <- LED off
  R 0x0A=01  R 0x0B=00     Flags1 bit0 = flash time-out flag (0x00 after
                           the first pulse, 0x01 after every later pulse)

This unlock had 6 pulses. The last one was turned off after ~3 ms, once the face was recognised. The driver never wrote the torch registers (0x05/0x06) or the LED2 flash register (0x04), and never used IR mode, STROBE or TX. The only values written to 0x01 were 0x03 and 0x0F.

The LM3643 transactions with timestamps are attached. The raw ETL/XML trace is available on request.

--
Rillian Grant

10:47:24.493347  LM3643 W 0x0A | R 0x00
10:47:24.493427  -- controller 0x3CF250DE2738 D-state 0x1
10:47:24.504454  LM3643 W 0x0B | R 0x00
10:47:24.504748  LM3643 W 0x01 | R 0x03
10:47:24.505018  LM3643 W 0x0C | R 0x02
10:47:24.505315  LM3643 W 0x07 | R 0x09
10:47:24.505564  LM3643 W 0x0709
10:47:24.505741  LM3643 W 0x01 | R 0x03
10:47:24.506021  LM3643 W 0x0103
10:47:24.506229  LM3643 W 0x01 | R 0x03
10:47:24.506479  LM3643 W 0x01 | R 0x03
10:47:24.506736  LM3643 W 0x0103
10:47:24.507080  LM3643 W 0x08 | R 0x00
10:47:24.507317  LM3643 W 0x0800
10:47:24.507537  LM3643 W 0x01 | R 0x03
10:47:24.507777  LM3643 W 0x0103
10:47:24.507935  LM3643 W 0x01 | R 0x03
10:47:24.508181  LM3643 W 0x0103
10:47:24.552953  HM1092(IR) W 0x010000
10:47:24.632548  HM1092(IR) W 0x010001
10:47:24.669203  -- controller 0x3CF250DDE738 D-state 0x1
10:47:24.675057  LM3643 W 0x01 | R 0x03
10:47:24.675520  LM3643 W 0x0103
10:47:24.675806  LM3643 W 0x08 | R 0x00
10:47:24.676278  LM3643 W 0x080F
10:47:24.676575  LM3643 W 0x03 | R 0x99
10:47:24.676857  LM3643 W 0x0399
10:47:24.677059  LM3643 W 0x01 | R 0x03
10:47:24.677316  LM3643 W 0x010F
10:47:24.681780  IMX471(RGB) W 0x010000
10:47:24.741365  LM3643 W 0x08 | R 0x0F
10:47:24.741647  LM3643 W 0x0800
10:47:24.741872  LM3643 W 0x01 | R 0x0F
10:47:24.742178  LM3643 W 0x0103
10:47:24.742372  LM3643 W 0x0A | R 0x00
10:47:24.742621  LM3643 W 0x0B | R 0x00
10:47:24.742969  IMX471(RGB) W 0x010001
10:47:24.807808  LM3643 W 0x01 | R 0x03
10:47:24.808069  LM3643 W 0x0103
10:47:24.808246  LM3643 W 0x08 | R 0x00
10:47:24.808503  LM3643 W 0x080F
10:47:24.808776  LM3643 W 0x03 | R 0x99
10:47:24.809023  LM3643 W 0x0399
10:47:24.809178  LM3643 W 0x01 | R 0x03
10:47:24.809456  LM3643 W 0x010F
10:47:24.874410  LM3643 W 0x08 | R 0x0F
10:47:24.874691  LM3643 W 0x0800
10:47:24.874913  LM3643 W 0x01 | R 0x0F
10:47:24.875165  LM3643 W 0x0103
10:47:24.875371  LM3643 W 0x0A | R 0x01
10:47:24.875619  LM3643 W 0x0B | R 0x00
10:47:24.940847  LM3643 W 0x01 | R 0x03
10:47:24.941139  LM3643 W 0x0103
10:47:24.941306  LM3643 W 0x08 | R 0x00
10:47:24.941552  LM3643 W 0x080F
10:47:24.941768  LM3643 W 0x03 | R 0x99
10:47:24.942104  LM3643 W 0x0399
10:47:24.942271  LM3643 W 0x01 | R 0x03
10:47:24.942513  LM3643 W 0x010F
10:47:25.007386  LM3643 W 0x08 | R 0x0F
10:47:25.007683  LM3643 W 0x0800
10:47:25.007896  LM3643 W 0x01 | R 0x0F
10:47:25.008160  LM3643 W 0x0103
10:47:25.008322  LM3643 W 0x0A | R 0x01
10:47:25.008577  LM3643 W 0x0B | R 0x00
10:47:25.073907  LM3643 W 0x01 | R 0x03
10:47:25.074207  LM3643 W 0x0103
10:47:25.074392  LM3643 W 0x08 | R 0x00
10:47:25.074649  LM3643 W 0x080F
10:47:25.074867  LM3643 W 0x03 | R 0x99
10:47:25.075123  LM3643 W 0x0399
10:47:25.075366  LM3643 W 0x01 | R 0x03
10:47:25.075621  LM3643 W 0x010F
10:47:25.140383  LM3643 W 0x08 | R 0x0F
10:47:25.140639  LM3643 W 0x0800
10:47:25.140858  LM3643 W 0x01 | R 0x0F
10:47:25.141103  LM3643 W 0x0103
10:47:25.141266  LM3643 W 0x0A | R 0x01
10:47:25.141507  LM3643 W 0x0B | R 0x00
10:47:25.206938  LM3643 W 0x01 | R 0x03
10:47:25.207270  LM3643 W 0x0103
10:47:25.207443  LM3643 W 0x08 | R 0x00
10:47:25.207689  LM3643 W 0x080F
10:47:25.207897  LM3643 W 0x03 | R 0x99
10:47:25.208150  LM3643 W 0x0399
10:47:25.208318  LM3643 W 0x01 | R 0x03
10:47:25.208581  LM3643 W 0x010F
10:47:25.273481  LM3643 W 0x08 | R 0x0F
10:47:25.273782  LM3643 W 0x0800
10:47:25.274032  LM3643 W 0x01 | R 0x0F
10:47:25.274303  LM3643 W 0x0103
10:47:25.274485  LM3643 W 0x0A | R 0x01
10:47:25.274721  LM3643 W 0x0B | R 0x00
10:47:25.339949  LM3643 W 0x01 | R 0x03
10:47:25.340239  LM3643 W 0x0103
10:47:25.340418  LM3643 W 0x08 | R 0x00
10:47:25.340684  LM3643 W 0x080F
10:47:25.340932  LM3643 W 0x03 | R 0x99
10:47:25.341210  LM3643 W 0x0399
10:47:25.341406  LM3643 W 0x01 | R 0x03
10:47:25.341660  LM3643 W 0x010F
10:47:25.344654  LM3643 W 0x08 | R 0x0F
10:47:25.344956  LM3643 W 0x0800
10:47:25.345171  LM3643 W 0x01 | R 0x0F
10:47:25.345419  LM3643 W 0x0103
10:47:25.345575  LM3643 W 0x0A | R 0x01
10:47:25.345808  LM3643 W 0x0B | R 0x00
10:47:25.473344  HM1092(IR) W 0x010000
10:47:25.474241  LM3643 W 0x08 | R 0x00
10:47:25.474501  LM3643 W 0x0800
10:47:25.474715  LM3643 W 0x01 | R 0x03
10:47:25.474956  LM3643 W 0x0103
10:47:25.475106  LM3643 W 0x01 | R 0x03
10:47:25.475403  LM3643 W 0x0103
10:47:25.486367  LM3643 W 0x0A | R 0x01
10:47:25.486763  LM3643 W 0x0B | R 0x00
10:47:25.623971  IMX471(RGB) W 0x010000
10:47:27.620034  -- controller 0x3CF250DE2738 D-state 0x4
10:47:28.631808  -- controller 0x3CF250DDE738 D-state 0x4