* [PATCH v8 0/2] Add Lontium LT7911EXC eDP to MIPI DSI bridge
@ 2026-05-15 8:09 syyang
2026-05-15 8:09 ` [PATCH v8 1/2] dt-bindings: bridge: " syyang
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: syyang @ 2026-05-15 8:09 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 v8:
- dt-binding:
- drm/bridge:
1. Protect firmware upgrade and DRM bridge callback paths with ocm_lock. [sashiko-bot]
2. Remove the hardware reset from the remove callback, and ensure that
all hardware reset operations are protected by ocm_lock.
3. crc reconstruction explicitly casts each byte to u32 before shifting
4. The display configuration is handled by the firmware, and the MIPI
DSI host registration issue has been fixed.
5. The batch register read/write operations have already been updated
to include return value checking.
6. The dev_err_probe() used outside of probe context has been fixed.
- Link to v7: https://lore.kernel.org/lkml/20260512064013.40066-1-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 | 642 ++++++++++++++++++
4 files changed, 747 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] 6+ messages in thread
* [PATCH v8 1/2] dt-bindings: bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
2026-05-15 8:09 [PATCH v8 0/2] Add Lontium LT7911EXC eDP to MIPI DSI bridge syyang
@ 2026-05-15 8:09 ` syyang
2026-05-15 23:43 ` Claude review: " Claude Code Review Bot
2026-05-15 8:09 ` [PATCH v8 2/2] drm/bridge: " syyang
2026-05-15 23:43 ` Claude review: " Claude Code Review Bot
2 siblings, 1 reply; 6+ messages in thread
From: syyang @ 2026-05-15 8:09 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 = <<7911exc_1v2>;
+ vcc-supply = <<7911exc_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] 6+ messages in thread
* [PATCH v8 2/2] drm/bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
2026-05-15 8:09 [PATCH v8 0/2] Add Lontium LT7911EXC eDP to MIPI DSI bridge syyang
2026-05-15 8:09 ` [PATCH v8 1/2] dt-bindings: bridge: " syyang
@ 2026-05-15 8:09 ` syyang
2026-05-15 23:43 ` Claude review: " Claude Code Review Bot
2026-05-15 23:43 ` Claude review: " Claude Code Review Bot
2 siblings, 1 reply; 6+ messages in thread
From: syyang @ 2026-05-15 8:09 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>
Add support for the Lontium LT7911EXC bridge chip, which converts
eDP input to MIPI DSI output using an internal firmware-controlled
pipeline.
The driver provides:
- DRM bridge integration for eDP-to-DSI routing
- MIPI DSI host interface for downstream panel attachment
- Firmware upgrade mechanism over I2C (erase/program/verify)
- GPIO-based reset and regulator management
- Basic register access protection using a mutex-controlled OCM lock
Display timing and DSI packet generation are handled by the chip
firmware and are not configured by the driver.
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 | 642 +++++++++++++++++++++
3 files changed, 658 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..e7b8a22b2e29
--- /dev/null
+++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
@@ -0,0 +1,642 @@
+// 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_atomic_helper.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_dsi_output {
+ struct mipi_dsi_device *dev;
+ struct drm_panel *panel;
+ struct drm_bridge *bridge;
+ };
+
+struct lt7911exc {
+ struct device *dev;
+ struct i2c_client *client;
+ struct drm_bridge bridge;
+
+ struct mipi_dsi_host dsi_host;
+ struct lt7911exc_dsi_output output;
+
+ 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 inline struct lt7911exc *dsi_host_to_lt7911exc(struct mipi_dsi_host *host)
+{
+ return container_of(host, struct lt7911exc, dsi_host);
+}
+
+/*
+ * Purpose of this function is rest gpio: high -> low -> high
+ * This clears the previous configuration in the chip,
+ * and finally remains high to allow the firmware to run again.
+ */
+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(<7911exc->ocm_lock);
+ regmap_write(lt7911exc->regmap, 0xe0ee, 0x01);
+}
+
+static void lt7911exc_unlock(struct lt7911exc *lt7911exc)
+{
+ regmap_write(lt7911exc->regmap, 0xe0ee, 0x00);
+ mutex_unlock(<7911exc->ocm_lock);
+}
+
+static int lt7911exc_block_erase(struct lt7911exc *lt7911exc)
+{
+ struct device *dev = lt7911exc->dev;
+ const u32 addr = 0x00;
+ int ret;
+
+ 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),
+ };
+
+ ret = regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
+ if (ret)
+ return ret;
+
+ msleep(200);
+ dev_dbg(dev, "erase flash done.\n");
+
+ return 0;
+}
+
+static int lt7911exc_prog_init(struct lt7911exc *lt7911exc, u64 addr)
+{
+ int ret;
+
+ 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),
+ };
+
+ ret = regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+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);
+ ret = lt7911exc_prog_init(lt7911exc, addr);
+ if (ret)
+ return ret;
+
+ 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 = ((u32)crc_tmp[0] << 24) | ((u32)crc_tmp[1] << 16) |
+ ((u32)crc_tmp[2] << 8) | ((u32)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) {
+ dev_err(dev, "failed to load '%s'\n", FW_FILE);
+ return ret;
+ }
+
+ /* 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);
+
+ ret = lt7911exc_block_erase(lt7911exc);
+ if (ret) {
+ dev_err(dev, "failed to block erase.\n");
+ goto out_release_fw;
+ }
+
+ 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);
+
+ guard(mutex)(<7911exc->ocm_lock);
+
+ 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);
+
+ guard(mutex)(<7911exc->ocm_lock);
+
+ gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
+}
+
+static int lt7911exc_bridge_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(encoder, lt7911exc->output.bridge, bridge, flags);
+}
+
+static const struct drm_bridge_funcs lt7911exc_bridge_funcs = {
+ .attach = lt7911exc_bridge_attach,
+ .atomic_pre_enable = lt7911exc_atomic_pre_enable,
+ .atomic_disable = lt7911exc_atomic_disable,
+ .atomic_post_disable = lt7911exc_atomic_post_disable,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+};
+
+static int lt7911exc_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *dev)
+{
+ struct lt7911exc *lt7911exc = dsi_host_to_lt7911exc(host);
+ struct drm_bridge *bridge;
+ struct drm_panel *panel;
+ int ret;
+
+ ret = drm_of_find_panel_or_bridge(host->dev->of_node, 1, 0, &panel, &bridge);
+ if (ret)
+ return ret;
+
+ if (panel) {
+ bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI);
+ if (IS_ERR(bridge))
+ return PTR_ERR(bridge);
+ }
+ lt7911exc->output.dev = dev;
+ lt7911exc->output.bridge = bridge;
+ lt7911exc->output.panel = panel;
+
+ drm_bridge_add(<7911exc->bridge);
+ return 0;
+}
+
+static int lt7911exc_dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *dev)
+{
+ struct lt7911exc *lt7911exc = dsi_host_to_lt7911exc(host);
+
+ guard(mutex)(<7911exc->ocm_lock);
+
+ drm_bridge_remove(<7911exc->bridge);
+ if (lt7911exc->output.panel)
+ drm_panel_bridge_remove(lt7911exc->output.bridge);
+
+ gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
+
+ return 0;
+}
+
+/*
+ * The firmware running inside the LT7911EXC chip
+ * sends display commands configuration and cannot
+ * be configured by an external driver.
+ */
+static ssize_t lt7911exc_dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct lt7911exc *lt7911exc = dsi_host_to_lt7911exc(host);
+ unsigned int val;
+ int ret;
+
+ guard(mutex)(<7911exc->ocm_lock);
+
+ /* ensure eDP input video ready */
+ ret = regmap_read_poll_timeout(lt7911exc->regmap, 0xe084, val, (val & 0x01), 5000, 50000);
+ if (ret) {
+ dev_warn(lt7911exc->dev, "Video source not ready within 50ms\n");
+ return -EBUSY;
+ }
+
+ /* enable mipi dsi output stream and firmware will send dcs */
+ ret = regmap_write(lt7911exc->regmap, 0xe0b0, 0x01);
+ if (ret)
+ return ret;
+
+ return msg->tx_len;
+}
+
+static const struct mipi_dsi_host_ops lt7911exc_dsi_host_ops = {
+ .attach = lt7911exc_dsi_host_attach,
+ .detach = lt7911exc_dsi_host_detach,
+ .transfer = lt7911exc_dsi_host_transfer,
+};
+
+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;
+
+ lt7911exc_lock(lt7911exc);
+
+ /* 0 - ensure chip run */
+ gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
+ msleep(400);
+
+ 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[] = {
+ <7911exc_attr_group,
+ NULL,
+};
+
+static int lt7911exc_probe(struct i2c_client *client)
+{
+ struct lt7911exc *lt7911exc;
+ struct device *dev = &client->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ 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, <7911exc_bridge_funcs);
+ if (IS_ERR(lt7911exc))
+ return dev_err_probe(dev, PTR_ERR(lt7911exc), "drm bridge alloc failed.\n");
+
+ dev_set_drvdata(dev, lt7911exc);
+
+ lt7911exc->client = client;
+ lt7911exc->dev = dev;
+
+ ret = devm_mutex_init(dev, <7911exc->ocm_lock);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to init mutex\n");
+
+ lt7911exc->regmap = devm_regmap_init_i2c(client, <7911exc_regmap_config);
+ if (IS_ERR(lt7911exc->regmap))
+ return dev_err_probe(dev, PTR_ERR(lt7911exc->regmap), "regmap i2c init failed\n");
+
+ /*
+ * reset GPIO is defined as active low in device tree.
+ * gpiod_set_value_cansleep() uses logical value:
+ * 1 -> asserted (active) -> physical low -> reset enabled -> chip stop
+ * 0 -> deasserted (inactive) -> physical high -> reset released -> chip run
+ */
+ 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->dsi_host.dev = dev;
+ lt7911exc->dsi_host.ops = <7911exc_dsi_host_ops;
+ lt7911exc->bridge.of_node = np;
+
+ i2c_set_clientdata(client, lt7911exc);
+
+ return mipi_dsi_host_register(<7911exc->dsi_host);
+}
+
+static void lt7911exc_remove(struct i2c_client *client)
+{
+ struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
+
+ mipi_dsi_host_unregister(<7911exc->dsi_host);
+}
+
+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] 6+ messages in thread
* Claude review: Add Lontium LT7911EXC eDP to MIPI DSI bridge
2026-05-15 8:09 [PATCH v8 0/2] Add Lontium LT7911EXC eDP to MIPI DSI bridge syyang
2026-05-15 8:09 ` [PATCH v8 1/2] dt-bindings: bridge: " syyang
2026-05-15 8:09 ` [PATCH v8 2/2] drm/bridge: " syyang
@ 2026-05-15 23:43 ` Claude Code Review Bot
2 siblings, 0 replies; 6+ messages in thread
From: Claude Code Review Bot @ 2026-05-15 23:43 UTC (permalink / raw)
To: dri-devel-reviews
Overall Series Review
Subject: Add Lontium LT7911EXC eDP to MIPI DSI bridge
Author: syyang@lontium.com
Patches: 3
Reviewed: 2026-05-16T09:43:27.762700
---
This is v8 of a 2-patch series adding a DRM bridge driver for the Lontium LT7911EXC, an eDP 1.4 to MIPI DSI converter. The DT binding (patch 1) already has a Reviewed-by from Krzysztof Kozlowski and looks clean. The driver (patch 2) has a Reviewed-by from Dmitry Baryshkov and follows the established pattern for bridge-chips-as-DSI-host (similar to tc358768, ssd2825), where `drm_bridge_add` is deferred to the DSI host attach callback.
The driver is generally well-structured for v8, but there are still several issues ranging from a real bug (sysfs version format truncation), unchecked error paths, unnecessary type widths, and some structural concerns around firmware upgrade safety.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 6+ messages in thread
* Claude review: dt-bindings: bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
2026-05-15 8:09 ` [PATCH v8 1/2] dt-bindings: bridge: " syyang
@ 2026-05-15 23:43 ` Claude Code Review Bot
0 siblings, 0 replies; 6+ messages in thread
From: Claude Code Review Bot @ 2026-05-15 23:43 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
This patch is clean and already has Krzysztof's Reviewed-by. No issues found.
---
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 6+ messages in thread
* Claude review: drm/bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge
2026-05-15 8:09 ` [PATCH v8 2/2] drm/bridge: " syyang
@ 2026-05-15 23:43 ` Claude Code Review Bot
0 siblings, 0 replies; 6+ messages in thread
From: Claude Code Review Bot @ 2026-05-15 23:43 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
**Kconfig issues:**
1. Missing space in help text:
```c
chip.The LT7911EXC converts eDP input to MIPI
```
Should be `chip. The LT7911EXC...`.
2. Consider adding `select DRM_MIPI_DSI` — the driver implements a `mipi_dsi_host` and uses MIPI DSI APIs.
**Struct formatting:**
3. Inconsistent closing brace indentation in `lt7911exc_dsi_output`:
```c
struct lt7911exc_dsi_output {
struct mipi_dsi_device *dev;
struct drm_panel *panel;
struct drm_bridge *bridge;
};
```
The `};` has an extra tab. Should be flush left.
**`fw_version` sysfs display bug:**
4. `lt7911exc_read_version` returns a 24-bit value:
```c
return (buf[0] << 16) | (buf[1] << 8) | buf[2];
```
But `lt7911exc_firmware_show` formats it as:
```c
return sysfs_emit(buf, "0x%04x\n", lt7911exc->fw_version);
```
`%04x` pads to 4 hex digits minimum but a 24-bit version (e.g., `0x010203`) needs 6 hex digits. Use `"0x%06x\n"` to match the 3-byte version, or `"0x%x\n"` if there's no fixed width convention.
**Unnecessary `u64` types:**
5. Several functions use `u64` for addresses and sizes that are at most 64KB:
```c
static u32 cal_crc32_custom(const u8 *data, u64 length)
static int lt7911exc_prog_init(struct lt7911exc *lt7911exc, u64 addr)
static int lt7911exc_write_data(struct lt7911exc *lt7911exc, const struct firmware *fw, u64 addr)
```
These should be `size_t` for lengths and `u32` for addresses. Firmware is capped at `FW_SIZE` (64KB), so `u64` is misleading. This also causes mixed-type arithmetic in `lt7911exc_write_data`:
```c
u64 size, offset;
...
page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
```
Here `page` is `int` but the RHS is `u64`, resulting in silent truncation.
**`cal_crc32_custom` fragility:**
6. The function accesses `data[i+3]` without checking that `length` is a multiple of 4:
```c
for (i = 0; i < length; i += 4) {
buf[0] = data[i + 3];
```
Currently all callers pass `FW_SIZE - 4 = 65532` which is divisible by 4, so there's no actual bug. But the function is fragile — consider adding a `WARN_ON(length % 4)` or a comment documenting the requirement.
**Unchecked `regmap_write` return values:**
7. Several `regmap_write` calls in `lt7911exc_write_crc` ignore return values:
```c
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);
```
Same issue in `lt7911exc_write_data` for the short-page handling path:
```c
if (page_len < LT_PAGE_SIZE) {
regmap_write(lt7911exc->regmap, 0xe05f, 0x05);
regmap_write(lt7911exc->regmap, 0xe05f, 0x01);
```
And in `lt7911exc_upgrade_result`:
```c
regmap_write(lt7911exc->regmap, 0xe0ee, 0x01);
regmap_write(lt7911exc->regmap, 0xe07b, 0x60);
regmap_write(lt7911exc->regmap, 0xe07b, 0x40);
```
These should check return values or at minimum use `regmap_multi_reg_write` as done in the erase function for consistency.
**`lt7911exc_lock`/`lt7911exc_unlock` vs. internal OCM register writes:**
8. `lt7911exc_lock` writes `0xe0ee = 0x01` to stop the on-chip MCU, but `lt7911exc_block_erase` and `lt7911exc_prog_init` also write `0xe0ee = 0x01` via their `seq_write` arrays:
```c
const struct reg_sequence seq_write[] = {
REG_SEQ0(0xe0ee, 0x01), /* redundant when caller holds lock */
REG_SEQ0(0xe054, 0x01),
```
This is harmless but confusing — the OCM lock is managed at two different layers. Consider removing the redundant `0xe0ee` writes from the firmware functions since the caller always holds the lock, or document that these functions must not be called without the lock held.
**Redundant `dev_set_drvdata` / `i2c_set_clientdata`:**
9. In probe:
```c
dev_set_drvdata(dev, lt7911exc);
...
i2c_set_clientdata(client, lt7911exc);
```
`i2c_set_clientdata` calls `dev_set_drvdata(&client->dev, data)` internally, so this is redundant. One call to `i2c_set_clientdata` is sufficient (and is what `lt7911exc_remove` uses to retrieve it).
**Comment style:**
10. Missing spaces after `/*` in comments:
```c
/*3. calculate crc32 */
/*4. firmware upgrade */
/*5. check upgrade of result */
```
Should be `/* 3.` etc.
**`lt7911exc_prog_init` unnecessary early return:**
11. The function ends with:
```c
ret = regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
if (ret)
return ret;
return 0;
```
This is just `return regmap_multi_reg_write(...)`.
**Firmware upgrade safety:**
12. `lt7911exc_firmware_store` can be triggered while the display pipeline is active. The function takes `ocm_lock`, resets the chip, erases flash, and reprograms firmware — all while a display may be running. After the upgrade completes and the lock is released, the bridge is in an undefined state relative to the DRM pipeline. There's no check for whether the bridge is currently attached/enabled. Other drivers with similar sysfs firmware interfaces (lt9611uxc) have the same issue, so this may be accepted practice, but it's worth noting.
**`lt7911exc_dsi_host_transfer` semantics:**
13. The transfer function doesn't actually forward DSI messages — it waits for video input and enables DSI output:
```c
ret = regmap_read_poll_timeout(lt7911exc->regmap, 0xe084, val, (val & 0x01), 5000, 50000);
...
ret = regmap_write(lt7911exc->regmap, 0xe0b0, 0x01);
...
return msg->tx_len;
```
Every DSI transfer from the panel driver will re-poll the video-ready register (up to 50ms) and re-write the output enable register. If a panel driver sends multiple DCS init commands, this could add significant latency. Is the intent that only a single transfer ever happens (i.e., the panel driver sends exactly one DCS command to trigger output)? If so, this should be documented. If not, the enable should happen once and subsequent transfers should be no-ops.
**Missing `atomic_enable` callback:**
14. The bridge provides `atomic_pre_enable` and `atomic_disable`/`atomic_post_disable` but no `atomic_enable`. The enable logic appears to be implicitly handled through the DSI host transfer mechanism, but this coupling between bridge lifecycle and DSI host transfer is unusual and could use a comment explaining the design.
**Minor: `#include <linux/platform_device.h>`:**
15. This is an I2C driver, not a platform driver. The `platform_device.h` include appears unused.
**Minor: `np` check:**
16. In probe:
```c
if (!np)
return -ENODEV;
```
The driver has `depends on OF` in Kconfig and the `of_match_table` is set, so `of_node` will always be non-NULL when probe is called via DT matching. This check is unnecessary.
**Summary:** The driver structure is sound and follows existing patterns (especially the tc358768 model for DSI-host bridges). The main items to address are: the sysfs version format bug (#4), unchecked regmap return values (#7), the u64/type-width cleanup (#5), and the struct indentation (#3). The rest are minor improvements and style points.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-05-15 23:43 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-15 8:09 [PATCH v8 0/2] Add Lontium LT7911EXC eDP to MIPI DSI bridge syyang
2026-05-15 8:09 ` [PATCH v8 1/2] dt-bindings: bridge: " syyang
2026-05-15 23:43 ` Claude review: " Claude Code Review Bot
2026-05-15 8:09 ` [PATCH v8 2/2] drm/bridge: " syyang
2026-05-15 23:43 ` Claude review: " Claude Code Review Bot
2026-05-15 23:43 ` Claude review: " Claude Code Review Bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox