[PATCH] video: bridge: add ITE IT66121 DPI-to-HDMI bridge driver
Shaurya Rane
ssrane_b23 at ee.vjti.ac.in
Sun Apr 12 09:58:22 CEST 2026
Add support for the ITE IT66121 DPI-to-HDMI bridge chip. The IT66121
converts parallel RGB/DPI video input to HDMI output. It is found on
boards like the BeaglePlay where the TI AM625 SoC's DPI output is
routed to an HDMI connector through this chip.
The driver is based on the IT66121 Programmer Guide and the Linux
kernel driver drivers/gpu/drm/bridge/ite-it66121.c.
Tested on BeaglePlay hardware with HDMI output via TI TIDSS.
Signed-off-by: Shaurya Rane <ssrane_b23 at ee.vjti.ac.in>
---
drivers/video/bridge/Kconfig | 10 +
drivers/video/bridge/Makefile | 1 +
drivers/video/bridge/it66121.c | 535 +++++++++++++++++++++++++++++++++
3 files changed, 546 insertions(+)
create mode 100644 drivers/video/bridge/it66121.c
diff --git a/drivers/video/bridge/Kconfig b/drivers/video/bridge/Kconfig
index 5322a002928..175e836e2f3 100644
--- a/drivers/video/bridge/Kconfig
+++ b/drivers/video/bridge/Kconfig
@@ -44,6 +44,16 @@ config VIDEO_BRIDGE_ANALOGIX_ANX6345
The Analogix ANX6345 is RGB-to-DP converter. It enables an eDP LCD
panel to be connected to an parallel LCD interface.
+config VIDEO_BRIDGE_ITE_IT66121
+ bool "Support ITE IT66121 DPI->HDMI bridge"
+ depends on VIDEO_BRIDGE
+ select DM_I2C
+ help
+ The ITE IT66121 is a DPI-to-HDMI bridge chip. It converts a parallel
+ RGB/DPI video input to an HDMI output. This is used on boards like
+ the BeaglePlay where the SoC's DPI output is connected to an HDMI
+ connector via this bridge chip.
+
config VIDEO_BRIDGE_SOLOMON_SSD2825
bool "Solomon SSD2825 bridge driver"
depends on VIDEO_BRIDGE && PANEL && DM_GPIO
diff --git a/drivers/video/bridge/Makefile b/drivers/video/bridge/Makefile
index 520f36a7a6f..bd048b7333e 100644
--- a/drivers/video/bridge/Makefile
+++ b/drivers/video/bridge/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_VIDEO_BRIDGE_PARADE_DP501) += dp501.o
obj-$(CONFIG_VIDEO_BRIDGE_PARADE_PS862X) += ps862x.o
obj-$(CONFIG_VIDEO_BRIDGE_NXP_PTN3460) += ptn3460.o
obj-$(CONFIG_VIDEO_BRIDGE_ANALOGIX_ANX6345) += anx6345.o
+obj-$(CONFIG_VIDEO_BRIDGE_ITE_IT66121) += it66121.o
obj-$(CONFIG_VIDEO_BRIDGE_SOLOMON_SSD2825) += ssd2825.o
obj-$(CONFIG_VIDEO_BRIDGE_TOSHIBA_TC358768) += tc358768.o
obj-$(CONFIG_VIDEO_BRIDGE_LVDS_CODEC) += lvds-codec.o
diff --git a/drivers/video/bridge/it66121.c b/drivers/video/bridge/it66121.c
new file mode 100644
index 00000000000..11c46768341
--- /dev/null
+++ b/drivers/video/bridge/it66121.c
@@ -0,0 +1,535 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Shaurya Rane <ssrane_b23 at ee.vjti.ac.in>
+ */
+
+#include <dm.h>
+#include <i2c.h>
+#include <edid.h>
+#include <log.h>
+#include <video_bridge.h>
+#include <linux/delay.h>
+
+#define IT66121_VENDOR_ID0 0x00
+#define IT66121_VENDOR_ID1 0x01
+#define IT66121_DEVICE_ID0 0x02
+#define IT66121_DEVICE_ID1 0x03
+
+#define IT66121_VENDOR_ID0_VAL 0x54
+#define IT66121_VENDOR_ID1_VAL 0x49
+#define IT66121_DEVICE_ID0_VAL 0x12
+#define IT66121_DEVICE_ID1_MASK 0x0F
+#define IT66121_DEVICE_ID1_VAL 0x06
+
+#define IT66121_SW_RST 0x04
+#define IT66121_SW_RST_REF BIT(5)
+#define IT66121_SW_RST_AREF BIT(4)
+#define IT66121_SW_RST_VID BIT(3)
+#define IT66121_SW_RST_AUD BIT(2)
+#define IT66121_SW_RST_HDCP BIT(0)
+
+#define IT66121_CLK_BANK_REG 0x0F
+#define IT66121_CLK_BANK_PWROFF_RCLK BIT(6)
+#define IT66121_CLK_BANK_PWROFF_TXCLK BIT(4)
+#define IT66121_CLK_BANK_SEL 0x03
+
+#define IT66121_SYS_STATUS 0x0E
+#define IT66121_SYS_STATUS_HPD BIT(6)
+
+#define IT66121_INT_MASK1 0x09
+#define IT66121_INT_MASK2 0x0A
+#define IT66121_INT_MASK3 0x0B
+
+#define IT66121_PCLK_CNT_REG 0x05
+
+#define IT66121_DDC_MASTER_SEL 0x10
+#define IT66121_DDC_HEADER 0x11
+#define IT66121_DDC_REQOFF 0x12
+#define IT66121_DDC_REQCOUNT 0x13
+#define IT66121_DDC_EDIDSEG 0x14
+#define IT66121_DDC_CMD 0x15
+#define IT66121_DDC_STATUS 0x16
+#define IT66121_DDC_READFIFO 0x17
+
+#define IT66121_DDC_STATUS_DONE BIT(7)
+#define IT66121_DDC_STATUS_ERROR_MASK (BIT(5) | BIT(4) | BIT(3))
+
+#define IT66121_DDC_CMD_EDID_RD 0x03
+#define IT66121_DDC_CMD_FIFO_CLR 0x09
+#define IT66121_DDC_CMD_ABORT 0x0F
+
+#define IT66121_INPUT_MODE 0x70
+#define IT66121_INPUT_MODE_RGB 0x00
+
+#define IT66121_INPUT_CSC 0x72
+#define IT66121_INPUT_CSC_NO_CONV 0x00
+
+#define IT66121_AFE_DRV_REG 0x61
+#define IT66121_AFE_DRV_RST BIT(4)
+#define IT66121_AFE_XP_REG 0x62
+#define IT66121_AFE_IP_REG 0x64
+#define IT66121_AFE_XP_EC1 0x68
+
+#define IT66121_HDMI_MODE 0xC0
+#define IT66121_HDMI_MODE_HDMI BIT(0)
+
+#define IT66121_AV_MUTE 0xC1
+#define IT66121_AV_MUTE_ON BIT(0)
+
+#define IT66121_PKT_GEN_CTRL 0xC6
+#define IT66121_PKT_GEN_CTRL_RPT BIT(1)
+#define IT66121_PKT_GEN_CTRL_EN BIT(0)
+
+#define IT66121_DDC_FIFO_SIZE 32
+#define IT66121_EDID_SLAVE_ADDR 0xA0
+#define IT66121_EDID_BUF_SIZE EDID_EXT_SIZE
+
+struct it66121_priv {
+ u8 edid[IT66121_EDID_BUF_SIZE];
+ int edid_len;
+};
+
+static int it66121_reg_read(struct udevice *dev, u8 reg, u8 *val)
+{
+ return dm_i2c_read(dev, reg, val, 1);
+}
+
+static int it66121_reg_write(struct udevice *dev, u8 reg, u8 val)
+{
+ return dm_i2c_write(dev, reg, &val, 1);
+}
+
+static int it66121_reg_set(struct udevice *dev, u8 reg, u8 mask, u8 val)
+{
+ u8 orig;
+ int ret;
+
+ ret = it66121_reg_read(dev, reg, &orig);
+ if (ret)
+ return ret;
+
+ orig = (orig & ~mask) | (val & mask);
+
+ return it66121_reg_write(dev, reg, orig);
+}
+
+static int it66121_identify(struct udevice *dev)
+{
+ u8 v0, v1, d0, d1;
+ int ret;
+
+ ret = it66121_reg_read(dev, IT66121_VENDOR_ID0, &v0);
+ if (ret)
+ return ret;
+ ret = it66121_reg_read(dev, IT66121_VENDOR_ID1, &v1);
+ if (ret)
+ return ret;
+ ret = it66121_reg_read(dev, IT66121_DEVICE_ID0, &d0);
+ if (ret)
+ return ret;
+ ret = it66121_reg_read(dev, IT66121_DEVICE_ID1, &d1);
+ if (ret)
+ return ret;
+
+ if (v0 != IT66121_VENDOR_ID0_VAL || v1 != IT66121_VENDOR_ID1_VAL ||
+ d0 != IT66121_DEVICE_ID0_VAL ||
+ (d1 & IT66121_DEVICE_ID1_MASK) != IT66121_DEVICE_ID1_VAL) {
+ debug("IT66121: chip not found (vendor=%02x%02x dev=%02x%02x)\n",
+ v1, v0, d1, d0);
+ return -ENODEV;
+ }
+
+ debug("IT66121: found chip (vendor=%02x%02x dev=%02x%02x)\n",
+ v1, v0, d1, d0);
+ return 0;
+}
+
+static int it66121_hw_init(struct udevice *dev)
+{
+ int ret;
+
+ ret = it66121_reg_set(dev, IT66121_CLK_BANK_REG,
+ IT66121_CLK_BANK_PWROFF_RCLK, 0);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_CLK_BANK_REG,
+ IT66121_CLK_BANK_SEL, 0);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_PCLK_CNT_REG, BIT(0), 0);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_AFE_DRV_REG, BIT(5), 0);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_AFE_XP_REG, BIT(6) | BIT(2), 0);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_AFE_IP_REG, BIT(6), 0);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_AFE_DRV_REG, BIT(4), 0);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_AFE_XP_REG, BIT(3), BIT(3));
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_AFE_IP_REG, BIT(2), BIT(2));
+ if (ret)
+ return ret;
+
+ /* Keep AREF/VID/AUD/HDCP in reset, leave REF clock running. */
+ ret = it66121_reg_write(dev, IT66121_SW_RST,
+ IT66121_SW_RST_AREF | IT66121_SW_RST_VID |
+ IT66121_SW_RST_AUD | IT66121_SW_RST_HDCP);
+ if (ret)
+ return ret;
+
+ mdelay(5);
+
+ ret = it66121_reg_write(dev, IT66121_HDMI_MODE, 0x00);
+ if (ret)
+ return ret;
+ ret = it66121_reg_write(dev, IT66121_AV_MUTE, 0x00);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_INT_MASK1, 0xFF);
+ if (ret)
+ return ret;
+ ret = it66121_reg_write(dev, IT66121_INT_MASK2, 0xFF);
+ if (ret)
+ return ret;
+ ret = it66121_reg_write(dev, IT66121_INT_MASK3, 0xFF);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int it66121_ddc_wait(struct udevice *dev)
+{
+ u8 status;
+ int i, ret;
+
+ for (i = 0; i < 200; i++) {
+ ret = it66121_reg_read(dev, IT66121_DDC_STATUS, &status);
+ if (ret)
+ return ret;
+
+ if (status & IT66121_DDC_STATUS_DONE)
+ return 0;
+
+ if (status & IT66121_DDC_STATUS_ERROR_MASK) {
+ debug("IT66121: DDC error status=0x%02x\n", status);
+ return -EIO;
+ }
+
+ mdelay(2);
+ }
+
+ debug("IT66121: DDC timeout\n");
+ return -ETIMEDOUT;
+}
+
+static int it66121_ddc_fifo_clear(struct udevice *dev)
+{
+ int ret;
+
+ ret = it66121_reg_write(dev, IT66121_DDC_CMD,
+ IT66121_DDC_CMD_FIFO_CLR);
+ if (ret)
+ return ret;
+
+ return it66121_ddc_wait(dev);
+}
+
+static int it66121_read_edid_block(struct udevice *dev, u8 *buf,
+ int offset, int segment, int len)
+{
+ int ret, i, bytes_read = 0;
+
+ while (bytes_read < len) {
+ int chunk = len - bytes_read;
+
+ if (chunk > IT66121_DDC_FIFO_SIZE)
+ chunk = IT66121_DDC_FIFO_SIZE;
+
+ ret = it66121_reg_write(dev, IT66121_DDC_MASTER_SEL, 0x01);
+ if (ret)
+ return ret;
+
+ ret = it66121_ddc_fifo_clear(dev);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_DDC_HEADER,
+ IT66121_EDID_SLAVE_ADDR);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_DDC_REQOFF,
+ (offset + bytes_read) & 0xFF);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_DDC_REQCOUNT, chunk);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_DDC_EDIDSEG, segment);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_DDC_CMD,
+ IT66121_DDC_CMD_EDID_RD);
+ if (ret)
+ return ret;
+
+ ret = it66121_ddc_wait(dev);
+ if (ret) {
+ debug("IT66121: EDID read failed at offset %d\n",
+ offset + bytes_read);
+ it66121_reg_write(dev, IT66121_DDC_CMD,
+ IT66121_DDC_CMD_ABORT);
+ return ret;
+ }
+
+ for (i = 0; i < chunk; i++) {
+ ret = it66121_reg_read(dev, IT66121_DDC_READFIFO,
+ &buf[bytes_read + i]);
+ if (ret)
+ return ret;
+ }
+
+ bytes_read += chunk;
+ }
+
+ return bytes_read;
+}
+
+static int it66121_setup_afe(struct udevice *dev, unsigned long pclk_khz)
+{
+ int ret;
+
+ ret = it66121_reg_write(dev, IT66121_AFE_DRV_REG, IT66121_AFE_DRV_RST);
+ if (ret)
+ return ret;
+
+ if (pclk_khz > 80000) {
+ ret = it66121_reg_set(dev, IT66121_AFE_XP_REG, 0x90, 0x80);
+ if (ret)
+ return ret;
+ ret = it66121_reg_set(dev, IT66121_AFE_IP_REG, 0x89, 0x80);
+ if (ret)
+ return ret;
+ ret = it66121_reg_set(dev, IT66121_AFE_XP_EC1, 0x10, 0x00);
+ if (ret)
+ return ret;
+ } else {
+ ret = it66121_reg_set(dev, IT66121_AFE_XP_REG, 0x90, 0x10);
+ if (ret)
+ return ret;
+ ret = it66121_reg_set(dev, IT66121_AFE_IP_REG, 0x89, 0x09);
+ if (ret)
+ return ret;
+ ret = it66121_reg_set(dev, IT66121_AFE_XP_EC1, 0x10, 0x10);
+ if (ret)
+ return ret;
+ }
+
+ ret = it66121_reg_set(dev, IT66121_SW_RST,
+ IT66121_SW_RST_REF | IT66121_SW_RST_VID, 0);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_AFE_DRV_REG, 0x00);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/* Basic HDMI RGB output; AVI InfoFrame programming is not implemented. */
+static int it66121_setup_video(struct udevice *dev, unsigned long pclk_khz)
+{
+ int ret;
+
+ ret = it66121_reg_set(dev, IT66121_AV_MUTE,
+ IT66121_AV_MUTE_ON, IT66121_AV_MUTE_ON);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_HDMI_MODE,
+ IT66121_HDMI_MODE_HDMI);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_CLK_BANK_REG,
+ IT66121_CLK_BANK_PWROFF_TXCLK,
+ IT66121_CLK_BANK_PWROFF_TXCLK);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_INPUT_MODE,
+ IT66121_INPUT_MODE_RGB);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_INPUT_CSC,
+ IT66121_INPUT_CSC_NO_CONV);
+ if (ret)
+ return ret;
+
+ ret = it66121_setup_afe(dev, pclk_khz);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_CLK_BANK_REG,
+ IT66121_CLK_BANK_PWROFF_TXCLK, 0);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_write(dev, IT66121_PKT_GEN_CTRL,
+ IT66121_PKT_GEN_CTRL_RPT |
+ IT66121_PKT_GEN_CTRL_EN);
+ if (ret)
+ return ret;
+
+ ret = it66121_reg_set(dev, IT66121_AV_MUTE, IT66121_AV_MUTE_ON, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int it66121_attach(struct udevice *dev)
+{
+ struct it66121_priv *priv = dev_get_priv(dev);
+ unsigned long pclk_khz;
+ u8 status;
+ int ret;
+
+ ret = video_bridge_set_active(dev, true);
+ if (ret) {
+ debug("IT66121: set_active failed: %d\n", ret);
+ return ret;
+ }
+
+ mdelay(50);
+
+ ret = it66121_identify(dev);
+ if (ret)
+ return ret;
+
+ ret = it66121_hw_init(dev);
+ if (ret) {
+ debug("IT66121: hw_init failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = it66121_reg_read(dev, IT66121_SYS_STATUS, &status);
+ if (ret)
+ return ret;
+
+ if (!(status & IT66121_SYS_STATUS_HPD)) {
+ debug("IT66121: no HPD, skipping EDID read\n");
+ return 0;
+ }
+
+ ret = it66121_read_edid_block(dev, priv->edid, 0, 0, 128);
+ if (ret < 0) {
+ debug("IT66121: EDID block 0 read failed: %d\n", ret);
+ return ret;
+ }
+
+ priv->edid_len = 128;
+
+ if (priv->edid[0x7E] > 0) {
+ ret = it66121_read_edid_block(dev, priv->edid + 128,
+ 128, 0, 128);
+ if (ret < 0)
+ debug("IT66121: EDID block 1 read failed: %d\n", ret);
+ else
+ priv->edid_len = 256;
+ }
+
+ /* Preferred DTD pixel clock: bytes 54-55, units of 10 kHz. */
+ pclk_khz = ((unsigned long)priv->edid[55] << 8 |
+ priv->edid[54]) * 10;
+ if (pclk_khz > 0) {
+ debug("IT66121: configuring video for %lu kHz pclk\n",
+ pclk_khz);
+ ret = it66121_setup_video(dev, pclk_khz);
+ if (ret)
+ debug("IT66121: setup_video failed: %d\n", ret);
+ }
+
+ return 0;
+}
+
+static int it66121_check_attached(struct udevice *dev)
+{
+ u8 status;
+ int ret;
+
+ ret = it66121_reg_read(dev, IT66121_SYS_STATUS, &status);
+ if (ret)
+ return ret;
+
+ if (status & IT66121_SYS_STATUS_HPD)
+ return 0;
+
+ return -ENOTCONN;
+}
+
+static int it66121_read_edid(struct udevice *dev, u8 *buf, int buf_size)
+{
+ struct it66121_priv *priv = dev_get_priv(dev);
+ int size;
+
+ if (!priv->edid_len)
+ return -ENODATA;
+
+ size = min_t(int, buf_size, priv->edid_len);
+ memcpy(buf, priv->edid, size);
+
+ return size;
+}
+
+static int it66121_probe(struct udevice *dev)
+{
+ if (device_get_uclass_id(dev->parent) != UCLASS_I2C)
+ return -EPROTONOSUPPORT;
+
+ debug("IT66121: probed on I2C bus\n");
+ return 0;
+}
+
+static const struct video_bridge_ops it66121_ops = {
+ .attach = it66121_attach,
+ .check_attached = it66121_check_attached,
+ .read_edid = it66121_read_edid,
+};
+
+static const struct udevice_id it66121_ids[] = {
+ { .compatible = "ite,it66121" },
+ { }
+};
+
+U_BOOT_DRIVER(ite_it66121) = {
+ .name = "ite_it66121",
+ .id = UCLASS_VIDEO_BRIDGE,
+ .of_match = it66121_ids,
+ .probe = it66121_probe,
+ .ops = &it66121_ops,
+ .priv_auto = sizeof(struct it66121_priv),
+};
--
2.34.1
More information about the U-Boot
mailing list