* [PATCH v6 0/2] This series adds LT9211C bridge driver by extending LT9211
@ 2026-07-19 15:57 vishnu.saini
2026-07-19 15:57 ` [PATCH v6 1/2] dt-bindings: display: bridge: lontium,lt9211: Add lt9211c support vishnu.saini
2026-07-19 15:57 ` [PATCH v6 2/2] drm/bridge: add support for lontium lt9211c bridge vishnu.saini
0 siblings, 2 replies; 4+ messages in thread
From: vishnu.saini @ 2026-07-19 15:57 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut
Cc: dri-devel, devicetree, linux-kernel, Yi Zhang, Nilesh Laad,
Gopi Botlagunta, Vishnu Saini, venkata.valluru, Jessica Zhang
LT9211c is a Single/Dual-Link DSI/LVDS or Single DPI input to
Single-link/Dual-Link DSI/LVDS or Single DPI output bridge chip.
This adds support for DSI to LVDS bridge configuration. By extending the
existing lontium-lt9211 driver to support DSI-to-LVDS bridge configuration.
Signed-off-by: Yi Zhang <zhanyi@qti.qualcomm.com>
Signed-off-by: Nilesh Laad <nilesh.laad@oss.qualcomm.com>
Signed-off-by: Gopi Botlagunta <venkata.botlagunta@oss.qualcomm.com>
Signed-off-by: Vishnu Saini <vishnu.saini@oss.qualcomm.com>
---
Changes in v6:
- Nilesh Laad and Gopi Botlagunta moved to different org in Qualcomm. Further, i will followup on this series with community, will also submit the dt series where LT9211C bridge is used.
- Rebased onto current drm-misc-next (commit 671b7825dbfe)
- Fixed lt9211_read_chipid: LT9211 match no longer checks chipid[2],
preserving compatibility with LT9211 rev U5 (db74b04edce1)
- Fixed indentation in lt9211_atomic_enable LT9211 path (was double-indented)
- Fixed indentation in lt9211_delayed_work_func body (was double-indented)
- Gated workqueue creation on LT9211C chip type only
- Fixed few formatting warnings with coccinelle and W=1
- Link to v5: https://lore.kernel.org/r/20260323-add-lt9211c-bridge-v5-0-9c63bb035c17@oss.qualcomm.com
Changes in v5:
- Addressed code formatting in lt9211 driver (no functional or design changes)
- Addressed v4 comments on lontium-lt9211.yaml
- Link to v4: https://lore.kernel.org/r/20251224-add-lt9211c-bridge-v4-0-406e73ec28c5@oss.qualcomm.com
Changes in v4:
- Removed lontium-lt9211c.yaml
- Extended lontium-lt9211.yaml to support LT9211C
- Link to v3: https://lore.kernel.org/r/20251218-add-lt9211c-bridge-v3-0-1ee0670a0db2@oss.qualcomm.com
Changes in v3:
- Removed lontium-lt9211c as separate driver
- Add support to lontium-lt9211c bridge driver by extending the existing lontium-lt9211.c
- Fixed kernel test robot reported build errors
- Link to v2: https://lore.kernel.org/lkml/20251107-add-lt9211c-bridge-v2-0-b0616e23407c@oss.qualcomm.com/
Changes in v2:
- Combined driver patch from https://lore.kernel.org/lkml/20250911-lt9211c-bridge-support-v1-1-c221202cbcd5@oss.qualcomm.com/
- Added MODULE_AUTHOR
- Uppercase to lowercase for hex values
- Link to v1: https://lore.kernel.org/r/20250910-add-lt9211c-bridge-v1-1-4f23740fe101@oss.qualcomm.com
---
Yi Zhang (2):
dt-bindings: display: bridge: lontium,lt9211: Add lt9211c support
drm/bridge: add support for lontium lt9211c bridge
.../bindings/display/bridge/lontium,lt9211.yaml | 5 +-
drivers/gpu/drm/bridge/lontium-lt9211.c | 796 ++++++++++++++++++++-
2 files changed, 782 insertions(+), 19 deletions(-)
---
base-commit: 671b7825dbfe9ea6e3ad3001003aeee0df48d1b5
change-id: 20250910-add-lt9211c-bridge-5a21fcb1c803
Best regards,
--
Vishnu Saini <vishnu.saini@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v6 1/2] dt-bindings: display: bridge: lontium,lt9211: Add lt9211c support
2026-07-19 15:57 [PATCH v6 0/2] This series adds LT9211C bridge driver by extending LT9211 vishnu.saini
@ 2026-07-19 15:57 ` vishnu.saini
2026-07-19 15:57 ` [PATCH v6 2/2] drm/bridge: add support for lontium lt9211c bridge vishnu.saini
1 sibling, 0 replies; 4+ messages in thread
From: vishnu.saini @ 2026-07-19 15:57 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut
Cc: dri-devel, devicetree, linux-kernel, Yi Zhang, Nilesh Laad,
Gopi Botlagunta, Vishnu Saini, venkata.valluru, Jessica Zhang
From: Yi Zhang <zhanyi@qti.qualcomm.com>
Add binding for the Lontium LT9211C bridge chip.
Signed-off-by: Yi Zhang <zhanyi@qti.qualcomm.com>
Signed-off-by: Nilesh Laad <nilesh.laad@oss.qualcomm.com>
Signed-off-by: Gopi Botlagunta <venkata.botlagunta@oss.qualcomm.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Vishnu Saini <vishnu.saini@oss.qualcomm.com>
---
Documentation/devicetree/bindings/display/bridge/lontium,lt9211.yaml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9211.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9211.yaml
index 7cfe92a8bcd7..de61bda09db9 100644
--- a/Documentation/devicetree/bindings/display/bridge/lontium,lt9211.yaml
+++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9211.yaml
@@ -4,19 +4,20 @@
$id: http://devicetree.org/schemas/display/bridge/lontium,lt9211.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Lontium LT9211 DSI/LVDS/DPI to DSI/LVDS/DPI bridge.
+title: Lontium LT9211/LT9211C DSI/LVDS/DPI to DSI/LVDS/DPI bridge.
maintainers:
- Marek Vasut <marex@denx.de>
description: |
- The LT9211 are bridge devices which convert Single/Dual-Link DSI/LVDS
+ The LT9211 and LT9211C are bridge devices which convert Single/Dual-Link DSI/LVDS
or Single DPI to Single/Dual-Link DSI/LVDS or Single DPI.
properties:
compatible:
enum:
- lontium,lt9211
+ - lontium,lt9211c
reg:
maxItems: 1
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v6 2/2] drm/bridge: add support for lontium lt9211c bridge
2026-07-19 15:57 [PATCH v6 0/2] This series adds LT9211C bridge driver by extending LT9211 vishnu.saini
2026-07-19 15:57 ` [PATCH v6 1/2] dt-bindings: display: bridge: lontium,lt9211: Add lt9211c support vishnu.saini
@ 2026-07-19 15:57 ` vishnu.saini
2026-07-19 16:12 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: vishnu.saini @ 2026-07-19 15:57 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut
Cc: dri-devel, devicetree, linux-kernel, Yi Zhang, Nilesh Laad,
Gopi Botlagunta, Vishnu Saini, venkata.valluru, Jessica Zhang
From: Yi Zhang <zhanyi@qti.qualcomm.com>
LT9211C is a Single/Dual-Link DSI/LVDS or Single DPI input to
Single-Link/Dual-Link DSI/LVDS or Single DPI output bridge chip.
Extend the existing lontium-lt9211 driver to support DSI-to-LVDS
bridge configuration by detecting and handling both LT9211 and LT9211C
variants from a single driver.
Chip detection in lt9211_read_chipid() is extended to identify the
LT9211C by its distinct chip ID registers.
A separate regmap configuration is introduced for LT9211C to cover
its extended register map. The chip_type field added to struct lt9211
selects the appropriate regmap and code paths at probe time using
of_device_is_compatible().
Five new functions implement the LT9211C DSI-to-LVDS initialisation
sequence: lt9211c_configure_rx(), lt9211c_autodetect_rx(),
lt9211c_configure_timing(), lt9211c_configure_plls() and
lt9211c_configure_tx(). The LT9211C requires DSI clocks to be running
before the bridge can be initialised, so lt9211_atomic_enable() queues
a delayed workqueue item for LT9211C rather than initialising
synchronously as LT9211 does.
LT9211C uses VIDEO|LPM DSI mode flags, which differ from the
sync-pulse mode used by LT9211.
Signed-off-by: Yi Zhang <zhanyi@qti.qualcomm.com>
Signed-off-by: Nilesh Laad <nilesh.laad@oss.qualcomm.com>
Signed-off-by: Gopi Botlagunta <venkata.botlagunta@oss.qualcomm.com>
Signed-off-by: Vishnu Saini <vishnu.saini@oss.qualcomm.com>
---
drivers/gpu/drm/bridge/lontium-lt9211.c | 796 +++++++++++++++++++++++++++++++-
1 file changed, 779 insertions(+), 17 deletions(-)
diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c
index f39d83a5ae37..36c004df34fc 100644
--- a/drivers/gpu/drm/bridge/lontium-lt9211.c
+++ b/drivers/gpu/drm/bridge/lontium-lt9211.c
@@ -19,6 +19,7 @@
#include <linux/of_graph.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
@@ -36,10 +37,21 @@
#define REG_CHIPID2 0x8102
#define REG_CHIPID2_VALUE 0xe3
+/* LT9211C chip ID values */
+#define REG_CHIPID0_LT9211C_VALUE 0x21
+#define REG_CHIPID1_LT9211C_VALUE 0x03
+#define REG_CHIPID2_LT9211C_VALUE 0xe1
+
#define REG_DSI_LANE 0xd000
/* DSI lane count - 0 means 4 lanes ; 1, 2, 3 means 1, 2, 3 lanes. */
#define REG_DSI_LANE_COUNT(n) ((n) & 3)
+/* Chip type enum */
+enum lt9211_chip_type {
+ LT9211,
+ LT9211C,
+};
+
struct lt9211 {
struct drm_bridge bridge;
struct device *dev;
@@ -50,6 +62,14 @@ struct lt9211 {
struct regulator *vccio;
bool lvds_dual_link;
bool lvds_dual_link_even_odd_swap;
+ /* LT9211C specific fields */
+ enum lt9211_chip_type chip_type;
+ struct workqueue_struct *wq;
+ struct delayed_work lt9211_dw;
+ struct drm_display_mode mode;
+ bool bpp24;
+ bool jeida;
+ bool de;
};
static const struct regmap_range lt9211_rw_ranges[] = {
@@ -93,6 +113,49 @@ static const struct regmap_config lt9211_regmap_config = {
.max_register = 0xda00,
};
+static const struct regmap_range lt9211c_rw_ranges[] = {
+ regmap_reg_range(0xff, 0xff),
+ regmap_reg_range(0x8100, 0x8182),
+ regmap_reg_range(0x8200, 0x82aa),
+ regmap_reg_range(0x8500, 0x85ff),
+ regmap_reg_range(0x8600, 0x86a0),
+ regmap_reg_range(0x8700, 0x8746),
+ regmap_reg_range(0xd000, 0xd0a7),
+ regmap_reg_range(0xd400, 0xd42c),
+ regmap_reg_range(0xd800, 0xd838),
+ regmap_reg_range(0xd9c0, 0xd9d5),
+};
+
+static const struct regmap_access_table lt9211c_rw_table = {
+ .yes_ranges = lt9211c_rw_ranges,
+ .n_yes_ranges = ARRAY_SIZE(lt9211c_rw_ranges),
+};
+
+static const struct regmap_range_cfg lt9211c_range = {
+ .name = "lt9211c",
+ .range_min = 0x0000,
+ .range_max = 0xda00,
+ .selector_reg = REG_PAGE_CONTROL,
+ .selector_mask = 0xff,
+ .selector_shift = 0,
+ .window_start = 0,
+ .window_len = 0x100,
+};
+
+static const struct regmap_config lt9211c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = <9211c_rw_table,
+ .wr_table = <9211c_rw_table,
+ .volatile_table = <9211c_rw_table,
+ .ranges = <9211c_range,
+ .num_ranges = 1,
+ .cache_type = REGCACHE_RBTREE,
+ .max_register = 0xda00,
+};
+
+static void lt9211_delayed_work_func(struct work_struct *work);
+
static struct lt9211 *bridge_to_lt9211(struct drm_bridge *bridge)
{
return container_of(bridge, struct lt9211, bridge);
@@ -120,14 +183,23 @@ static int lt9211_read_chipid(struct lt9211 *ctx)
return ret;
}
- /* Test for known Chip ID. */
- if (chipid[0] != REG_CHIPID0_VALUE || chipid[1] != REG_CHIPID1_VALUE) {
- dev_err(ctx->dev, "Unknown Chip ID: 0x%02x 0x%02x 0x%02x\n",
- chipid[0], chipid[1], chipid[2]);
- return -EINVAL;
+ /* Test for LT9211 Chip ID. */
+ if (chipid[0] == REG_CHIPID0_VALUE && chipid[1] == REG_CHIPID1_VALUE) {
+ dev_dbg(ctx->dev, "Detected LT9211 chip\n");
+ return 0;
}
- return 0;
+ /* Test for LT9211C Chip ID. */
+ if (chipid[0] == REG_CHIPID0_LT9211C_VALUE &&
+ chipid[1] == REG_CHIPID1_LT9211C_VALUE &&
+ chipid[2] == REG_CHIPID2_LT9211C_VALUE) {
+ dev_dbg(ctx->dev, "Detected LT9211C chip\n");
+ return 0;
+ }
+
+ dev_err(ctx->dev, "Unknown Chip ID: 0x%02x 0x%02x 0x%02x\n",
+ chipid[0], chipid[1], chipid[2]);
+ return -EINVAL;
}
static int lt9211_system_init(struct lt9211 *ctx)
@@ -454,6 +526,651 @@ static int lt9211_configure_tx(struct lt9211 *ctx, bool jeida,
return 0;
}
+static int lt9211c_configure_rx(struct lt9211 *ctx)
+{
+ unsigned int pval;
+
+ const struct reg_sequence lt9211c_rx_phy_seq[] = {
+ { REG_DSI_LANE, REG_DSI_LANE_COUNT(ctx->dsi->lanes) },
+ { 0x8201, 0x11 },
+ { 0x8218, 0x48 },
+ { 0x8201, 0x91 },
+ { 0x8202, 0x00 },
+ { 0x8203, 0xee },
+ { 0x8209, 0x21 },
+ { 0x8204, 0x44 },
+ { 0x8205, 0xc4 },
+ { 0x8206, 0x44 },
+ { 0x8213, 0x0c },
+
+ { 0xd001, 0x00 },
+ { 0xd002, 0x0e },
+ { 0xd005, 0x00 },
+ { 0xd00a, 0x59 },
+ { 0xd00b, 0x20 },
+ };
+
+ const struct reg_sequence lt9211c_rx_phy_reset_seq[] = {
+ { 0x8109, 0xde },
+ { 0x8109, 0xdf },
+ };
+
+ const struct reg_sequence lt9211c_rx_clk_sel_seq[] = {
+ { 0x85e9, 0x88 },
+ { 0x8180, 0x51 },
+ { 0x8181, 0x10 },
+ { 0x8632, 0x03 },
+ };
+
+ const struct reg_sequence lt9211c_rx_input_sel_seq[] = {
+ { 0xd004, 0x00 },
+ { 0xd021, 0x46 },
+ };
+
+ const struct reg_sequence lt9211c_rx_dig_seq[] = {
+ { 0x853f, 0x08 },
+ { 0x8540, 0x04 },
+ { 0x8541, 0x03 },
+ { 0x8542, 0x02 },
+ { 0x8543, 0x01 },
+ { 0x8545, 0x04 },
+ { 0x8546, 0x03 },
+ { 0x8547, 0x02 },
+ { 0x8548, 0x01 },
+ { 0x8544, 0x00 },
+ { 0x8549, 0x00 },
+ };
+
+ int ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_phy_seq,
+ ARRAY_SIZE(lt9211c_rx_phy_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_phy_reset_seq,
+ ARRAY_SIZE(lt9211c_rx_phy_reset_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_clk_sel_seq,
+ ARRAY_SIZE(lt9211c_rx_clk_sel_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_read(ctx->regmap, 0x8180, &pval);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0x8180, ((pval & 0xfc) | 0x03));
+ if (ret)
+ return ret;
+
+ ret = regmap_read(ctx->regmap, 0x8680, &pval);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0x863f, (pval & 0xf8));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0x863f, 0x05);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(ctx->regmap, 0x8530, &pval);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0x8530, ((pval & 0xf8) | 0x11));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_input_sel_seq,
+ ARRAY_SIZE(lt9211c_rx_input_sel_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_dig_seq,
+ ARRAY_SIZE(lt9211c_rx_dig_seq));
+ if (ret)
+ return ret;
+
+ /* Give the chip time to lock onto RX stream. */
+ msleep(100);
+
+ return 0;
+}
+
+static int lt9211c_autodetect_rx(struct lt9211 *ctx,
+ const struct drm_display_mode *mode)
+{
+ u16 width, height;
+ u8 buf[5];
+ u8 format;
+ u8 sot[8];
+ int ret;
+
+ /* Read the SOT from the chip. */
+ ret = regmap_bulk_read(ctx->regmap, 0xd088, sot, sizeof(sot));
+ if (ret)
+ return ret;
+
+ dev_dbg(ctx->dev, "Sot Num = 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", sot[0],
+ sot[2], sot[4], sot[6]);
+
+ dev_dbg(ctx->dev, "Sot Data = 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", sot[1],
+ sot[3], sot[5], sot[7]);
+ /* HS Settle Set */
+ if (sot[0] > 0x10 && sot[0] < 0x50)
+ regmap_write(ctx->regmap, 0xd002, sot[0] - 5);
+ else
+ regmap_write(ctx->regmap, 0xd002, 0x08);
+
+ /* Width/Height/Format Auto-detection */
+ ret = regmap_bulk_read(ctx->regmap, 0xd082, buf, sizeof(buf));
+ if (ret)
+ return ret;
+
+ width = (buf[0] << 8) | buf[1];
+ height = (buf[3] << 8) | buf[4];
+ format = buf[2] & 0xf;
+
+ if (format == 0x3) { /* YUV422 16bit */
+ width /= 2;
+ } else if (format == 0xa) { /* RGB888 24bit */
+ width /= 3;
+ } else {
+ dev_err(ctx->dev, "Unsupported DSI format 0x%01x\n", format);
+ return -EINVAL;
+ }
+
+ if (width != mode->hdisplay) {
+ dev_err(ctx->dev,
+ "RX: Detected DSI width (%d) does not match mode hdisplay (%d)\n",
+ width, mode->hdisplay);
+ return -EINVAL;
+ }
+
+ if (height != mode->vdisplay) {
+ dev_err(ctx->dev,
+ "RX: Detected DSI height (%d) does not match mode vdisplay (%d)\n",
+ height, mode->vdisplay);
+ return -EINVAL;
+ }
+
+ dev_dbg(ctx->dev, "RX: %dx%d format=0x%01x\n", width, height, format);
+ return 0;
+}
+
+static int lt9211c_configure_timing(struct lt9211 *ctx,
+ const struct drm_display_mode *mode)
+{
+ const struct reg_sequence lt9211c_timing[] = {
+ { 0xd00d, (mode->vtotal >> 8) & 0xff },
+ { 0xd00e, mode->vtotal & 0xff },
+ { 0xd00f, (mode->vdisplay >> 8) & 0xff },
+ { 0xd010, mode->vdisplay & 0xff },
+ { 0xd011, (mode->htotal >> 8) & 0xff },
+ { 0xd012, mode->htotal & 0xff },
+ { 0xd013, (mode->hdisplay >> 8) & 0xff },
+ { 0xd014, mode->hdisplay & 0xff },
+ { 0xd015, (mode->vsync_end - mode->vsync_start) & 0xff },
+ { 0xd04c, ((mode->hsync_end - mode->hsync_start) >> 8) & 0xff },
+ { 0xd016, (mode->hsync_end - mode->hsync_start) & 0xff },
+ { 0xd017, ((mode->vsync_start - mode->vdisplay) >> 8) & 0xff },
+ { 0xd018, (mode->vsync_start - mode->vdisplay) & 0xff },
+ { 0xd019, ((mode->hsync_start - mode->hdisplay) >> 8) & 0xff },
+ { 0xd01a, (mode->hsync_start - mode->hdisplay) & 0xff },
+ };
+
+ return regmap_multi_reg_write(ctx->regmap, lt9211c_timing,
+ ARRAY_SIZE(lt9211c_timing));
+}
+
+static int lt9211c_configure_plls(struct lt9211 *ctx,
+ const struct drm_display_mode *mode)
+{
+ const struct reg_sequence lt9211c_dessc_pll_reset[] = {
+ { 0x8103, 0xfe, 2000 },
+ { 0x8103, 0xff, 0 },
+ };
+
+ const struct reg_sequence lt9211c_pcr_cali_seq[] = {
+ { 0xd00a, 0x5f },
+ { 0xd01e, 0x51 },
+ { 0xd023, 0x80 },
+ { 0xd024, 0x70 },
+ { 0xd025, 0x80 },
+ { 0xd02a, 0x10 },
+ { 0xd021, 0x4f },
+ { 0xd022, 0xf0 },
+ { 0xd038, 0x04 },
+ { 0xd039, 0x08 },
+ { 0xd03a, 0x10 },
+ { 0xd03b, 0x20 },
+ { 0xd03f, 0x04 },
+ { 0xd040, 0x08 },
+ { 0xd041, 0x10 },
+ { 0xd042, 0x20 },
+ { 0xd02b, 0xa0 },
+ };
+
+ const struct reg_sequence lt9211c_pcr_reset_seq[] = {
+ { 0xd009, 0xdb },
+ { 0xd009, 0xdf },
+ { 0xd008, 0x80 },
+ { 0xd008, 0x00 },
+ };
+
+ unsigned int pval;
+ int ret;
+ u8 div;
+ u32 pcr_m;
+ u32 pcr_k;
+ u32 pcr_up;
+ u32 pcr_down;
+
+ /* DeSSC PLL reference clock is 25 MHz XTal. */
+ ret = regmap_write(ctx->regmap, 0x8226, 0x20);
+ if (ret)
+ return ret;
+
+ /* Prediv = 0 */
+ ret = regmap_write(ctx->regmap, 0x8227, 0x40);
+ if (ret)
+ return ret;
+
+ if (mode->clock < 22000) {
+ ret = regmap_write(ctx->regmap, 0x822f, 0x07);
+ ret |= regmap_write(ctx->regmap, 0x822c, 0x01);
+ div = 16;
+ } else if (mode->clock < 44000) {
+ ret = regmap_write(ctx->regmap, 0x822f, 0x07);
+ div = 16;
+ } else if (mode->clock < 88000) {
+ ret = regmap_write(ctx->regmap, 0x822f, 0x06);
+ div = 8;
+ } else if (mode->clock < 176000) {
+ ret = regmap_write(ctx->regmap, 0x822f, 0x05);
+ div = 4;
+ } else {
+ ret = regmap_write(ctx->regmap, 0x822f, 0x04);
+ div = 2;
+ }
+
+ if (ret)
+ return ret;
+
+ pcr_m = (mode->clock * div) / 25;
+ pcr_k = pcr_m % 1000;
+ pcr_m /= 1000;
+
+ pcr_up = pcr_m + 1;
+ pcr_down = pcr_m - 1;
+
+ pcr_k <<= 14;
+
+ ret = regmap_write(ctx->regmap, 0xd008, 0x00);
+ if (ret < 0)
+ return ret;
+
+ /* 0xd026: pcr_m */
+ ret = regmap_write(ctx->regmap, 0xd026, (0x80 | (u8)pcr_m) & 0x7f);
+ if (ret < 0)
+ return ret;
+
+ /* 0xd027 0xd028 0xd029: pcr_k */
+ ret = regmap_write(ctx->regmap, 0xd027, (pcr_k >> 16) & 0xff);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0xd028, (pcr_k >> 8) & 0xff);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0xd029, pcr_k & 0xff);
+ if (ret < 0)
+ return ret;
+
+ /* 0xd02d: pcr_m overflow limit setting */
+ ret = regmap_write(ctx->regmap, 0xd02d, pcr_up);
+ if (ret < 0)
+ return ret;
+
+ /* 0xd031: pcr_m underflow limit setting */
+ ret = regmap_write(ctx->regmap, 0xd031, pcr_down);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_dessc_pll_reset,
+ ARRAY_SIZE(lt9211c_dessc_pll_reset));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_pcr_cali_seq,
+ ARRAY_SIZE(lt9211c_pcr_cali_seq));
+ if (ret)
+ return ret;
+
+ if (mode->clock < 44000) {
+ ret = regmap_write(ctx->regmap, 0xd00c, 0x60);
+ ret |= regmap_write(ctx->regmap, 0xd01b, 0x00);
+ ret |= regmap_write(ctx->regmap, 0xd01c, 0x60);
+ } else {
+ ret = regmap_write(ctx->regmap, 0xd00c, 0x40);
+ ret |= regmap_write(ctx->regmap, 0xd01b, 0x00);
+ ret |= regmap_write(ctx->regmap, 0xd01c, 0x40);
+ }
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_pcr_reset_seq,
+ ARRAY_SIZE(lt9211c_pcr_reset_seq));
+ if (ret)
+ return ret;
+
+ /* PCR stability test takes seconds. */
+ ret = regmap_read_poll_timeout(ctx->regmap, 0xd087, pval,
+ ((pval & 0x18) == 0x18), 20000, 3000000);
+ if (ret)
+ dev_err(ctx->dev, "PCR unstable, ret=%i\n", ret);
+
+ ret = regmap_write(ctx->regmap, 0x8180, 0x51);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0x863f, 0x00);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0x863f, 0x01);
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(ctx->regmap, 0x8640, pval,
+ ((pval & 0x01) == 0x01), 50000, 250000);
+ if (ret)
+ dev_err(ctx->dev, "Video check not stable, ret=%i\n", ret);
+
+ return ret;
+}
+
+static int lt9211c_configure_tx(struct lt9211 *ctx,
+ const struct drm_display_mode *mode)
+{
+ const struct reg_sequence lt9211c_tx_phy_off_seq[] = {
+ { 0x8236, 0x00 },
+ { 0x8237, 0x00 },
+ { 0x8108, 0x6f },
+ { 0x8103, 0xbf },
+ };
+
+ const struct reg_sequence lt9211c_tx_phy_seq[] = {
+ { 0x8236, 0x03 },
+ { 0x8237, 0x44 },
+ { 0x8238, 0x14 },
+ { 0x8239, 0x31 },
+ { 0x823a, 0xc8 },
+ { 0x823b, 0x00 },
+ { 0x823c, 0x0f },
+ { 0x8246, 0x40 },
+ { 0x8247, 0x40 },
+ { 0x8248, 0x40 },
+ { 0x8249, 0x40 },
+ { 0x824a, 0x40 },
+ { 0x824b, 0x40 },
+ { 0x824c, 0x40 },
+ { 0x824d, 0x40 },
+ { 0x824e, 0x40 },
+ { 0x824f, 0x40 },
+ { 0x8250, 0x40 },
+ { 0x8251, 0x40 },
+ };
+
+ const struct reg_sequence lt9211c_tx_mltx_reset[] = {
+ { 0x8103, 0xbf },
+ { 0x8103, 0xff },
+ };
+
+ const struct reg_sequence lt9211c_tx_dig_seq[] = {
+ { 0x854a, 0x01 },
+ { 0x854b, 0x00 },
+ { 0x854c, 0x10 },
+ { 0x854d, 0x20 },
+ { 0x854e, 0x50 },
+ { 0x854f, 0x30 },
+ { 0x8550, 0x46 },
+ { 0x8551, 0x10 },
+ { 0x8552, 0x20 },
+ { 0x8553, 0x50 },
+ { 0x8554, 0x30 },
+ { 0x8555, 0x00 },
+ { 0x8556, 0x20 },
+
+ { 0x8568, 0x00 },
+ { 0x856e, 0x10 | (ctx->de ? BIT(6) : 0) },
+ { 0x856f, 0x81 | (ctx->jeida ? BIT(6) : 0) |
+ (ctx->lvds_dual_link ? BIT(4) : 0) |
+ (ctx->bpp24 ? BIT(2) : 0) },
+ };
+
+ const struct reg_sequence lt9211c_tx_ssc_seq[] = {
+ { 0x8234, 0x00 },
+ { 0x856e, 0x10 },
+ { 0x8181, 0x15 },
+ { 0x871e, 0x00 },
+ { 0x8717, 0x02 },
+ { 0x8718, 0x04 },
+ { 0x8719, 0xd4 },
+ { 0x871a, 0x00 },
+ { 0x871b, 0x12 },
+ { 0x871c, 0x00 },
+ { 0x871d, 0x24 },
+ { 0x871f, 0x1c },
+ { 0x8720, 0x00 },
+ { 0x8721, 0x00 },
+ { 0x871e, 0x02 },
+ };
+
+ const struct reg_sequence lt9211c_tx_pll_reset_seq[] = {
+ { 0x810c, 0xfe, 2000 },
+ { 0x810c, 0xff, 0 },
+ };
+
+ const struct reg_sequence lt9211c_tx_sw_reset_seq[] = {
+ { 0x8108, 0x6f, 2000 },
+ { 0x8108, 0x7f, 0 },
+ };
+
+ unsigned int pval;
+ int ret;
+ u32 phy_clk;
+ u8 pixclk_div;
+ u8 pre_div;
+ u8 div_set;
+ u8 sericlk_div;
+ u8 val;
+
+ dev_info(ctx->dev,
+ "dual_link=%d,even_odd_swap=%d,bpp24=%d,jeida=%d,de=%d\n",
+ ctx->lvds_dual_link, ctx->lvds_dual_link_even_odd_swap,
+ ctx->bpp24, ctx->jeida, ctx->de);
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_phy_off_seq,
+ ARRAY_SIZE(lt9211c_tx_phy_off_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_read(ctx->regmap, 0x8530, &pval);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0x8530, ((pval & 0x3f) | 0x40));
+ if (ret)
+ return ret;
+
+ /* [7]0:txpll normal work; txpll ref clk sel pix clk */
+ ret = regmap_write(ctx->regmap, 0x8230, 0x00);
+ if (ret)
+ return ret;
+
+ if (ctx->lvds_dual_link)
+ phy_clk = (u32)(mode->clock * 7 / 2);
+ else
+ phy_clk = (u32)(mode->clock * 7);
+
+ /* 0x8231: prediv sel */
+ if (mode->clock < 20000) {
+ val = 0x28;
+ pre_div = 1;
+ } else if (mode->clock < 40000) {
+ val = 0x28;
+ pre_div = 1;
+ } else if (mode->clock < 80000) {
+ val = 0x29;
+ pre_div = 2;
+ } else if (mode->clock < 160000) {
+ val = 0x2a;
+ pre_div = 4;
+ } else if (mode->clock < 320000) {
+ val = 0x2b;
+ pre_div = 8;
+ } else {
+ val = 0x2f;
+ pre_div = 16;
+ }
+ ret = regmap_write(ctx->regmap, 0x8231, val);
+ if (ret < 0)
+ return ret;
+
+ /* 0x8232: serickdiv sel */
+ if (phy_clk < 80000) {
+ val = 0x32;
+ sericlk_div = 16;
+ } else if (phy_clk < 160000) {
+ val = 0x22;
+ sericlk_div = 8;
+ } else if (phy_clk < 320000) {
+ val = 0x12;
+ sericlk_div = 4;
+ } else if (phy_clk < 640000) {
+ val = 0x02;
+ sericlk_div = 2;
+ } else {
+ val = 0x42;
+ sericlk_div = 1;
+ }
+ ret = regmap_write(ctx->regmap, 0x8232, val);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * 0x8233: pix_mux sel & pix_div sel
+ * To avoid floating point operations, The pixclk_div is enlarged by 10 times
+ */
+ if (mode->clock > 150000) {
+ val = 0x04;
+ pixclk_div = 35;
+ } else {
+ pixclk_div =
+ (u8)((phy_clk * sericlk_div * 10) / (mode->clock * 7));
+ if (pixclk_div <= 10)
+ val = 0x00;
+ else if (pixclk_div <= 20)
+ val = 0x01;
+ else if (pixclk_div <= 40)
+ val = 0x02;
+ else
+ val = 0x03;
+ }
+ ret = regmap_write(ctx->regmap, 0x8233, val);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0x8234, 0x01);
+ if (ret < 0)
+ return ret;
+
+ /* 0x8235: div set */
+ div_set = (u8)(phy_clk * sericlk_div / mode->clock / pre_div);
+ ret = regmap_write(ctx->regmap, 0x8235, div_set);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_ssc_seq,
+ ARRAY_SIZE(lt9211c_tx_ssc_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_pll_reset_seq,
+ ARRAY_SIZE(lt9211c_tx_pll_reset_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(ctx->regmap, 0x8739, pval, pval & 0x04,
+ 10000, 1000000);
+ if (ret) {
+ dev_err(ctx->dev, "TX PLL unstable, ret=%i\n", ret);
+ return ret;
+ }
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_phy_seq,
+ ARRAY_SIZE(lt9211c_tx_phy_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_mltx_reset,
+ ARRAY_SIZE(lt9211c_tx_mltx_reset));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_dig_seq,
+ ARRAY_SIZE(lt9211c_tx_dig_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_tx_sw_reset_seq,
+ ARRAY_SIZE(lt9211c_tx_sw_reset_seq));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void lt9211_delayed_work_func(struct work_struct *work)
+{
+ struct delayed_work *dw = to_delayed_work(work);
+ struct lt9211 *ctx = container_of(dw, struct lt9211, lt9211_dw);
+ const struct drm_display_mode *mode = &ctx->mode;
+ int ret;
+
+ if (ctx->chip_type != LT9211C) {
+ dev_err(ctx->dev, "LT9211: Delayed work called for non-LT9211C chip\n");
+ return;
+ }
+
+ ret = lt9211c_configure_rx(ctx);
+ if (ret)
+ return;
+
+ ret = lt9211c_autodetect_rx(ctx, mode);
+ if (ret)
+ return;
+
+ ret = lt9211c_configure_timing(ctx, mode);
+ if (ret)
+ return;
+
+ ret = lt9211c_configure_plls(ctx, mode);
+ if (ret)
+ return;
+
+ ret = lt9211c_configure_tx(ctx, mode);
+ if (ret)
+ return;
+}
+
static void lt9211_atomic_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
@@ -523,6 +1240,15 @@ static void lt9211_atomic_enable(struct drm_bridge *bridge,
if (ret)
return;
+ if (ctx->chip_type == LT9211C && ctx->wq) {
+ drm_mode_copy(&ctx->mode, mode);
+ /* LT9211C must enable after mipi clock enable */
+ queue_delayed_work(ctx->wq, &ctx->lt9211_dw,
+ msecs_to_jiffies(100));
+ dev_dbg(ctx->dev, "LT9211C enabled.\n");
+ return;
+ }
+
ret = lt9211_system_init(ctx);
if (ret)
return;
@@ -671,11 +1397,6 @@ static int lt9211_parse_dt(struct lt9211 *ctx)
static int lt9211_host_attach(struct lt9211 *ctx)
{
- const struct mipi_dsi_device_info info = {
- .type = "lt9211",
- .channel = 0,
- .node = NULL,
- };
struct device *dev = ctx->dev;
struct device_node *host_node;
struct device_node *endpoint;
@@ -697,7 +1418,22 @@ static int lt9211_host_attach(struct lt9211 *ctx)
if (dsi_lanes < 0)
return dsi_lanes;
- dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
+ if (ctx->chip_type == LT9211C) {
+ const struct mipi_dsi_device_info info = {
+ .type = "lt9211c",
+ .channel = 0,
+ .node = NULL,
+ };
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
+ } else {
+ const struct mipi_dsi_device_info info = {
+ .type = "lt9211",
+ .channel = 0,
+ .node = NULL,
+ };
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
+ }
+
if (IS_ERR(dsi))
return dev_err_probe(dev, PTR_ERR(dsi),
"failed to create dsi device\n");
@@ -706,10 +1442,16 @@ static int lt9211_host_attach(struct lt9211 *ctx)
dsi->lanes = dsi_lanes;
dsi->format = MIPI_DSI_FMT_RGB888;
- dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
- MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO_NO_HSA |
- MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP |
- MIPI_DSI_MODE_NO_EOT_PACKET;
+
+ if (ctx->chip_type == LT9211C) {
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM;
+ } else {
+ dsi->mode_flags =
+ MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO_NO_HSA |
+ MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP |
+ MIPI_DSI_MODE_NO_EOT_PACKET;
+ }
ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) {
@@ -747,10 +1489,25 @@ static int lt9211_probe(struct i2c_client *client)
if (ret)
return ret;
- ctx->regmap = devm_regmap_init_i2c(client, <9211_regmap_config);
+ if (of_device_is_compatible(dev->of_node, "lontium,lt9211c")) {
+ ctx->chip_type = LT9211C;
+ ctx->regmap =
+ devm_regmap_init_i2c(client, <9211c_regmap_config);
+ } else {
+ ctx->chip_type = LT9211;
+ ctx->regmap =
+ devm_regmap_init_i2c(client, <9211_regmap_config);
+ }
if (IS_ERR(ctx->regmap))
return PTR_ERR(ctx->regmap);
+ if (ctx->chip_type == LT9211C) {
+ ctx->wq = create_workqueue("lt9211_work");
+ if (!ctx->wq)
+ return -ENOMEM;
+ INIT_DELAYED_WORK(&ctx->lt9211_dw, lt9211_delayed_work_func);
+ }
+
dev_set_drvdata(dev, ctx);
i2c_set_clientdata(client, ctx);
@@ -768,17 +1525,22 @@ static void lt9211_remove(struct i2c_client *client)
{
struct lt9211 *ctx = i2c_get_clientdata(client);
+ if (ctx->wq)
+ destroy_workqueue(ctx->wq);
+
drm_bridge_remove(&ctx->bridge);
}
static const struct i2c_device_id lt9211_id[] = {
{ "lontium,lt9211" },
+ { "lontium,lt9211c" },
{},
};
MODULE_DEVICE_TABLE(i2c, lt9211_id);
static const struct of_device_id lt9211_match_table[] = {
{ .compatible = "lontium,lt9211" },
+ { .compatible = "lontium,lt9211c" },
{},
};
MODULE_DEVICE_TABLE(of, lt9211_match_table);
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v6 2/2] drm/bridge: add support for lontium lt9211c bridge
2026-07-19 15:57 ` [PATCH v6 2/2] drm/bridge: add support for lontium lt9211c bridge vishnu.saini
@ 2026-07-19 16:12 ` sashiko-bot
0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-07-19 16:12 UTC (permalink / raw)
To: vishnu.saini; +Cc: conor+dt, dri-devel, robh, devicetree
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Missing cancellation of the delayed work in the disable path leads to I2C writes to an unpowered/reset device.
- [High] The delayed work timer is not cancelled during driver removal, resulting in a critical use-after-free.
- [High] The dynamically allocated workqueue is leaked if probe fails during host attach.
- [High] LVDS format configurations (bpp24, jeida, de) are left uninitialized when queuing the delayed work, resulting in incorrect hardware configuration.
- [High] Deferring hardware enablement to a delayed workqueue violates DRM atomic subsystem guarantees.
- [Low] Signed error codes from `regmap_write()` are accumulated using bitwise OR, mangling the error values.
--
--- Patch [2]: [PATCH v6 2/2] drm/bridge: add support for lontium lt9211c bridge ---
commit 1ae4415156f1af5c0293e73eda918915f0e313c8
Author: Yi Zhang <zhanyi@qti.qualcomm.com>
drm/bridge: add support for lontium lt9211c bridge
Adds support for the LT9211C variant to the existing lt9211 driver.
It detects the chip by ID, adds a new regmap for LT9211C, and implements
DSI-to-LVDS init sequences. A delayed workqueue is used to configure
LT9211C after the DSI clock is running.
> diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c
> index f39d83a5ae377..36c004df34fcd 100644
> --- a/drivers/gpu/drm/bridge/lontium-lt9211.c
> +++ b/drivers/gpu/drm/bridge/lontium-lt9211.c
> @@ -454,6 +526,651 @@ static int lt9211_configure_tx(struct lt9211 *ctx, bool jeida,
> [ ... ]
> +static int lt9211c_configure_plls(struct lt9211 *ctx,
> + const struct drm_display_mode *mode)
> +{
> [ ... ]
> + if (mode->clock < 44000) {
> + ret = regmap_write(ctx->regmap, 0xd00c, 0x60);
> + ret |= regmap_write(ctx->regmap, 0xd01b, 0x00);
> + ret |= regmap_write(ctx->regmap, 0xd01c, 0x60);
> + } else {
> + ret = regmap_write(ctx->regmap, 0xd00c, 0x40);
> + ret |= regmap_write(ctx->regmap, 0xd01b, 0x00);
> + ret |= regmap_write(ctx->regmap, 0xd01c, 0x40);
> + }
[Severity: Low]
Does using bitwise OR here mangle the signed error codes returned by
regmap_write()?
Accumulating negative error values via bitwise OR creates a different
negative value, which destroys the semantic meaning of the original error
code.
> [ ... ]
> @@ -523,6 +1240,15 @@ static void lt9211_atomic_enable(struct drm_bridge *bridge,
> if (ret)
> return;
>
> + if (ctx->chip_type == LT9211C && ctx->wq) {
> + drm_mode_copy(&ctx->mode, mode);
> + /* LT9211C must enable after mipi clock enable */
> + queue_delayed_work(ctx->wq, &ctx->lt9211_dw,
> + msecs_to_jiffies(100));
> + dev_dbg(ctx->dev, "LT9211C enabled.\n");
> + return;
> + }
[Severity: High]
Does this code fail to save the LVDS format configurations to the context
struct?
The variables jeida, bpp24, and de are extracted from the bridge state
earlier in this function, but they are never saved to ctx->jeida, ctx->bpp24,
or ctx->de before queuing the work. When lt9211c_configure_tx() runs later
in the workqueue, won't it read uninitialized (zero) values for these fields?
[Severity: High]
By deferring the hardware initialization to a delayed workqueue, does this
introduce a regression violating the DRM atomic subsystem's expectation that
the hardware is fully configured when atomic_enable completes?
If the goal is to wait for the upstream DSI host to enable its clock first,
would it be better to set the pre_enable_prev_first flag on the bridge rather
than bypassing the synchronous atomic commit design?
[Severity: High]
What happens if the display pipeline is rapidly disabled before this 100ms
timer expires?
Since this patch introduces a delayed work item, if the driver's disable path
powers off the chip and asserts reset:
lt9211_atomic_disable()
gpiod_set_value(ctx->reset_gpio, 0);
usleep_range(10000, 11000);
ret = regulator_disable(ctx->vccio);
without calling cancel_delayed_work_sync(), the delayed work could execute
on a powered-down device, leading to I2C timeouts and bus lockups.
> [ ... ]
> @@ -747,10 +1489,25 @@ static int lt9211_probe(struct i2c_client *client)
> [ ... ]
> + if (ctx->chip_type == LT9211C) {
> + ctx->wq = create_workqueue("lt9211_work");
> + if (!ctx->wq)
> + return -ENOMEM;
> + INIT_DELAYED_WORK(&ctx->lt9211_dw, lt9211_delayed_work_func);
> + }
> +
> dev_set_drvdata(dev, ctx);
> i2c_set_clientdata(client, ctx);
>
> @@ -768,17 +1525,22 @@ static void lt9211_remove(struct i2c_client *client)
> ctx->bridge.of_node = dev->of_node;
> drm_bridge_add(&ctx->bridge);
>
> ret = lt9211_host_attach(ctx);
> if (ret)
> drm_bridge_remove(&ctx->bridge);
>
> return ret;
> }
[Severity: High]
If lt9211_host_attach() returns an error like -EPROBE_DEFER, does this error
path leak the dynamically allocated workqueue?
It looks like destroy_workqueue(ctx->wq) is missing before returning, which
could lead to resource exhaustion if probe is deferred multiple times.
> static void lt9211_remove(struct i2c_client *client)
> {
> struct lt9211 *ctx = i2c_get_clientdata(client);
>
> + if (ctx->wq)
> + destroy_workqueue(ctx->wq);
> +
> drm_bridge_remove(&ctx->bridge);
> }
[Severity: High]
Can this result in a regression where a use-after-free occurs if the delayed
work timer is still pending during driver removal?
destroy_workqueue() flushes active work, but it does not cancel pending
timers for delayed works. Should we call
cancel_delayed_work_sync(&ctx->lt9211_dw) before destroying the workqueue to
prevent the timer from firing after the context is freed?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260719-add-lt9211c-bridge-v6-0-e56d22152586@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-19 16:12 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-19 15:57 [PATCH v6 0/2] This series adds LT9211C bridge driver by extending LT9211 vishnu.saini
2026-07-19 15:57 ` [PATCH v6 1/2] dt-bindings: display: bridge: lontium,lt9211: Add lt9211c support vishnu.saini
2026-07-19 15:57 ` [PATCH v6 2/2] drm/bridge: add support for lontium lt9211c bridge vishnu.saini
2026-07-19 16:12 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox