* [PATCH v4 0/2] Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
@ 2026-05-06 9:50 syyang
2026-05-06 9:50 ` [PATCH v4 1/2] dt-bindings: bridge: " syyang
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: syyang @ 2026-05-06 9:50 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, xmzhu, rlyu,
xbpeng, Sunyun Yang
From: Sunyun Yang <syyang@lontium.com>
This series introduces:
- A device tree binding YAML file describing the hardware
- A new DRM bridge driver implementing the basic functionality
LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port
mipi dsi and output hdmi, differences in hardware features:
- LT9611C: supports 1-port mipi dsi to hdmi 1.4
- LT9611EX: supports 2-port mipi dsi to hdmi 1.4
- LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0
Signed-off-by: Sunyun Yang<syyang@lontium.com>
---
Changes in v4:
- dt-binding:
1. fix commit message [Krzysztof]
- drm/bridge:
1. use lt9611c_fw.bin [Dmitry]
2. use drm_bridge::next_bridge
3. use enum lt9611_chip_type
4. remove *fw from the lt9611c struct
5. "bool hdmi_connected;" variable add comment
6. use read_poll_timeout and -ETIMEDOUT in the "lt9611c_read_write_flow" function
7. replace mutex_lock(<9611c->ocm_lock) with guard(mutex)(<9611c->ocm_lock) in some place
8. switch to ARRAY_SIZE instead of specifying lengths directly
9. use dev_err_probe
10. add clear functions for AVI and audio infoframes
11. add DRM_BRIDGE_OP_HDMI and remove .hpd_notify
12. remove the sample_rate validation code
13. fix MODULE_DESCRIPTION
14. replace "GPL v2" with "GPL"
- Link to v3: https://lore.kernel.org/lkml/20260420061644.1251070-1-syyang@lontium.com/
Changes in v3:
- dt-binding:
1. lt9611c(ex/uxd) content merged into lontium,lt9611.yaml
- drm/bridge:
1. Drop the licence text, only use SPDX header
2. Sort the headers
3. Use library functions for crc8
4. Drop i2c_read_byte and i2c_write_byte
5. Lowercase all hex values
6. Use paged writes as implemented for LT9611C(EX/UXD)
7. Drop dev_info, use dev_dbg
8. Modify lt9611c_get_edid_block, don't store EDID in the long-term structures
9. Use HDMI audio helpers.
10.Remove unnecessary flags,Implement proper cleanup path, unwinding resources one by one.
11.Replace devm_kzalloc with devm_drm_bridge_alloc.
12.Remove extra kthread.
- Link to v1: https://lore.kernel.org/lkml/20250903123825.1721443-1-syyang@lontium.com/
Changes in v2:
1. Forget modify code, operation error, Please disregard this submit.
Changes in v1:
- dt-binding:
1. Submit the first version of the code.
- drm/bridge:
1. Submit the first version of the code.
---
Sunyun Yang (2):
dt-bindings: bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI
driver
drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
.../display/bridge/lontium,lt9611.yaml | 8 +-
drivers/gpu/drm/bridge/Kconfig | 18 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/lontium-lt9611c.c | 1211 +++++++++++++++++
4 files changed, 1236 insertions(+), 2 deletions(-)
create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v4 1/2] dt-bindings: bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
2026-05-06 9:50 [PATCH v4 0/2] Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver syyang
@ 2026-05-06 9:50 ` syyang
2026-05-07 3:46 ` Claude review: " Claude Code Review Bot
2026-05-06 9:51 ` [PATCH v4 2/2] drm/bridge: " syyang
2026-05-07 3:46 ` Claude review: " Claude Code Review Bot
2 siblings, 1 reply; 7+ messages in thread
From: syyang @ 2026-05-06 9:50 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, xmzhu, rlyu,
xbpeng, Sunyun Yang
From: Sunyun Yang <syyang@lontium.com>
LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port
mipi dsi and output hdmi, differences in hardware features:
- LT9611C: supports 1-port mipi dsi to hdmi 1.4
- LT9611EX: supports 2-port mipi dsi to hdmi 1.4
- LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0
Signed-off-by: Sunyun Yang <syyang@lontium.com>
---
.../bindings/display/bridge/lontium,lt9611.yaml | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
index 429a06057ae8..b20a43456b01 100644
--- a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
+++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
@@ -4,19 +4,23 @@
$id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Lontium LT9611(UXC) 2 Port MIPI to HDMI Bridge
+title: Lontium LT9611(UXC/C/EX/UXD) 2 Port MIPI DSI to HDMI Bridge
maintainers:
- Vinod Koul <vkoul@kernel.org>
description: |
- The LT9611 and LT9611UXC are bridge devices which convert DSI to HDMI
+ The LT9611、LT9611UXC、LT9611C、LT9611EX and LT9611UXD
+ are bridge devices which convert DSI to HDMI
properties:
compatible:
enum:
- lontium,lt9611
- lontium,lt9611uxc
+ - lontium,lt9611c
+ - lontium,lt9611ex
+ - lontium,lt9611uxd
reg:
maxItems: 1
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v4 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
2026-05-06 9:50 [PATCH v4 0/2] Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver syyang
2026-05-06 9:50 ` [PATCH v4 1/2] dt-bindings: bridge: " syyang
@ 2026-05-06 9:51 ` syyang
2026-05-06 20:06 ` Dmitry Baryshkov
2026-05-07 3:46 ` Claude review: " Claude Code Review Bot
2026-05-07 3:46 ` Claude review: " Claude Code Review Bot
2 siblings, 2 replies; 7+ messages in thread
From: syyang @ 2026-05-06 9:51 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, xmzhu, rlyu,
xbpeng, Sunyun Yang
From: Sunyun Yang <syyang@lontium.com>
LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port
mipi dsi and output hdmi, differences in hardware features:
- LT9611C: supports 1-port mipi dsi to hdmi 1.4
- LT9611EX: supports 2-port mipi dsi to hdmi 1.4
- LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0
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-lt9611c.c | 1211 ++++++++++++++++++++++
3 files changed, 1230 insertions(+)
create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index c3209b0f4678..32b85a2a65d9 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -177,6 +177,24 @@ config DRM_LONTIUM_LT9611
HDMI signals
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_LONTIUM_LT9611UXC
tristate "Lontium LT9611UXC DSI/HDMI bridge"
select SND_SOC_HDMI_CODEC if SND_SOC
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index beab5b695a6e..92688be9692f 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
+obj-$(CONFIG_DRM_LONTIUM_LT9611C) += lontium-lt9611c.o
obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
diff --git a/drivers/gpu/drm/bridge/lontium-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c
new file mode 100644
index 000000000000..3131f4a32dfd
--- /dev/null
+++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c
@@ -0,0 +1,1211 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Lontium Semiconductor, Inc.
+ */
+
+#include <linux/crc8.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/display/drm_hdmi_audio_helper.h>
+#include <drm/display/drm_hdmi_state_helper.h>
+#include <sound/hdmi-codec.h>
+
+#define FW_SIZE (64 * 1024)
+#define LT_PAGE_SIZE 256
+#define FW_FILE "lt9611c_fw.bin"
+#define LT9611C_CRC_POLYNOMIAL 0x31
+#define LT9611C_PAGE_CONTROL 0xff
+
+enum lt9611_chip_type {
+ CHIP_LT9611C = 0,
+ CHIP_LT9611EX,
+ CHIP_LT9611UXD,
+};
+
+struct lt9611c {
+ struct device *dev;
+ struct i2c_client *client;
+ struct drm_bridge bridge;
+ struct regmap *regmap;
+ /* Protects all accesses to registers by stopping the on-chip MCU */
+ struct mutex ocm_lock;
+ struct work_struct work;
+ struct device_node *dsi0_node;
+ struct device_node *dsi1_node;
+ struct mipi_dsi_device *dsi0;
+ struct mipi_dsi_device *dsi1;
+ struct gpio_desc *reset_gpio;
+ struct regulator_bulk_data supplies[2];
+ int fw_version;
+ /* Chip variant: C/EX/UXD */
+ enum lt9611_chip_type chip_type;
+ /* HDMI cable connection status */
+ bool hdmi_connected;
+};
+
+DECLARE_CRC8_TABLE(lt9611c_crc8_table);
+
+static const struct regmap_range_cfg lt9611c_ranges[] = {
+ {
+ .name = "register_range",
+ .range_min = 0,
+ .range_max = 0xfe9c,
+ .selector_reg = LT9611C_PAGE_CONTROL,
+ .selector_mask = 0xff,
+ .selector_shift = 0,
+ .window_start = 0,
+ .window_len = 0x100,
+ },
+};
+
+static const struct regmap_config lt9611c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xfe9c,
+ .ranges = lt9611c_ranges,
+ .num_ranges = ARRAY_SIZE(lt9611c_ranges),
+};
+
+static int lt9611c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
+ unsigned int param_count, u8 *return_buffer,
+ unsigned int return_count)
+{
+ int ret;
+ unsigned int i;
+ unsigned int temp;
+ unsigned int max_params = 0xe0dd - 0xe0b0 + 1;
+
+ regmap_write(lt9611c->regmap, 0xe0de, 0x01);
+
+ ret = regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp,
+ temp == 0x01, 1000, 100 * 1000);
+ if (ret)
+ return -ETIMEDOUT;
+
+ for (i = 0; i < param_count && i < max_params; i++)
+ regmap_write(lt9611c->regmap, 0xe0b0 + i, params[i]);
+
+ regmap_write(lt9611c->regmap, 0xe0de, 0x02);
+
+ ret = regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp,
+ temp == 0x02, 1000, 100 * 1000);
+ if (ret)
+ return -ETIMEDOUT;
+
+ return regmap_bulk_read(lt9611c->regmap, 0xe085, return_buffer,
+ return_count);
+}
+
+static void lt9611c_config_parameters(struct lt9611c *lt9611c)
+{
+ const struct reg_sequence seq_write_paras[] = {
+ REG_SEQ0(0xe0ee, 0x01),
+ REG_SEQ0(0xe103, 0x3f), /*fifo rst*/
+ REG_SEQ0(0xe103, 0xff),
+ REG_SEQ0(0xe05e, 0xc1),
+ REG_SEQ0(0xe058, 0x00),
+ REG_SEQ0(0xe059, 0x50),
+ REG_SEQ0(0xe05a, 0x10),
+ REG_SEQ0(0xe05a, 0x00),
+ REG_SEQ0(0xe058, 0x21),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write_paras, ARRAY_SIZE(seq_write_paras));
+}
+
+static void lt9611c_wren(struct lt9611c *lt9611c)
+{
+ regmap_write(lt9611c->regmap, 0xe05a, 0x04);
+ regmap_write(lt9611c->regmap, 0xe05a, 0x00);
+}
+
+static void lt9611c_wrdi(struct lt9611c *lt9611c)
+{
+ regmap_write(lt9611c->regmap, 0xe05a, 0x08);
+ regmap_write(lt9611c->regmap, 0xe05a, 0x00);
+}
+
+static void lt9611c_erase_op(struct lt9611c *lt9611c, u32 addr)
+{
+ const struct reg_sequence seq_write[] = {
+ REG_SEQ0(0xe0ee, 0x01),
+ REG_SEQ0(0xe05a, 0x04),
+ REG_SEQ0(0xe05a, 0x00),
+ REG_SEQ0(0xe05b, (addr >> 16) & 0xff),
+ REG_SEQ0(0xe05c, (addr >> 8) & 0xff),
+ REG_SEQ0(0xe05d, addr & 0xff),
+ REG_SEQ0(0xe05a, 0x01),
+ REG_SEQ0(0xe05a, 0x00),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write));
+}
+
+static void read_flash_reg_status(struct lt9611c *lt9611c, unsigned int *status)
+{
+ const struct reg_sequence seq_write[] = {
+ REG_SEQ0(0xe103, 0x3f),
+ REG_SEQ0(0xe103, 0xff),
+ REG_SEQ0(0xe05e, 0x40),
+ REG_SEQ0(0xe056, 0x05),
+ REG_SEQ0(0xe055, 0x25),
+ REG_SEQ0(0xe055, 0x01),
+ REG_SEQ0(0xe058, 0x21),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write));
+
+ regmap_read(lt9611c->regmap, 0xe05f, status);
+}
+
+static void lt9611c_crc_to_sram(struct lt9611c *lt9611c)
+{
+ const struct reg_sequence seq_write[] = {
+ REG_SEQ0(0xe051, 0x00),
+ REG_SEQ0(0xe055, 0xc0),
+ REG_SEQ0(0xe055, 0x80),
+ REG_SEQ0(0xe05e, 0xc0),
+ REG_SEQ0(0xe058, 0x21),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write));
+}
+
+static void lt9611c_data_to_sram(struct lt9611c *lt9611c)
+{
+ const struct reg_sequence seq_write[] = {
+ REG_SEQ0(0xe051, 0xff),
+ REG_SEQ0(0xe055, 0x80),
+ REG_SEQ0(0xe05e, 0xc0),
+ REG_SEQ0(0xe058, 0x21),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write));
+}
+
+static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, size_t addr)
+{
+ const struct reg_sequence seq_write[] = {
+ REG_SEQ0(0xe05b, (addr >> 16) & 0xff),
+ REG_SEQ0(0xe05c, (addr >> 8) & 0xff),
+ REG_SEQ0(0xe05d, addr & 0xff),
+ REG_SEQ0(0xe05a, 0x30),
+ REG_SEQ0(0xe05a, 0x00),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write));
+}
+
+static void lt9611c_block_erase(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int i;
+ unsigned int block_num;
+ unsigned int flash_status = 0;
+ u32 flash_addr = 0;
+
+ for (block_num = 0; block_num < 2; block_num++) {
+ flash_addr = (block_num * 0x008000);
+ lt9611c_erase_op(lt9611c, flash_addr);
+ msleep(100);
+ i = 0;
+ while (1) {
+ read_flash_reg_status(lt9611c, &flash_status);
+ if ((flash_status & 0x01) == 0)
+ break;
+
+ if (i > 50)
+ break;
+
+ i++;
+ msleep(50);
+ }
+ }
+
+ dev_dbg(dev, "erase flash done.\n");
+}
+
+static int lt9611c_write_data(struct lt9611c *lt9611c, const struct firmware *fw, size_t addr)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+ unsigned int page = 0, num = 0, i = 0;
+ size_t size, index;
+ const u8 *data;
+ u8 value;
+
+ data = fw->data;
+ size = fw->size;
+ page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
+ if (page * LT_PAGE_SIZE > FW_SIZE) {
+ dev_err(dev, "firmware size out of range\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "%u pages, total size %zu byte\n", page, size);
+
+ for (num = 0; num < page; num++) {
+ lt9611c_data_to_sram(lt9611c);
+
+ for (i = 0; i < LT_PAGE_SIZE; i++) {
+ index = num * LT_PAGE_SIZE + i;
+ value = (index < size) ? data[index] : 0xff;
+
+ ret = regmap_write(lt9611c->regmap, 0xe059, value);
+ if (ret < 0) {
+ dev_err(dev, "write error at page %u, index %u\n", num, i);
+ return ret;
+ }
+ }
+
+ lt9611c_wren(lt9611c);
+ lt9611c_sram_to_flash(lt9611c, addr);
+
+ addr += LT_PAGE_SIZE;
+ }
+
+ lt9611c_wrdi(lt9611c);
+
+ return 0;
+}
+
+static int lt9611c_write_crc(struct lt9611c *lt9611c, u8 fw_crc, size_t addr)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+
+ lt9611c_crc_to_sram(lt9611c);
+ ret = regmap_write(lt9611c->regmap, 0xe059, fw_crc);
+ if (ret < 0) {
+ dev_err(dev, "failed to write crc\n");
+ return ret;
+ }
+
+ lt9611c_wren(lt9611c);
+ lt9611c_sram_to_flash(lt9611c, addr);
+ lt9611c_wrdi(lt9611c);
+
+ dev_dbg(dev, "crc 0x%02x written to flash at addr 0x%zx\n", fw_crc, addr);
+
+ return 0;
+}
+
+static void lt9611c_reset(struct lt9611c *lt9611c)
+{
+ gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
+ msleep(20);
+
+ gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
+ msleep(20);
+
+ gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
+ msleep(400);
+
+ dev_dbg(lt9611c->dev, "lt9611c reset");
+}
+
+static int lt9611c_upgrade_result(struct lt9611c *lt9611c, u8 fw_crc)
+{
+ struct device *dev = lt9611c->dev;
+ unsigned int crc_result;
+
+ regmap_write(lt9611c->regmap, 0xe0ee, 0x01);
+ regmap_read(lt9611c->regmap, 0xe021, &crc_result);
+
+ if (crc_result != fw_crc) {
+ dev_err(dev, "lt9611c fw upgrade failed, expected crc=0x%02x, read crc=0x%02x\n",
+ fw_crc, crc_result);
+ return -1;
+ }
+
+ dev_dbg(dev, "lt9611c firmware upgrade success, crc=0x%02x\n", crc_result);
+ return 0;
+}
+
+static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ const struct firmware *fw;
+ u8 *buffer;
+ size_t total_size = FW_SIZE - 1;
+ u8 fw_crc;
+ int ret;
+
+ /*1. load firmware*/
+ ret = request_firmware(&fw, FW_FILE, dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to load '%s'\n", FW_FILE);
+
+ /*2. check size*/
+ if (fw->size > total_size) {
+ dev_err(dev, "firmware too large (%zu > %zu)\n", fw->size, total_size);
+ ret = -EINVAL;
+ goto out_release_fw;
+ }
+ dev_dbg(dev, "firmware size: %zu bytes\n", fw->size);
+
+ /*3. calculate crc8 */
+ buffer = kzalloc(total_size, GFP_KERNEL);
+ if (!buffer) {
+ ret = -ENOMEM;
+ goto out_release_fw;
+ }
+
+ memset(buffer, 0xff, total_size);
+ memcpy(buffer, fw->data, fw->size);
+
+ fw_crc = crc8(lt9611c_crc8_table, buffer, total_size, 0);
+ kfree(buffer);
+
+ dev_dbg(dev, "firmware crc: 0x%02x\n", fw_crc);
+ dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", fw->size);
+
+ lt9611c_config_parameters(lt9611c);
+ lt9611c_block_erase(lt9611c);
+
+ ret = lt9611c_write_data(lt9611c, fw, 0);
+ if (ret < 0) {
+ dev_err(dev, "failed to write firmware data\n");
+ goto out_release_fw;
+ }
+
+ ret = lt9611c_write_crc(lt9611c, fw_crc, FW_SIZE - 1);
+ if (ret < 0) {
+ dev_err(dev, "failed to write firmware crc\n");
+ goto out_release_fw;
+ }
+
+ /*5. check upgrade of result*/
+ lt9611c_reset(lt9611c);
+ ret = lt9611c_upgrade_result(lt9611c, fw_crc);
+
+out_release_fw:
+ release_firmware(fw);
+ return ret;
+}
+
+static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct lt9611c, bridge);
+}
+
+/*read only*/
+static const struct lt9611c *bridge_to_lt9611c_const(const struct drm_bridge *bridge)
+{
+ return container_of(bridge, const struct lt9611c, bridge);
+}
+
+static void lt9611c_lock(struct lt9611c *lt9611c)
+{
+ mutex_lock(<9611c->ocm_lock);
+ regmap_write(lt9611c->regmap, 0xe0ee, 0x01);
+}
+
+static void lt9611c_unlock(struct lt9611c *lt9611c)
+{
+ regmap_write(lt9611c->regmap, 0xe0ee, 0x00);
+ mutex_unlock(<9611c->ocm_lock);
+}
+
+static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id)
+{
+ struct lt9611c *lt9611c = dev_id;
+ struct device *dev = lt9611c->dev;
+ int ret;
+ unsigned int irq_status;
+ u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
+ u8 data[5];
+
+ guard(mutex)(<9611c->ocm_lock);
+
+ ret = regmap_read(lt9611c->regmap, 0xe084, &irq_status);
+ if (ret) {
+ dev_err(dev, "failed to read irq status: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ if (!(irq_status & BIT(0)))
+ return IRQ_HANDLED;
+
+ ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd), data, ARRAY_SIZE(data));
+ if (ret) {
+ dev_err(dev, "failed to read HPD status\n");
+ } else {
+ lt9611c->hdmi_connected = (data[4] == 0x02);
+ dev_dbg(dev, "HDMI %s\n", lt9611c->hdmi_connected ? "connected" : "disconnected");
+ }
+
+ /*Clear interrupt: hardware requires two writes with delay*/
+ regmap_write(lt9611c->regmap, 0xe0df, irq_status & BIT(0));
+ usleep_range(10000, 12000);
+ regmap_write(lt9611c->regmap, 0xe0df, irq_status & (~BIT(0)));
+
+ schedule_work(<9611c->work);
+
+ return IRQ_HANDLED;
+}
+
+static void lt9611c_hpd_work(struct work_struct *work)
+{
+ struct lt9611c *lt9611c = container_of(work, struct lt9611c, work);
+ bool connected;
+
+ mutex_lock(<9611c->ocm_lock);
+ connected = lt9611c->hdmi_connected;
+ mutex_unlock(<9611c->ocm_lock);
+
+ drm_bridge_hpd_notify(<9611c->bridge,
+ connected ? connector_status_connected :
+ connector_status_disconnected);
+}
+
+static int lt9611c_regulator_init(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+
+ lt9611c->supplies[0].supply = "vcc";
+ lt9611c->supplies[1].supply = "vdd";
+
+ ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies);
+
+ return ret;
+}
+
+static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c,
+ struct device_node *dsi_node)
+{
+ const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL };
+ struct mipi_dsi_device *dsi;
+ struct mipi_dsi_host *host;
+ struct device *dev = lt9611c->dev;
+ int ret;
+
+ host = of_find_mipi_dsi_host_by_node(dsi_node);
+ if (!host)
+ return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n"));
+
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
+ if (IS_ERR(dsi))
+ return ERR_PTR(dev_err_probe(dev, PTR_ERR(dsi), "failed to create dsi device\n"));
+
+ dsi->lanes = 4;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_VIDEO_HSE;
+
+ ret = devm_mipi_dsi_attach(dev, dsi);
+ if (ret < 0)
+ return ERR_PTR(dev_err_probe(dev, ret, "failed to attach dsi to host\n"));
+
+ return dsi;
+}
+
+static int lt9611c_bridge_attach(struct drm_bridge *bridge,
+ struct drm_encoder *encoder,
+ enum drm_bridge_attach_flags flags)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+
+ return drm_bridge_attach(encoder, lt9611c->bridge.next_bridge, bridge, flags);
+}
+
+static enum drm_mode_status
+lt9611c_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ unsigned long long tmds_rate)
+{
+ const struct lt9611c *lt9611c = bridge_to_lt9611c_const(bridge);
+
+ if (lt9611c->chip_type == CHIP_LT9611UXD) {
+ if (tmds_rate > 600000000)
+ return MODE_CLOCK_HIGH;
+
+ } else {
+ if (tmds_rate > 340000000)
+ return MODE_CLOCK_HIGH;
+ }
+
+ if (tmds_rate < 25000000)
+ return MODE_CLOCK_LOW;
+
+ return MODE_OK;
+}
+
+static void lt9611c_video_setup(struct lt9611c *lt9611c,
+ const struct drm_display_mode *mode)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+ u32 h_total, hactive, hsync_len, hfront_porch, hback_porch;
+ u32 v_total, vactive, vsync_len, vfront_porch, vback_porch;
+ u8 timing_set_cmd[26] = {0x57, 0x4d, 0x33, 0x3a};
+ u8 return_param[3];
+ u8 framerate;
+ u8 vic = 0x00;
+
+ guard(mutex)(<9611c->ocm_lock);
+ h_total = mode->htotal;
+ hactive = mode->hdisplay;
+ hsync_len = mode->hsync_end - mode->hsync_start;
+ hfront_porch = mode->hsync_start - mode->hdisplay;
+ hback_porch = mode->htotal - mode->hsync_end;
+
+ v_total = mode->vtotal;
+ vactive = mode->vdisplay;
+ vsync_len = mode->vsync_end - mode->vsync_start;
+ vfront_porch = mode->vsync_start - mode->vdisplay;
+ vback_porch = mode->vtotal - mode->vsync_end;
+ framerate = drm_mode_vrefresh(mode);
+ vic = drm_match_cea_mode(mode);
+
+ dev_dbg(dev, "hactive=%d, vactive=%d\n", hactive, vactive);
+ dev_dbg(dev, "framerate=%d\n", framerate);
+ dev_dbg(dev, "vic = 0x%02x\n", vic);
+
+ timing_set_cmd[4] = (h_total >> 8) & 0xff;
+ timing_set_cmd[5] = h_total & 0xff;
+ timing_set_cmd[6] = (hactive >> 8) & 0xff;
+ timing_set_cmd[7] = hactive & 0xff;
+ timing_set_cmd[8] = (hfront_porch >> 8) & 0xff;
+ timing_set_cmd[9] = hfront_porch & 0xff;
+ timing_set_cmd[10] = (hsync_len >> 8) & 0xff;
+ timing_set_cmd[11] = hsync_len & 0xff;
+ timing_set_cmd[12] = (hback_porch >> 8) & 0xff;
+ timing_set_cmd[13] = hback_porch & 0xff;
+ timing_set_cmd[14] = (v_total >> 8) & 0xff;
+ timing_set_cmd[15] = v_total & 0xff;
+ timing_set_cmd[16] = (vactive >> 8) & 0xff;
+ timing_set_cmd[17] = vactive & 0xFF;
+ timing_set_cmd[18] = (vfront_porch >> 8) & 0xff;
+ timing_set_cmd[19] = vfront_porch & 0xff;
+ timing_set_cmd[20] = (vsync_len >> 8) & 0xff;
+ timing_set_cmd[21] = vsync_len & 0xff;
+ timing_set_cmd[22] = (vback_porch >> 8) & 0xff;
+ timing_set_cmd[23] = vback_porch & 0xff;
+ timing_set_cmd[24] = framerate;
+ timing_set_cmd[25] = vic;
+
+ ret = lt9611c_read_write_flow(lt9611c,
+ timing_set_cmd, ARRAY_SIZE(timing_set_cmd),
+ return_param, ARRAY_SIZE(return_param));
+ if (ret)
+ dev_err(dev, "video set failed\n");
+}
+
+static void lt9611c_bridge_atomic_enable(struct drm_bridge *bridge,
+ struct drm_atomic_state *state)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ struct drm_connector *connector;
+ struct drm_connector_state *conn_state;
+ struct drm_crtc_state *crtc_state;
+ struct drm_display_mode *mode;
+
+ connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
+ if (WARN_ON(!connector))
+ return;
+
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
+ if (WARN_ON(!conn_state))
+ return;
+
+ crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
+ if (WARN_ON(!crtc_state))
+ return;
+
+ mode = &crtc_state->adjusted_mode;
+
+ lt9611c_video_setup(lt9611c, mode);
+}
+
+static enum drm_connector_status
+lt9611c_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ struct device *dev = lt9611c->dev;
+ int ret;
+ bool connected = false;
+ u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
+ u8 data[5];
+
+ guard(mutex)(<9611c->ocm_lock);
+
+ ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd), data, ARRAY_SIZE(data));
+ if (ret)
+ dev_err(dev, "failed to read HPD status (err=%d)\n", ret);
+ else
+ connected = (data[4] == 0x02);
+
+ lt9611c->hdmi_connected = connected;
+
+ return connected ? connector_status_connected :
+ connector_status_disconnected;
+}
+
+static int lt9611c_get_edid_block(void *data, u8 *buf,
+ unsigned int block, size_t len)
+{
+ struct lt9611c *lt9611c = data;
+ struct device *dev = lt9611c->dev;
+ u8 cmd[5] = {0x52, 0x48, 0x33, 0x3a, 0x00};
+ u8 packet[37];
+ int ret, i, offset = 0;
+
+ if (len != 128)
+ return -EINVAL;
+ guard(mutex)(<9611c->ocm_lock);
+
+ for (i = 0; i < 4; i++) {
+ cmd[4] = block * 4 + i;
+ ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd),
+ packet, ARRAY_SIZE(packet));
+ if (ret) {
+ dev_err(dev, "Failed to read EDID block %u packet %d\n",
+ block, i);
+ return ret;
+ }
+ memcpy(buf + offset, &packet[5], 32);
+ offset += 32;
+ }
+
+ return 0;
+}
+
+static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+
+ return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c);
+}
+
+static int lt9611c_hdmi_write_avi_infoframe(struct drm_bridge *bridge,
+ const u8 *buffer, size_t len)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ u8 *cmd;
+ u8 data[5];
+ int ret;
+
+ guard(mutex)(<9611c->ocm_lock);
+
+ cmd = kmalloc(5 + len, GFP_KERNEL);
+ if (!cmd)
+ return -ENOMEM;
+
+ cmd[0] = 0x57;
+ cmd[1] = 0x48;
+ cmd[2] = 0x35;
+ cmd[3] = 0x3a;
+ cmd[4] = 0x01;/*write avi*/
+ memcpy(cmd + 5, buffer, len);
+
+ ret = lt9611c_read_write_flow(lt9611c, cmd, 5 + len,
+ data, ARRAY_SIZE(data));
+ kfree(cmd);
+
+ if (ret < 0) {
+ dev_err(lt9611c->dev, "write avi infoframe failed!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt9611c_hdmi_clear_avi_infoframe(struct drm_bridge *bridge)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ u8 cmd[5] = {0x57, 0x48, 0x42, 0x3a, 0x01};
+ u8 data[5];
+ int ret;
+
+ guard(mutex)(<9611c->ocm_lock);
+
+ ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd),
+ data, ARRAY_SIZE(data));
+
+ if (ret < 0) {
+ dev_err(lt9611c->dev, "clear avi infoframe failed!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt9611c_hdmi_write_audio_infoframe(struct drm_bridge *bridge,
+ const u8 *buffer, size_t len)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ u8 *cmd;
+ u8 data[5];
+ int ret;
+
+ guard(mutex)(<9611c->ocm_lock);
+
+ cmd = kmalloc(5 + len, GFP_KERNEL);
+ if (!cmd)
+ return -ENOMEM;
+
+ cmd[0] = 0x57;
+ cmd[1] = 0x48;
+ cmd[2] = 0x35;
+ cmd[3] = 0x3a;
+ cmd[4] = 0x02;/*write audio*/
+ memcpy(cmd + 5, buffer, len);
+
+ ret = lt9611c_read_write_flow(lt9611c, cmd, 5 + len,
+ data, ARRAY_SIZE(data));
+
+ kfree(cmd);
+
+ if (ret < 0) {
+ dev_err(lt9611c->dev, "write audio infoframe failed!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt9611c_hdmi_clear_audio_infoframe(struct drm_bridge *bridge)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ u8 cmd[5] = {0x57, 0x48, 0x42, 0x3a, 0x02};
+ u8 data[5];
+ int ret;
+
+ guard(mutex)(<9611c->ocm_lock);
+
+ ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd),
+ data, ARRAY_SIZE(data));
+
+ if (ret < 0) {
+ dev_err(lt9611c->dev, "clear audio infoframe failed!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt9611c_hdmi_audio_prepare(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ struct hdmi_codec_daifmt *fmt,
+ struct hdmi_codec_params *hparms)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ u8 audio_cmd[6] = {0x57, 0x48, 0x36, 0x3a};
+ u8 data[5];
+ int ret;
+
+ if (hparms->sample_width == 32)
+ return -EINVAL;
+
+ switch (fmt->fmt) {
+ case HDMI_I2S:
+ audio_cmd[4] = 0x01;
+ break;
+ case HDMI_SPDIF:
+ audio_cmd[4] = 0x02;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ audio_cmd[5] = hparms->channels;
+ guard(mutex)(<9611c->ocm_lock);
+
+ ret = lt9611c_read_write_flow(lt9611c, audio_cmd, sizeof(audio_cmd),
+ data, sizeof(data));
+ if (ret < 0) {
+ dev_err(lt9611c->dev, "set audio info failed!\n");
+ return ret;
+ }
+
+ return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector,
+ &hparms->cea);
+}
+
+static void lt9611c_hdmi_audio_shutdown(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector);
+}
+
+static int lt9611c_hdmi_audio_startup(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ return 0;
+}
+
+static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
+ .attach = lt9611c_bridge_attach,
+ .detect = lt9611c_bridge_detect,
+ .edid_read = lt9611c_bridge_edid_read,
+ .atomic_enable = lt9611c_bridge_atomic_enable,
+
+ .hdmi_tmds_char_rate_valid = lt9611c_hdmi_tmds_char_rate_valid,
+ .hdmi_write_avi_infoframe = lt9611c_hdmi_write_avi_infoframe,
+ .hdmi_clear_avi_infoframe = lt9611c_hdmi_clear_avi_infoframe,
+ .hdmi_write_audio_infoframe = lt9611c_hdmi_write_audio_infoframe,
+ .hdmi_clear_audio_infoframe = lt9611c_hdmi_clear_audio_infoframe,
+
+ .hdmi_audio_startup = lt9611c_hdmi_audio_startup,
+ .hdmi_audio_prepare = lt9611c_hdmi_audio_prepare,
+ .hdmi_audio_shutdown = lt9611c_hdmi_audio_shutdown,
+};
+
+static int lt9611c_parse_dt(struct device *dev,
+ struct lt9611c *lt9611c)
+{
+ lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1);
+ if (!lt9611c->dsi0_node)
+ return dev_err_probe(dev, -ENODEV, "failed to get remote node for primary dsi\n");
+
+ lt9611c->dsi1_node = of_graph_get_remote_node(dev->of_node, 1, -1);
+
+ return drm_of_find_panel_or_bridge(dev->of_node, 2, -1, NULL, <9611c->bridge.next_bridge);
+}
+
+static int lt9611c_gpio_init(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+
+ lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(lt9611c->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(lt9611c->reset_gpio),
+ "failed to acquire reset gpio\n");
+
+ return 0;
+}
+
+static int lt9611c_read_version(struct lt9611c *lt9611c)
+{
+ u8 buf[2];
+ int ret;
+
+ ret = regmap_write(lt9611c->regmap, 0xe0ee, 0x01);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(lt9611c->regmap, 0xe080, buf, ARRAY_SIZE(buf));
+ if (ret)
+ return ret;
+
+ return (buf[0] << 8) | buf[1];
+}
+
+static int lt9611c_read_chipid(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ u8 chipid[2];
+ int ret;
+
+ ret = regmap_write(lt9611c->regmap, 0xe0ee, 0x01);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(lt9611c->regmap, 0xe100, chipid, 2);
+ if (ret)
+ return ret;
+
+ if (chipid[0] != 0x23 || chipid[1] != 0x06) {
+ dev_err(dev, "ChipID: 0x%02x 0x%02x\n", chipid[0], chipid[1]);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static ssize_t lt9611c_firmware_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct lt9611c *lt9611c = dev_get_drvdata(dev);
+ int ret;
+
+ lt9611c_lock(lt9611c);
+
+ ret = lt9611c_firmware_upgrade(lt9611c);
+ if (ret < 0)
+ dev_err(dev, "upgrade failure\n");
+
+ lt9611c_unlock(lt9611c);
+
+ return ret < 0 ? ret : len;
+}
+
+static ssize_t lt9611c_firmware_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct lt9611c *lt9611c = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "0x%04x\n", lt9611c->fw_version);
+}
+
+static DEVICE_ATTR_RW(lt9611c_firmware);
+
+static struct attribute *lt9611c_attrs[] = {
+ &dev_attr_lt9611c_firmware.attr,
+ NULL,
+};
+
+static const struct attribute_group lt9611c_attr_group = {
+ .attrs = lt9611c_attrs,
+};
+
+static const struct attribute_group *lt9611c_attr_groups[] = {
+ <9611c_attr_group,
+ NULL,
+};
+
+static int lt9611c_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct lt9611c *lt9611c;
+ struct device *dev = &client->dev;
+ bool fw_updated = false;
+ int ret;
+
+ crc8_populate_msb(lt9611c_crc8_table, LT9611C_CRC_POLYNOMIAL);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return dev_err_probe(dev, -ENODEV, "device doesn't support I2C\n");
+
+ lt9611c = devm_drm_bridge_alloc(dev, struct lt9611c, bridge, <9611c_bridge_funcs);
+ if (IS_ERR(lt9611c))
+ return dev_err_probe(dev, PTR_ERR(lt9611c), "drm bridge alloc failed.\n");
+
+ lt9611c->dev = dev;
+ lt9611c->client = client;
+ lt9611c->chip_type = id->driver_data;
+ ret = devm_mutex_init(dev, <9611c->ocm_lock);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to init mutex\n");
+
+ lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config);
+ if (IS_ERR(lt9611c->regmap))
+ return dev_err_probe(dev, PTR_ERR(lt9611c->regmap), "regmap i2c init failed\n");
+
+ ret = lt9611c_parse_dt(dev, lt9611c);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to parse device tree\n");
+
+ ret = lt9611c_gpio_init(lt9611c);
+ if (ret < 0)
+ goto err_of_put;
+
+ ret = lt9611c_regulator_init(lt9611c);
+ if (ret < 0)
+ goto err_of_put;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
+ if (ret)
+ goto err_of_put;
+
+ lt9611c_reset(lt9611c);
+
+ lt9611c_lock(lt9611c);
+
+ ret = lt9611c_read_chipid(lt9611c);
+ if (ret < 0) {
+ dev_err(dev, "failed to read chip id.\n");
+ lt9611c_unlock(lt9611c);
+ goto err_disable_regulators;
+ }
+
+retry:
+ lt9611c->fw_version = lt9611c_read_version(lt9611c);
+ if (lt9611c->fw_version < 0) {
+ dev_err(dev, "failed to read fw version\n");
+ ret = -EOPNOTSUPP;
+ lt9611c_unlock(lt9611c);
+ goto err_disable_regulators;
+
+ } else if (lt9611c->fw_version == 0) {
+ if (!fw_updated) {
+ fw_updated = true;
+ ret = lt9611c_firmware_upgrade(lt9611c);
+ if (ret < 0) {
+ lt9611c_unlock(lt9611c);
+ goto err_disable_regulators;
+ }
+
+ goto retry;
+
+ } else {
+ dev_err(dev, "fw version 0x%04x, update failed\n", lt9611c->fw_version);
+ ret = -EOPNOTSUPP;
+ lt9611c_unlock(lt9611c);
+ goto err_disable_regulators;
+ }
+ }
+
+ lt9611c_unlock(lt9611c);
+ dev_dbg(dev, "current version:0x%04x", lt9611c->fw_version);
+
+ INIT_WORK(<9611c->work, lt9611c_hpd_work);
+
+ ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ lt9611c_irq_thread_handler,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT |
+ IRQF_NO_AUTOEN,
+ "lt9611c", lt9611c);
+ if (ret) {
+ dev_err(dev, "failed to request irq\n");
+ goto err_disable_regulators;
+ }
+
+ lt9611c->bridge.of_node = client->dev.of_node;
+ lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT |
+ DRM_BRIDGE_OP_EDID |
+ DRM_BRIDGE_OP_HPD |
+ DRM_BRIDGE_OP_HDMI |
+ DRM_BRIDGE_OP_HDMI_AUDIO;
+ lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+
+ lt9611c->bridge.hdmi_audio_dev = dev;
+ lt9611c->bridge.hdmi_audio_max_i2s_playback_channels = 8;
+ lt9611c->bridge.hdmi_audio_dai_port = 2;
+
+ devm_drm_bridge_add(dev, <9611c->bridge);
+
+ /* Attach primary DSI */
+ lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node);
+ if (IS_ERR(lt9611c->dsi0)) {
+ ret = PTR_ERR(lt9611c->dsi0);
+ goto err_remove_bridge;
+ }
+
+ /* Attach secondary DSI, if specified */
+ if (lt9611c->dsi1_node) {
+ lt9611c->dsi1 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi1_node);
+ if (IS_ERR(lt9611c->dsi1)) {
+ ret = PTR_ERR(lt9611c->dsi1);
+ goto err_remove_bridge;
+ }
+ }
+
+ lt9611c->hdmi_connected = false;
+ i2c_set_clientdata(client, lt9611c);
+ enable_irq(client->irq);
+ lt9611c_reset(lt9611c);
+
+ return 0;
+
+err_remove_bridge:
+ free_irq(client->irq, lt9611c);
+ cancel_work_sync(<9611c->work);
+ drm_bridge_remove(<9611c->bridge);
+
+err_disable_regulators:
+ regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
+
+err_of_put:
+ of_node_put(lt9611c->dsi1_node);
+ of_node_put(lt9611c->dsi0_node);
+
+ return ret;
+}
+
+static void lt9611c_remove(struct i2c_client *client)
+{
+ struct lt9611c *lt9611c = i2c_get_clientdata(client);
+
+ free_irq(client->irq, lt9611c);
+ cancel_work_sync(<9611c->work);
+ regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
+ of_node_put(lt9611c->dsi1_node);
+ of_node_put(lt9611c->dsi0_node);
+}
+
+static int lt9611c_bridge_suspend(struct device *dev)
+{
+ struct lt9611c *lt9611c = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(lt9611c->dev, "suspend\n");
+ disable_irq(lt9611c->client->irq);
+ ret = regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
+ if (ret) {
+ dev_err(lt9611c->dev, "regulator bulk disable failed.\n");
+ return ret;
+ }
+ gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
+
+ return ret;
+}
+
+static int lt9611c_bridge_resume(struct device *dev)
+{
+ struct lt9611c *lt9611c = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
+ if (ret) {
+ dev_err(lt9611c->dev, "regulator bulk enable failed.\n");
+ return ret;
+ }
+ enable_irq(lt9611c->client->irq);
+ lt9611c_reset(lt9611c);
+ dev_dbg(lt9611c->dev, "resume\n");
+
+ return ret;
+}
+
+static const struct dev_pm_ops lt9611c_bridge_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend,
+ lt9611c_bridge_resume)
+};
+
+static struct i2c_device_id lt9611c_id[] = {
+ /* chip_type */
+ { "lontium,lt9611c", 0 },
+ { "lontium,lt9611ex", 1 },
+ { "lontium,lt9611uxd", 2 },
+ { /* sentinel */ }
+};
+
+static const struct of_device_id lt9611c_match_table[] = {
+ { .compatible = "lontium,lt9611c" },
+ { .compatible = "lontium,lt9611ex" },
+ { .compatible = "lontium,lt9611uxd" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, lt9611c_match_table);
+
+static struct i2c_driver lt9611c_driver = {
+ .driver = {
+ .name = "lt9611c",
+ .of_match_table = lt9611c_match_table,
+ .pm = <9611c_bridge_pm_ops,
+ .dev_groups = lt9611c_attr_groups,
+ },
+ .probe = lt9611c_probe,
+ .remove = lt9611c_remove,
+ .id_table = lt9611c_id,
+};
+module_i2c_driver(lt9611c_driver);
+
+MODULE_AUTHOR("SunYun Yang <syyang@lontium.com>");
+MODULE_DESCRIPTION("Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(FW_FILE);
+
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v4 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
2026-05-06 9:51 ` [PATCH v4 2/2] drm/bridge: " syyang
@ 2026-05-06 20:06 ` Dmitry Baryshkov
2026-05-07 3:46 ` Claude review: " Claude Code Review Bot
1 sibling, 0 replies; 7+ messages in thread
From: Dmitry Baryshkov @ 2026-05-06 20:06 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, xmzhu, rlyu, xbpeng
On Wed, May 06, 2026 at 05:51:00PM +0800, syyang@lontium.com wrote:
> From: Sunyun Yang <syyang@lontium.com>
>
> LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port
> mipi dsi and output hdmi, differences in hardware features:
> - LT9611C: supports 1-port mipi dsi to hdmi 1.4
> - LT9611EX: supports 2-port mipi dsi to hdmi 1.4
> - LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0
>
> 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-lt9611c.c | 1211 ++++++++++++++++++++++
> 3 files changed, 1230 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c
>
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index c3209b0f4678..32b85a2a65d9 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -177,6 +177,24 @@ config DRM_LONTIUM_LT9611
> HDMI signals
> 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_LONTIUM_LT9611UXC
> tristate "Lontium LT9611UXC DSI/HDMI bridge"
> select SND_SOC_HDMI_CODEC if SND_SOC
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index beab5b695a6e..92688be9692f 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
> obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
> obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> +obj-$(CONFIG_DRM_LONTIUM_LT9611C) += lontium-lt9611c.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
> obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> diff --git a/drivers/gpu/drm/bridge/lontium-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c
> new file mode 100644
> index 000000000000..3131f4a32dfd
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c
> @@ -0,0 +1,1211 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2026 Lontium Semiconductor, Inc.
> + */
> +
> +#include <linux/crc8.h>
> +#include <linux/firmware.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_modes.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/display/drm_hdmi_audio_helper.h>
> +#include <drm/display/drm_hdmi_state_helper.h>
> +#include <sound/hdmi-codec.h>
> +
> +#define FW_SIZE (64 * 1024)
> +#define LT_PAGE_SIZE 256
> +#define FW_FILE "lt9611c_fw.bin"
So, this file doesn't have any subdir (like all other Lontium firmware
files), but then you submit a file to linux-firmware with the Lontium/
subdir. Could you please settle on one location?
> +#define LT9611C_CRC_POLYNOMIAL 0x31
> +#define LT9611C_PAGE_CONTROL 0xff
> +
[...]
> +
> +static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
> + .attach = lt9611c_bridge_attach,
> + .detect = lt9611c_bridge_detect,
> + .edid_read = lt9611c_bridge_edid_read,
> + .atomic_enable = lt9611c_bridge_atomic_enable,
There is .atomic_enable, but no atomic state management here. Was it
actually tested?
> +
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 7+ messages in thread
* Claude review: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
2026-05-06 9:50 [PATCH v4 0/2] Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver syyang
2026-05-06 9:50 ` [PATCH v4 1/2] dt-bindings: bridge: " syyang
2026-05-06 9:51 ` [PATCH v4 2/2] drm/bridge: " syyang
@ 2026-05-07 3:46 ` Claude Code Review Bot
2 siblings, 0 replies; 7+ messages in thread
From: Claude Code Review Bot @ 2026-05-07 3:46 UTC (permalink / raw)
To: dri-devel-reviews
Overall Series Review
Subject: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
Author: syyang@lontium.com
Patches: 4
Reviewed: 2026-05-07T13:46:46.865125
---
This is v4 of a driver for the Lontium LT9611C/EX/UXD MIPI DSI to HDMI bridge chips. The driver follows the modern DRM bridge HDMI helper pattern (`DRM_BRIDGE_OP_HDMI`) and uses `devm_drm_bridge_alloc`, which is good. However, there are several significant bugs in the driver, particularly around **resource management** (mixing devm and manual cleanup causes double-free and leak issues), **missing required bridge callbacks** (atomic state helpers), and a **dt-binding encoding issue**. The driver needs another revision to address these before it can be merged.
---
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 7+ messages in thread
* Claude review: dt-bindings: bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
2026-05-06 9:50 ` [PATCH v4 1/2] dt-bindings: bridge: " syyang
@ 2026-05-07 3:46 ` Claude Code Review Bot
0 siblings, 0 replies; 7+ messages in thread
From: Claude Code Review Bot @ 2026-05-07 3:46 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
**Encoding issue in description:**
```yaml
- The LT9611 and LT9611UXC are bridge devices which convert DSI to HDMI
+ The LT9611、LT9611UXC、LT9611C、LT9611EX and LT9611UXD
+ are bridge devices which convert DSI to HDMI
```
The fullwidth ideographic commas (`、`, U+3001) must be replaced with standard commas (`,`). This is a DT binding document, not CJK text.
**Sharing a binding with a different driver:** The three new compatible strings (`lontium,lt9611c`, `lontium,lt9611ex`, `lontium,lt9611uxd`) are being added to the existing `lontium,lt9611.yaml`, but patch 2 creates an entirely separate driver (`lontium-lt9611c.c`) for these chips. While the hardware is similar enough that the DT properties are the same, this coupling is fragile. If the C/EX/UXD variants ever diverge in DT requirements, it will require disentangling the bindings. This is likely acceptable if the DT maintainers have already agreed to it (the v3 changelog says merging was done per review), but it should be noted.
**Missing example update:** Adding an example for one of the new compatible strings (e.g., `lontium,lt9611c`) would strengthen the binding validation.
---
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 7+ messages in thread
* Claude review: drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
2026-05-06 9:51 ` [PATCH v4 2/2] drm/bridge: " syyang
2026-05-06 20:06 ` Dmitry Baryshkov
@ 2026-05-07 3:46 ` Claude Code Review Bot
1 sibling, 0 replies; 7+ messages in thread
From: Claude Code Review Bot @ 2026-05-07 3:46 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
#### Critical Issues
**1. Missing atomic state helper callbacks (will crash or fail to enable)**
The driver sets `DRM_BRIDGE_OP_HDMI` and implements `atomic_enable`, but does not implement the required atomic state management callbacks. The existing `lontium-lt9611.c` driver correctly provides these:
```c
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
```
Without these, the HDMI state helper infrastructure will not function correctly. These must be added to `lt9611c_bridge_funcs`.
**2. Double-free of IRQ: `devm_request_threaded_irq` + manual `free_irq`**
The IRQ is allocated with `devm_request_threaded_irq` at line ~1453, but `free_irq` is called manually in both the `err_remove_bridge` error path and in `lt9611c_remove`:
```c
err_remove_bridge:
free_irq(client->irq, lt9611c); /* manual free of devm resource */
```
```c
static void lt9611c_remove(struct i2c_client *client)
{
free_irq(client->irq, lt9611c); /* double-free: devm will also free */
```
Either use `devm_request_threaded_irq` and let devm handle teardown, or use `request_threaded_irq` with manual cleanup. Don't mix them.
**3. Double-removal of bridge: `devm_drm_bridge_add` + manual `drm_bridge_remove`**
Same pattern: `devm_drm_bridge_add` is called at line ~1476, but `drm_bridge_remove` is called in `err_remove_bridge`. `devm_drm_bridge_add` registers a devm action that calls `drm_bridge_remove` automatically. The manual call causes a double-removal.
**4. of_node reference leak from `lt9611c_parse_dt` on failure**
When `lt9611c_parse_dt` fails, the probe function returns directly:
```c
ret = lt9611c_parse_dt(dev, lt9611c);
if (ret)
return dev_err_probe(dev, ret, "failed to parse device tree\n");
```
But `lt9611c_parse_dt` may have already acquired references via `of_graph_get_remote_node` for `dsi0_node` (and possibly `dsi1_node`) before failing at `drm_of_find_panel_or_bridge`. Those references are never released because the code returns instead of jumping to `err_of_put`. The early returns before the goto-based error cleanup section (for `lt9611c_gpio_init` and `lt9611c_regulator_init`) have the same problem if `lt9611c_parse_dt` succeeded.
**5. `lt9611c_upgrade_result` returns bare `-1`**
```c
if (crc_result != fw_crc) {
...
return -1;
}
```
Should return a proper errno like `-EIO`.
#### Moderate Issues
**6. Missing `atomic_disable` callback**
The driver has `atomic_enable` but no `atomic_disable`. Compare with the existing lt9611 driver which implements both. Even if the on-chip MCU handles everything, the framework expects symmetry. At minimum, consider if any state needs to be cleaned up on disable (e.g., clearing infoframes).
**7. `SET_SYSTEM_SLEEP_PM_OPS` is deprecated**
```c
static const struct dev_pm_ops lt9611c_bridge_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend,
lt9611c_bridge_resume)
};
```
Use the modern pattern (as `lontium-lt8912b.c` does):
```c
static DEFINE_SIMPLE_DEV_PM_OPS(lt9611c_bridge_pm_ops,
lt9611c_bridge_suspend,
lt9611c_bridge_resume);
```
And reference it via `pm_sleep_ptr(<9611c_bridge_pm_ops)` in the driver struct.
**8. `i2c_device_id` table missing `const`**
```c
static struct i2c_device_id lt9611c_id[] = {
```
Should be `static const struct i2c_device_id`.
**9. `lt9611c_block_erase` ignores erase failures**
The busy-wait loop breaks after 50 iterations but doesn't return an error:
```c
if (i > 50)
break;
```
If flash erase fails, the driver proceeds to write firmware on top of unerased flash, silently producing corrupted firmware. The function should return `int` and propagate the timeout.
**10. `lt9611c_write_data` doesn't poll flash write completion**
After `lt9611c_sram_to_flash`, there's no check that the page write completed before proceeding to the next page. Compare with the erase path which at least polls `read_flash_reg_status`.
**11. Firmware sysfs attribute ignores input**
```c
static ssize_t lt9611c_firmware_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t len)
{
...
ret = lt9611c_firmware_upgrade(lt9611c);
```
The `buf` parameter is completely ignored. Any write triggers a full firmware flash. Consider at minimum requiring a specific value (e.g., "1") to trigger the upgrade to prevent accidental writes.
**12. Inconsistent use of `sizeof` vs `ARRAY_SIZE` for u8 arrays**
In `lt9611c_hdmi_audio_prepare`:
```c
ret = lt9611c_read_write_flow(lt9611c, audio_cmd, sizeof(audio_cmd),
data, sizeof(data));
```
Elsewhere the driver uses `ARRAY_SIZE`. For `u8[]` these are equivalent, but using `ARRAY_SIZE` consistently avoids bugs if the type ever changes.
#### Minor Issues
**13. `lt9611c_regulator_init` has unnecessary temporary variable**
```c
ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies);
return ret;
```
Can just be `return devm_regulator_bulk_get(...)`.
**14. Stale numbered comments in `lt9611c_firmware_upgrade`**
```c
/*1. load firmware*/
...
/*2. check size*/
...
/*3. calculate crc8 */
...
/*5. check upgrade of result*/
```
Comment 4 is missing (was presumably the write+erase step). Either fix the numbering or remove these comments entirely.
**15. `bridge_to_lt9611c_const` with `/*read only*/` comment**
```c
/*read only*/
static const struct lt9611c *bridge_to_lt9611c_const(const struct drm_bridge *bridge)
```
The comment adds nothing — the `const` qualifier already conveys the intent. Remove the comment.
**16. Regmap error checking in `lt9611c_read_write_flow`**
The `regmap_write` calls for the initial protocol handshake (writing to 0xe0de, writing parameters to 0xe0b0+i) don't check return values. A failed I2C transaction here would go unnoticed.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-05-07 3:46 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-06 9:50 [PATCH v4 0/2] Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver syyang
2026-05-06 9:50 ` [PATCH v4 1/2] dt-bindings: bridge: " syyang
2026-05-07 3:46 ` Claude review: " Claude Code Review Bot
2026-05-06 9:51 ` [PATCH v4 2/2] drm/bridge: " syyang
2026-05-06 20:06 ` Dmitry Baryshkov
2026-05-07 3:46 ` Claude review: " Claude Code Review Bot
2026-05-07 3:46 ` Claude review: " Claude Code Review Bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox