* [PATCH v3 2/2] drm/bridge: add support for lontium lt9211c bridge
2025-12-18 6:24 [PATCH v3 0/2] This series adds LT9211C bridge driver by extending LT9211 Venkata Gopi Nagaraju Botlagunta
2025-12-18 6:24 ` [PATCH v3 1/2] dt-bindings: bridge: lt9211c: Add bindings Venkata Gopi Nagaraju Botlagunta
@ 2025-12-18 6:24 ` Venkata Gopi Nagaraju Botlagunta
1 sibling, 0 replies; 4+ messages in thread
From: Venkata Gopi Nagaraju Botlagunta @ 2025-12-18 6:24 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Marek Vasut
Cc: dri-devel, devicetree, linux-kernel, Gopi Botlagunta, Nilesh Laad,
venkata.valluru, jessica.zhang, Yi 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.
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>
---
drivers/gpu/drm/bridge/lontium-lt9211.c | 829 +++++++++++++++++++++++++++++---
1 file changed, 768 insertions(+), 61 deletions(-)
diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c
index 399fa7eebd49..c8318bf81392 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,20 +37,37 @@
#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)
+enum lt9211_chip_type {
+ LT9211,
+ LT9211C,
+};
+
struct lt9211 {
struct drm_bridge bridge;
struct device *dev;
struct regmap *regmap;
- struct mipi_dsi_device *dsi;
+ struct mipi_dsi_device *dsi;
struct drm_bridge *panel_bridge;
struct gpio_desc *reset_gpio;
struct regulator *vccio;
- bool lvds_dual_link;
- bool lvds_dual_link_even_odd_swap;
+ bool lvds_dual_link;
+ bool lvds_dual_link_even_odd_swap;
+ 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[] = {
@@ -70,6 +88,14 @@ static const struct regmap_access_table lt9211_rw_table = {
.n_yes_ranges = ARRAY_SIZE(lt9211_rw_ranges),
};
+static const struct mipi_dsi_device_info lt9211_info = {
+ "lt9211", 0, NULL
+};
+
+static const struct mipi_dsi_device_info lt9211c_info = {
+ "lt9211c", 0, NULL
+};
+
static const struct regmap_range_cfg lt9211_range = {
.name = "lt9211",
.range_min = 0x0000,
@@ -93,6 +119,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,15 +189,24 @@ 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 ||
- chipid[2] != REG_CHIPID2_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 &&
+ chipid[2] == REG_CHIPID2_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)
@@ -366,8 +444,8 @@ static int lt9211_configure_plls(struct lt9211 *ctx,
return ret;
}
-static int lt9211_configure_tx(struct lt9211 *ctx, bool jeida,
- bool bpp24, bool de)
+static int lt9211_configure_tx(struct lt9211 *ctx, bool jeida, bool bpp24,
+ bool de)
{
const struct reg_sequence system_lt9211_tx_phy_seq[] = {
/* DPI output disable */
@@ -394,8 +472,8 @@ static int lt9211_configure_tx(struct lt9211 *ctx, bool jeida,
};
const struct reg_sequence system_lt9211_tx_dig_seq[] = {
- { 0x8559, 0x40 | (jeida ? BIT(7) : 0) |
- (de ? BIT(5) : 0) | (bpp24 ? BIT(4) : 0) },
+ { 0x8559, 0x40 | (jeida ? BIT(7) : 0) | (de ? BIT(5) : 0) |
+ (bpp24 ? BIT(4) : 0) },
{ 0x855a, 0xaa },
{ 0x855b, 0xaa },
{ 0x855c, ctx->lvds_dual_link ? BIT(0) : 0 },
@@ -464,8 +542,6 @@ static void lt9211_atomic_enable(struct drm_bridge *bridge,
const struct drm_display_mode *mode;
struct drm_connector *connector;
struct drm_crtc *crtc;
- bool lvds_format_24bpp;
- bool lvds_format_jeida;
u32 bus_flags;
int ret;
@@ -483,18 +559,19 @@ static void lt9211_atomic_enable(struct drm_bridge *bridge,
bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
bus_flags = bridge_state->output_bus_cfg.flags;
+ ctx->de = !!(bus_flags & DRM_BUS_FLAG_DE_HIGH);
switch (bridge_state->output_bus_cfg.format) {
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
- lvds_format_24bpp = false;
- lvds_format_jeida = true;
+ ctx->bpp24 = false;
+ ctx->jeida = true;
break;
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
- lvds_format_24bpp = true;
- lvds_format_jeida = true;
+ ctx->bpp24 = true;
+ ctx->jeida = true;
break;
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
- lvds_format_24bpp = true;
- lvds_format_jeida = false;
+ ctx->bpp24 = true;
+ ctx->jeida = false;
break;
default:
/*
@@ -502,8 +579,8 @@ static void lt9211_atomic_enable(struct drm_bridge *bridge,
* LVDS bus pixel format, use SPWG24 default
* format until those are fixed.
*/
- lvds_format_24bpp = true;
- lvds_format_jeida = false;
+ ctx->bpp24 = true;
+ ctx->jeida = false;
dev_warn(ctx->dev,
"Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n",
bridge_state->output_bus_cfg.format);
@@ -519,35 +596,42 @@ static void lt9211_atomic_enable(struct drm_bridge *bridge,
crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
mode = &crtc_state->adjusted_mode;
-
ret = lt9211_read_chipid(ctx);
if (ret)
return;
- ret = lt9211_system_init(ctx);
- if (ret)
- return;
-
- ret = lt9211_configure_rx(ctx);
- if (ret)
- return;
-
- ret = lt9211_autodetect_rx(ctx, mode);
- if (ret)
- return;
-
- ret = lt9211_configure_timing(ctx, mode);
- if (ret)
- return;
-
- ret = lt9211_configure_plls(ctx, mode);
- if (ret)
- return;
-
- ret = lt9211_configure_tx(ctx, lvds_format_jeida, lvds_format_24bpp,
- bus_flags & DRM_BUS_FLAG_DE_HIGH);
- 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));
+ } else if (ctx->chip_type == LT9211) {
+ ret = lt9211_system_init(ctx);
+ if (ret)
+ return;
+
+ ret = lt9211_configure_rx(ctx);
+ if (ret)
+ return;
+
+ ret = lt9211_autodetect_rx(ctx, mode);
+ if (ret)
+ return;
+
+ ret = lt9211_configure_timing(ctx, mode);
+ if (ret)
+ return;
+
+ ret = lt9211_configure_plls(ctx, mode);
+ if (ret)
+ return;
+
+ ret = lt9211_configure_tx(ctx, ctx->jeida,
+ ctx->bpp24,
+ ctx->de);
+ if (ret)
+ return;
+ }
dev_dbg(ctx->dev, "LT9211 enabled.\n");
}
@@ -672,11 +756,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;
@@ -698,7 +777,11 @@ 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)
+ dsi = devm_mipi_dsi_device_register_full(dev, host, <9211c_info);
+ else
+ dsi = devm_mipi_dsi_device_register_full(dev, host, <9211_info);
+
if (IS_ERR(dsi))
return dev_err_probe(dev, PTR_ERR(dsi),
"failed to create dsi device\n");
@@ -707,11 +790,17 @@ 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) {
dev_err(dev, "failed to attach dsi to host: %d\n", ret);
@@ -732,6 +821,7 @@ static int lt9211_probe(struct i2c_client *client)
return PTR_ERR(ctx);
ctx->dev = dev;
+ ctx->chip_type = LT9211;
/*
* Put the chip in reset, pull nRST line low,
@@ -748,10 +838,24 @@ 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);
+ /* Initialize LT9211C-specific fields */
+ 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);
@@ -769,17 +873,620 @@ 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[] = {
+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);
+ int ret;
+ const struct drm_display_mode *mode = &ctx->mode;
+
+ /* For LT9211C */
+ if (ctx->chip_type == LT9211C) {
+ 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;
+ } else {
+ dev_err(ctx->dev, "LT9211: Delayed work called for non-LT9211C chip\n");
+ }
+}
+
+static 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