* [PATCH 0/2] Add LT7911EXC edp to mipi bridge driver
@ 2026-04-20 2:33 syyang
2026-04-20 2:33 ` [PATCH 1/2] dt-bindings:bridge Add LT7911EXC binding syyang
2026-04-20 2:33 ` [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver syyang
0 siblings, 2 replies; 15+ messages in thread
From: syyang @ 2026-04-20 2:33 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, yangsunyun1993, xmzhu, Sunyun Yang
From: Sunyun Yang <syyang@lontium.com>
This patch series adds support for the LT7911EXC edp to mipi
bidge chip.
The LT7911EXC is an I2C-controlled bridge that Receiver eDP1.4
and output signal/dual port mipi. 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>
---
Sunyun Yang (2):
dt-bindings:bridge Add LT7911EXC binding
drm/bridge: Add LT7911EXC edp to mipi bridge driver
.../display/bridge/lontium,lt7911exc.yaml | 91 +++
drivers/gpu/drm/bridge/Kconfig | 18 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/lontium-lt7911exc.c | 571 ++++++++++++++++++
4 files changed, 681 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] 15+ messages in thread
* [PATCH 1/2] dt-bindings:bridge Add LT7911EXC binding
2026-04-20 2:33 [PATCH 0/2] Add LT7911EXC edp to mipi bridge driver syyang
@ 2026-04-20 2:33 ` syyang
2026-04-20 3:12 ` Dmitry Baryshkov
2026-04-20 2:33 ` [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver syyang
1 sibling, 1 reply; 15+ messages in thread
From: syyang @ 2026-04-20 2:33 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, yangsunyun1993, xmzhu, Sunyun Yang
From: Sunyun Yang <syyang@lontium.com>
-binding for lt7911exc.
Signed-off-by: Sunyun Yang <syyang@lontium.com>
---
.../display/bridge/lontium,lt7911exc.yaml | 91 +++++++++++++++++++
1 file changed, 91 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..54a73d41635a
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.yaml
@@ -0,0 +1,91 @@
+# 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 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 mipi dsi output.
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Video port for eDP input.
+
+ 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_HIGH>;
+ vdd-supply = <<7911exc_1v2>;
+ vcc-supply = <<7911exc_3v3>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ bridge_out: endpoint {
+ remote-endpoint = <&panel_in>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ bridge_in: endpoint {
+ remote-endpoint = <&edp_out>;
+ };
+ };
+ };
+ };
+ };
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
2026-04-20 2:33 [PATCH 0/2] Add LT7911EXC edp to mipi bridge driver syyang
2026-04-20 2:33 ` [PATCH 1/2] dt-bindings:bridge Add LT7911EXC binding syyang
@ 2026-04-20 2:33 ` syyang
2026-04-20 3:57 ` Dmitry Baryshkov
2026-04-20 5:05 ` Quentin Freimanis
1 sibling, 2 replies; 15+ messages in thread
From: syyang @ 2026-04-20 2:33 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, yangsunyun1993, xmzhu, Sunyun Yang
From: Sunyun Yang <syyang@lontium.com>
LT7911EXC is a high performance eDP1.4 to MIPI chip for
VR/Display application.
-eDP1.4Receiver
1.Support SSC
2.Support 1/2/4 lanes
3.Support up to 4K@60HzRGB/YCbCr4:4:48bpc
4.Support lane swap and PN swap
-MIPI Transmitter
1.CompliantwithD-PHY1.2&DSI1.1&CSI-22.0;1 clock lane,
and1/2/3/4 configurable data lanes:2.5Gbpsperdatalane
2.CompliantwithC-PHY1.0&DSI-21.0&CSI-22.0;
1/2/3 configurable data trio;2.5Gsps perdatatrio
3.Support1/2configurable ports
4.DSISupport16/20/24-bit YCbCr4:2:2,16/18/24/30-bit RGB
Signed-off-by: Sunyun Yang <syyang@lontium.com>
---
drivers/gpu/drm/bridge/Kconfig | 18 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/lontium-lt7911exc.c | 571 +++++++++++++++++++++
3 files changed, 590 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..bae8cdaea666 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -202,6 +202,24 @@ config DRM_LONTIUM_LT8713SX
to 3 configurable Type-C/DP1.4/HDMI2.0 outputs
Please say Y if you have such hardware.
+config DRM_LONTIUM_LT9611C
+ tristate "Lontium LT9611C DSI/HDMI bridge"
+ select SND_SOC_HDMI_CODEC if SND_SOC
+ depends on OF
+ select CRC8
+ select FW_LOADER
+ select DRM_PANEL_BRIDGE
+ select DRM_KMS_HELPER
+ select DRM_MIPI_DSI
+ select DRM_DISPLAY_HELPER
+ select DRM_DISPLAY_HDMI_STATE_HELPER
+ select REGMAP_I2C
+ help
+ Driver for Lontium DSI to HDMI bridge
+ chip driver that converts dual DSI and I2S to
+ HDMI signals
+ Please say Y if you have such hardware.
+
config DRM_ITE_IT66121
tristate "ITE IT66121 HDMI bridge"
depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index beab5b695a6e..54b293d1663e 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
+obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.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..d1c1d9e073ef
--- /dev/null
+++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
@@ -0,0 +1,571 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Lontium Semiconductor, Inc.
+ */
+
+#include <linux/crc32.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <drm/drm_of.h>
+
+#define FW_SIZE (64 * 1024)
+#define LT_PAGE_SIZE 32
+#define FW_FILE "LT7911EXC.bin"
+#define LT7911EXC_PAGE_CONTROL 0xff
+
+struct lt7911exc {
+ struct device *dev;
+ struct i2c_client *client;
+ struct drm_bridge bridge;
+ struct drm_bridge *panel_bridge;
+ struct regmap *regmap;
+ /* Protects all accesses to registers by stopping the on-chip MCU */
+ struct mutex ocm_lock;
+ struct regulator_bulk_data supplies[2];
+
+ struct gpio_desc *reset_gpio;
+ const struct firmware *fw;
+ int fw_version;
+ u32 fw_crc;
+
+ bool enabled;
+};
+
+static const struct regmap_range_cfg lt7911exc_ranges[] = {
+ {
+ .name = "register_range",
+ .range_min = 0,
+ .range_max = 0xffff,
+ .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 = 0xffff,
+ .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 int lt7911exc_regulator_enable(struct lt7911exc *lt7911exc)
+{
+ int ret;
+
+ ret = regulator_enable(lt7911exc->supplies[0].consumer);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(5000, 10000);
+
+ ret = regulator_enable(lt7911exc->supplies[1].consumer);
+ if (ret < 0) {
+ regulator_disable(lt7911exc->supplies[0].consumer);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt7911exc_regulator_disable(struct lt7911exc *lt7911exc)
+{
+ int ret;
+
+ ret = regulator_disable(lt7911exc->supplies[1].consumer);
+ if (ret < 0)
+ return ret;
+
+ ret = regulator_disable(lt7911exc->supplies[0].consumer);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void lt7911exc_reset(struct lt7911exc *lt7911exc)
+{
+ gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
+ msleep(20);
+
+ gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
+ msleep(20);
+
+ gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
+ msleep(400);
+
+ dev_dbg(lt7911exc->dev, "lt7911exc reset");
+}
+
+static int lt7911exc_parse_dt(struct lt7911exc *lt7911exc)
+{
+ int ret;
+
+ lt7911exc->supplies[0].supply = "vcc";
+ lt7911exc->supplies[1].supply = "vdd";
+
+ ret = devm_regulator_bulk_get(lt7911exc->dev, 2, lt7911exc->supplies);
+ if (ret) {
+ dev_err(lt7911exc->dev, "failed get regulator\n");
+ return ret;
+ }
+
+ lt7911exc->reset_gpio = devm_gpiod_get(lt7911exc->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(lt7911exc->reset_gpio)) {
+ dev_err(lt7911exc->dev, "failed to acquire reset gpio\n");
+ return PTR_ERR(lt7911exc->reset_gpio);
+ }
+
+ return 0;
+}
+
+static int lt7911exc_read_version(struct lt7911exc *lt7911exc)
+{
+ u8 buf[2];
+ int ret;
+
+ ret = regmap_bulk_read(lt7911exc->regmap, 0xe081, buf, 3);
+ 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_prepare_firmware_data(struct lt7911exc *lt7911exc)
+{
+ struct device *dev = lt7911exc->dev;
+ int ret;
+ u8 *buffer;
+ size_t total_size = FW_SIZE - 4;
+
+ ret = request_firmware(<7911exc->fw, FW_FILE, dev);
+ if (ret) {
+ dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
+ return ret;
+ }
+
+ if (lt7911exc->fw->size > total_size) {
+ dev_err(dev, "firmware too large (%zu > %zu)\n", lt7911exc->fw->size, total_size);
+ release_firmware(lt7911exc->fw);
+ lt7911exc->fw = NULL;
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "firmware size: %zu bytes\n", lt7911exc->fw->size);
+
+ buffer = kzalloc(total_size, GFP_KERNEL);
+ if (!buffer) {
+ release_firmware(lt7911exc->fw);
+ lt7911exc->fw = NULL;
+ return -ENOMEM;
+ }
+
+ memset(buffer, 0xff, total_size);
+ memcpy(buffer, lt7911exc->fw->data, lt7911exc->fw->size);
+
+ lt7911exc->fw_crc = cal_crc32_custom(buffer, total_size);
+ dev_dbg(dev, "firmware crc: 0x%08x\n", lt7911exc->fw_crc);
+
+ kfree(buffer);
+ return 0;
+}
+
+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, u64 addr)
+{
+ struct device *dev = lt7911exc->dev;
+ int ret;
+ int page = 0, num = 0, page_len = 0;
+ u64 size, offset;
+ const u8 *data;
+
+ data = lt7911exc->fw->data;
+ size = lt7911exc->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, u64 addr)
+{
+ u8 crc[4];
+ int ret;
+
+ crc[0] = lt7911exc->fw_crc & 0xff;
+ crc[1] = (lt7911exc->fw_crc >> 8) & 0xff;
+ crc[2] = (lt7911exc->fw_crc >> 16) & 0xff;
+ crc[3] = (lt7911exc->fw_crc >> 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_firmware_upgrade(struct lt7911exc *lt7911exc)
+{
+ struct device *dev = lt7911exc->dev;
+ int ret;
+
+ ret = lt7911exc_prepare_firmware_data(lt7911exc);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", lt7911exc->fw->size);
+
+ lt7911exc_block_erase(lt7911exc);
+
+ ret = lt7911exc_write_data(lt7911exc, 0);
+ if (ret < 0) {
+ dev_err(dev, "failed to write firmware data\n");
+ return ret;
+ }
+
+ release_firmware(lt7911exc->fw);
+ lt7911exc->fw = NULL;
+
+ ret = lt7911exc_write_crc(lt7911exc, FW_SIZE - 4);
+ if (ret < 0) {
+ dev_err(dev, "failed to write firmware crc\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc)
+{
+ 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, 4);
+ 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 != lt7911exc->fw_crc) {
+ dev_err(dev, "lt7911exc firmware upgrade failed, expected CRC=0x%08x, read CRC=0x%08x\n",
+ lt7911exc->fw_crc, read_hw_crc);
+ return -EIO;
+ }
+
+ dev_dbg(dev, "lt7911exc firmware upgrade success, CRC=0x%08x\n", read_hw_crc);
+ return 0;
+}
+
+static void lt7911exc_pre_enable(struct drm_bridge *bridge)
+{
+ struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
+ int ret;
+
+ if (lt7911exc->enabled)
+ return;
+
+ ret = lt7911exc_regulator_enable(lt7911exc);
+ if (ret)
+ return;
+
+ lt7911exc_reset(lt7911exc);
+
+ lt7911exc->enabled = true;
+}
+
+static void lt7911exc_disable(struct drm_bridge *bridge)
+{
+ /* Delay after panel is disabled */
+ msleep(20);
+}
+
+static void lt7911exc_post_disable(struct drm_bridge *bridge)
+{
+ struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
+ int ret;
+
+ if (!lt7911exc->enabled)
+ return;
+
+ lt7911exc->enabled = false;
+
+ ret = lt7911exc_regulator_disable(lt7911exc);
+ if (ret)
+ return;
+
+ gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
+}
+
+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->panel_bridge,
+ <7911exc->bridge, flags);
+}
+
+static const struct drm_bridge_funcs lt7911exc_bridge_funcs = {
+ .pre_enable = lt7911exc_pre_enable,
+ .disable = lt7911exc_disable,
+ .post_disable = lt7911exc_post_disable,
+ .attach = lt7911exc_attach,
+};
+
+static int lt7911exc_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct lt7911exc *lt7911exc;
+ struct drm_bridge *panel_bridge;
+ bool fw_updated = false;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "device doesn't support I2C\n");
+ return -ENODEV;
+ }
+
+ lt7911exc = devm_drm_bridge_alloc(dev, struct lt7911exc, bridge,
+ <7911exc_bridge_funcs);
+ if (IS_ERR(lt7911exc))
+ return PTR_ERR(lt7911exc);
+
+ panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
+ if (IS_ERR(panel_bridge))
+ return PTR_ERR(panel_bridge);
+
+ lt7911exc->panel_bridge = panel_bridge;
+ lt7911exc->client = client;
+ lt7911exc->dev = dev;
+ i2c_set_clientdata(client, lt7911exc);
+ mutex_init(<7911exc->ocm_lock);
+
+ lt7911exc->regmap = devm_regmap_init_i2c(client, <7911exc_regmap_config);
+ if (IS_ERR(lt7911exc->regmap)) {
+ dev_err(dev, "regmap i2c init failed\n");
+ return PTR_ERR(lt7911exc->regmap);
+ }
+
+ ret = lt7911exc_parse_dt(lt7911exc);
+ if (ret)
+ return ret;
+
+ ret = lt7911exc_regulator_enable(lt7911exc);
+ if (ret)
+ return ret;
+
+ lt7911exc_reset(lt7911exc);
+ lt7911exc->enabled = true;
+ lt7911exc_lock(lt7911exc);
+
+retry:
+ lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
+ if (lt7911exc->fw_version < 0) {
+ dev_err(dev, "failed to read FW version\n");
+ lt7911exc_unlock(lt7911exc);
+ goto err_disable_regulators;
+
+ } else if (lt7911exc->fw_version == 0) {
+ if (!fw_updated) {
+ fw_updated = true;
+ ret = lt7911exc_firmware_upgrade(lt7911exc);
+ if (ret < 0) {
+ lt7911exc_unlock(lt7911exc);
+ goto err_disable_regulators;
+ }
+
+ lt7911exc_reset(lt7911exc);
+
+ ret = lt7911exc_upgrade_result(lt7911exc);
+ if (ret < 0) {
+ lt7911exc_unlock(lt7911exc);
+ goto err_disable_regulators;
+ }
+
+ goto retry;
+
+ } else {
+ dev_err(dev, "fw version 0x%04x, update failed\n", lt7911exc->fw_version);
+ ret = -EOPNOTSUPP;
+ lt7911exc_unlock(lt7911exc);
+ goto err_disable_regulators;
+ }
+ }
+
+ lt7911exc_unlock(lt7911exc);
+
+ lt7911exc->bridge.type = DRM_MODE_CONNECTOR_DSI;
+ lt7911exc->bridge.of_node = dev->of_node;
+ drm_bridge_add(<7911exc->bridge);
+
+ return 0;
+
+err_disable_regulators:
+ regulator_bulk_disable(ARRAY_SIZE(lt7911exc->supplies), lt7911exc->supplies);
+ if (lt7911exc->fw) {
+ release_firmware(lt7911exc->fw);
+ lt7911exc->fw = NULL;
+ }
+
+ return ret;
+}
+
+static void lt7911exc_remove(struct i2c_client *client)
+{
+ struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
+
+ drm_bridge_remove(<7911exc->bridge);
+ mutex_destroy(<7911exc->ocm_lock);
+}
+
+static const struct i2c_device_id lt7911exc_i2c_table[] = {
+ {"lontium, lt7911exc", 0},
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(i2c, lt7911exc_i2c_table);
+
+static const struct of_device_id lt7911exc_devices[] = {
+ {.compatible = "lontium,lt7911exc",},
+ {}
+};
+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,
+ },
+};
+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 v2");
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH 1/2] dt-bindings:bridge Add LT7911EXC binding
2026-04-20 2:33 ` [PATCH 1/2] dt-bindings:bridge Add LT7911EXC binding syyang
@ 2026-04-20 3:12 ` Dmitry Baryshkov
2026-04-21 1:33 ` 杨孙运
0 siblings, 1 reply; 15+ messages in thread
From: Dmitry Baryshkov @ 2026-04-20 3:12 UTC (permalink / raw)
To: syyang
Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, tzimmermann,
jonas, jernej.skrabec, devicetree, dri-devel, linux-kernel,
yangsunyun1993, xmzhu
ote, your message didn't reach dri-devel. Please check why. You might
need to switch to B4 Web relay submission, if there are any issues
with the SMTP on your side.
On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
>
> From: Sunyun Yang <syyang@lontium.com>
>
> -binding for lt7911exc.
Less is more, but here please settle for slightly more information
about the chip.
>
> Signed-off-by: Sunyun Yang <syyang@lontium.com>
> ---
> .../display/bridge/lontium,lt7911exc.yaml | 91 +++++++++++++++++++
> 1 file changed, 91 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..54a73d41635a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.yaml
> @@ -0,0 +1,91 @@
> +# 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 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 mipi dsi output.
MIPI, DSI
> +
> + port@1:
> + $ref: /schemas/graph.yaml#/properties/port
> + description: Video port for eDP input.
> +
> + 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_HIGH>;
Reset pins are usually active low.
> + vdd-supply = <<7911exc_1v2>;
> + vcc-supply = <<7911exc_3v3>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> +
> + bridge_out: endpoint {
> + remote-endpoint = <&panel_in>;
> + };
> + };
> +
> + port@1 {
> + reg = <1>;
> +
> + bridge_in: endpoint {
> + remote-endpoint = <&edp_out>;
> + };
> + };
> + };
> + };
> + };
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
2026-04-20 2:33 ` [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver syyang
@ 2026-04-20 3:57 ` Dmitry Baryshkov
2026-04-21 3:13 ` 杨孙运
2026-04-20 5:05 ` Quentin Freimanis
1 sibling, 1 reply; 15+ messages in thread
From: Dmitry Baryshkov @ 2026-04-20 3:57 UTC (permalink / raw)
To: syyang
Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, tzimmermann,
jonas, jernej.skrabec, devicetree, dri-devel, linux-kernel,
yangsunyun1993, xmzhu
On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
>
> From: Sunyun Yang <syyang@lontium.com>
>
> LT7911EXC is a high performance eDP1.4 to MIPI chip for
MIPI what?
> VR/Display application.
>
> -eDP1.4Receiver
> 1.Support SSC
> 2.Support 1/2/4 lanes
> 3.Support up to 4K@60HzRGB/YCbCr4:4:48bpc
> 4.Support lane swap and PN swap
>
> -MIPI Transmitter
> 1.CompliantwithD-PHY1.2&DSI1.1&CSI-22.0;1 clock lane,
> and1/2/3/4 configurable data lanes:2.5Gbpsperdatalane
> 2.CompliantwithC-PHY1.0&DSI-21.0&CSI-22.0;
> 1/2/3 configurable data trio;2.5Gsps perdatatrio
> 3.Support1/2configurable ports
> 4.DSISupport16/20/24-bit YCbCr4:2:2,16/18/24/30-bit RGB
>
> Signed-off-by: Sunyun Yang <syyang@lontium.com>
> ---
> drivers/gpu/drm/bridge/Kconfig | 18 +
> drivers/gpu/drm/bridge/Makefile | 1 +
> drivers/gpu/drm/bridge/lontium-lt7911exc.c | 571 +++++++++++++++++++++
> 3 files changed, 590 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..bae8cdaea666 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -202,6 +202,24 @@ config DRM_LONTIUM_LT8713SX
> to 3 configurable Type-C/DP1.4/HDMI2.0 outputs
> Please say Y if you have such hardware.
>
> +config DRM_LONTIUM_LT9611C
I thought the patch is for LT7911EXC
> + tristate "Lontium LT9611C DSI/HDMI bridge"
> + select SND_SOC_HDMI_CODEC if SND_SOC
> + depends on OF
> + select CRC8
> + select FW_LOADER
> + select DRM_PANEL_BRIDGE
> + select DRM_KMS_HELPER
> + select DRM_MIPI_DSI
> + select DRM_DISPLAY_HELPER
> + select DRM_DISPLAY_HDMI_STATE_HELPER
> + select REGMAP_I2C
> + help
> + Driver for Lontium DSI to HDMI bridge
> + chip driver that converts dual DSI and I2S to
> + HDMI signals
> + Please say Y if you have such hardware.
> +
> config DRM_ITE_IT66121
> tristate "ITE IT66121 HDMI bridge"
> depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index beab5b695a6e..54b293d1663e 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
> +obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
Keep the list sorted, please.
> obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
> obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.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..d1c1d9e073ef
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> @@ -0,0 +1,571 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2026 Lontium Semiconductor, Inc.
> + */
> +
> +#include <linux/crc32.h>
> +#include <linux/firmware.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/regmap.h>
I think you use more than that.
> +#include <drm/drm_of.h>
> +
> +#define FW_SIZE (64 * 1024)
> +#define LT_PAGE_SIZE 32
> +#define FW_FILE "LT7911EXC.bin"
> +#define LT7911EXC_PAGE_CONTROL 0xff
> +
> +struct lt7911exc {
> + struct device *dev;
> + struct i2c_client *client;
> + struct drm_bridge bridge;
> + struct drm_bridge *panel_bridge;
Use next_bridge from struct drm_bridge instead.
> + struct regmap *regmap;
> + /* Protects all accesses to registers by stopping the on-chip MCU */
> + struct mutex ocm_lock;
> + struct regulator_bulk_data supplies[2];
> +
> + struct gpio_desc *reset_gpio;
> + const struct firmware *fw;
Do you need to store it during the runtime? If not, please remove from
the data struct.
> + int fw_version;
> + u32 fw_crc;
> +
> + bool enabled;
What for?
> +};
> +
> +static const struct regmap_range_cfg lt7911exc_ranges[] = {
> + {
> + .name = "register_range",
> + .range_min = 0,
> + .range_max = 0xffff,
Is it an actual range?
> + .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 = 0xffff,
> + .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);
How is it different from crc32_le()?
> + }
Wrong alignment.
> +
> + return crc;
> +}
> +
> +static inline struct lt7911exc *
> + bridge_to_lt7911exc(struct drm_bridge *bridge)
One line, please.
> +{
> + return container_of(bridge, struct lt7911exc, bridge);
> +}
> +
> +static int lt7911exc_regulator_enable(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + ret = regulator_enable(lt7911exc->supplies[0].consumer);
If you are not using bulk interface here, why did you declare supplies as bulks?
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(5000, 10000);
> +
> + ret = regulator_enable(lt7911exc->supplies[1].consumer);
> + if (ret < 0) {
> + regulator_disable(lt7911exc->supplies[0].consumer);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_regulator_disable(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + ret = regulator_disable(lt7911exc->supplies[1].consumer);
> + if (ret < 0)
> + return ret;
> +
> + ret = regulator_disable(lt7911exc->supplies[0].consumer);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static void lt7911exc_reset(struct lt7911exc *lt7911exc)
> +{
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> + msleep(20);
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> + msleep(20);
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
Yep, it's active low. Invert polarities here.
> + msleep(400);
> +
> + dev_dbg(lt7911exc->dev, "lt7911exc reset");
> +}
> +
> +static int lt7911exc_parse_dt(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + lt7911exc->supplies[0].supply = "vcc";
> + lt7911exc->supplies[1].supply = "vdd";
> +
> + ret = devm_regulator_bulk_get(lt7911exc->dev, 2, lt7911exc->supplies);
> + if (ret) {
> + dev_err(lt7911exc->dev, "failed get regulator\n");
> + return ret;
return dev_err_probe();
> + }
> +
> + lt7911exc->reset_gpio = devm_gpiod_get(lt7911exc->dev, "reset", GPIOD_OUT_LOW);
> + if (IS_ERR(lt7911exc->reset_gpio)) {
> + dev_err(lt7911exc->dev, "failed to acquire reset gpio\n");
return dev_err_probe();
> + return PTR_ERR(lt7911exc->reset_gpio);
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_read_version(struct lt7911exc *lt7911exc)
> +{
> + u8 buf[2];
> + int ret;
> +
> + ret = regmap_bulk_read(lt7911exc->regmap, 0xe081, buf, 3);
Do you see a buffer overflow here?
> + 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_prepare_firmware_data(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> + u8 *buffer;
> + size_t total_size = FW_SIZE - 4;
> +
> + ret = request_firmware(<7911exc->fw, FW_FILE, dev);
> + if (ret) {
> + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> + return ret;
> + }
> +
> + if (lt7911exc->fw->size > total_size) {
> + dev_err(dev, "firmware too large (%zu > %zu)\n", lt7911exc->fw->size, total_size);
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + return -EINVAL;
> + }
> +
> + dev_dbg(dev, "firmware size: %zu bytes\n", lt7911exc->fw->size);
> +
> + buffer = kzalloc(total_size, GFP_KERNEL);
> + if (!buffer) {
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + return -ENOMEM;
> + }
> +
> + memset(buffer, 0xff, total_size);
> + memcpy(buffer, lt7911exc->fw->data, lt7911exc->fw->size);
> +
> + lt7911exc->fw_crc = cal_crc32_custom(buffer, total_size);
> + dev_dbg(dev, "firmware crc: 0x%08x\n", lt7911exc->fw_crc);
> +
> + kfree(buffer);
> + return 0;
> +}
> +
> +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, u64 addr)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> + int page = 0, num = 0, page_len = 0;
> + u64 size, offset;
> + const u8 *data;
> +
> + data = lt7911exc->fw->data;
> + size = lt7911exc->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, u64 addr)
> +{
> + u8 crc[4];
> + int ret;
> +
> + crc[0] = lt7911exc->fw_crc & 0xff;
> + crc[1] = (lt7911exc->fw_crc >> 8) & 0xff;
> + crc[2] = (lt7911exc->fw_crc >> 16) & 0xff;
> + crc[3] = (lt7911exc->fw_crc >> 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_firmware_upgrade(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> +
> + ret = lt7911exc_prepare_firmware_data(lt7911exc);
> + if (ret < 0)
> + return ret;
> +
> + dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", lt7911exc->fw->size);
> +
> + lt7911exc_block_erase(lt7911exc);
> +
> + ret = lt7911exc_write_data(lt7911exc, 0);
> + if (ret < 0) {
> + dev_err(dev, "failed to write firmware data\n");
> + return ret;
> + }
> +
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> +
> + ret = lt7911exc_write_crc(lt7911exc, FW_SIZE - 4);
> + if (ret < 0) {
> + dev_err(dev, "failed to write firmware crc\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc)
> +{
> + 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, 4);
> + 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 != lt7911exc->fw_crc) {
> + dev_err(dev, "lt7911exc firmware upgrade failed, expected CRC=0x%08x, read CRC=0x%08x\n",
> + lt7911exc->fw_crc, read_hw_crc);
> + return -EIO;
> + }
> +
> + dev_dbg(dev, "lt7911exc firmware upgrade success, CRC=0x%08x\n", read_hw_crc);
> + return 0;
> +}
> +
> +static void lt7911exc_pre_enable(struct drm_bridge *bridge)
> +{
> + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> + int ret;
> +
> + if (lt7911exc->enabled)
> + return;
> +
> + ret = lt7911exc_regulator_enable(lt7911exc);
> + if (ret)
> + return;
> +
> + lt7911exc_reset(lt7911exc);
> +
> + lt7911exc->enabled = true;
> +}
> +
> +static void lt7911exc_disable(struct drm_bridge *bridge)
> +{
> + /* Delay after panel is disabled */
> + msleep(20);
> +}
> +
> +static void lt7911exc_post_disable(struct drm_bridge *bridge)
> +{
> + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> + int ret;
> +
> + if (!lt7911exc->enabled)
> + return;
> +
> + lt7911exc->enabled = false;
> +
> + ret = lt7911exc_regulator_disable(lt7911exc);
> + if (ret)
> + return;
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> +}
> +
> +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->panel_bridge,
> + <7911exc->bridge, flags);
> +}
> +
> +static const struct drm_bridge_funcs lt7911exc_bridge_funcs = {
> + .pre_enable = lt7911exc_pre_enable,
> + .disable = lt7911exc_disable,
> + .post_disable = lt7911exc_post_disable,
> + .attach = lt7911exc_attach,
> +};
> +
> +static int lt7911exc_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct lt7911exc *lt7911exc;
> + struct drm_bridge *panel_bridge;
> + bool fw_updated = false;
> + int ret;
> +
> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> + dev_err(dev, "device doesn't support I2C\n");
> + return -ENODEV;
> + }
> +
> + lt7911exc = devm_drm_bridge_alloc(dev, struct lt7911exc, bridge,
> + <7911exc_bridge_funcs);
> + if (IS_ERR(lt7911exc))
> + return PTR_ERR(lt7911exc);
> +
> + panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
> + if (IS_ERR(panel_bridge))
> + return PTR_ERR(panel_bridge);
> +
> + lt7911exc->panel_bridge = panel_bridge;
> + lt7911exc->client = client;
> + lt7911exc->dev = dev;
> + i2c_set_clientdata(client, lt7911exc);
> + mutex_init(<7911exc->ocm_lock);
devm_mutex_init()
> +
> + lt7911exc->regmap = devm_regmap_init_i2c(client, <7911exc_regmap_config);
> + if (IS_ERR(lt7911exc->regmap)) {
> + dev_err(dev, "regmap i2c init failed\n");
> + return PTR_ERR(lt7911exc->regmap);
> + }
> +
> + ret = lt7911exc_parse_dt(lt7911exc);
> + if (ret)
> + return ret;
> +
> + ret = lt7911exc_regulator_enable(lt7911exc);
> + if (ret)
> + return ret;
> +
> + lt7911exc_reset(lt7911exc);
> + lt7911exc->enabled = true;
> + lt7911exc_lock(lt7911exc);
> +
> +retry:
> + lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
> + if (lt7911exc->fw_version < 0) {
> + dev_err(dev, "failed to read FW version\n");
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> +
> + } else if (lt7911exc->fw_version == 0) {
> + if (!fw_updated) {
> + fw_updated = true;
> + ret = lt7911exc_firmware_upgrade(lt7911exc);
> + if (ret < 0) {
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> +
> + lt7911exc_reset(lt7911exc);
> +
> + ret = lt7911exc_upgrade_result(lt7911exc);
> + if (ret < 0) {
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> +
> + goto retry;
> +
> + } else {
> + dev_err(dev, "fw version 0x%04x, update failed\n", lt7911exc->fw_version);
> + ret = -EOPNOTSUPP;
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> + }
> +
> + lt7911exc_unlock(lt7911exc);
> +
> + lt7911exc->bridge.type = DRM_MODE_CONNECTOR_DSI;
So, this is the DSI host. Where do you register one? Where do you
populate the DT entries (if there is a panel attached to this bridge
it will be a child node).
> + lt7911exc->bridge.of_node = dev->of_node;
> + drm_bridge_add(<7911exc->bridge);
devm_drm_bridge_add().
> +
> + return 0;
> +
> +err_disable_regulators:
> + regulator_bulk_disable(ARRAY_SIZE(lt7911exc->supplies), lt7911exc->supplies);
> + if (lt7911exc->fw) {
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + }
> +
> + return ret;
> +}
> +
> +static void lt7911exc_remove(struct i2c_client *client)
> +{
> + struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
> +
> + drm_bridge_remove(<7911exc->bridge);
> + mutex_destroy(<7911exc->ocm_lock);
> +}
> +
> +static const struct i2c_device_id lt7911exc_i2c_table[] = {
> + {"lontium, lt7911exc", 0},
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, lt7911exc_i2c_table);
> +
> +static const struct of_device_id lt7911exc_devices[] = {
> + {.compatible = "lontium,lt7911exc",},
> + {}
> +};
> +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,
> + },
> +};
> +module_i2c_driver(lt7911exc_driver);
> +
> +MODULE_AUTHOR("SunYun Yang <syyang@lontium.com>");
> +MODULE_DESCRIPTION("Lontium lt7911exc edp to mipi dsi bridge driver");
MIPI, DSI. It LT7911EXC or lt7911exc?
> +MODULE_LICENSE("GPL v2");
> --
> 2.34.1
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
2026-04-20 2:33 ` [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver syyang
2026-04-20 3:57 ` Dmitry Baryshkov
@ 2026-04-20 5:05 ` Quentin Freimanis
2026-04-21 3:26 ` 杨孙运
1 sibling, 1 reply; 15+ messages in thread
From: Quentin Freimanis @ 2026-04-20 5:05 UTC (permalink / raw)
To: syyang, 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, yangsunyun1993, xmzhu
On 2026-04-19 7:33 p.m., syyang@lontium.com wrote:
> From: Sunyun Yang <syyang@lontium.com>
>
> LT7911EXC is a high performance eDP1.4 to MIPI chip for
> VR/Display application.
>
> -eDP1.4Receiver
> 1.Support SSC
s/1.Support/1. Supports/
> 2.Support 1/2/4 lanes
> 3.Support up to 4K@60HzRGB/YCbCr4:4:48bpc
> 4.Support lane swap and PN swap
Same for these
>
> -MIPI Transmitter
> 1.CompliantwithD-PHY1.2&DSI1.1&CSI-22.0;1 clock lane,
> and1/2/3/4 configurable data lanes:2.5Gbpsperdatalane
> 2.CompliantwithC-PHY1.0&DSI-21.0&CSI-22.0;
> 1/2/3 configurable data trio;2.5Gsps perdatatrio
> 3.Support1/2configurable ports
> 4.DSISupport16/20/24-bit YCbCr4:2:2,16/18/24/30-bit RGB
Missing spaces, this is hard to read and needs to be cleaned up
>
> Signed-off-by: Sunyun Yang <syyang@lontium.com>
> ---
> drivers/gpu/drm/bridge/Kconfig | 18 +
> drivers/gpu/drm/bridge/Makefile | 1 +
> drivers/gpu/drm/bridge/lontium-lt7911exc.c | 571 +++++++++++++++++++++
> 3 files changed, 590 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..bae8cdaea666 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -202,6 +202,24 @@ config DRM_LONTIUM_LT8713SX
> to 3 configurable Type-C/DP1.4/HDMI2.0 outputs
> Please say Y if you have such hardware.
>
> +config DRM_LONTIUM_LT9611C
> + tristate "Lontium LT9611C DSI/HDMI bridge"
> + select SND_SOC_HDMI_CODEC if SND_SOC
> + depends on OF
> + select CRC8
> + select FW_LOADER
> + select DRM_PANEL_BRIDGE
> + select DRM_KMS_HELPER
> + select DRM_MIPI_DSI
> + select DRM_DISPLAY_HELPER
> + select DRM_DISPLAY_HDMI_STATE_HELPER
> + select REGMAP_I2C
> + help
> + Driver for Lontium DSI to HDMI bridge
> + chip driver that converts dual DSI and I2S to
> + HDMI signals
> + Please say Y if you have such hardware.
> +
> config DRM_ITE_IT66121
> tristate "ITE IT66121 HDMI bridge"
> depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index beab5b695a6e..54b293d1663e 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
> +obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
> obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
> obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.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..d1c1d9e073ef
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> @@ -0,0 +1,571 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2026 Lontium Semiconductor, Inc.
> + */
> +
> +#include <linux/crc32.h>
> +#include <linux/firmware.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/regmap.h>
> +#include <drm/drm_of.h>
> +
> +#define FW_SIZE (64 * 1024)
> +#define LT_PAGE_SIZE 32
> +#define FW_FILE "LT7911EXC.bin"
Other lontium bridge chips use lowercase and a _fw.bin suffix, such as
"lt9611uxc_fw.bin". Rename to be consistent
> +#define LT7911EXC_PAGE_CONTROL 0xff
> +
> +struct lt7911exc {
> + struct device *dev;
> + struct i2c_client *client;
> + struct drm_bridge bridge;
> + struct drm_bridge *panel_bridge;
> + struct regmap *regmap;
> + /* Protects all accesses to registers by stopping the on-chip MCU */
> + struct mutex ocm_lock;
> + struct regulator_bulk_data supplies[2];
> +
> + struct gpio_desc *reset_gpio;
> + const struct firmware *fw;
> + int fw_version;
> + u32 fw_crc;
> +
> + bool enabled;
> +};
> +
> +static const struct regmap_range_cfg lt7911exc_ranges[] = {
> + {
> + .name = "register_range",
> + .range_min = 0,
> + .range_max = 0xffff,
> + .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 = 0xffff,
> + .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 int lt7911exc_regulator_enable(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + ret = regulator_enable(lt7911exc->supplies[0].consumer);
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(5000, 10000);
> +
> + ret = regulator_enable(lt7911exc->supplies[1].consumer);
> + if (ret < 0) {
> + regulator_disable(lt7911exc->supplies[0].consumer);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_regulator_disable(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + ret = regulator_disable(lt7911exc->supplies[1].consumer);
> + if (ret < 0)
> + return ret;
> +
> + ret = regulator_disable(lt7911exc->supplies[0].consumer);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static void lt7911exc_reset(struct lt7911exc *lt7911exc)
> +{
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> + msleep(20);
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> + msleep(20);
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> + msleep(400);
> +
> + dev_dbg(lt7911exc->dev, "lt7911exc reset");
missing newline in dev_dbg(), all other calls have it.
> +}
> +
> +static int lt7911exc_parse_dt(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + lt7911exc->supplies[0].supply = "vcc";
> + lt7911exc->supplies[1].supply = "vdd";
> +
> + ret = devm_regulator_bulk_get(lt7911exc->dev, 2, lt7911exc->supplies);
> + if (ret) {
> + dev_err(lt7911exc->dev, "failed get regulator\n");
> + return ret;
> + }
> +
> + lt7911exc->reset_gpio = devm_gpiod_get(lt7911exc->dev, "reset", GPIOD_OUT_LOW);
> + if (IS_ERR(lt7911exc->reset_gpio)) {
> + dev_err(lt7911exc->dev, "failed to acquire reset gpio\n");
> + return PTR_ERR(lt7911exc->reset_gpio);
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_read_version(struct lt7911exc *lt7911exc)
> +{
> + u8 buf[2];
> + int ret;
> +
> + ret = regmap_bulk_read(lt7911exc->regmap, 0xe081, buf, 3);
> + 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_prepare_firmware_data(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> + u8 *buffer;
> + size_t total_size = FW_SIZE - 4;
> +
> + ret = request_firmware(<7911exc->fw, FW_FILE, dev);
> + if (ret) {
> + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> + return ret;
> + }
> +
> + if (lt7911exc->fw->size > total_size) {
> + dev_err(dev, "firmware too large (%zu > %zu)\n", lt7911exc->fw->size, total_size);
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + return -EINVAL;
> + }
> +
> + dev_dbg(dev, "firmware size: %zu bytes\n", lt7911exc->fw->size);
> +
> + buffer = kzalloc(total_size, GFP_KERNEL);
> + if (!buffer) {
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + return -ENOMEM;
> + }
> +
> + memset(buffer, 0xff, total_size);
> + memcpy(buffer, lt7911exc->fw->data, lt7911exc->fw->size);
> +
> + lt7911exc->fw_crc = cal_crc32_custom(buffer, total_size);
> + dev_dbg(dev, "firmware crc: 0x%08x\n", lt7911exc->fw_crc);
> +
> + kfree(buffer);
> + return 0;
> +}
> +
> +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, u64 addr)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> + int page = 0, num = 0, page_len = 0;
> + u64 size, offset;
> + const u8 *data;
> +
> + data = lt7911exc->fw->data;
> + size = lt7911exc->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, u64 addr)
> +{
> + u8 crc[4];
> + int ret;
> +
> + crc[0] = lt7911exc->fw_crc & 0xff;
> + crc[1] = (lt7911exc->fw_crc >> 8) & 0xff;
> + crc[2] = (lt7911exc->fw_crc >> 16) & 0xff;
> + crc[3] = (lt7911exc->fw_crc >> 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;
nit: Newline here makes it more readable
> + 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_firmware_upgrade(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> +
> + ret = lt7911exc_prepare_firmware_data(lt7911exc);
> + if (ret < 0)
> + return ret;
> +
> + dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", lt7911exc->fw->size);
> +
> + lt7911exc_block_erase(lt7911exc);
> +
> + ret = lt7911exc_write_data(lt7911exc, 0);
> + if (ret < 0) {
> + dev_err(dev, "failed to write firmware data\n");
> + return ret;
> + }
> +
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> +
> + ret = lt7911exc_write_crc(lt7911exc, FW_SIZE - 4);
> + if (ret < 0) {
> + dev_err(dev, "failed to write firmware crc\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc)
> +{
> + 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, 4);
> + 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 != lt7911exc->fw_crc) {
> + dev_err(dev, "lt7911exc firmware upgrade failed, expected CRC=0x%08x, read CRC=0x%08x\n",
> + lt7911exc->fw_crc, read_hw_crc);
> + return -EIO;
> + }
> +
> + dev_dbg(dev, "lt7911exc firmware upgrade success, CRC=0x%08x\n", read_hw_crc);
> + return 0;
> +}
> +
> +static void lt7911exc_pre_enable(struct drm_bridge *bridge)
> +{
> + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> + int ret;
> +
> + if (lt7911exc->enabled)
> + return;
> +
> + ret = lt7911exc_regulator_enable(lt7911exc);
> + if (ret)
> + return;
> +
> + lt7911exc_reset(lt7911exc);
> +
> + lt7911exc->enabled = true;
> +}
> +
> +static void lt7911exc_disable(struct drm_bridge *bridge)
> +{
> + /* Delay after panel is disabled */
> + msleep(20);
> +}
> +
> +static void lt7911exc_post_disable(struct drm_bridge *bridge)
> +{
> + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> + int ret;
> +
> + if (!lt7911exc->enabled)
> + return;
> +
> + lt7911exc->enabled = false;
> +
> + ret = lt7911exc_regulator_disable(lt7911exc);
> + if (ret)
> + return;
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> +}
> +
> +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->panel_bridge,
> + <7911exc->bridge, flags);
> +}
> +
> +static const struct drm_bridge_funcs lt7911exc_bridge_funcs = {
> + .pre_enable = lt7911exc_pre_enable,
> + .disable = lt7911exc_disable,
> + .post_disable = lt7911exc_post_disable,
These are all deprecated, is there any reason to use them and not the
atomic_* callbacks?
> + .attach = lt7911exc_attach,
> +};
> +
> +static int lt7911exc_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct lt7911exc *lt7911exc;
> + struct drm_bridge *panel_bridge;
> + bool fw_updated = false;
> + int ret;
> +
> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> + dev_err(dev, "device doesn't support I2C\n");
> + return -ENODEV;
> + }
> +
> + lt7911exc = devm_drm_bridge_alloc(dev, struct lt7911exc, bridge,
> + <7911exc_bridge_funcs);
> + if (IS_ERR(lt7911exc))
> + return PTR_ERR(lt7911exc);
> +
> + panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
> + if (IS_ERR(panel_bridge))
> + return PTR_ERR(panel_bridge);
> +
> + lt7911exc->panel_bridge = panel_bridge;
> + lt7911exc->client = client;
> + lt7911exc->dev = dev;
> + i2c_set_clientdata(client, lt7911exc);
> + mutex_init(<7911exc->ocm_lock);
> +
> + lt7911exc->regmap = devm_regmap_init_i2c(client, <7911exc_regmap_config);
> + if (IS_ERR(lt7911exc->regmap)) {
> + dev_err(dev, "regmap i2c init failed\n");
> + return PTR_ERR(lt7911exc->regmap);
> + }
> +
> + ret = lt7911exc_parse_dt(lt7911exc);
> + if (ret)
> + return ret;
> +
> + ret = lt7911exc_regulator_enable(lt7911exc);
> + if (ret)
> + return ret;
> +
> + lt7911exc_reset(lt7911exc);
> + lt7911exc->enabled = true;
> + lt7911exc_lock(lt7911exc);
> +
> +retry:
> + lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
> + if (lt7911exc->fw_version < 0) {
> + dev_err(dev, "failed to read FW version\n");
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> +
> + } else if (lt7911exc->fw_version == 0) {
> + if (!fw_updated) {
> + fw_updated = true;
> + ret = lt7911exc_firmware_upgrade(lt7911exc);
> + if (ret < 0) {
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> +
> + lt7911exc_reset(lt7911exc);
> +
> + ret = lt7911exc_upgrade_result(lt7911exc);
> + if (ret < 0) {
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> +
> + goto retry;
> +
> + } else {
> + dev_err(dev, "fw version 0x%04x, update failed\n", lt7911exc->fw_version);
> + ret = -EOPNOTSUPP;
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> + }
> +
> + lt7911exc_unlock(lt7911exc);
> +
> + lt7911exc->bridge.type = DRM_MODE_CONNECTOR_DSI;
> + lt7911exc->bridge.of_node = dev->of_node;
> + drm_bridge_add(<7911exc->bridge);
> +
> + return 0;
> +
> +err_disable_regulators:
> + regulator_bulk_disable(ARRAY_SIZE(lt7911exc->supplies), lt7911exc->supplies);
> + if (lt7911exc->fw) {
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + }
> +
> + return ret;
> +}
> +
> +static void lt7911exc_remove(struct i2c_client *client)
> +{
> + struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
> +
> + drm_bridge_remove(<7911exc->bridge);
> + mutex_destroy(<7911exc->ocm_lock);
> +}
> +
> +static const struct i2c_device_id lt7911exc_i2c_table[] = {
> + {"lontium, lt7911exc", 0},
Extra space here in the device id
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, lt7911exc_i2c_table);
> +
> +static const struct of_device_id lt7911exc_devices[] = {
> + {.compatible = "lontium,lt7911exc",},
> + {}
> +};
> +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,
> + },
> +};
> +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 v2");
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/2] dt-bindings:bridge Add LT7911EXC binding
2026-04-20 3:12 ` Dmitry Baryshkov
@ 2026-04-21 1:33 ` 杨孙运
2026-04-21 11:07 ` Dmitry Baryshkov
0 siblings, 1 reply; 15+ messages in thread
From: 杨孙运 @ 2026-04-21 1:33 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, jonas,
jernej.skrabec, devicetree, dri-devel, linux-kernel, xmzhu,
tzimmermann, rlyu, xbpeng
Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月20日周一 11:12写道:
>
> ote, your message didn't reach dri-devel. Please check why. You might
> need to switch to B4 Web relay submission, if there are any issues
> with the SMTP on your side.
>
> On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
> >
> > From: Sunyun Yang <syyang@lontium.com>
> >
> > -binding for lt7911exc.
>
> Less is more, but here please settle for slightly more information
> about the chip.
>
It will be modified in the next version.
> >
> > Signed-off-by: Sunyun Yang <syyang@lontium.com>
> > ---
> > .../display/bridge/lontium,lt7911exc.yaml | 91 +++++++++++++++++++
> > 1 file changed, 91 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..54a73d41635a
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.yaml
> > @@ -0,0 +1,91 @@
> > +# 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 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 mipi dsi output.
>
> MIPI, DSI
>
It will be use MIPI DSI, in the next version.
> > +
> > + port@1:
> > + $ref: /schemas/graph.yaml#/properties/port
> > + description: Video port for eDP input.
> > +
> > + 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_HIGH>;
>
> Reset pins are usually active low.
>
if reset pins use active low, lt7911exc_reset function needs to be modified to:
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");
}
I think the two are essentially the same. Please confirm whether I
need to make the change?
> > + vdd-supply = <<7911exc_1v2>;
> > + vcc-supply = <<7911exc_3v3>;
> > +
> > + ports {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + port@0 {
> > + reg = <0>;
> > +
> > + bridge_out: endpoint {
> > + remote-endpoint = <&panel_in>;
> > + };
> > + };
> > +
> > + port@1 {
> > + reg = <1>;
> > +
> > + bridge_in: endpoint {
> > + remote-endpoint = <&edp_out>;
> > + };
> > + };
> > + };
> > + };
> > + };
>
> --
> With best wishes
> Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
2026-04-20 3:57 ` Dmitry Baryshkov
@ 2026-04-21 3:13 ` 杨孙运
2026-04-21 11:15 ` Dmitry Baryshkov
0 siblings, 1 reply; 15+ messages in thread
From: 杨孙运 @ 2026-04-21 3:13 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, jonas,
jernej.skrabec, devicetree, dri-devel, linux-kernel, xmzhu,
tzimmermann, xbpeng, rlyu, xmzhu
Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月20日周一 11:57写道:
>
> On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
> >
> > From: Sunyun Yang <syyang@lontium.com>
> >
> > LT7911EXC is a high performance eDP1.4 to MIPI chip for
>
> MIPI what?
>
MIPI DSI,
It will be modified in the next version.
> > VR/Display application.
> >
> > -eDP1.4Receiver
> > 1.Support SSC
> > 2.Support 1/2/4 lanes
> > 3.Support up to 4K@60HzRGB/YCbCr4:4:48bpc
> > 4.Support lane swap and PN swap
> >
> > -MIPI Transmitter
> > 1.CompliantwithD-PHY1.2&DSI1.1&CSI-22.0;1 clock lane,
> > and1/2/3/4 configurable data lanes:2.5Gbpsperdatalane
> > 2.CompliantwithC-PHY1.0&DSI-21.0&CSI-22.0;
> > 1/2/3 configurable data trio;2.5Gsps perdatatrio
> > 3.Support1/2configurable ports
> > 4.DSISupport16/20/24-bit YCbCr4:2:2,16/18/24/30-bit RGB
> >
> > Signed-off-by: Sunyun Yang <syyang@lontium.com>
> > ---
> > drivers/gpu/drm/bridge/Kconfig | 18 +
> > drivers/gpu/drm/bridge/Makefile | 1 +
> > drivers/gpu/drm/bridge/lontium-lt7911exc.c | 571 +++++++++++++++++++++
> > 3 files changed, 590 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..bae8cdaea666 100644
> > --- a/drivers/gpu/drm/bridge/Kconfig
> > +++ b/drivers/gpu/drm/bridge/Kconfig
> > @@ -202,6 +202,24 @@ config DRM_LONTIUM_LT8713SX
> > to 3 configurable Type-C/DP1.4/HDMI2.0 outputs
> > Please say Y if you have such hardware.
> >
> > +config DRM_LONTIUM_LT9611C
>
> I thought the patch is for LT7911EXC
>
Yes, it is LT7911EXC, It will be modified in the next version.
> > + tristate "Lontium LT9611C DSI/HDMI bridge"
> > + select SND_SOC_HDMI_CODEC if SND_SOC
> > + depends on OF
> > + select CRC8
> > + select FW_LOADER
> > + select DRM_PANEL_BRIDGE
> > + select DRM_KMS_HELPER
> > + select DRM_MIPI_DSI
> > + select DRM_DISPLAY_HELPER
> > + select DRM_DISPLAY_HDMI_STATE_HELPER
> > + select REGMAP_I2C
> > + help
> > + Driver for Lontium DSI to HDMI bridge
> > + chip driver that converts dual DSI and I2S to
> > + HDMI signals
> > + Please say Y if you have such hardware.
> > +
> > config DRM_ITE_IT66121
> > tristate "ITE IT66121 HDMI bridge"
> > depends on OF
> > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > index beab5b695a6e..54b293d1663e 100644
> > --- a/drivers/gpu/drm/bridge/Makefile
> > +++ b/drivers/gpu/drm/bridge/Makefile
> > @@ -18,6 +18,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> > obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> > obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> > obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
> > +obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
>
> Keep the list sorted, please.
>
Where can I see the sorting rules? Please guide me.
it is:
obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
?
> > obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> > obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
> > obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.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..d1c1d9e073ef
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> > @@ -0,0 +1,571 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2026 Lontium Semiconductor, Inc.
> > + */
> > +
> > +#include <linux/crc32.h>
> > +#include <linux/firmware.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/regmap.h>
>
> I think you use more than that.
>
It will be modified in the next version.
> > +#include <drm/drm_of.h>
> > +
> > +#define FW_SIZE (64 * 1024)
> > +#define LT_PAGE_SIZE 32
> > +#define FW_FILE "LT7911EXC.bin"
> > +#define LT7911EXC_PAGE_CONTROL 0xff
> > +
> > +struct lt7911exc {
> > + struct device *dev;
> > + struct i2c_client *client;
> > + struct drm_bridge bridge;
> > + struct drm_bridge *panel_bridge;
>
> Use next_bridge from struct drm_bridge instead.
>
It will be modified in the next version.
> > + struct regmap *regmap;
> > + /* Protects all accesses to registers by stopping the on-chip MCU */
> > + struct mutex ocm_lock;
> > + struct regulator_bulk_data supplies[2];
> > +
> > + struct gpio_desc *reset_gpio;
> > + const struct firmware *fw;
>
> Do you need to store it during the runtime? If not, please remove from
> the data struct.
>
Don't need store during the runtime.
Can I use the global variable 'fw'?
Because I need use 'fw' to calculate the CRC32 and burn the firmware.
> > + int fw_version;
> > + u32 fw_crc;
> > +
> > + bool enabled;
>
> What for?
>
(bool enabled;) is used as a flag in the code to reduce the frequency
of power supply switching.
Of course, it can also be removed in the next version.
> > +};
> > +
> > +static const struct regmap_range_cfg lt7911exc_ranges[] = {
> > + {
> > + .name = "register_range",
> > + .range_min = 0,
> > + .range_max = 0xffff,
>
> Is it an actual range?
>
0xe8ff is actual range.
> > + .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 = 0xffff,
> > + .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);
>
> How is it different from crc32_le()?
>
The implementation differs from crc32_le() in both byte ordering and
processing granularity.
This function performs a 32-bit word-wise byte swap (little-endian to
big-endian) before feeding data into crc32_be(), while crc32_le()
processes the input stream directly in little-endian order without
transformation.
Therefore, the result is not equivalent to crc32_le(), and is required
to match the firmware's expected big-endian word-based CRC format.
> > + }
>
> Wrong alignment.
>
it will be fixed in the next version
> > +
> > + return crc;
> > +}
> > +
> > +static inline struct lt7911exc *
> > + bridge_to_lt7911exc(struct drm_bridge *bridge)
>
> One line, please.
>
it will be fixed in the next version
> > +{
> > + return container_of(bridge, struct lt7911exc, bridge);
> > +}
> > +
> > +static int lt7911exc_regulator_enable(struct lt7911exc *lt7911exc)
> > +{
> > + int ret;
> > +
> > + ret = regulator_enable(lt7911exc->supplies[0].consumer);
>
> If you are not using bulk interface here, why did you declare supplies as bulks?
>
it will be fixed in the next version
> > + if (ret < 0)
> > + return ret;
> > +
> > + usleep_range(5000, 10000);
> > +
> > + ret = regulator_enable(lt7911exc->supplies[1].consumer);
> > + if (ret < 0) {
> > + regulator_disable(lt7911exc->supplies[0].consumer);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lt7911exc_regulator_disable(struct lt7911exc *lt7911exc)
> > +{
> > + int ret;
> > +
> > + ret = regulator_disable(lt7911exc->supplies[1].consumer);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = regulator_disable(lt7911exc->supplies[0].consumer);
> > + if (ret < 0)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +
> > +static void lt7911exc_reset(struct lt7911exc *lt7911exc)
> > +{
> > + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> > + msleep(20);
> > +
> > + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> > + msleep(20);
> > +
> > + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
>
> Yep, it's active low. Invert polarities here.
>
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);
next version, fixed.
> > + msleep(400);
> > +
> > + dev_dbg(lt7911exc->dev, "lt7911exc reset");
> > +}
> > +
> > +static int lt7911exc_parse_dt(struct lt7911exc *lt7911exc)
> > +{
> > + int ret;
> > +
> > + lt7911exc->supplies[0].supply = "vcc";
> > + lt7911exc->supplies[1].supply = "vdd";
> > +
> > + ret = devm_regulator_bulk_get(lt7911exc->dev, 2, lt7911exc->supplies);
> > + if (ret) {
> > + dev_err(lt7911exc->dev, "failed get regulator\n");
> > + return ret;
>
> return dev_err_probe();
>
it will be fixed in the next version
> > + }
> > +
> > + lt7911exc->reset_gpio = devm_gpiod_get(lt7911exc->dev, "reset", GPIOD_OUT_LOW);
> > + if (IS_ERR(lt7911exc->reset_gpio)) {
> > + dev_err(lt7911exc->dev, "failed to acquire reset gpio\n");
>
> return dev_err_probe();
>
it will be fixed in the next version
> > + return PTR_ERR(lt7911exc->reset_gpio);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lt7911exc_read_version(struct lt7911exc *lt7911exc)
> > +{
> > + u8 buf[2];
> > + int ret;
> > +
> > + ret = regmap_bulk_read(lt7911exc->regmap, 0xe081, buf, 3);
>
> Do you see a buffer overflow here?
>
sorry, it will be fixed in the next version
> > + 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_prepare_firmware_data(struct lt7911exc *lt7911exc)
> > +{
> > + struct device *dev = lt7911exc->dev;
> > + int ret;
> > + u8 *buffer;
> > + size_t total_size = FW_SIZE - 4;
> > +
> > + ret = request_firmware(<7911exc->fw, FW_FILE, dev);
> > + if (ret) {
> > + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> > + return ret;
> > + }
> > +
> > + if (lt7911exc->fw->size > total_size) {
> > + dev_err(dev, "firmware too large (%zu > %zu)\n", lt7911exc->fw->size, total_size);
> > + release_firmware(lt7911exc->fw);
> > + lt7911exc->fw = NULL;
> > + return -EINVAL;
> > + }
> > +
> > + dev_dbg(dev, "firmware size: %zu bytes\n", lt7911exc->fw->size);
> > +
> > + buffer = kzalloc(total_size, GFP_KERNEL);
> > + if (!buffer) {
> > + release_firmware(lt7911exc->fw);
> > + lt7911exc->fw = NULL;
> > + return -ENOMEM;
> > + }
> > +
> > + memset(buffer, 0xff, total_size);
> > + memcpy(buffer, lt7911exc->fw->data, lt7911exc->fw->size);
> > +
> > + lt7911exc->fw_crc = cal_crc32_custom(buffer, total_size);
> > + dev_dbg(dev, "firmware crc: 0x%08x\n", lt7911exc->fw_crc);
> > +
> > + kfree(buffer);
> > + return 0;
> > +}
> > +
> > +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, u64 addr)
> > +{
> > + struct device *dev = lt7911exc->dev;
> > + int ret;
> > + int page = 0, num = 0, page_len = 0;
> > + u64 size, offset;
> > + const u8 *data;
> > +
> > + data = lt7911exc->fw->data;
> > + size = lt7911exc->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, u64 addr)
> > +{
> > + u8 crc[4];
> > + int ret;
> > +
> > + crc[0] = lt7911exc->fw_crc & 0xff;
> > + crc[1] = (lt7911exc->fw_crc >> 8) & 0xff;
> > + crc[2] = (lt7911exc->fw_crc >> 16) & 0xff;
> > + crc[3] = (lt7911exc->fw_crc >> 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_firmware_upgrade(struct lt7911exc *lt7911exc)
> > +{
> > + struct device *dev = lt7911exc->dev;
> > + int ret;
> > +
> > + ret = lt7911exc_prepare_firmware_data(lt7911exc);
> > + if (ret < 0)
> > + return ret;
> > +
> > + dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", lt7911exc->fw->size);
> > +
> > + lt7911exc_block_erase(lt7911exc);
> > +
> > + ret = lt7911exc_write_data(lt7911exc, 0);
> > + if (ret < 0) {
> > + dev_err(dev, "failed to write firmware data\n");
> > + return ret;
> > + }
> > +
> > + release_firmware(lt7911exc->fw);
> > + lt7911exc->fw = NULL;
> > +
> > + ret = lt7911exc_write_crc(lt7911exc, FW_SIZE - 4);
> > + if (ret < 0) {
> > + dev_err(dev, "failed to write firmware crc\n");
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc)
> > +{
> > + 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, 4);
> > + 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 != lt7911exc->fw_crc) {
> > + dev_err(dev, "lt7911exc firmware upgrade failed, expected CRC=0x%08x, read CRC=0x%08x\n",
> > + lt7911exc->fw_crc, read_hw_crc);
> > + return -EIO;
> > + }
> > +
> > + dev_dbg(dev, "lt7911exc firmware upgrade success, CRC=0x%08x\n", read_hw_crc);
> > + return 0;
> > +}
> > +
> > +static void lt7911exc_pre_enable(struct drm_bridge *bridge)
> > +{
> > + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> > + int ret;
> > +
> > + if (lt7911exc->enabled)
> > + return;
> > +
> > + ret = lt7911exc_regulator_enable(lt7911exc);
> > + if (ret)
> > + return;
> > +
> > + lt7911exc_reset(lt7911exc);
> > +
> > + lt7911exc->enabled = true;
> > +}
> > +
> > +static void lt7911exc_disable(struct drm_bridge *bridge)
> > +{
> > + /* Delay after panel is disabled */
> > + msleep(20);
> > +}
> > +
> > +static void lt7911exc_post_disable(struct drm_bridge *bridge)
> > +{
> > + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> > + int ret;
> > +
> > + if (!lt7911exc->enabled)
> > + return;
> > +
> > + lt7911exc->enabled = false;
> > +
> > + ret = lt7911exc_regulator_disable(lt7911exc);
> > + if (ret)
> > + return;
> > +
> > + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> > +}
> > +
> > +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->panel_bridge,
> > + <7911exc->bridge, flags);
> > +}
> > +
> > +static const struct drm_bridge_funcs lt7911exc_bridge_funcs = {
> > + .pre_enable = lt7911exc_pre_enable,
> > + .disable = lt7911exc_disable,
> > + .post_disable = lt7911exc_post_disable,
> > + .attach = lt7911exc_attach,
> > +};
> > +
> > +static int lt7911exc_probe(struct i2c_client *client)
> > +{
> > + struct device *dev = &client->dev;
> > + struct lt7911exc *lt7911exc;
> > + struct drm_bridge *panel_bridge;
> > + bool fw_updated = false;
> > + int ret;
> > +
> > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> > + dev_err(dev, "device doesn't support I2C\n");
> > + return -ENODEV;
> > + }
> > +
> > + lt7911exc = devm_drm_bridge_alloc(dev, struct lt7911exc, bridge,
> > + <7911exc_bridge_funcs);
> > + if (IS_ERR(lt7911exc))
> > + return PTR_ERR(lt7911exc);
> > +
> > + panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
> > + if (IS_ERR(panel_bridge))
> > + return PTR_ERR(panel_bridge);
> > +
> > + lt7911exc->panel_bridge = panel_bridge;
> > + lt7911exc->client = client;
> > + lt7911exc->dev = dev;
> > + i2c_set_clientdata(client, lt7911exc);
> > + mutex_init(<7911exc->ocm_lock);
>
> devm_mutex_init()
>
it will be fixed in the next version
> > +
> > + lt7911exc->regmap = devm_regmap_init_i2c(client, <7911exc_regmap_config);
> > + if (IS_ERR(lt7911exc->regmap)) {
> > + dev_err(dev, "regmap i2c init failed\n");
> > + return PTR_ERR(lt7911exc->regmap);
> > + }
> > +
> > + ret = lt7911exc_parse_dt(lt7911exc);
> > + if (ret)
> > + return ret;
> > +
> > + ret = lt7911exc_regulator_enable(lt7911exc);
> > + if (ret)
> > + return ret;
> > +
> > + lt7911exc_reset(lt7911exc);
> > + lt7911exc->enabled = true;
> > + lt7911exc_lock(lt7911exc);
> > +
> > +retry:
> > + lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
> > + if (lt7911exc->fw_version < 0) {
> > + dev_err(dev, "failed to read FW version\n");
> > + lt7911exc_unlock(lt7911exc);
> > + goto err_disable_regulators;
> > +
> > + } else if (lt7911exc->fw_version == 0) {
> > + if (!fw_updated) {
> > + fw_updated = true;
> > + ret = lt7911exc_firmware_upgrade(lt7911exc);
> > + if (ret < 0) {
> > + lt7911exc_unlock(lt7911exc);
> > + goto err_disable_regulators;
> > + }
> > +
> > + lt7911exc_reset(lt7911exc);
> > +
> > + ret = lt7911exc_upgrade_result(lt7911exc);
> > + if (ret < 0) {
> > + lt7911exc_unlock(lt7911exc);
> > + goto err_disable_regulators;
> > + }
> > +
> > + goto retry;
> > +
> > + } else {
> > + dev_err(dev, "fw version 0x%04x, update failed\n", lt7911exc->fw_version);
> > + ret = -EOPNOTSUPP;
> > + lt7911exc_unlock(lt7911exc);
> > + goto err_disable_regulators;
> > + }
> > + }
> > +
> > + lt7911exc_unlock(lt7911exc);
> > +
> > + lt7911exc->bridge.type = DRM_MODE_CONNECTOR_DSI;
>
> So, this is the DSI host. Where do you register one? Where do you
> populate the DT entries (if there is a panel attached to this bridge
> it will be a child node).
>
it will be fixed in the next version
> > + lt7911exc->bridge.of_node = dev->of_node;
> > + drm_bridge_add(<7911exc->bridge);
>
> devm_drm_bridge_add().
>
it will be fixed in the next version
> > +
> > + return 0;
> > +
> > +err_disable_regulators:
> > + regulator_bulk_disable(ARRAY_SIZE(lt7911exc->supplies), lt7911exc->supplies);
> > + if (lt7911exc->fw) {
> > + release_firmware(lt7911exc->fw);
> > + lt7911exc->fw = NULL;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static void lt7911exc_remove(struct i2c_client *client)
> > +{
> > + struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
> > +
> > + drm_bridge_remove(<7911exc->bridge);
> > + mutex_destroy(<7911exc->ocm_lock);
> > +}
> > +
> > +static const struct i2c_device_id lt7911exc_i2c_table[] = {
> > + {"lontium, lt7911exc", 0},
> > + { /* sentinel */ }
> > +};
> > +
> > +MODULE_DEVICE_TABLE(i2c, lt7911exc_i2c_table);
> > +
> > +static const struct of_device_id lt7911exc_devices[] = {
> > + {.compatible = "lontium,lt7911exc",},
> > + {}
> > +};
> > +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,
> > + },
> > +};
> > +module_i2c_driver(lt7911exc_driver);
> > +
> > +MODULE_AUTHOR("SunYun Yang <syyang@lontium.com>");
> > +MODULE_DESCRIPTION("Lontium lt7911exc edp to mipi dsi bridge driver");
>
> MIPI, DSI. It LT7911EXC or lt7911exc?
>
it will be fixed in the next version:Lontium lLT7911EXC to MIPI DSI
bridge driver
> > +MODULE_LICENSE("GPL v2");
Should GPL be used here? I got an error during testing: GPL v2;
> > --
> > 2.34.1
> >
>
>
> --
> With best wishes
> Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
2026-04-20 5:05 ` Quentin Freimanis
@ 2026-04-21 3:26 ` 杨孙运
0 siblings, 0 replies; 15+ messages in thread
From: 杨孙运 @ 2026-04-21 3:26 UTC (permalink / raw)
To: Quentin Freimanis
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
Laurent.pinchart, jonas, jernej.skrabec, devicetree, dri-devel,
linux-kernel, xmzhu, tzimmermann, xmzhu, xbpeng, rlyu
Quentin Freimanis <quentin@q-lab.dev> 于2026年4月20日周一 13:06写道:
>
> On 2026-04-19 7:33 p.m., syyang@lontium.com wrote:
> > From: Sunyun Yang <syyang@lontium.com>
> >
> > LT7911EXC is a high performance eDP1.4 to MIPI chip for
> > VR/Display application.
> >
> > -eDP1.4Receiver
> > 1.Support SSC
>
> s/1.Support/1. Supports/
>
it will be fixed in the next version.
> > 2.Support 1/2/4 lanes
> > 3.Support up to 4K@60HzRGB/YCbCr4:4:48bpc
> > 4.Support lane swap and PN swap
>
> Same for these
>
it will be fixed in the next version.
> >
> > -MIPI Transmitter
> > 1.CompliantwithD-PHY1.2&DSI1.1&CSI-22.0;1 clock lane,
> > and1/2/3/4 configurable data lanes:2.5Gbpsperdatalane
> > 2.CompliantwithC-PHY1.0&DSI-21.0&CSI-22.0;
> > 1/2/3 configurable data trio;2.5Gsps perdatatrio
> > 3.Support1/2configurable ports
> > 4.DSISupport16/20/24-bit YCbCr4:2:2,16/18/24/30-bit RGB
>
> Missing spaces, this is hard to read and needs to be cleaned up
>
it will be fixed in the next version.
> >
> > Signed-off-by: Sunyun Yang <syyang@lontium.com>
> > ---
> > drivers/gpu/drm/bridge/Kconfig | 18 +
> > drivers/gpu/drm/bridge/Makefile | 1 +
> > drivers/gpu/drm/bridge/lontium-lt7911exc.c | 571 +++++++++++++++++++++
> > 3 files changed, 590 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..bae8cdaea666 100644
> > --- a/drivers/gpu/drm/bridge/Kconfig
> > +++ b/drivers/gpu/drm/bridge/Kconfig
> > @@ -202,6 +202,24 @@ config DRM_LONTIUM_LT8713SX
> > to 3 configurable Type-C/DP1.4/HDMI2.0 outputs
> > Please say Y if you have such hardware.
> >
> > +config DRM_LONTIUM_LT9611C
> > + tristate "Lontium LT9611C DSI/HDMI bridge"
> > + select SND_SOC_HDMI_CODEC if SND_SOC
> > + depends on OF
> > + select CRC8
> > + select FW_LOADER
> > + select DRM_PANEL_BRIDGE
> > + select DRM_KMS_HELPER
> > + select DRM_MIPI_DSI
> > + select DRM_DISPLAY_HELPER
> > + select DRM_DISPLAY_HDMI_STATE_HELPER
> > + select REGMAP_I2C
> > + help
> > + Driver for Lontium DSI to HDMI bridge
> > + chip driver that converts dual DSI and I2S to
> > + HDMI signals
> > + Please say Y if you have such hardware.
> > +
> > config DRM_ITE_IT66121
> > tristate "ITE IT66121 HDMI bridge"
> > depends on OF
> > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > index beab5b695a6e..54b293d1663e 100644
> > --- a/drivers/gpu/drm/bridge/Makefile
> > +++ b/drivers/gpu/drm/bridge/Makefile
> > @@ -18,6 +18,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> > obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> > obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> > obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
> > +obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
> > obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> > obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
> > obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.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..d1c1d9e073ef
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> > @@ -0,0 +1,571 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2026 Lontium Semiconductor, Inc.
> > + */
> > +
> > +#include <linux/crc32.h>
> > +#include <linux/firmware.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/regmap.h>
> > +#include <drm/drm_of.h>
> > +
> > +#define FW_SIZE (64 * 1024)
> > +#define LT_PAGE_SIZE 32
> > +#define FW_FILE "LT7911EXC.bin"
>
> Other lontium bridge chips use lowercase and a _fw.bin suffix, such as
> "lt9611uxc_fw.bin". Rename to be consistent
>
it will be fixed in the next version.
> > +#define LT7911EXC_PAGE_CONTROL 0xff
> > +
> > +struct lt7911exc {
> > + struct device *dev;
> > + struct i2c_client *client;
> > + struct drm_bridge bridge;
> > + struct drm_bridge *panel_bridge;
> > + struct regmap *regmap;
> > + /* Protects all accesses to registers by stopping the on-chip MCU */
> > + struct mutex ocm_lock;
> > + struct regulator_bulk_data supplies[2];
> > +
> > + struct gpio_desc *reset_gpio;
> > + const struct firmware *fw;
> > + int fw_version;
> > + u32 fw_crc;
> > +
> > + bool enabled;
> > +};
> > +
> > +static const struct regmap_range_cfg lt7911exc_ranges[] = {
> > + {
> > + .name = "register_range",
> > + .range_min = 0,
> > + .range_max = 0xffff,
> > + .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 = 0xffff,
> > + .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 int lt7911exc_regulator_enable(struct lt7911exc *lt7911exc)
> > +{
> > + int ret;
> > +
> > + ret = regulator_enable(lt7911exc->supplies[0].consumer);
> > + if (ret < 0)
> > + return ret;
> > +
> > + usleep_range(5000, 10000);
> > +
> > + ret = regulator_enable(lt7911exc->supplies[1].consumer);
> > + if (ret < 0) {
> > + regulator_disable(lt7911exc->supplies[0].consumer);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lt7911exc_regulator_disable(struct lt7911exc *lt7911exc)
> > +{
> > + int ret;
> > +
> > + ret = regulator_disable(lt7911exc->supplies[1].consumer);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = regulator_disable(lt7911exc->supplies[0].consumer);
> > + if (ret < 0)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +
> > +static void lt7911exc_reset(struct lt7911exc *lt7911exc)
> > +{
> > + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> > + msleep(20);
> > +
> > + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> > + msleep(20);
> > +
> > + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> > + msleep(400);
> > +
> > + dev_dbg(lt7911exc->dev, "lt7911exc reset");
>
> missing newline in dev_dbg(), all other calls have it.
>
it will be fixed in the next version.
dev_dbg(lt7911exc->dev, "lt7911exc reset\n");
> > +}
> > +
> > +static int lt7911exc_parse_dt(struct lt7911exc *lt7911exc)
> > +{
> > + int ret;
> > +
> > + lt7911exc->supplies[0].supply = "vcc";
> > + lt7911exc->supplies[1].supply = "vdd";
> > +
> > + ret = devm_regulator_bulk_get(lt7911exc->dev, 2, lt7911exc->supplies);
> > + if (ret) {
> > + dev_err(lt7911exc->dev, "failed get regulator\n");
> > + return ret;
> > + }
> > +
> > + lt7911exc->reset_gpio = devm_gpiod_get(lt7911exc->dev, "reset", GPIOD_OUT_LOW);
> > + if (IS_ERR(lt7911exc->reset_gpio)) {
> > + dev_err(lt7911exc->dev, "failed to acquire reset gpio\n");
> > + return PTR_ERR(lt7911exc->reset_gpio);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lt7911exc_read_version(struct lt7911exc *lt7911exc)
> > +{
> > + u8 buf[2];
> > + int ret;
> > +
> > + ret = regmap_bulk_read(lt7911exc->regmap, 0xe081, buf, 3);
> > + 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_prepare_firmware_data(struct lt7911exc *lt7911exc)
> > +{
> > + struct device *dev = lt7911exc->dev;
> > + int ret;
> > + u8 *buffer;
> > + size_t total_size = FW_SIZE - 4;
> > +
> > + ret = request_firmware(<7911exc->fw, FW_FILE, dev);
> > + if (ret) {
> > + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> > + return ret;
> > + }
> > +
> > + if (lt7911exc->fw->size > total_size) {
> > + dev_err(dev, "firmware too large (%zu > %zu)\n", lt7911exc->fw->size, total_size);
> > + release_firmware(lt7911exc->fw);
> > + lt7911exc->fw = NULL;
> > + return -EINVAL;
> > + }
> > +
> > + dev_dbg(dev, "firmware size: %zu bytes\n", lt7911exc->fw->size);
> > +
> > + buffer = kzalloc(total_size, GFP_KERNEL);
> > + if (!buffer) {
> > + release_firmware(lt7911exc->fw);
> > + lt7911exc->fw = NULL;
> > + return -ENOMEM;
> > + }
> > +
> > + memset(buffer, 0xff, total_size);
> > + memcpy(buffer, lt7911exc->fw->data, lt7911exc->fw->size);
> > +
> > + lt7911exc->fw_crc = cal_crc32_custom(buffer, total_size);
> > + dev_dbg(dev, "firmware crc: 0x%08x\n", lt7911exc->fw_crc);
> > +
> > + kfree(buffer);
> > + return 0;
> > +}
> > +
> > +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, u64 addr)
> > +{
> > + struct device *dev = lt7911exc->dev;
> > + int ret;
> > + int page = 0, num = 0, page_len = 0;
> > + u64 size, offset;
> > + const u8 *data;
> > +
> > + data = lt7911exc->fw->data;
> > + size = lt7911exc->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, u64 addr)
> > +{
> > + u8 crc[4];
> > + int ret;
> > +
> > + crc[0] = lt7911exc->fw_crc & 0xff;
> > + crc[1] = (lt7911exc->fw_crc >> 8) & 0xff;
> > + crc[2] = (lt7911exc->fw_crc >> 16) & 0xff;
> > + crc[3] = (lt7911exc->fw_crc >> 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;
>
> nit: Newline here makes it more readable
>
it will be fixed in the next version.
> > + 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_firmware_upgrade(struct lt7911exc *lt7911exc)
> > +{
> > + struct device *dev = lt7911exc->dev;
> > + int ret;
> > +
> > + ret = lt7911exc_prepare_firmware_data(lt7911exc);
> > + if (ret < 0)
> > + return ret;
> > +
> > + dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", lt7911exc->fw->size);
> > +
> > + lt7911exc_block_erase(lt7911exc);
> > +
> > + ret = lt7911exc_write_data(lt7911exc, 0);
> > + if (ret < 0) {
> > + dev_err(dev, "failed to write firmware data\n");
> > + return ret;
> > + }
> > +
> > + release_firmware(lt7911exc->fw);
> > + lt7911exc->fw = NULL;
> > +
> > + ret = lt7911exc_write_crc(lt7911exc, FW_SIZE - 4);
> > + if (ret < 0) {
> > + dev_err(dev, "failed to write firmware crc\n");
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc)
> > +{
> > + 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, 4);
> > + 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 != lt7911exc->fw_crc) {
> > + dev_err(dev, "lt7911exc firmware upgrade failed, expected CRC=0x%08x, read CRC=0x%08x\n",
> > + lt7911exc->fw_crc, read_hw_crc);
> > + return -EIO;
> > + }
> > +
> > + dev_dbg(dev, "lt7911exc firmware upgrade success, CRC=0x%08x\n", read_hw_crc);
> > + return 0;
> > +}
> > +
> > +static void lt7911exc_pre_enable(struct drm_bridge *bridge)
> > +{
> > + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> > + int ret;
> > +
> > + if (lt7911exc->enabled)
> > + return;
> > +
> > + ret = lt7911exc_regulator_enable(lt7911exc);
> > + if (ret)
> > + return;
> > +
> > + lt7911exc_reset(lt7911exc);
> > +
> > + lt7911exc->enabled = true;
> > +}
> > +
> > +static void lt7911exc_disable(struct drm_bridge *bridge)
> > +{
> > + /* Delay after panel is disabled */
> > + msleep(20);
> > +}
> > +
> > +static void lt7911exc_post_disable(struct drm_bridge *bridge)
> > +{
> > + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> > + int ret;
> > +
> > + if (!lt7911exc->enabled)
> > + return;
> > +
> > + lt7911exc->enabled = false;
> > +
> > + ret = lt7911exc_regulator_disable(lt7911exc);
> > + if (ret)
> > + return;
> > +
> > + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> > +}
> > +
> > +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->panel_bridge,
> > + <7911exc->bridge, flags);
> > +}
> > +
> > +static const struct drm_bridge_funcs lt7911exc_bridge_funcs = {
> > + .pre_enable = lt7911exc_pre_enable,
> > + .disable = lt7911exc_disable,
> > + .post_disable = lt7911exc_post_disable,
>
> These are all deprecated, is there any reason to use them and not the
> atomic_* callbacks?
>
it will be fixed in the next version.
> > + .attach = lt7911exc_attach,
> > +};
> > +
> > +static int lt7911exc_probe(struct i2c_client *client)
> > +{
> > + struct device *dev = &client->dev;
> > + struct lt7911exc *lt7911exc;
> > + struct drm_bridge *panel_bridge;
> > + bool fw_updated = false;
> > + int ret;
> > +
> > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> > + dev_err(dev, "device doesn't support I2C\n");
> > + return -ENODEV;
> > + }
> > +
> > + lt7911exc = devm_drm_bridge_alloc(dev, struct lt7911exc, bridge,
> > + <7911exc_bridge_funcs);
> > + if (IS_ERR(lt7911exc))
> > + return PTR_ERR(lt7911exc);
> > +
> > + panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
> > + if (IS_ERR(panel_bridge))
> > + return PTR_ERR(panel_bridge);
> > +
> > + lt7911exc->panel_bridge = panel_bridge;
> > + lt7911exc->client = client;
> > + lt7911exc->dev = dev;
> > + i2c_set_clientdata(client, lt7911exc);
> > + mutex_init(<7911exc->ocm_lock);
> > +
> > + lt7911exc->regmap = devm_regmap_init_i2c(client, <7911exc_regmap_config);
> > + if (IS_ERR(lt7911exc->regmap)) {
> > + dev_err(dev, "regmap i2c init failed\n");
> > + return PTR_ERR(lt7911exc->regmap);
> > + }
> > +
> > + ret = lt7911exc_parse_dt(lt7911exc);
> > + if (ret)
> > + return ret;
> > +
> > + ret = lt7911exc_regulator_enable(lt7911exc);
> > + if (ret)
> > + return ret;
> > +
> > + lt7911exc_reset(lt7911exc);
> > + lt7911exc->enabled = true;
> > + lt7911exc_lock(lt7911exc);
> > +
> > +retry:
> > + lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
> > + if (lt7911exc->fw_version < 0) {
> > + dev_err(dev, "failed to read FW version\n");
> > + lt7911exc_unlock(lt7911exc);
> > + goto err_disable_regulators;
> > +
> > + } else if (lt7911exc->fw_version == 0) {
> > + if (!fw_updated) {
> > + fw_updated = true;
> > + ret = lt7911exc_firmware_upgrade(lt7911exc);
> > + if (ret < 0) {
> > + lt7911exc_unlock(lt7911exc);
> > + goto err_disable_regulators;
> > + }
> > +
> > + lt7911exc_reset(lt7911exc);
> > +
> > + ret = lt7911exc_upgrade_result(lt7911exc);
> > + if (ret < 0) {
> > + lt7911exc_unlock(lt7911exc);
> > + goto err_disable_regulators;
> > + }
> > +
> > + goto retry;
> > +
> > + } else {
> > + dev_err(dev, "fw version 0x%04x, update failed\n", lt7911exc->fw_version);
> > + ret = -EOPNOTSUPP;
> > + lt7911exc_unlock(lt7911exc);
> > + goto err_disable_regulators;
> > + }
> > + }
> > +
> > + lt7911exc_unlock(lt7911exc);
> > +
> > + lt7911exc->bridge.type = DRM_MODE_CONNECTOR_DSI;
> > + lt7911exc->bridge.of_node = dev->of_node;
> > + drm_bridge_add(<7911exc->bridge);
> > +
> > + return 0;
> > +
> > +err_disable_regulators:
> > + regulator_bulk_disable(ARRAY_SIZE(lt7911exc->supplies), lt7911exc->supplies);
> > + if (lt7911exc->fw) {
> > + release_firmware(lt7911exc->fw);
> > + lt7911exc->fw = NULL;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static void lt7911exc_remove(struct i2c_client *client)
> > +{
> > + struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
> > +
> > + drm_bridge_remove(<7911exc->bridge);
> > + mutex_destroy(<7911exc->ocm_lock);
> > +}
> > +
> > +static const struct i2c_device_id lt7911exc_i2c_table[] = {
> > + {"lontium, lt7911exc", 0},
>
> Extra space here in the device id
>
it will be fixed in the next version. {"lontium, lt7911exc"},
> > + { /* sentinel */ }
> > +};
> > +
> > +MODULE_DEVICE_TABLE(i2c, lt7911exc_i2c_table);
> > +
> > +static const struct of_device_id lt7911exc_devices[] = {
> > + {.compatible = "lontium,lt7911exc",},
> > + {}
> > +};
> > +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,
> > + },
> > +};
> > +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 v2");
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/2] dt-bindings:bridge Add LT7911EXC binding
2026-04-21 1:33 ` 杨孙运
@ 2026-04-21 11:07 ` Dmitry Baryshkov
2026-04-22 0:46 ` 杨孙运
0 siblings, 1 reply; 15+ messages in thread
From: Dmitry Baryshkov @ 2026-04-21 11:07 UTC (permalink / raw)
To: 杨孙运
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, jonas,
jernej.skrabec, devicetree, dri-devel, linux-kernel, xmzhu,
tzimmermann, rlyu, xbpeng
On Tue, Apr 21, 2026 at 09:33:34AM +0800, 杨孙运 wrote:
> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月20日周一 11:12写道:
> >
> > ote, your message didn't reach dri-devel. Please check why. You might
> > need to switch to B4 Web relay submission, if there are any issues
> > with the SMTP on your side.
> >
> > On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
> > >
> > > From: Sunyun Yang <syyang@lontium.com>
> > >
> > > -binding for lt7911exc.
> >
> > Less is more, but here please settle for slightly more information
> > about the chip.
> >
>
> It will be modified in the next version.
>
> > >
> > > Signed-off-by: Sunyun Yang <syyang@lontium.com>
> > > ---
> > > .../display/bridge/lontium,lt7911exc.yaml | 91 +++++++++++++++++++
> > > 1 file changed, 91 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..54a73d41635a
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.yaml
> > > @@ -0,0 +1,91 @@
> > > +# 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 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 mipi dsi output.
> >
> > MIPI, DSI
> >
> It will be use MIPI DSI, in the next version.
>
> > > +
> > > + port@1:
> > > + $ref: /schemas/graph.yaml#/properties/port
> > > + description: Video port for eDP input.
> > > +
> > > + 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_HIGH>;
> >
> > Reset pins are usually active low.
> >
> if reset pins use active low, lt7911exc_reset function needs to be modified to:
> 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");
> }
>
> I think the two are essentially the same. Please confirm whether I
> need to make the change?
If the GPIO is active-low in the hardware, it should be declared so in
DT.
>
> > > + vdd-supply = <<7911exc_1v2>;
> > > + vcc-supply = <<7911exc_3v3>;
> > > +
> > > + ports {
> > > + #address-cells = <1>;
> > > + #size-cells = <0>;
> > > +
> > > + port@0 {
> > > + reg = <0>;
> > > +
> > > + bridge_out: endpoint {
> > > + remote-endpoint = <&panel_in>;
> > > + };
> > > + };
> > > +
> > > + port@1 {
> > > + reg = <1>;
> > > +
> > > + bridge_in: endpoint {
> > > + remote-endpoint = <&edp_out>;
> > > + };
> > > + };
> > > + };
> > > + };
> > > + };
> >
> > --
> > With best wishes
> > Dmitry
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
2026-04-21 3:13 ` 杨孙运
@ 2026-04-21 11:15 ` Dmitry Baryshkov
2026-04-22 1:06 ` 杨孙运
0 siblings, 1 reply; 15+ messages in thread
From: Dmitry Baryshkov @ 2026-04-21 11:15 UTC (permalink / raw)
To: 杨孙运
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, jonas,
jernej.skrabec, devicetree, dri-devel, linux-kernel, xmzhu,
tzimmermann, xbpeng, rlyu, xmzhu
On Tue, Apr 21, 2026 at 11:13:30AM +0800, 杨孙运 wrote:
> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月20日周一 11:57写道:
> >
> > On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
> > >
> > > From: Sunyun Yang <syyang@lontium.com>
> > >
> > > LT7911EXC is a high performance eDP1.4 to MIPI chip for
> >
> > MIPI what?
> >
> MIPI DSI,
> It will be modified in the next version.
>
> > > VR/Display application.
> > >
> > > -eDP1.4Receiver
> > > 1.Support SSC
> > > 2.Support 1/2/4 lanes
> > > 3.Support up to 4K@60HzRGB/YCbCr4:4:48bpc
> > > 4.Support lane swap and PN swap
> > >
> > > -MIPI Transmitter
> > > 1.CompliantwithD-PHY1.2&DSI1.1&CSI-22.0;1 clock lane,
> > > and1/2/3/4 configurable data lanes:2.5Gbpsperdatalane
> > > 2.CompliantwithC-PHY1.0&DSI-21.0&CSI-22.0;
> > > 1/2/3 configurable data trio;2.5Gsps perdatatrio
> > > 3.Support1/2configurable ports
> > > 4.DSISupport16/20/24-bit YCbCr4:2:2,16/18/24/30-bit RGB
> > >
> > > Signed-off-by: Sunyun Yang <syyang@lontium.com>
> > > ---
> > > drivers/gpu/drm/bridge/Kconfig | 18 +
> > > drivers/gpu/drm/bridge/Makefile | 1 +
> > > drivers/gpu/drm/bridge/lontium-lt7911exc.c | 571 +++++++++++++++++++++
> > > 3 files changed, 590 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..bae8cdaea666 100644
> > > --- a/drivers/gpu/drm/bridge/Kconfig
> > > +++ b/drivers/gpu/drm/bridge/Kconfig
> > > @@ -202,6 +202,24 @@ config DRM_LONTIUM_LT8713SX
> > > to 3 configurable Type-C/DP1.4/HDMI2.0 outputs
> > > Please say Y if you have such hardware.
> > >
> > > +config DRM_LONTIUM_LT9611C
> >
> > I thought the patch is for LT7911EXC
> >
> Yes, it is LT7911EXC, It will be modified in the next version.
>
> > > + tristate "Lontium LT9611C DSI/HDMI bridge"
> > > + select SND_SOC_HDMI_CODEC if SND_SOC
> > > + depends on OF
> > > + select CRC8
> > > + select FW_LOADER
> > > + select DRM_PANEL_BRIDGE
> > > + select DRM_KMS_HELPER
> > > + select DRM_MIPI_DSI
> > > + select DRM_DISPLAY_HELPER
> > > + select DRM_DISPLAY_HDMI_STATE_HELPER
> > > + select REGMAP_I2C
> > > + help
> > > + Driver for Lontium DSI to HDMI bridge
> > > + chip driver that converts dual DSI and I2S to
> > > + HDMI signals
> > > + Please say Y if you have such hardware.
> > > +
> > > config DRM_ITE_IT66121
> > > tristate "ITE IT66121 HDMI bridge"
> > > depends on OF
> > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > > index beab5b695a6e..54b293d1663e 100644
> > > --- a/drivers/gpu/drm/bridge/Makefile
> > > +++ b/drivers/gpu/drm/bridge/Makefile
> > > @@ -18,6 +18,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> > > obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> > > obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> > > obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
> > > +obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
> >
> > Keep the list sorted, please.
> >
> Where can I see the sorting rules? Please guide me.
> it is:
> obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
> obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
zoom out, check the overall contents, submit a patch fixing the error.
> ?
>
> > > obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> > > obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
> > > obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.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..d1c1d9e073ef
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> > > @@ -0,0 +1,571 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Copyright (C) 2026 Lontium Semiconductor, Inc.
> > > + */
> > > +
> > > +#include <linux/crc32.h>
> > > +#include <linux/firmware.h>
> > > +#include <linux/gpio/consumer.h>
> > > +#include <linux/i2c.h>
> > > +#include <linux/regmap.h>
> >
> > I think you use more than that.
> >
> It will be modified in the next version.
>
> > > +#include <drm/drm_of.h>
> > > +
> > > +#define FW_SIZE (64 * 1024)
> > > +#define LT_PAGE_SIZE 32
> > > +#define FW_FILE "LT7911EXC.bin"
> > > +#define LT7911EXC_PAGE_CONTROL 0xff
> > > +
> > > +struct lt7911exc {
> > > + struct device *dev;
> > > + struct i2c_client *client;
> > > + struct drm_bridge bridge;
> > > + struct drm_bridge *panel_bridge;
> >
> > Use next_bridge from struct drm_bridge instead.
> >
> It will be modified in the next version.
>
> > > + struct regmap *regmap;
> > > + /* Protects all accesses to registers by stopping the on-chip MCU */
> > > + struct mutex ocm_lock;
> > > + struct regulator_bulk_data supplies[2];
> > > +
> > > + struct gpio_desc *reset_gpio;
> > > + const struct firmware *fw;
> >
> > Do you need to store it during the runtime? If not, please remove from
> > the data struct.
> >
> Don't need store during the runtime.
> Can I use the global variable 'fw'?
Of course not.
> Because I need use 'fw' to calculate the CRC32 and burn the firmware.
Sure, but what does it have to do with the field in struct lt7911exc?
>
> > > + int fw_version;
> > > + u32 fw_crc;
> > > +
> > > + bool enabled;
> >
> > What for?
> >
> (bool enabled;) is used as a flag in the code to reduce the frequency
> of power supply switching.
How does it help to reduce the rate?
> Of course, it can also be removed in the next version.
>
> > > +};
> > > +
> > > +static const struct regmap_range_cfg lt7911exc_ranges[] = {
> > > + {
> > > + .name = "register_range",
> > > + .range_min = 0,
> > > + .range_max = 0xffff,
> >
> > Is it an actual range?
> >
> 0xe8ff is actual range.
Then why?
>
> > > + .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 = 0xffff,
> > > + .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);
> >
> > How is it different from crc32_le()?
> >
> The implementation differs from crc32_le() in both byte ordering and
> processing granularity.
> This function performs a 32-bit word-wise byte swap (little-endian to
> big-endian) before feeding data into crc32_be(), while crc32_le()
> processes the input stream directly in little-endian order without
> transformation.
> Therefore, the result is not equivalent to crc32_le(), and is required
> to match the firmware's expected big-endian word-based CRC format.
ack.
>
>
> > > +MODULE_LICENSE("GPL v2");
> Should GPL be used here? I got an error during testing: GPL v2;
Which error?
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/2] dt-bindings:bridge Add LT7911EXC binding
2026-04-21 11:07 ` Dmitry Baryshkov
@ 2026-04-22 0:46 ` 杨孙运
0 siblings, 0 replies; 15+ messages in thread
From: 杨孙运 @ 2026-04-22 0:46 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, jonas,
jernej.skrabec, devicetree, dri-devel, linux-kernel, xmzhu,
tzimmermann, rlyu, xbpeng
Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月21日周二 19:08写道:
>
> On Tue, Apr 21, 2026 at 09:33:34AM +0800, 杨孙运 wrote:
> > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月20日周一 11:12写道:
> > >
> > > ote, your message didn't reach dri-devel. Please check why. You might
> > > need to switch to B4 Web relay submission, if there are any issues
> > > with the SMTP on your side.
> > >
> > > On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
> > > >
> > > > From: Sunyun Yang <syyang@lontium.com>
> > > >
> > > > -binding for lt7911exc.
> > >
> > > Less is more, but here please settle for slightly more information
> > > about the chip.
> > >
> >
> > It will be modified in the next version.
> >
> > > >
> > > > Signed-off-by: Sunyun Yang <syyang@lontium.com>
> > > > ---
> > > > .../display/bridge/lontium,lt7911exc.yaml | 91 +++++++++++++++++++
> > > > 1 file changed, 91 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..54a73d41635a
> > > > --- /dev/null
> > > > +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.yaml
> > > > @@ -0,0 +1,91 @@
> > > > +# 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 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 mipi dsi output.
> > >
> > > MIPI, DSI
> > >
> > It will be use MIPI DSI, in the next version.
> >
> > > > +
> > > > + port@1:
> > > > + $ref: /schemas/graph.yaml#/properties/port
> > > > + description: Video port for eDP input.
> > > > +
> > > > + 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_HIGH>;
> > >
> > > Reset pins are usually active low.
> > >
> > if reset pins use active low, lt7911exc_reset function needs to be modified to:
> > 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");
> > }
> >
> > I think the two are essentially the same. Please confirm whether I
> > need to make the change?
>
> If the GPIO is active-low in the hardware, it should be declared so in
> DT.
>
It will use active-low in the next version.
> >
> > > > + vdd-supply = <<7911exc_1v2>;
> > > > + vcc-supply = <<7911exc_3v3>;
> > > > +
> > > > + ports {
> > > > + #address-cells = <1>;
> > > > + #size-cells = <0>;
> > > > +
> > > > + port@0 {
> > > > + reg = <0>;
> > > > +
> > > > + bridge_out: endpoint {
> > > > + remote-endpoint = <&panel_in>;
> > > > + };
> > > > + };
> > > > +
> > > > + port@1 {
> > > > + reg = <1>;
> > > > +
> > > > + bridge_in: endpoint {
> > > > + remote-endpoint = <&edp_out>;
> > > > + };
> > > > + };
> > > > + };
> > > > + };
> > > > + };
> > >
> > > --
> > > With best wishes
> > > Dmitry
>
> --
> With best wishes
> Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
2026-04-21 11:15 ` Dmitry Baryshkov
@ 2026-04-22 1:06 ` 杨孙运
2026-04-22 18:58 ` Dmitry Baryshkov
0 siblings, 1 reply; 15+ messages in thread
From: 杨孙运 @ 2026-04-22 1:06 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, jonas,
jernej.skrabec, devicetree, dri-devel, linux-kernel, xmzhu,
tzimmermann, xbpeng, rlyu, xmzhu
Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月21日周二 19:15写道:
>
> On Tue, Apr 21, 2026 at 11:13:30AM +0800, 杨孙运 wrote:
> > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月20日周一 11:57写道:
> > >
> > > On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
> > > >
> > > > From: Sunyun Yang <syyang@lontium.com>
> > > >
> > > > LT7911EXC is a high performance eDP1.4 to MIPI chip for
> > >
> > > MIPI what?
> > >
> > MIPI DSI,
> > It will be modified in the next version.
> >
> > > > VR/Display application.
> > > >
> > > > -eDP1.4Receiver
> > > > 1.Support SSC
> > > > 2.Support 1/2/4 lanes
> > > > 3.Support up to 4K@60HzRGB/YCbCr4:4:48bpc
> > > > 4.Support lane swap and PN swap
> > > >
> > > > -MIPI Transmitter
> > > > 1.CompliantwithD-PHY1.2&DSI1.1&CSI-22.0;1 clock lane,
> > > > and1/2/3/4 configurable data lanes:2.5Gbpsperdatalane
> > > > 2.CompliantwithC-PHY1.0&DSI-21.0&CSI-22.0;
> > > > 1/2/3 configurable data trio;2.5Gsps perdatatrio
> > > > 3.Support1/2configurable ports
> > > > 4.DSISupport16/20/24-bit YCbCr4:2:2,16/18/24/30-bit RGB
> > > >
> > > > Signed-off-by: Sunyun Yang <syyang@lontium.com>
> > > > ---
> > > > drivers/gpu/drm/bridge/Kconfig | 18 +
> > > > drivers/gpu/drm/bridge/Makefile | 1 +
> > > > drivers/gpu/drm/bridge/lontium-lt7911exc.c | 571 +++++++++++++++++++++
> > > > 3 files changed, 590 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..bae8cdaea666 100644
> > > > --- a/drivers/gpu/drm/bridge/Kconfig
> > > > +++ b/drivers/gpu/drm/bridge/Kconfig
> > > > @@ -202,6 +202,24 @@ config DRM_LONTIUM_LT8713SX
> > > > to 3 configurable Type-C/DP1.4/HDMI2.0 outputs
> > > > Please say Y if you have such hardware.
> > > >
> > > > +config DRM_LONTIUM_LT9611C
> > >
> > > I thought the patch is for LT7911EXC
> > >
> > Yes, it is LT7911EXC, It will be modified in the next version.
> >
> > > > + tristate "Lontium LT9611C DSI/HDMI bridge"
> > > > + select SND_SOC_HDMI_CODEC if SND_SOC
> > > > + depends on OF
> > > > + select CRC8
> > > > + select FW_LOADER
> > > > + select DRM_PANEL_BRIDGE
> > > > + select DRM_KMS_HELPER
> > > > + select DRM_MIPI_DSI
> > > > + select DRM_DISPLAY_HELPER
> > > > + select DRM_DISPLAY_HDMI_STATE_HELPER
> > > > + select REGMAP_I2C
> > > > + help
> > > > + Driver for Lontium DSI to HDMI bridge
> > > > + chip driver that converts dual DSI and I2S to
> > > > + HDMI signals
> > > > + Please say Y if you have such hardware.
> > > > +
> > > > config DRM_ITE_IT66121
> > > > tristate "ITE IT66121 HDMI bridge"
> > > > depends on OF
> > > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > > > index beab5b695a6e..54b293d1663e 100644
> > > > --- a/drivers/gpu/drm/bridge/Makefile
> > > > +++ b/drivers/gpu/drm/bridge/Makefile
> > > > @@ -18,6 +18,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> > > > obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> > > > obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> > > > obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
> > > > +obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
> > >
> > > Keep the list sorted, please.
> > >
> > Where can I see the sorting rules? Please guide me.
> > it is:
> > obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
> > obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
>
> zoom out, check the overall contents, submit a patch fixing the error.
>
It will be fixed in the next version
> >
> > > > obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> > > > obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
> > > > obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.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..d1c1d9e073ef
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> > > > @@ -0,0 +1,571 @@
> > > > +// SPDX-License-Identifier: GPL-2.0
> > > > +/*
> > > > + * Copyright (C) 2026 Lontium Semiconductor, Inc.
> > > > + */
> > > > +
> > > > +#include <linux/crc32.h>
> > > > +#include <linux/firmware.h>
> > > > +#include <linux/gpio/consumer.h>
> > > > +#include <linux/i2c.h>
> > > > +#include <linux/regmap.h>
> > >
> > > I think you use more than that.
> > >
> > It will be modified in the next version.
> >
> > > > +#include <drm/drm_of.h>
> > > > +
> > > > +#define FW_SIZE (64 * 1024)
> > > > +#define LT_PAGE_SIZE 32
> > > > +#define FW_FILE "LT7911EXC.bin"
> > > > +#define LT7911EXC_PAGE_CONTROL 0xff
> > > > +
> > > > +struct lt7911exc {
> > > > + struct device *dev;
> > > > + struct i2c_client *client;
> > > > + struct drm_bridge bridge;
> > > > + struct drm_bridge *panel_bridge;
> > >
> > > Use next_bridge from struct drm_bridge instead.
> > >
> > It will be modified in the next version.
> >
> > > > + struct regmap *regmap;
> > > > + /* Protects all accesses to registers by stopping the on-chip MCU */
> > > > + struct mutex ocm_lock;
> > > > + struct regulator_bulk_data supplies[2];
> > > > +
> > > > + struct gpio_desc *reset_gpio;
> > > > + const struct firmware *fw;
> > >
> > > Do you need to store it during the runtime? If not, please remove from
> > > the data struct.
> > >
> > Don't need store during the runtime.
> > Can I use the global variable 'fw'?
>
> Of course not.
>
It will use local variable in the next version
> > Because I need use 'fw' to calculate the CRC32 and burn the firmware.
>
> Sure, but what does it have to do with the field in struct lt7911exc?
>
You are correct, and I will fix according to your suggestions.
> >
> > > > + int fw_version;
> > > > + u32 fw_crc;
> > > > +
> > > > + bool enabled;
> > >
> > > What for?
> > >
> > (bool enabled;) is used as a flag in the code to reduce the frequency
> > of power supply switching.
>
> How does it help to reduce the rate?
>
The enabled flag is used to track the hardware power state. In
lt7911exc_pre_enable(), we check if (lt7911exc->enabled) return; to
avoid redundant regulator enabling and reset pulses when the DRM
framework calls pre_enable multiple times.
Similarly, lt7911exc_post_disable() uses it to prevent multiple
power-down sequences. This reduces unnecessary power supply switching
and potential side effects.
However, if you consider this optimization unnecessary, I can remove
the flag and the related checks in the next version. Please let me
know your preference.
> > Of course, it can also be removed in the next version.
> >
> > > > +};
> > > > +
> > > > +static const struct regmap_range_cfg lt7911exc_ranges[] = {
> > > > + {
> > > > + .name = "register_range",
> > > > + .range_min = 0,
> > > > + .range_max = 0xffff,
> > >
> > > Is it an actual range?
> > >
> > 0xe8ff is actual range.
>
> Then why?
>
The actual valid register address ends at 0xe8ff. I mistakenly set
range_max to 0xffff.
I will fix it to 0xe8ff in the next version. Thank you for catching this.
> >
> > > > + .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 = 0xffff,
> > > > + .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);
> > >
> > > How is it different from crc32_le()?
> > >
> > The implementation differs from crc32_le() in both byte ordering and
> > processing granularity.
> > This function performs a 32-bit word-wise byte swap (little-endian to
> > big-endian) before feeding data into crc32_be(), while crc32_le()
> > processes the input stream directly in little-endian order without
> > transformation.
> > Therefore, the result is not equivalent to crc32_le(), and is required
> > to match the firmware's expected big-endian word-based CRC format.
>
> ack.
>
> >
> >
> > > > +MODULE_LICENSE("GPL v2");
> > Should GPL be used here? I got an error during testing: GPL v2;
>
> Which error?
>
Sorry, is warning:
WARNING: Prefer "GPL" over "GPL v2" - see commit bf7fbeeae6db
("module: Cure the MODULE_LICENSE "GPL" vs. "GPL v2" bogosity")
#649: FILE: drivers/gpu/drm/bridge/lontium-lt7911exc.c:571:
+MODULE_LICENSE("GPL v2");
It will be fixed in the next version.
> --
> With best wishes
> Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
2026-04-22 1:06 ` 杨孙运
@ 2026-04-22 18:58 ` Dmitry Baryshkov
2026-04-23 0:54 ` 杨孙运
0 siblings, 1 reply; 15+ messages in thread
From: Dmitry Baryshkov @ 2026-04-22 18:58 UTC (permalink / raw)
To: 杨孙运
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, jonas,
jernej.skrabec, devicetree, dri-devel, linux-kernel, xmzhu,
tzimmermann, xbpeng, rlyu, xmzhu
On Wed, Apr 22, 2026 at 09:06:22AM +0800, 杨孙运 wrote:
> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月21日周二 19:15写道:
> >
> > On Tue, Apr 21, 2026 at 11:13:30AM +0800, 杨孙运 wrote:
> > > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月20日周一 11:57写道:
> > > >
> > > > On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
> > > > >
> > > > > From: Sunyun Yang <syyang@lontium.com>
> > > > > + int fw_version;
> > > > > + u32 fw_crc;
> > > > > +
> > > > > + bool enabled;
> > > >
> > > > What for?
> > > >
> > > (bool enabled;) is used as a flag in the code to reduce the frequency
> > > of power supply switching.
> >
> > How does it help to reduce the rate?
> >
> The enabled flag is used to track the hardware power state. In
> lt7911exc_pre_enable(), we check if (lt7911exc->enabled) return; to
> avoid redundant regulator enabling and reset pulses when the DRM
> framework calls pre_enable multiple times.
>
> Similarly, lt7911exc_post_disable() uses it to prevent multiple
> power-down sequences. This reduces unnecessary power supply switching
> and potential side effects.
Is there a code path leading to multiple enable or multiple disable calls?
>
> However, if you consider this optimization unnecessary, I can remove
> the flag and the related checks in the next version. Please let me
> know your preference.
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
2026-04-22 18:58 ` Dmitry Baryshkov
@ 2026-04-23 0:54 ` 杨孙运
0 siblings, 0 replies; 15+ messages in thread
From: 杨孙运 @ 2026-04-23 0:54 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, jonas,
jernej.skrabec, devicetree, dri-devel, linux-kernel, xmzhu,
tzimmermann, xbpeng, rlyu, xmzhu
Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月23日周四 02:58写道:
>
> On Wed, Apr 22, 2026 at 09:06:22AM +0800, 杨孙运 wrote:
> > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月21日周二 19:15写道:
> > >
> > > On Tue, Apr 21, 2026 at 11:13:30AM +0800, 杨孙运 wrote:
> > > > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年4月20日周一 11:57写道:
> > > > >
> > > > > On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
> > > > > >
> > > > > > From: Sunyun Yang <syyang@lontium.com>
>
> > > > > > + int fw_version;
> > > > > > + u32 fw_crc;
> > > > > > +
> > > > > > + bool enabled;
> > > > >
> > > > > What for?
> > > > >
> > > > (bool enabled;) is used as a flag in the code to reduce the frequency
> > > > of power supply switching.
> > >
> > > How does it help to reduce the rate?
> > >
> > The enabled flag is used to track the hardware power state. In
> > lt7911exc_pre_enable(), we check if (lt7911exc->enabled) return; to
> > avoid redundant regulator enabling and reset pulses when the DRM
> > framework calls pre_enable multiple times.
> >
> > Similarly, lt7911exc_post_disable() uses it to prevent multiple
> > power-down sequences. This reduces unnecessary power supply switching
> > and potential side effects.
>
> Is there a code path leading to multiple enable or multiple disable calls?
>
Sorry, this is my mistake.
it will be fixed in next version.
Thanks, Dmitry.
> >
> > However, if you consider this optimization unnecessary, I can remove
> > the flag and the related checks in the next version. Please let me
> > know your preference.
> >
>
> --
> With best wishes
> Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-04-23 0:55 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-20 2:33 [PATCH 0/2] Add LT7911EXC edp to mipi bridge driver syyang
2026-04-20 2:33 ` [PATCH 1/2] dt-bindings:bridge Add LT7911EXC binding syyang
2026-04-20 3:12 ` Dmitry Baryshkov
2026-04-21 1:33 ` 杨孙运
2026-04-21 11:07 ` Dmitry Baryshkov
2026-04-22 0:46 ` 杨孙运
2026-04-20 2:33 ` [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver syyang
2026-04-20 3:57 ` Dmitry Baryshkov
2026-04-21 3:13 ` 杨孙运
2026-04-21 11:15 ` Dmitry Baryshkov
2026-04-22 1:06 ` 杨孙运
2026-04-22 18:58 ` Dmitry Baryshkov
2026-04-23 0:54 ` 杨孙运
2026-04-20 5:05 ` Quentin Freimanis
2026-04-21 3:26 ` 杨孙运
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox