[PATCH RFC 8/9] drm/bridge: tc358762: Basic support for 2 lanes

From: Andreas Kemnade

Date: Tue Sep 22 2026 - 02:18:19 EST


Add support for 2 data lanes. Enabling the PLL is required. This are just
the values working for the Epson Moverio BT-200. With the standard mode
flags, the panel mode was deemed unusable, adjust them to have more freedom
in case of 2 data lanes operation.
Nice looking values for the input clock for the PLL were not found.

Setting the required register needs special sequences, entering ULPS in
between is the way to do it in the vendor kernel. It seems that some
stop mode transitions are also enough. This all seems not to work
if LPM is used for writing the register, to temporarily disable that.

Signed-off-by: Andreas Kemnade <andreas@xxxxxxxxxxxx>
---
drivers/gpu/drm/bridge/tc358762.c | 87 +++++++++++++++++++++++++++++++++++----
1 file changed, 78 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c
index 194785bff997..632597fec20d 100644
--- a/drivers/gpu/drm/bridge/tc358762.c
+++ b/drivers/gpu/drm/bridge/tc358762.c
@@ -100,6 +100,8 @@
#define SYSCTRL_PCLKDIV_DIV_2 2
#define SYSCTRL_PCLKDIV_DIV_3 4

+#define SYSPLL3 0x0470 /* */
+
#define IDREG 0x04A0 /* Chip and Revision ID */

struct tc358762 {
@@ -183,6 +185,7 @@ static void tc358762_post_disable(struct drm_bridge *bridge,
if (!ctx->pre_enabled)
return;

+ to_mipi_dsi_device(ctx->dev)->mode_flags |= MIPI_DSI_MODE_LPM;
ctx->pre_enabled = false;

/* Turn off the DPI output */
@@ -208,7 +211,9 @@ static void tc358762_pre_enable(struct drm_bridge *bridge,
u32 lcdctrl;
int ret;
u32 id;
+ u32 sysctrl;

+ to_mipi_dsi_device(ctx->dev)->mode_flags |= MIPI_DSI_MODE_LPM;
ret = regulator_enable(ctx->regulator);
if (ret < 0)
dev_err(ctx->dev, "error enabling regulators (%d)\n", ret);
@@ -233,15 +238,26 @@ static void tc358762_pre_enable(struct drm_bridge *bridge,

tc358762_write(ctx, RDPKTLN, 3);

- tc358762_write(ctx, SYSCTRL,
- FIELD_PREP(SYSCTRL_DPIDATA_IO_MASK, SYSCTRL_DPIDATA_IO_4MA) |
- FIELD_PREP(SYSCTRL_DPISTB_IO_MASK, SYSCTRL_DPISTB_IO_4MA) |
- FIELD_PREP(SYSCTRL_PCLKDIV_MASK, SYSCTRL_PCLKDIV_DIV_3));
+ sysctrl = FIELD_PREP(SYSCTRL_DPIDATA_IO_MASK, SYSCTRL_DPIDATA_IO_4MA) |
+ FIELD_PREP(SYSCTRL_DPISTB_IO_MASK, SYSCTRL_DPISTB_IO_4MA);
+
+ if (to_mipi_dsi_device(ctx->dev)->lanes == 2)
+ sysctrl |= FIELD_PREP(SYSCTRL_PCLKDIV_MASK, SYSCTRL_PCLKDIV_DIV_2);
+ else
+ sysctrl |= FIELD_PREP(SYSCTRL_PCLKDIV_MASK, SYSCTRL_PCLKDIV_DIV_3);
+
+ tc358762_write(ctx, SYSCTRL, sysctrl);

msleep(100);

- tc358762_write(ctx, DSI_LANEENABLE,
- DSI_LANEENABLE_L0EN | DSI_LANEENABLE_CLEN);
+ if (to_mipi_dsi_device(ctx->dev)->lanes == 2) {
+ tc358762_write(ctx, DSI_LANEENABLE,
+ DSI_LANEENABLE_L0EN | DSI_LANEENABLE_CLEN |
+ DSI_LANEENABLE_L1EN);
+ } else {
+ tc358762_write(ctx, DSI_LANEENABLE,
+ DSI_LANEENABLE_L0EN | DSI_LANEENABLE_CLEN);
+ }
tc358762_write(ctx, PPI_D0S_CLRSIPOCOUNT, 5);
tc358762_write(ctx, PPI_D1S_CLRSIPOCOUNT, 5);
tc358762_write(ctx, PPI_D0S_ATMR, 0);
@@ -329,6 +345,43 @@ static void tc358762_pre_enable(struct drm_bridge *bridge,
static void tc358762_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
+ struct tc358762 *ctx = bridge_to_tc358762(bridge);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+ u32 syspll;
+
+ if (dsi->lanes == 2) {
+ tc358762_read(ctx, SYSPLL3, &syspll);
+ dev_dbg(ctx->dev, "syspll reg: %x\n", syspll);
+ /*
+ * as handling of these registers is bound to certain
+ * conditions, avoid write if already set
+ */
+ if (syspll != 0xB8640000) {
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+ /*
+ * SYSPLL3 seems to get set to 0x47000000 without it.
+ * Also trigger some stop mode transitions
+ * TODO: find some sane value for clock input and add proper dividers
+ */
+ tc358762_write(ctx, SYSPLL3, 0xB8640000);
+ ret = tc358762_clear_error(ctx);
+ if (ret) {
+ dev_err(ctx->dev, "error writing to SYSPLL3: %d\n", ret);
+ return;
+ }
+
+ msleep(20);
+ /*
+ * control read to see if this is wonky, probably also includes
+ * stop mode transitions
+ */
+ ret = tc358762_read(ctx, SYSPLL3, &syspll);
+
+ if ((ret) || (syspll != 0xB8640000))
+ dev_err(ctx->dev, "failed to configure PLL %d\n", ret);
+ }
+ }
}

static int tc358762_attach(struct drm_bridge *bridge,
@@ -355,6 +408,7 @@ static int tc358762_parse_dt(struct tc358762 *ctx)
{
struct drm_bridge *panel_bridge;
struct device *dev = ctx->dev;
+ u32 lanes;

panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0);
if (IS_ERR(panel_bridge))
@@ -367,6 +421,13 @@ static int tc358762_parse_dt(struct tc358762 *ctx)
if (IS_ERR(ctx->reset_gpio))
return PTR_ERR(ctx->reset_gpio);

+ if (!device_property_read_u32(dev, "dsi-lanes", &lanes)) {
+ if (lanes > 2)
+ return -EINVAL;
+
+ to_mipi_dsi_device(ctx->dev)->lanes = lanes;
+ }
+
ctx->lpx_period = 3;
device_property_read_u32(dev, "toshiba,lptxtimecnt", &ctx->lpx_period);

@@ -459,13 +520,21 @@ static int tc358762_probe(struct mipi_dsi_device *dsi)
*/
dsi->lanes = 1;
dsi->format = MIPI_DSI_FMT_RGB888;
- dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
- MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO_HSE;
-
ret = tc358762_parse_dt(ctx);
if (ret < 0)
return ret;

+ if (dsi->lanes == 2) {
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_CLOCK_NON_CONTINUOUS |
+ MIPI_DSI_MODE_LPM;
+
+ dsi->hs_rate = 800000000;
+ dsi->lp_rate = 9200000;
+ } else
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO_HSE;
+
ret = tc358762_configure_regulators(ctx);
if (ret < 0)
return ret;

--
2.47.3