Devicetree
 help / color / mirror / Atom feed
* [PATCH v7 0/2] Add Lontium LT7911EXC eDP to MIPI DSI bridge
@ 2026-05-12  6:40 syyang
  2026-05-12  6:40 ` [PATCH v7 1/2] dt-bindings: bridge: " syyang
  2026-05-12  6:40 ` [PATCH v7 2/2] drm/bridge: " syyang
  0 siblings, 2 replies; 5+ messages in thread
From: syyang @ 2026-05-12  6:40 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard
  Cc: Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng, Sunyun Yang

From: Sunyun Yang <syyang@lontium.com>

The LT7911EXC is an I2C-controlled bridge that receives eDP1.4
and output mipi dsi. This series introduces:

- A device tree binding YAML file describing the hardware
- A new DRM bridge driver implementing the basic functionality

Signed-off-by: Sunyun Yang<syyang@lontium.com>
---
Change in v7:
- dt-binding:
 1. fix commit message typos(Receiver、signal)                            [sashiko-bot]
 2. remove the ambiguity caused by "signal/dual".
- drm/bridge:
 1. using devm_regulator_get_enable avoids power leaks.                   [sashiko-bot]
 2. set reset gpio is low after cutting off power in lt7911exc_remove function, avoid backpowering.
 3. synchronous request_firmware() call cause a permanent probe failure if the driver is built-in,
    probe executes before the root filesystem is mounted, which would cause this to fail with -ENOENT,
    we have removed this functionality. Use trigger to upgrade.
 4. add `depends on I2C` and `select REGMAP_I2C` in Kconfig.
 5. add return value of `devm_drm_bridge_add()` in `probe()`.
 6. add directly header files (linux/slab.h, linux/delay.h, linux/regulator/consumer.h)
- Link to v6: https://lore.kernel.org/lkml/20260508134702.4713-1-syyang@lontium.com/

Change in v6:
- dt-binding:
- drm/bridge:
 1. use #define FW_FILE  "Lontium/lt7911exc_fw.bin" to match linux-firmware
- Link to v5: https://lore.kernel.org/lkml/20260506013153.2240-1-syyang@lontium.com/

Change in v5:
- dt-binding:
- drm/bridge:
 1. Change "mipi" to "mipi dsi" in the commit message.     [Dmitry]
 2. Change "eDP/MIPI" to "eDP/MIPI DSI" in Kconfig.
- Link to v4: https://lore.kernel.org/lkml/20260430094612.3408174-1-syyang@lontium.com/

Change in v4:
- dt-binding:
 1. Fix the missing spaces on the "subject".             [Krzysztof]
 2. Fix the error descriptions for port@0 and port@1.
- drm/bridge:
- Link to v3: https://lore.kernel.org/lkml/20260429040541.3404116-1-syyang@lontium.com/

Change in v3:
- dt-binding:
- drm/bridge:
 1. already submit lt7911exc_fw.bin to linux-firmware.  [Dmitry]
 2. remove lt7911exc_remove function.
 3. drop  the "lontium, "  in lt7911exc_i2c_table.
- Link to v2: https://lore.kernel.org/lkml/20260428063224.3316655-1-syyang@lontium.com/

Change in v2:
- dt-binding:
 1. reset pins use active low.                        [Dmitry]
- drm/bridge:
 1. use atomic_* callbacks.                           [Quentin]
 2. fix the incorrect formatting and spaces.
 3. add the required header files.                    [Dmitry]
 4. remove "enabled" flag.
 5. remove *fw from the lt7911exc struct.
 6. .max_register and .range_max use actual range.
 7. regulator use bulk interface.
 8. use dev_err_probe, devm_mutex_init and devm_drm_bridge_add.
 9. Replace GPL v2 with GPL.
- Link to v1: https://lore.kernel.org/lkml/20260420023354.1192642-1-syyang@lontium.com/
---
Sunyun Yang (2):
  dt-bindings: bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
  drm/bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge

 .../display/bridge/lontium,lt7911exc.yaml     |  89 +++
 drivers/gpu/drm/bridge/Kconfig                |  15 +
 drivers/gpu/drm/bridge/Makefile               |   1 +
 drivers/gpu/drm/bridge/lontium-lt7911exc.c    | 514 ++++++++++++++++++
 4 files changed, 619 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.yaml
 create mode 100644 drivers/gpu/drm/bridge/lontium-lt7911exc.c

-- 
2.34.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v7 1/2] dt-bindings: bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
  2026-05-12  6:40 [PATCH v7 0/2] Add Lontium LT7911EXC eDP to MIPI DSI bridge syyang
@ 2026-05-12  6:40 ` syyang
  2026-05-12  6:40 ` [PATCH v7 2/2] drm/bridge: " syyang
  1 sibling, 0 replies; 5+ messages in thread
From: syyang @ 2026-05-12  6:40 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard
  Cc: Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng, Sunyun Yang,
	Krzysztof Kozlowski

From: Sunyun Yang <syyang@lontium.com>

This commit adds the device tree binding schema for the Lontium LT7911EXC.
This device is an I2C-controlled bridge that converts eDP 1.4 input to MIPI
DSI output.

Signed-off-by: Sunyun Yang <syyang@lontium.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
 .../display/bridge/lontium,lt7911exc.yaml     | 89 +++++++++++++++++++
 1 file changed, 89 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.yaml

diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.yaml
new file mode 100644
index 000000000000..3290b10ce883
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.yaml
@@ -0,0 +1,89 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/bridge/lontium,lt7911exc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Lontium LT7911EXC eDP to MIPI DSI Bridge
+
+maintainers:
+  - Sunyun Yang <syyang@lontium.com>
+
+properties:
+  compatible:
+    enum:
+      - lontium,lt7911exc
+
+  reg:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+    description: GPIO connected to RST_ pin.
+
+  vdd-supply:
+    description: Regulator for 1.2V MIPI phy power.
+
+  vcc-supply:
+    description: Regulator for 3.3V IO power.
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Video port for eDP input.
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Video port for MIPI DSI output.
+
+    required:
+      - port@0
+      - port@1
+
+required:
+  - compatible
+  - reg
+  - reset-gpios
+  - vdd-supply
+  - vcc-supply
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        mipi-bridge@41 {
+            compatible = "lontium,lt7911exc";
+            reg = <0x41>;
+            reset-gpios = <&gpy8 8 GPIO_ACTIVE_LOW>;
+            vdd-supply = <&lt7911exc_1v2>;
+            vcc-supply = <&lt7911exc_3v3>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    bridge_in: endpoint {
+                        remote-endpoint = <&edp_out>;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+                    bridge_out: endpoint {
+                        remote-endpoint = <&panel_in>;
+                    };
+                };
+            };
+        };
+    };
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v7 2/2] drm/bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
  2026-05-12  6:40 [PATCH v7 0/2] Add Lontium LT7911EXC eDP to MIPI DSI bridge syyang
  2026-05-12  6:40 ` [PATCH v7 1/2] dt-bindings: bridge: " syyang
@ 2026-05-12  6:40 ` syyang
  2026-05-13  6:04   ` sashiko-bot
  1 sibling, 1 reply; 5+ messages in thread
From: syyang @ 2026-05-12  6:40 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard
  Cc: Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng, Sunyun Yang

From: Sunyun Yang <syyang@lontium.com>

This commit adds support for the Lontium LT7911EXC eDP to MIPI DSI
bridge. It implements I2C-based setup, display configuration, and
provides a firmware update mechanism.

Signed-off-by: Sunyun Yang <syyang@lontium.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
 drivers/gpu/drm/bridge/Kconfig             |  15 +
 drivers/gpu/drm/bridge/Makefile            |   1 +
 drivers/gpu/drm/bridge/lontium-lt7911exc.c | 514 +++++++++++++++++++++
 3 files changed, 530 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/lontium-lt7911exc.c

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index c3209b0f4678..cb74730c6ef4 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -132,6 +132,21 @@ config DRM_ITE_IT6505
 	help
 	  ITE IT6505 DisplayPort bridge chip driver.
 
+config DRM_LONTIUM_LT7911EXC
+	tristate "Lontium eDP/MIPI DSI bridge"
+	depends on OF
+	depends on I2C
+	select CRC32
+	select FW_LOADER
+	select DRM_PANEL
+	select DRM_KMS_HELPER
+	select REGMAP_I2C
+	help
+	  DRM driver for the Lontium LT7911EXC bridge
+	  chip.The LT7911EXC converts eDP input to MIPI
+	  DSI output.
+	  Please say Y if you have such hardware.
+
 config DRM_LONTIUM_LT8912B
 	tristate "Lontium LT8912B DSI/HDMI bridge"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index beab5b695a6e..70ddca75dd3a 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o
 obj-$(CONFIG_DRM_INNO_HDMI) += inno-hdmi.o
 obj-$(CONFIG_DRM_ITE_IT6263) += ite-it6263.o
 obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
+obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
 obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
 obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
 obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
diff --git a/drivers/gpu/drm/bridge/lontium-lt7911exc.c b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
new file mode 100644
index 000000000000..ccea435b3c7b
--- /dev/null
+++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
@@ -0,0 +1,514 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Lontium Semiconductor, Inc.
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_of.h>
+
+#define FW_SIZE (64 * 1024)
+#define LT_PAGE_SIZE 32
+#define FW_FILE  "Lontium/lt7911exc_fw.bin"
+#define LT7911EXC_PAGE_CONTROL 0xff
+
+struct lt7911exc {
+	struct device *dev;
+	struct i2c_client *client;
+	struct drm_bridge bridge;
+	struct regmap *regmap;
+	/* Protects all accesses to registers by stopping the on-chip MCU */
+	struct mutex ocm_lock;
+	struct gpio_desc *reset_gpio;
+	int fw_version;
+};
+
+static const struct regmap_range_cfg lt7911exc_ranges[] = {
+	{
+		.name = "register_range",
+		.range_min =  0,
+		.range_max = 0xe8ff,
+		.selector_reg = LT7911EXC_PAGE_CONTROL,
+		.selector_mask = 0xff,
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = 0x100,
+	},
+};
+
+static const struct regmap_config lt7911exc_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0xe8ff,
+	.ranges = lt7911exc_ranges,
+	.num_ranges = ARRAY_SIZE(lt7911exc_ranges),
+};
+
+static u32 cal_crc32_custom(const u8 *data, u64 length)
+{
+	u32 crc = 0xffffffff;
+	u8 buf[4];
+	u64 i;
+
+	for (i = 0; i < length; i += 4) {
+		buf[0] = data[i + 3];
+		buf[1] = data[i + 2];
+		buf[2] = data[i + 1];
+		buf[3] = data[i + 0];
+		crc = crc32_be(crc, buf, 4);
+	}
+
+	return crc;
+}
+
+static inline struct lt7911exc *bridge_to_lt7911exc(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct lt7911exc, bridge);
+}
+
+static void lt7911exc_reset(struct lt7911exc *lt7911exc)
+{
+	gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
+	msleep(20);
+
+	gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
+	msleep(20);
+
+	gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
+	msleep(400);
+
+	dev_dbg(lt7911exc->dev, "lt7911exc reset.\n");
+}
+
+static int lt7911exc_regulator_enable(struct lt7911exc *lt7911exc)
+{
+	int ret;
+
+	ret = devm_regulator_get_enable(lt7911exc->dev, "vcc");
+	if (ret < 0)
+		return dev_err_probe(lt7911exc->dev, ret, "failed to enable vcc regulator\n");
+
+	usleep_range(5000, 10000);
+
+	ret = devm_regulator_get_enable(lt7911exc->dev, "vdd");
+	if (ret < 0)
+		return dev_err_probe(lt7911exc->dev, ret, "failed to enable vdd regulator\n");
+
+	return 0;
+}
+
+static int lt7911exc_read_version(struct lt7911exc *lt7911exc)
+{
+	u8 buf[3];
+	int ret;
+
+	ret = regmap_write(lt7911exc->regmap, 0xe0ee, 0x01);
+	if (ret)
+		return ret;
+	ret = regmap_bulk_read(lt7911exc->regmap, 0xe081, buf, ARRAY_SIZE(buf));
+	if (ret)
+		return ret;
+
+	return (buf[0] << 16) | (buf[1] << 8) | buf[2];
+}
+
+static void lt7911exc_lock(struct lt7911exc *lt7911exc)
+{
+	mutex_lock(&lt7911exc->ocm_lock);
+	regmap_write(lt7911exc->regmap, 0xe0ee, 0x01);
+}
+
+static void lt7911exc_unlock(struct lt7911exc *lt7911exc)
+{
+	regmap_write(lt7911exc->regmap, 0xe0ee, 0x00);
+	mutex_unlock(&lt7911exc->ocm_lock);
+}
+
+static void lt7911exc_block_erase(struct lt7911exc *lt7911exc)
+{
+	struct device *dev = lt7911exc->dev;
+	const u32 addr = 0x00;
+
+	const struct reg_sequence seq_write[] = {
+		REG_SEQ0(0xe0ee, 0x01),
+		REG_SEQ0(0xe054, 0x01),
+		REG_SEQ0(0xe055, 0x06),
+		REG_SEQ0(0xe051, 0x01),
+		REG_SEQ0(0xe051, 0x00),
+		REG_SEQ0(0xe054, 0x05),
+		REG_SEQ0(0xe055, 0xd8),
+		REG_SEQ0(0xe05a, (addr >> 16) & 0xff),
+		REG_SEQ0(0xe05b, (addr >> 8) & 0xff),
+		REG_SEQ0(0xe05c, addr & 0xff),
+		REG_SEQ0(0xe051, 0x01),
+		REG_SEQ0(0xe050, 0x00),
+	};
+
+	regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
+
+	msleep(200);
+	dev_dbg(dev, "erase flash done.\n");
+}
+
+static void lt7911exc_prog_init(struct lt7911exc *lt7911exc, u64 addr)
+{
+	const struct reg_sequence seq_write[] = {
+		REG_SEQ0(0xe0ee, 0x01),
+		REG_SEQ0(0xe05f, 0x01),
+		REG_SEQ0(0xe05a, (addr >> 16) & 0xff),
+		REG_SEQ0(0xe05b, (addr >> 8) & 0xff),
+		REG_SEQ0(0xe05c, addr & 0xff),
+	};
+
+	regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
+}
+
+static int lt7911exc_write_data(struct lt7911exc *lt7911exc, const struct firmware *fw, u64 addr)
+{
+	struct device *dev = lt7911exc->dev;
+	int ret;
+	int page = 0, num = 0, page_len = 0;
+	u64 size, offset;
+	const u8 *data;
+
+	data = fw->data;
+	size = fw->size;
+	page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
+	if (page * LT_PAGE_SIZE > FW_SIZE) {
+		dev_err(dev, "firmware size out of range\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "%u pages, total size %llu byte\n", page, size);
+
+	for (num = 0; num < page; num++) {
+		offset = num * LT_PAGE_SIZE;
+		page_len = (offset + LT_PAGE_SIZE <= size) ? LT_PAGE_SIZE : (size - offset);
+		lt7911exc_prog_init(lt7911exc, addr);
+
+		ret = regmap_raw_write(lt7911exc->regmap, 0xe05d, &data[offset], page_len);
+		if (ret) {
+			dev_err(dev, "write error at page %d\n", num);
+			return ret;
+		}
+
+		if (page_len < LT_PAGE_SIZE) {
+			regmap_write(lt7911exc->regmap, 0xe05f, 0x05);
+			regmap_write(lt7911exc->regmap, 0xe05f, 0x01);
+			//hardware requires delay
+			usleep_range(1000, 2000);
+		}
+
+		regmap_write(lt7911exc->regmap, 0xe05f, 0x00);
+		addr += LT_PAGE_SIZE;
+	}
+
+	return 0;
+}
+
+static int lt7911exc_write_crc(struct lt7911exc *lt7911exc, u32 crc32, u64 addr)
+{
+	u8 crc[4];
+	int ret;
+
+	crc[0] = crc32 & 0xff;
+	crc[1] = (crc32 >> 8) & 0xff;
+	crc[2] = (crc32 >> 16) & 0xff;
+	crc[3] = (crc32 >> 24) & 0xff;
+
+	regmap_write(lt7911exc->regmap, 0xe05f, 0x01);
+	regmap_write(lt7911exc->regmap, 0xe05a, (addr >> 16) & 0xff);
+	regmap_write(lt7911exc->regmap, 0xe05b, (addr >> 8) & 0xff);
+	regmap_write(lt7911exc->regmap, 0xe05c, addr & 0xff);
+
+	ret = regmap_raw_write(lt7911exc->regmap, 0xe05d, crc, 4);
+	if (ret)
+		return ret;
+
+	regmap_write(lt7911exc->regmap, 0xe05f, 0x05);
+	regmap_write(lt7911exc->regmap, 0xe05f, 0x01);
+	usleep_range(1000, 2000);
+	regmap_write(lt7911exc->regmap, 0xe05f, 0x00);
+
+	return 0;
+}
+
+static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc, u32 crc32)
+{
+	struct device *dev = lt7911exc->dev;
+	u32 read_hw_crc = 0;
+	u8 crc_tmp[4];
+	int ret;
+
+	regmap_write(lt7911exc->regmap, 0xe0ee, 0x01);
+	regmap_write(lt7911exc->regmap, 0xe07b, 0x60);
+	regmap_write(lt7911exc->regmap, 0xe07b, 0x40);
+	msleep(150);
+	ret = regmap_bulk_read(lt7911exc->regmap, 0x22, crc_tmp, ARRAY_SIZE(crc_tmp));
+	if (ret) {
+		dev_err(lt7911exc->dev, "Failed to read CRC: %d\n", ret);
+		return ret;
+	}
+
+	read_hw_crc = crc_tmp[0] << 24 | crc_tmp[1] << 16 |
+				crc_tmp[2] << 8 | crc_tmp[3];
+
+	if (read_hw_crc != crc32) {
+		dev_err(dev, "lt7911exc firmware upgrade failed, expected CRC=0x%08x, read CRC=0x%08x\n",
+			crc32, read_hw_crc);
+		return -EIO;
+	}
+
+	dev_dbg(dev, "lt7911exc firmware upgrade success, CRC=0x%08x\n", read_hw_crc);
+	return 0;
+}
+
+static int lt7911exc_firmware_upgrade(struct lt7911exc *lt7911exc)
+{
+	struct device *dev = lt7911exc->dev;
+	const struct firmware *fw;
+	u8 *buffer;
+	size_t total_size = FW_SIZE - 4;
+	u32 crc32;
+	int ret;
+
+	/*1. load firmware*/
+	ret = request_firmware(&fw, FW_FILE, dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to load '%s'\n", FW_FILE);
+
+	/*2. check size*/
+	if (fw->size > total_size) {
+		dev_err(dev, "firmware too large (%zu > %zu)\n", fw->size, total_size);
+		ret = -EINVAL;
+		goto out_release_fw;
+	}
+
+	/*3. calculate crc32 */
+	buffer = kzalloc(total_size, GFP_KERNEL);
+	if (!buffer) {
+		ret = -ENOMEM;
+		goto out_release_fw;
+	}
+	memset(buffer, 0xff, total_size);
+	memcpy(buffer, fw->data, fw->size);
+
+	crc32 = cal_crc32_custom(buffer, total_size);
+	kfree(buffer);
+
+	/*4. firmware upgrade */
+	dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", fw->size);
+
+	lt7911exc_block_erase(lt7911exc);
+
+	ret = lt7911exc_write_data(lt7911exc, fw, 0);
+	if (ret < 0) {
+		dev_err(dev, "failed to write firmware data\n");
+		goto out_release_fw;
+	}
+
+	ret = lt7911exc_write_crc(lt7911exc, crc32, FW_SIZE - 4);
+	if (ret < 0) {
+		dev_err(dev, "failed to write firmware crc\n");
+		goto out_release_fw;
+	}
+
+	/*5. check upgrade of result*/
+	lt7911exc_reset(lt7911exc);
+
+	ret = lt7911exc_upgrade_result(lt7911exc, crc32);
+
+out_release_fw:
+	release_firmware(fw);
+	return ret;
+}
+
+static void lt7911exc_atomic_pre_enable(struct drm_bridge *bridge, struct drm_atomic_state *state)
+{
+	struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
+
+	lt7911exc_reset(lt7911exc);
+}
+
+static void lt7911exc_atomic_disable(struct drm_bridge *bridge, struct drm_atomic_state *state)
+{
+	/* Delay after panel is disabled */
+	msleep(20);
+}
+
+static void lt7911exc_atomic_post_disable(struct drm_bridge *bridge, struct drm_atomic_state *state)
+{
+	struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
+
+	/* The reset GPIO is defined in device tree with GPIO_ACTIVE_LOW flag.
+	 * Set 1 means reset signal.
+	 */
+	gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
+}
+
+static int lt7911exc_attach(struct drm_bridge *bridge,
+			    struct drm_encoder *encoder,
+			    enum drm_bridge_attach_flags flags)
+{
+	struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
+
+	return drm_bridge_attach(lt7911exc->bridge.encoder, lt7911exc->bridge.next_bridge,
+				 &lt7911exc->bridge, flags);
+}
+
+static const struct drm_bridge_funcs lt7911exc_bridge_funcs = {
+	.attach = lt7911exc_attach,
+	.atomic_pre_enable = lt7911exc_atomic_pre_enable,
+	.atomic_disable = lt7911exc_atomic_disable,
+	.atomic_post_disable = lt7911exc_atomic_post_disable,
+};
+
+static ssize_t lt7911exc_firmware_store(struct device *dev, struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lt7911exc *lt7911exc = dev_get_drvdata(dev);
+	int ret;
+
+	/* set 0: release reset signal */
+	gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
+	msleep(400);
+
+	lt7911exc_lock(lt7911exc);
+
+	ret = lt7911exc_firmware_upgrade(lt7911exc);
+	if (ret < 0)
+		dev_err(dev, "upgrade failure\n");
+
+	lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
+
+	lt7911exc_unlock(lt7911exc);
+
+	return ret < 0 ? ret : len;
+}
+
+static ssize_t lt7911exc_firmware_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct lt7911exc *lt7911exc = dev_get_drvdata(dev);
+
+	return sysfs_emit(buf, "0x%04x\n", lt7911exc->fw_version);
+}
+
+static DEVICE_ATTR_RW(lt7911exc_firmware);
+
+static struct attribute *lt7911exc_attrs[] = {
+	&dev_attr_lt7911exc_firmware.attr,
+	NULL,
+};
+
+static const struct attribute_group lt7911exc_attr_group = {
+	.attrs = lt7911exc_attrs,
+};
+
+static const struct attribute_group *lt7911exc_attr_groups[] = {
+	&lt7911exc_attr_group,
+	NULL,
+};
+
+static int lt7911exc_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct lt7911exc *lt7911exc;
+	int ret;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+		return dev_err_probe(dev, -ENODEV, "device doesn't support I2C\n");
+
+	lt7911exc = devm_drm_bridge_alloc(dev, struct lt7911exc, bridge, &lt7911exc_bridge_funcs);
+	if (IS_ERR(lt7911exc))
+		return dev_err_probe(dev, PTR_ERR(lt7911exc), "drm bridge alloc failed.\n");
+
+	lt7911exc->bridge.next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0);
+	if (IS_ERR(lt7911exc->bridge.next_bridge))
+		return PTR_ERR(lt7911exc->bridge.next_bridge);
+
+	lt7911exc->client = client;
+	lt7911exc->dev = dev;
+	i2c_set_clientdata(client, lt7911exc);
+
+	ret = devm_mutex_init(dev, &lt7911exc->ocm_lock);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to init mutex\n");
+
+	lt7911exc->regmap = devm_regmap_init_i2c(client, &lt7911exc_regmap_config);
+	if (IS_ERR(lt7911exc->regmap))
+		return dev_err_probe(dev, PTR_ERR(lt7911exc->regmap), "regmap i2c init failed\n");
+
+	lt7911exc->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(lt7911exc->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(lt7911exc->reset_gpio),
+				     "failed to acquire reset gpio\n");
+
+	ret = lt7911exc_regulator_enable(lt7911exc);
+	if (ret)
+		return ret;
+
+	lt7911exc_reset(lt7911exc);
+
+	lt7911exc_lock(lt7911exc);
+
+	lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
+
+	lt7911exc_unlock(lt7911exc);
+	if (lt7911exc->fw_version < 0)
+		return dev_err_probe(dev, lt7911exc->fw_version, "failed read version of chip\n");
+
+	lt7911exc->bridge.of_node = dev->of_node;
+
+	return devm_drm_bridge_add(dev, &lt7911exc->bridge);
+}
+
+static void lt7911exc_remove(struct i2c_client *client)
+{
+	struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
+
+	/* pull reset low to prevent backpowering*/
+	gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
+}
+
+static const struct i2c_device_id lt7911exc_i2c_table[] = {
+	{"lt7911exc"},
+	{/* sentinel */}
+};
+
+MODULE_DEVICE_TABLE(i2c, lt7911exc_i2c_table);
+
+static const struct of_device_id lt7911exc_devices[] = {
+	{.compatible = "lontium,lt7911exc"},
+	{/* sentinel */}
+};
+MODULE_DEVICE_TABLE(of, lt7911exc_devices);
+
+static struct i2c_driver lt7911exc_driver = {
+	.id_table	= lt7911exc_i2c_table,
+	.probe		= lt7911exc_probe,
+	.remove     = lt7911exc_remove,
+	.driver		= {
+		.name	= "lt7911exc",
+		.of_match_table = lt7911exc_devices,
+		.dev_groups = lt7911exc_attr_groups,
+	},
+};
+module_i2c_driver(lt7911exc_driver);
+
+MODULE_AUTHOR("SunYun Yang <syyang@lontium.com>");
+MODULE_DESCRIPTION("Lontium LT7911EXC EDP to MIPI DSI bridge driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(FW_FILE);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v7 2/2] drm/bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
  2026-05-12  6:40 ` [PATCH v7 2/2] drm/bridge: " syyang
@ 2026-05-13  6:04   ` sashiko-bot
  2026-05-13  7:18     ` Sunyun Yang
  0 siblings, 1 reply; 5+ messages in thread
From: sashiko-bot @ 2026-05-13  6:04 UTC (permalink / raw)
  To: syyang; +Cc: conor+dt, krzk+dt, robh, devicetree

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] Firmware upgrade via sysfs races with DRM atomic modeset callbacks, risking hardware bricking.
- [High] Hardware reset in `remove` callback races with DRM bridge teardown.
- [Medium] Undefined behavior in CRC validation due to integer promotion.
- [Medium] Incomplete display configuration and missing MIPI DSI host registration.
- [Medium] Unbalanced reset pin state on probe failure creates backpowering risk.
- [Medium] Ignored return values for critical I2C writes during firmware upgrades.
- [Low] `dev_err_probe()` used outside of `probe` context.
--

commit 0bae053b4ef87003ffe3f7c844f0ddb1092f16bf
Author: Sunyun Yang <syyang@lontium.com>
Date:   Tue May 12 06:40:13 2026 +0000

    drm/bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
    
    This commit adds support for the Lontium LT7911EXC eDP to MIPI DSI
    bridge. It implements I2C-based setup, display configuration, and
    provides a firmware update mechanism.

The commit message states:
> It implements I2C-based setup, display configuration, and
> provides a firmware update mechanism.

Is this description completely accurate? Looking at the drm_bridge_funcs
implementation, there are no atomic_enable/disable functions configuring
the display. Additionally, as an eDP to MIPI DSI bridge, should it also
register a mipi_dsi_host so downstream panels can attach?

> diff --git a/drivers/gpu/drm/bridge/lontium-lt7911exc.c b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
[ ... ]
> +static void lt7911exc_block_erase(struct lt7911exc *lt7911exc)
> +{
[ ... ]
> +	regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
> +
> +	msleep(200);
> +	dev_dbg(dev, "erase flash done.\n");
> +}

Could this silently fail to erase the flash? If regmap_multi_reg_write()
fails due to hardware communication errors, the driver will continue and
attempt to write new firmware data to an unerased flash segment. Should
the return value be checked here and in lt7911exc_prog_init()?

[ ... ]
> +static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc, u32 crc32)
> +{
[ ... ]
> +	read_hw_crc = crc_tmp[0] << 24 | crc_tmp[1] << 16 |
> +				crc_tmp[2] << 8 | crc_tmp[3];

Can this cause a signed integer overflow? In C, u8 values are implicitly
promoted to signed 32-bit integers before shifting. If crc_tmp[0] is 128
or greater, shifting left by 24 bits will shift into the sign bit. Would
explicitly casting to u32 prevent this?

[ ... ]
> +static int lt7911exc_firmware_upgrade(struct lt7911exc *lt7911exc)
> +{
[ ... ]
> +	/*1. load firmware*/
> +	ret = request_firmware(&fw, FW_FILE, dev);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to load '%s'\n", FW_FILE);

Is it safe to use dev_err_probe() outside of a probe context? This function
is called exclusively from the sysfs store callback. Calling dev_err_probe()
here might interfere with deferred probe tracking.

[ ... ]
> +static void lt7911exc_atomic_pre_enable(struct drm_bridge *bridge, struct drm_atomic_state *state)
> +{
> +	struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> +
> +	lt7911exc_reset(lt7911exc);
> +}

[ ... ]

> +static ssize_t lt7911exc_firmware_store(struct device *dev, struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
[ ... ]
> +	lt7911exc_lock(lt7911exc);
> +
> +	ret = lt7911exc_firmware_upgrade(lt7911exc);

Can a DRM atomic modeset race with a firmware upgrade here? 

The sysfs attribute protects internal I2C sequences with ocm_lock, but the
DRM bridge callbacks toggle the reset_gpio without acquiring this lock.

If a display modeset occurs concurrently with a firmware flash, could the
hardware reset pin be toggled mid-flash and corrupt the firmware?

[ ... ]
> +static int lt7911exc_probe(struct i2c_client *client)
> +{
[ ... ]
> +	lt7911exc_reset(lt7911exc);
> +
> +	lt7911exc_lock(lt7911exc);
> +
> +	lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
> +
> +	lt7911exc_unlock(lt7911exc);
> +	if (lt7911exc->fw_version < 0)
> +		return dev_err_probe(dev, lt7911exc->fw_version, "failed read version of chip\n");

If lt7911exc_read_version() fails, does this leave the reset pin in an
unbalanced state? 

The lt7911exc_reset() call leaves the reset_gpio low, and as noted in the
remove function, it should be pulled high to prevent backpowering. Should
the error path assert the reset pin high?

[ ... ]
> +	return devm_drm_bridge_add(dev, &lt7911exc->bridge);
> +}
> +
> +static void lt7911exc_remove(struct i2c_client *client)
> +{
> +	struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
> +
> +	/* pull reset low to prevent backpowering*/
> +	gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> +}

Does this teardown sequence race with DRM operations? 

Because the bridge is registered with devm_drm_bridge_add(), it will be
unregistered during the devres cleanup phase, which happens after the
driver's remove callback has finished.

Since lt7911exc_remove() asserts the reset pin to shut down the hardware,
could concurrent DRM operations attempt to access the disabled hardware
while it is still registered?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260512064013.40066-1-syyang@lontium.com?part=2

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v7 2/2] drm/bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
  2026-05-13  6:04   ` sashiko-bot
@ 2026-05-13  7:18     ` Sunyun Yang
  0 siblings, 0 replies; 5+ messages in thread
From: Sunyun Yang @ 2026-05-13  7:18 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: conor+dt, krzk+dt, robh, devicetree

<sashiko-bot@kernel.org> 于2026年5月13日周三 14:04写道:
>
> Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
> - [Critical] Firmware upgrade via sysfs races with DRM atomic modeset callbacks, risking hardware bricking.
      it will be fixed in next version.

> - [High] Hardware reset in `remove` callback races with DRM bridge teardown.
       we will remove set reset gpio in `remove`.  the chip has
hardware protection, no need to consider backpowering.

> - [Medium] Undefined behavior in CRC validation due to integer promotion.
      it will be fixed in next version.

> - [Medium] Incomplete display configuration and missing MIPI DSI host registration.
        it will be fixed in next version.

> - [Medium] Unbalanced reset pin state on probe failure creates backpowering risk.
      the chip has hardware protection, no need to consider backpowering.

> - [Medium] Ignored return values for critical I2C writes during firmware upgrades.
       it will be fixed in next version.

> - [Low] `dev_err_probe()` used outside of `probe` context.
      it will be fixed in next version.
> --
>
> commit 0bae053b4ef87003ffe3f7c844f0ddb1092f16bf
> Author: Sunyun Yang <syyang@lontium.com>
> Date:   Tue May 12 06:40:13 2026 +0000
>
>     drm/bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
>
>     This commit adds support for the Lontium LT7911EXC eDP to MIPI DSI
>     bridge. It implements I2C-based setup, display configuration, and
>     provides a firmware update mechanism.
>
> The commit message states:
> > It implements I2C-based setup, display configuration, and
> > provides a firmware update mechanism.
>
> Is this description completely accurate? Looking at the drm_bridge_funcs
> implementation, there are no atomic_enable/disable functions configuring
> the display. Additionally, as an eDP to MIPI DSI bridge, should it also
> register a mipi_dsi_host so downstream panels can attach?
>
> > diff --git a/drivers/gpu/drm/bridge/lontium-lt7911exc.c b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> > new file mode 100644
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> [ ... ]
> > +static void lt7911exc_block_erase(struct lt7911exc *lt7911exc)
> > +{
> [ ... ]
> > +     regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
> > +
> > +     msleep(200);
> > +     dev_dbg(dev, "erase flash done.\n");
> > +}
>
> Could this silently fail to erase the flash? If regmap_multi_reg_write()
> fails due to hardware communication errors, the driver will continue and
> attempt to write new firmware data to an unerased flash segment. Should
> the return value be checked here and in lt7911exc_prog_init()?
>
> [ ... ]
> > +static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc, u32 crc32)
> > +{
> [ ... ]
> > +     read_hw_crc = crc_tmp[0] << 24 | crc_tmp[1] << 16 |
> > +                             crc_tmp[2] << 8 | crc_tmp[3];
>
> Can this cause a signed integer overflow? In C, u8 values are implicitly
> promoted to signed 32-bit integers before shifting. If crc_tmp[0] is 128
> or greater, shifting left by 24 bits will shift into the sign bit. Would
> explicitly casting to u32 prevent this?
>
> [ ... ]
> > +static int lt7911exc_firmware_upgrade(struct lt7911exc *lt7911exc)
> > +{
> [ ... ]
> > +     /*1. load firmware*/
> > +     ret = request_firmware(&fw, FW_FILE, dev);
> > +     if (ret)
> > +             return dev_err_probe(dev, ret, "failed to load '%s'\n", FW_FILE);
>
> Is it safe to use dev_err_probe() outside of a probe context? This function
> is called exclusively from the sysfs store callback. Calling dev_err_probe()
> here might interfere with deferred probe tracking.
>
> [ ... ]
> > +static void lt7911exc_atomic_pre_enable(struct drm_bridge *bridge, struct drm_atomic_state *state)
> > +{
> > +     struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> > +
> > +     lt7911exc_reset(lt7911exc);
> > +}
>
> [ ... ]
>
> > +static ssize_t lt7911exc_firmware_store(struct device *dev, struct device_attribute *attr,
> > +                                     const char *buf, size_t len)
> > +{
> [ ... ]
> > +     lt7911exc_lock(lt7911exc);
> > +
> > +     ret = lt7911exc_firmware_upgrade(lt7911exc);
>
> Can a DRM atomic modeset race with a firmware upgrade here?
>
> The sysfs attribute protects internal I2C sequences with ocm_lock, but the
> DRM bridge callbacks toggle the reset_gpio without acquiring this lock.
>
> If a display modeset occurs concurrently with a firmware flash, could the
> hardware reset pin be toggled mid-flash and corrupt the firmware?
>
> [ ... ]
> > +static int lt7911exc_probe(struct i2c_client *client)
> > +{
> [ ... ]
> > +     lt7911exc_reset(lt7911exc);
> > +
> > +     lt7911exc_lock(lt7911exc);
> > +
> > +     lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
> > +
> > +     lt7911exc_unlock(lt7911exc);
> > +     if (lt7911exc->fw_version < 0)
> > +             return dev_err_probe(dev, lt7911exc->fw_version, "failed read version of chip\n");
>
> If lt7911exc_read_version() fails, does this leave the reset pin in an
> unbalanced state?
>
> The lt7911exc_reset() call leaves the reset_gpio low, and as noted in the
> remove function, it should be pulled high to prevent backpowering. Should
> the error path assert the reset pin high?
>
> [ ... ]
> > +     return devm_drm_bridge_add(dev, &lt7911exc->bridge);
> > +}
> > +
> > +static void lt7911exc_remove(struct i2c_client *client)
> > +{
> > +     struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
> > +
> > +     /* pull reset low to prevent backpowering*/
> > +     gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> > +}
>
> Does this teardown sequence race with DRM operations?
>
> Because the bridge is registered with devm_drm_bridge_add(), it will be
> unregistered during the devres cleanup phase, which happens after the
> driver's remove callback has finished.
>
> Since lt7911exc_remove() asserts the reset pin to shut down the hardware,
> could concurrent DRM operations attempt to access the disabled hardware
> while it is still registered?
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260512064013.40066-1-syyang@lontium.com?part=2

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-05-13  7:24 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-12  6:40 [PATCH v7 0/2] Add Lontium LT7911EXC eDP to MIPI DSI bridge syyang
2026-05-12  6:40 ` [PATCH v7 1/2] dt-bindings: bridge: " syyang
2026-05-12  6:40 ` [PATCH v7 2/2] drm/bridge: " syyang
2026-05-13  6:04   ` sashiko-bot
2026-05-13  7:18     ` Sunyun Yang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox