[PATCH v7 2/2] drm/bridge: add support for lontium lt9211c bridge
[email protected] Sat, 22 Aug 2026 22:31:34 +0530
Newsgroups
org.kernel.vger.linux-devicetree,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel
Message-ID
<[email protected] >
From: Yi Zhang <[email protected] >
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.
Add LT9211C-specific regmap support and use lt9211_chip_data with
i2c_get_match_data() to provide per-chip configuration.
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().
Defer the remaining LT9211C initialization to a delayed work item
scheduled from atomic_enable(), since RX auto-detection requires an
active DSI stream.
Signed-off-by: Yi Zhang <[email protected] >
Signed-off-by: Nilesh Laad <[email protected] >
Signed-off-by: Gopi Botlagunta <[email protected] >
Signed-off-by: Vishnu Saini <[email protected] >
Tested-by: Philipp Zabel <[email protected] >
---
drivers/gpu/drm/bridge/lontium-lt9211.c | 838 ++++++++++++++++++++++++++++++--
1 file changed, 805 insertions(+), 33 deletions(-)
diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c
index 23682384daf4..d26789b610c1 100644
--- a/drivers/gpu/drm/bridge/lontium-lt9211.c
+++ b/drivers/gpu/drm/bridge/lontium-lt9211.c
@@ -8,6 +8,7 @@
* 1xDSI -> 1xLVDS
*
* Copyright (C) 2022 Marek Vasut <[email protected] >
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/bits.h>
@@ -19,6 +20,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>
@@ -27,6 +29,7 @@
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
#define REG_PAGE_CONTROL 0xff
#define REG_CHIPID0 0x8100
@@ -36,10 +39,28 @@
#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_chip_data {
+ enum lt9211_chip_type chip_type;
+ const struct regmap_config *regmap_config;
+ const char *dsi_type;
+ unsigned long dsi_mode_flags;
+};
+
struct lt9211 {
struct drm_bridge bridge;
struct device *dev;
@@ -50,6 +71,15 @@ struct lt9211 {
struct regulator *vccio;
bool lvds_dual_link;
bool lvds_dual_link_even_odd_swap;
+ const struct lt9211_chip_data *chip_data;
+ /* LT9211C specific fields */
+ struct workqueue_struct *wq;
+ struct delayed_work lt9211_dw;
+ struct drm_crtc *lt9211c_crtc;
+ struct drm_display_mode lt9211c_mode;
+ bool lt9211c_bpp24;
+ bool lt9211c_jeida;
+ bool lt9211c_de_high;
};
static const struct regmap_range lt9211_rw_ranges[] = {
@@ -93,6 +123,65 @@ 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_NONE,
+ .max_register = 0xda00,
+};
+
+static const struct lt9211_chip_data lt9211_chip_data = {
+ .chip_type = LT9211,
+ .regmap_config = <9211_regmap_config,
+ .dsi_type = "lt9211",
+ .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,
+};
+
+static const struct lt9211_chip_data lt9211c_chip_data = {
+ .chip_type = LT9211C,
+ .regmap_config = <9211c_regmap_config,
+ .dsi_type = "lt9211c",
+ .dsi_mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_LPM,
+};
+
static struct lt9211 *bridge_to_lt9211(struct drm_bridge *bridge)
{
return container_of(bridge, struct lt9211, bridge);
@@ -120,14 +209,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 +552,657 @@ 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) {
+ dev_err(ctx->dev, "RX: phy_seq write failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_phy_reset_seq,
+ ARRAY_SIZE(lt9211c_rx_phy_reset_seq));
+ if (ret) {
+ dev_err(ctx->dev, "RX: phy_reset_seq write failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_clk_sel_seq,
+ ARRAY_SIZE(lt9211c_rx_clk_sel_seq));
+ if (ret) {
+ dev_err(ctx->dev, "RX: clk_sel_seq write failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_write_bits(ctx->regmap, 0x8180, 0x3, 0x3);
+ 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_write_bits(ctx->regmap, 0x8530, 0x17, 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) {
+ dev_err(ctx->dev, "RX: input_sel_seq write failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211c_rx_dig_seq,
+ ARRAY_SIZE(lt9211c_rx_dig_seq));
+ if (ret) {
+ dev_err(ctx->dev, "RX: dig_seq write failed: %d\n", 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];
+ u8 hs_settle;
+ int ret;
+
+ /* Read the SOT from the chip. */
+ ret = regmap_bulk_read(ctx->regmap, 0xd088, sot, sizeof(sot));
+ if (ret) {
+ dev_err(ctx->dev, "RX: failed to read SOT (0xd088): %d\n", 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 */
+ hs_settle = (sot[0] > 0x10 && sot[0] < 0x50) ? sot[0] - 5 : 0x08;
+ ret = regmap_write(ctx->regmap, 0xd002, hs_settle);
+ if (ret)
+ return ret;
+
+ /* 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 (0xd082=%02x %02x %02x %02x %02x)\n",
+ format, buf[0], buf[1], buf[2], buf[3], buf[4]);
+ 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)
+{
+ int ret;
+
+ ret = lt9211_configure_timing(ctx, mode);
+ if (ret < 0)
+ return ret;
+
+ return regmap_write(ctx->regmap, 0xd04c,
+ ((mode->hsync_end - mode->hsync_start) >> 8) & 0xff);
+}
+
+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;
+ unsigned int val;
+ 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 < 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;
+
+ ret = regmap_write(ctx->regmap, 0x822c, mode->clock < 22000 ? 0x01 : 0x00);
+ 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, (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)
+ val = 0x60;
+ else
+ val = 0x40;
+ ret = regmap_write(ctx->regmap, 0xd00c, val);
+ if (ret)
+ return ret;
+ ret = regmap_write(ctx->regmap, 0xd01b, 0x00);
+ if (ret)
+ return ret;
+ ret = regmap_write(ctx->regmap, 0xd01c, val);
+ 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, bool jeida, bool bpp24,
+ bool de, 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 | (de ? BIT(6) : 0) },
+ { 0x856f, 0x81 | (jeida ? BIT(6) : 0) |
+ (ctx->lvds_dual_link ? BIT(4) : 0) |
+ (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_dbg(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,
+ bpp24, jeida, 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_write_bits(ctx->regmap, 0x8530, 0xc0, 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 < 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->lt9211c_mode;
+ int ret;
+
+ /* wait for vblank irq to correctly configure bridge and autodetect stream */
+ ret = drm_crtc_wait_one_vblank(ctx->lt9211c_crtc);
+ if (ret) {
+ dev_err(ctx->dev, "LT9211C: wait for vblank failed: %d\n", ret);
+ return;
+ }
+
+ ret = lt9211c_configure_rx(ctx);
+ if (ret) {
+ dev_err(ctx->dev, "LT9211C: configure_rx failed: %d\n", ret);
+ return;
+ }
+
+ ret = lt9211c_autodetect_rx(ctx, mode);
+ if (ret) {
+ dev_err(ctx->dev, "LT9211C: autodetect_rx failed: %d\n", ret);
+ return;
+ }
+
+ ret = lt9211c_configure_timing(ctx, mode);
+ if (ret) {
+ dev_err(ctx->dev, "LT9211C: configure_timing failed: %d\n", ret);
+ return;
+ }
+
+ ret = lt9211c_configure_plls(ctx, mode);
+ if (ret) {
+ dev_err(ctx->dev, "LT9211C: configure_plls failed: %d\n", ret);
+ return;
+ }
+
+ ret = lt9211c_configure_tx(ctx, ctx->lt9211c_jeida, ctx->lt9211c_bpp24,
+ ctx->lt9211c_de_high, mode);
+ if (ret) {
+ dev_err(ctx->dev, "LT9211C: configure_tx failed: %d\n", ret);
+ return;
+ }
+}
+
static void lt9211_atomic_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
@@ -523,6 +1272,21 @@ static void lt9211_atomic_enable(struct drm_bridge *bridge,
if (ret)
return;
+ if (ctx->chip_data->chip_type == LT9211C) {
+ drm_mode_copy(&ctx->lt9211c_mode, mode);
+ ctx->lt9211c_bpp24 = lvds_format_24bpp;
+ ctx->lt9211c_jeida = lvds_format_jeida;
+ ctx->lt9211c_de_high = !!(bus_flags & DRM_BUS_FLAG_DE_HIGH);
+ ctx->lt9211c_crtc = crtc;
+
+ /*
+ * Needs a live DSI stream to auto-detect RX resolution/format;
+ * defer bridge configuration until after vblank.
+ */
+ queue_delayed_work(ctx->wq, &ctx->lt9211_dw, 0);
+ return;
+ }
+
ret = lt9211_system_init(ctx);
if (ret)
return;
@@ -557,6 +1321,9 @@ static void lt9211_atomic_disable(struct drm_bridge *bridge,
struct lt9211 *ctx = bridge_to_lt9211(bridge);
int ret;
+ if (ctx->chip_data->chip_type == LT9211C)
+ cancel_delayed_work_sync(&ctx->lt9211_dw);
+
/*
* Put the chip in reset, pull nRST line low,
* and assure lengthy 10ms reset low timing.
@@ -672,14 +1439,13 @@ 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;
+ struct mipi_dsi_device_info info = {
+ .channel = 0,
+ .node = NULL,
+ };
struct mipi_dsi_device *dsi;
struct mipi_dsi_host *host;
int dsi_lanes;
@@ -698,6 +1464,7 @@ static int lt9211_host_attach(struct lt9211 *ctx)
if (dsi_lanes < 0)
return dsi_lanes;
+ strscpy(info.type, ctx->chip_data->dsi_type, sizeof(info.type));
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi))
return dev_err_probe(dev, PTR_ERR(dsi),
@@ -707,10 +1474,7 @@ 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;
+ dsi->mode_flags = ctx->chip_data->dsi_mode_flags;
ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) {
@@ -721,6 +1485,11 @@ static int lt9211_host_attach(struct lt9211 *ctx)
return 0;
}
+static void lt9211_destroy_wq(void *data)
+{
+ destroy_workqueue(data);
+}
+
static int lt9211_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -748,45 +1517,48 @@ static int lt9211_probe(struct i2c_client *client)
if (ret)
return ret;
- ctx->regmap = devm_regmap_init_i2c(client, <9211_regmap_config);
+ ctx->chip_data = i2c_get_match_data(client);
+ if (!ctx->chip_data)
+ return -ENODEV;
+
+ ctx->regmap = devm_regmap_init_i2c(client, ctx->chip_data->regmap_config);
if (IS_ERR(ctx->regmap))
return PTR_ERR(ctx->regmap);
- dev_set_drvdata(dev, ctx);
- i2c_set_clientdata(client, ctx);
+ if (ctx->chip_data->chip_type == LT9211C) {
+ ctx->wq = create_workqueue("lt9211_work");
+ if (!ctx->wq)
+ return -ENOMEM;
+ ret = devm_add_action_or_reset(dev, lt9211_destroy_wq, ctx->wq);
+ if (ret)
+ return ret;
+ INIT_DELAYED_WORK(&ctx->lt9211_dw, lt9211_delayed_work_func);
+ }
ctx->bridge.of_node = dev->of_node;
- drm_bridge_add(&ctx->bridge);
-
- ret = lt9211_host_attach(ctx);
+ ret = devm_drm_bridge_add(dev, &ctx->bridge);
if (ret)
- drm_bridge_remove(&ctx->bridge);
-
- return ret;
-}
-
-static void lt9211_remove(struct i2c_client *client)
-{
- struct lt9211 *ctx = i2c_get_clientdata(client);
+ return ret;
- drm_bridge_remove(&ctx->bridge);
+ return lt9211_host_attach(ctx);
}
static const struct i2c_device_id lt9211_id[] = {
- { .name = "lontium,lt9211" },
+ { .name = "lontium,lt9211", .driver_data = (kernel_ulong_t)<9211_chip_data },
+ { .name = "lontium,lt9211c", .driver_data = (kernel_ulong_t)<9211c_chip_data },
{ }
};
MODULE_DEVICE_TABLE(i2c, lt9211_id);
static const struct of_device_id lt9211_match_table[] = {
- { .compatible = "lontium,lt9211" },
+ { .compatible = "lontium,lt9211", .data = <9211_chip_data },
+ { .compatible = "lontium,lt9211c", .data = <9211c_chip_data },
{},
};
MODULE_DEVICE_TABLE(of, lt9211_match_table);
static struct i2c_driver lt9211_driver = {
.probe = lt9211_probe,
- .remove = lt9211_remove,
.id_table = lt9211_id,
.driver = {
.name = "lt9211",
--
2.34.1