* [PATCH 0/3] Add ITE IT6162 MIPI DSI to HDMI bridge driver
@ 2026-02-23 9:20 Hermes Wu via B4 Relay
2026-02-23 9:20 ` [PATCH 1/3] drm/bridge: " Hermes Wu via B4 Relay
` (3 more replies)
0 siblings, 4 replies; 11+ messages in thread
From: Hermes Wu via B4 Relay @ 2026-02-23 9:20 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Pet.Weng, Kenneth.Hung, Hermes Wu, dri-devel, devicetree,
linux-kernel, Hermes Wu
This patch series adds support for the ITE IT6162 MIPI DSI to HDMI
bridge chip. The IT6162 is an I2C-controlled bridge that receives MIPI
DSI input and outputs HDMI signals.
The device supports the following configurations:
- Single MIPI DSI input: up to 4K @ 30Hz
- Dual MIPI DSI input (combined): up to 4K @ 60Hz
This series introduces:
- dt-bindings: Add YAML binding document for ITE IT6162
- drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver
- MAINTAINERS: Add entry for ITE IT6162 bridge driver
Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw>
---
Hermes Wu (3):
drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver
dt-bindings: display: bridge: Add ITE IT6162 MIPI DSI to HDMI bridge
MAINTAINERS: Add entry for ITE IT6162 MIPI DSI to HDMI bridge driver
.../bindings/display/bridge/ite,it6162.yaml | 156 ++
MAINTAINERS | 8 +
drivers/gpu/drm/bridge/Kconfig | 17 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/ite-it6162.c | 1876 ++++++++++++++++++++
5 files changed, 2058 insertions(+)
---
base-commit: 38feb171b3f92d77e8061fafb5ddfffc2c13b672
change-id: 20260223-upstream-6162-3751e78dfcad
Best regards,
--
Hermes Wu <Hermes.wu@ite.com.tw>
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 1/3] drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver 2026-02-23 9:20 [PATCH 0/3] Add ITE IT6162 MIPI DSI to HDMI bridge driver Hermes Wu via B4 Relay @ 2026-02-23 9:20 ` Hermes Wu via B4 Relay 2026-02-24 0:21 ` Claude review: " Claude Code Review Bot 2026-02-23 9:20 ` [PATCH 2/3] dt-bindings: display: bridge: Add ITE IT6162 MIPI DSI to HDMI bridge Hermes Wu via B4 Relay ` (2 subsequent siblings) 3 siblings, 1 reply; 11+ messages in thread From: Hermes Wu via B4 Relay @ 2026-02-23 9:20 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: Pet.Weng, Kenneth.Hung, Hermes Wu, dri-devel, devicetree, linux-kernel, Hermes Wu From: Hermes Wu <Hermes.wu@ite.com.tw> Add support for the ITE IT6162 MIPI DSI to HDMI 2.0 bridge chip. The IT6162 is an I2C-controlled bridge that supports the following configurations: - Single MIPI DSI input: up to 4K @ 30Hz - Dual MIPI DSI input (combined): up to 4K @ 60Hz The driver implements the DRM bridge and connector frameworks, including mode setting, EDID retrieval, and HPD support. Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw> --- drivers/gpu/drm/bridge/Kconfig | 17 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ite-it6162.c | 1876 +++++++++++++++++++++++++++++++++++ 3 files changed, 1894 insertions(+) diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 39385deafc68e4bdc61088558036bf6938da7461..b53e23d421dfd2a875f92293847e694b175f6533 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -107,6 +107,23 @@ config DRM_INNO_HDMI select DRM_DISPLAY_HELPER select DRM_KMS_HELPER +config DRM_ITE_IT6162 + tristate "iTE IT6162 DSI to HDMI bridge" + depends on OF + select REGMAP_I2C + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_HDMI_HELPER + select DRM_DISPLAY_HDMI_HELPER + select DRM_DISPLAY_HDCP_HELPER + select DRM_DISPLAY_HELPER + help + Driver for iTE IT6162 DSI to HDMI bridge + chip driver that converts DSI to HDMI signals + support up to 4k60 with 2 MIPI DSI + Please say Y if you have such hardware. + config DRM_ITE_IT6263 tristate "ITE IT6263 LVDS/HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 909c21cc3acd31e36f85fcbf38032b912ad4d948..13dfa08aa6e9c5a885895ed40f813b1d8b532639 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -11,6 +11,7 @@ tda998x-y := tda998x_drv.o obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o obj-$(CONFIG_DRM_INNO_HDMI) += inno-hdmi.o +obj-$(CONFIG_DRM_ITE_IT6162) += ite-it6162.o obj-$(CONFIG_DRM_ITE_IT6263) += ite-it6263.o obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o diff --git a/drivers/gpu/drm/bridge/ite-it6162.c b/drivers/gpu/drm/bridge/ite-it6162.c new file mode 100644 index 0000000000000000000000000000000000000000..498b7c0c33fa8cf1bdd6c13be106030b3b6849ff --- /dev/null +++ b/drivers/gpu/drm/bridge/ite-it6162.c @@ -0,0 +1,1876 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2024 ITE + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_graph.h> +#include <linux/regmap.h> +#include <drm/drm_drv.h> +#include <drm/drm_of.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_edid.h> +#include <video/videomode.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_print.h> +#include <sound/hdmi-codec.h> +#include <linux/pm_runtime.h> + +#include <drm/display/drm_hdcp_helper.h> +#include <drm/drm_bridge_connector.h> +#include <drm/display/drm_dsc.h> +#include <drm/display/drm_hdmi_helper.h> +#include <drm/display/drm_hdmi_state_helper.h> + +#define DATA_BUFFER_DEPTH 32 + +#define OFFSET_CHIP_ID_L 0x00 +#define OFFSET_CHIP_ID_M 0x01 +#define OFFSET_CHIP_ID_H 0x02 +#define OFFSET_VERSION_L 0x03 +#define OFFSET_VERSION_M 0x04 +#define OFFSET_VERSION_H 0x03 +#define OFFSET_MIPI_CONFIG_L 0x06 +#define OFFSET_MIPI_CONFIG_H 0x07 +#define OFFSET_TX_CONFIG 0x08 +#define OFFSET_TX_SETTING 0x09 +#define OFFSET_MIPI_STATUS 0x0A + +#define OFFSET_TX_STATUS 0x0C +#define B_TX_STATUS_HPD 7 +#define B_TX_STATUS_VIDEO_STB 6 +#define B_TX_STATUS_HDCP 4 +#define M_TX_STATUS_HDCP 0x30 + +#define TX_VIDEO_STB BIT(B_TX_STATUS_VIDEO_STB) +#define TX_STATUS_HPD BIT(B_TX_STATUS_HPD) + +#define GET_TX_HPD_STATUS(x) (((x) & TX_STATUS_HPD) >> B_TX_STATUS_HPD) +#define GET_TX_VIDEO_STATUS(x) (((x) & TX_VIDEO_STB) >> B_TX_STATUS_VIDEO_STB) +#define GET_TX_HDCP_STATUS(x) (((x) & M_TX_STATUS_HDCP) >> B_TX_STATUS_HDCP) + +#define OFFSET_SINK_CAP 0x0D +#define B_SINK_CAP_HDCP_VER 4 +#define M_SINK_CAP_HDCP_VER 0x30 + +#define GET_SINK_CAP_HDCP_VER(x) (((x) & M_SINK_CAP_HDCP_VER) >> B_SINK_CAP_HDCP_VER) + +#define OFFSET_DRIVER_DATA 0x0E + +#define OFFSET_DATA_TYPE_IDX 0x0F +#define OFFSET_DATA_BUFFER 0x20 + +#define OFFSET_HOST_SETTING 0xFE +#define B_CONFIG_CHG BIT(7) +#define B_SET_CHG BIT(6) +#define HOST_SETTING_VIDEO_INFO (1) +#define HOST_SETTING_AUDIO_INFO (2) +#define HOST_SETTING_VIDEO_AUDIO_INFO (3) +#define HOST_SETTING_EDID_R (0x04) +#define HOST_SETTING_HDCP_R (0x05) + +#define OFFSET_EVENT_CHG 0xFF +#define B_EVENT_CHG_BUFFER 4 +#define M_EVENT_CHG_BUFFER_STS (0x30) + +#define B_EVENT_CHG 1 +#define B_EVENT_IC_READY 0 + +#define EVENT_CHG BIT(B_EVENT_CHG) +#define EVENT_READY BIT(B_EVENT_IC_READY) + +#define GET_BUFFER_STATUS(x) (((x) & M_EVENT_CHG_BUFFER_STS) >> B_EVENT_CHG_BUFFER) + +#define TIMEOUT_INFOBLOCK_MS 1800 + +enum it6162_audio_select { + I2S = 0, + SPDIF, +}; + +enum it6162_audio_word_length { + WORD_LENGTH_16BIT = 0x0, + WORD_LENGTH_18BIT = 0x1, + WORD_LENGTH_20BIT = 0x2, + WORD_LENGTH_24BIT = 0x3, +}; + +enum it6162_audio_sample_rate { + SAMPLE_RATE_32K = 0x3, + SAMPLE_RATE_48K = 0x2, + SAMPLE_RATE_64K = 0xB, + SAMPLE_RATE_96K = 0xA, + SAMPLE_RATE_192K = 0xE, + SAMPLE_RATE_44_1K = 0x0, + SAMPLE_RATE_88_2K = 0x8, + SAMPLE_RATE_176_4K = 0xC, +}; + +enum it6162_audio_type { + LPCM = 0, + NLPCM, +}; + +enum data_buf_sts { + NO_STS = 0x00, + BUF_READY = 0x01, + BUF_FAIL = 0x02, +}; + +enum hdcp_state { + NO_HDCP_STATE = 0x00, + AUTH_DONE = 0x01, + AUTH_FAIL = 0x02, +}; + +enum hdcp_ver { + NO_HDCP = 0x0, + HDCP_14 = 0x1, + HDCP_23 = 0x2, +}; + +struct it6162_chip_info { + u32 chip_id; + u32 version; +}; + +struct it6162_audio { + enum it6162_audio_select select; + enum it6162_audio_type type; + enum it6162_audio_sample_rate sample_rate; + unsigned int audio_enable; + unsigned int sample_width; + unsigned int channel_number; + unsigned int user_cts; + u8 infoframe[HDMI_INFOFRAME_SIZE(AUDIO)]; + unsigned char channel_status[AES_IEC958_STATUS_SIZE]; +}; + +struct it6162_video { + u8 vic; + u32 clock; + u16 htotal; + u16 hfp; + u16 hsw; + u16 hbp; + u16 hdew; + u16 vtotal; + u16 vfp; + u16 vsw; + u16 vbp; + u16 vdew; + u8 hpol; + u8 vpol; + u8 prog; + u16 v_aspect; + u16 h_aspect; + u8 pix_rep; + u8 colorspace; +}; + +enum sync_mode { + SYNC_EVENT = 0x0, + SYNC_PULSE = 0x1, + SYNC_AUTO = 0x2, +}; + +struct it6162_mipi_cfg { + bool en_port[2]; + u8 lane_num; + bool pn_swap; + bool lane_swap; + bool continuous_clk; + enum sync_mode mode; + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; +}; + +struct it6162_tx_out_set { + enum hdcp_ver hdcp_version; + bool hdcp_encyption; + u8 stream_ID; +}; + +struct it6162_infoblock_msg { + u8 action; + int len; + u8 msg[32]; +}; + +struct it6162 { + struct drm_bridge bridge; + struct drm_connector connector; + struct device *dev; + enum drm_connector_status connector_status; + struct drm_device *drm; + + struct i2c_client *it6162_i2c; + struct regmap *it6162_regmap; + + struct delayed_work hdcp_work; + + struct regulator *pwr1833; + struct regulator *ovdd; + struct regulator *ivdd; + struct gpio_desc *gpiod_reset; + + bool power_on; + bool is_hdmi; + bool has_audio; + bool en_hdcp; + + /* operations can only be served one at the time */ + struct mutex lock; + + /* it6162 DSI RX related params */ + struct mipi_dsi_device *dsi; + struct it6162_mipi_cfg mipi_cfg; + struct it6162_tx_out_set tx_out_set; + + bool support_audio; + struct it6162_audio audio_config; + struct platform_device *audio_pdev; + hdmi_codec_plugged_cb plugged_cb; + struct device *codec_dev; + + struct it6162_chip_info chip_info; + + enum data_buf_sts data_buf_sts; + enum hdcp_state hdcp_sts; + enum hdcp_ver hdcp_version; + + bool bridge_hpd_enable; + +}; + +static struct it6162 *bridge_to_it6162(struct drm_bridge *bridge) +{ + return container_of(bridge, struct it6162, bridge); +} + +static const struct regmap_config it6162_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, +}; + +static int it6162_i2c_regmap_init(struct i2c_client *client, + struct it6162 *it6162) +{ + it6162->it6162_i2c = client; + i2c_set_clientdata(client, it6162); + it6162->it6162_regmap = devm_regmap_init_i2c(it6162->it6162_i2c, + &it6162_regmap_config); + + if (IS_ERR(it6162->it6162_regmap)) + return PTR_ERR(it6162->it6162_regmap); + + return 0; +} + +static unsigned int it6162_infoblock_read(struct it6162 *it6162, + unsigned int reg) +{ + unsigned int val; + int err; + struct device *dev = it6162->dev; + + err = regmap_read(it6162->it6162_regmap, reg, &val); + if (err < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", reg, err); + return err; + } + + return val; +} + +static int it6162_infoblock_write(struct it6162 *it6162, unsigned int reg, + unsigned int val) +{ + int err; + struct device *dev = it6162->dev; + + err = regmap_write(it6162->it6162_regmap, reg, val); + + if (err < 0) { + dev_err(dev, "write failed rx reg[0x%x] = 0x%x err = %d", + reg, val, err); + return err; + } + + return 0; +} + +static inline void it6162_infoblock_update_bits(struct it6162 *it6162, + unsigned int reg, + unsigned int mask, + unsigned int val) +{ + regmap_update_bits(it6162->it6162_regmap, reg, mask, val); +} + +static inline void it6162_infoblock_bulk_read(struct it6162 *it6162, + unsigned int reg, + u8 *buf, size_t len) +{ + regmap_bulk_read(it6162->it6162_regmap, reg, buf, len); +} + +static inline void it6162_infoblock_read_bufer(struct it6162 *it6162, + u8 *buf, size_t len) +{ + regmap_bulk_read(it6162->it6162_regmap, + OFFSET_DATA_BUFFER, buf, len); +} + +static inline void it6162_infoblock_bulk_write(struct it6162 *it6162, + unsigned int reg, + u8 *buf, size_t len) +{ + regmap_bulk_write(it6162->it6162_regmap, reg, buf, len); +} + +static inline void it6162_infoblock_write_bufer(struct it6162 *it6162, + u8 *buf, size_t len) +{ + regmap_bulk_write(it6162->it6162_regmap, + OFFSET_DATA_BUFFER, buf, len); +} + +static bool it6162_infoblock_complete(struct it6162 *it6162) +{ + int tmp; + + tmp = it6162_infoblock_read(it6162, OFFSET_HOST_SETTING); + return tmp == 0 ? true : false; +} + +static int it6162_infoblock_wait_complete(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + int status; + bool val = 0; + + status = readx_poll_timeout(it6162_infoblock_complete, + it6162, + val, + val, + 50 * 1000, + TIMEOUT_INFOBLOCK_MS * 1000); + + if (status < 0) { + dev_err(dev, "%s err status = %d", __func__, status); + return -ETIMEDOUT; + } + + return 0; +} + +static int it6162_infoblock_host_set(struct it6162 *it6162, u8 setting) +{ + dev_info(it6162->dev, "%s %x", __func__, setting); + it6162_infoblock_write(it6162, OFFSET_HOST_SETTING, setting); + /*wait command complete*/ + it6162_infoblock_wait_complete(it6162); + + return 0; +} + +static int it6162_infoblock_request_data(struct it6162 *it6162, + u8 setting, u8 index, u8 *buf) +{ + struct device *dev = it6162->dev; + int status; + bool val = 0; + + it6162->data_buf_sts = NO_STS; + it6162_infoblock_write(it6162, OFFSET_DATA_TYPE_IDX, index); + it6162_infoblock_write(it6162, OFFSET_HOST_SETTING, setting); + + status = readx_poll_timeout(it6162_infoblock_complete, + it6162, + val, + val && it6162->data_buf_sts == BUF_READY, + 50 * 1000, + TIMEOUT_INFOBLOCK_MS * 1000); + + if (status < 0) { + dev_err(dev, + "%s err status = %d %d", + __func__, + status, + it6162->data_buf_sts); + return -ETIMEDOUT; + } + + dev_dbg(dev, "%s [0x%x] 0x%x", __func__, setting, it6162->data_buf_sts); + if (it6162->data_buf_sts != BUF_READY) + return -EIO; + + it6162_infoblock_read_bufer(it6162, buf, DATA_BUFFER_DEPTH); + return 0; +} + +static void it6162_infoblock_mipi_config(struct it6162 *it6162, + struct it6162_mipi_cfg *cfg) +{ + u8 cfg_val = 0; + + cfg_val = (cfg->continuous_clk << 6) | (cfg->en_port[1] << 5) | + (cfg->en_port[0] << 4) | (cfg->lane_swap << 3) | + (cfg->pn_swap << 2) | (cfg->lane_num - 1); + + dev_dbg(it6162->dev, "%s 0x%02x 0x%02x", __func__, + cfg_val, cfg->mode); + + it6162_infoblock_write(it6162, OFFSET_MIPI_CONFIG_L, cfg_val); + it6162_infoblock_write(it6162, OFFSET_MIPI_CONFIG_H, cfg->mode); +} + +static inline void it6162_infoblock_write_msg(struct it6162 *it6162, + struct it6162_infoblock_msg *msg) +{ + it6162_infoblock_write_bufer(it6162, msg->msg, msg->len); + it6162_infoblock_host_set(it6162, msg->action); +} + +static void it6162_set_default_config(struct it6162 *it6162) +{ + guard(mutex)(&it6162->lock); + it6162_infoblock_mipi_config(it6162, &it6162->mipi_cfg); + it6162_infoblock_update_bits(it6162, + OFFSET_MIPI_CONFIG_L, 0x30, 0x00); + it6162_infoblock_write(it6162, OFFSET_TX_CONFIG, 0x00); + it6162_infoblock_write(it6162, OFFSET_TX_SETTING, 0x00); + it6162_infoblock_host_set(it6162, B_CONFIG_CHG | B_SET_CHG); +} + +static void it6162_mipi_disable(struct it6162 *it6162) +{ + guard(mutex)(&it6162->lock); + it6162_infoblock_update_bits(it6162, OFFSET_MIPI_CONFIG_L, + 0x30, 0x00); + it6162_infoblock_host_set(it6162, B_CONFIG_CHG); +} + +static void it6162_mipi_enable(struct it6162 *it6162) +{ + unsigned int cfg_val = 0; + + cfg_val = it6162->mipi_cfg.en_port[0] << 4 | + it6162->mipi_cfg.en_port[1] << 5; + + guard(mutex)(&it6162->lock); + it6162_infoblock_update_bits(it6162, + OFFSET_MIPI_CONFIG_L, 0x30, cfg_val); + it6162_infoblock_host_set(it6162, B_CONFIG_CHG); +} + +static void it6162_tx_hdcp_enable(struct it6162 *it6162, + struct it6162_tx_out_set *tx_out) +{ + u8 tmp; + + it6162->hdcp_sts = NO_HDCP_STATE; + tmp = it6162_infoblock_read(it6162, OFFSET_TX_SETTING) & 0x0F; + tmp |= (tx_out->hdcp_version << 6) | + (tx_out->hdcp_encyption << 5) | + (tx_out->stream_ID << 4); + + guard(mutex)(&it6162->lock); + it6162_infoblock_write(it6162, OFFSET_TX_SETTING, tmp); + it6162_infoblock_host_set(it6162, B_SET_CHG); + + drm_dbg(it6162->drm, "%s 0x%02x", __func__, tmp); +} + +static void it6162_tx_hdcp_disable(struct it6162 *it6162) +{ + u8 tmp; + + it6162->hdcp_sts = NO_HDCP_STATE; + it6162->hdcp_version = NO_HDCP; + guard(mutex)(&it6162->lock); + tmp = it6162_infoblock_read(it6162, OFFSET_TX_SETTING); + tmp &= 0x0F; + it6162_infoblock_write(it6162, OFFSET_TX_SETTING, tmp); + it6162_infoblock_host_set(it6162, B_SET_CHG); + + drm_dbg(it6162->drm, "%s 0x%02x", __func__, tmp); +} + +static void it6162_tx_hdcp_setup(struct it6162 *it6162, + u8 ver, + bool encription) +{ + struct it6162_tx_out_set tx_out; + + drm_dbg(it6162->drm, "%s ver %x enc %d", __func__, ver, encription); + + if (ver != NO_HDCP) { + tx_out.hdcp_version = ver; + tx_out.hdcp_encyption = encription; + it6162_tx_hdcp_enable(it6162, &tx_out); + } else { + it6162_tx_hdcp_disable(it6162); + } +} + +static void it6162_tx_enable(struct it6162 *it6162) +{ + if (!it6162->en_hdcp) + it6162->hdcp_version = NO_HDCP; + else + it6162->hdcp_version = it6162->tx_out_set.hdcp_version; + + drm_dbg(it6162->drm, "%s %d", __func__, it6162->hdcp_version); +} + +static void it6162_tx_disable(struct it6162 *it6162) +{ + if (it6162->en_hdcp) { + it6162_tx_hdcp_setup(it6162, NO_HDCP, false); + cancel_delayed_work_sync(&it6162->hdcp_work); + } + drm_dbg(it6162->drm, "%s %d", __func__, it6162->hdcp_version); +} + +static void it6162_show_drm_video_mode(struct it6162 *it6162, + const struct videomode *vm) +{ + struct drm_device *drm = it6162->drm; + + drm_info(drm, "HActive = %u", vm->hactive); + drm_info(drm, "VActive = %u", vm->vactive); + drm_info(drm, "HTotal = %u", + vm->hactive + vm->hfront_porch + vm->hsync_len + + vm->hback_porch); + drm_info(drm, "VTotal = %u", + vm->vactive + vm->vfront_porch + vm->vsync_len + + vm->vback_porch); + drm_info(drm, "PCLK = %lukhz", vm->pixelclock / 1000); + drm_info(drm, "HFP = %u", vm->hfront_porch); + drm_info(drm, "HSW = %u", vm->hsync_len); + drm_info(drm, "HBP = %u", vm->hback_porch); + drm_info(drm, "VFP = %u", vm->vfront_porch); + drm_info(drm, "VSW = %u", vm->vsync_len); + drm_info(drm, "VBP = %u", vm->vback_porch); + if (vm->flags & DISPLAY_FLAGS_HSYNC_HIGH) + drm_info(drm, "HPOL +"); + else + drm_info(drm, "HPOL -"); + if (vm->flags & DISPLAY_FLAGS_VSYNC_HIGH) + drm_info(drm, "VPOL +"); + else + drm_info(drm, "VPOL -"); + if (vm->flags & DISPLAY_FLAGS_INTERLACED) + drm_info(drm, "Intelaced"); + else + drm_info(drm, "Progressive"); +} + +static void it6162_get_video_setting(struct it6162 *it6162, + struct it6162_video *video, + struct videomode *vm, + struct hdmi_avi_infoframe *avi_info) +{ + it6162_show_drm_video_mode(it6162, vm); + + if (vm->flags & DISPLAY_FLAGS_HSYNC_HIGH) + video->hpol = 1; + + if (vm->flags & DISPLAY_FLAGS_VSYNC_HIGH) + video->vpol = 1; + + if (vm->flags & DISPLAY_FLAGS_INTERLACED) + video->prog = false; + + video->clock = vm->pixelclock / 1000; + video->hdew = vm->hactive; + + video->hfp = vm->hfront_porch; + video->hsw = vm->hsync_len; + video->hbp = vm->hback_porch; + video->htotal = vm->hactive + + vm->hfront_porch + + vm->hsync_len + + vm->hback_porch; + + video->vdew = vm->vactive; + video->vfp = vm->vfront_porch; + video->vsw = vm->vsync_len; + video->vbp = vm->vback_porch; + video->vtotal = vm->vactive + + vm->vfront_porch + + vm->vsync_len + + vm->vback_porch; + + video->vic = avi_info->video_code; + + drm_dbg(it6162->drm, "vic %x", video->vic); + + switch (avi_info->picture_aspect) { + case HDMI_PICTURE_ASPECT_4_3: + video->h_aspect = 4; + video->v_aspect = 3; + break; + case HDMI_PICTURE_ASPECT_16_9: + video->h_aspect = 16; + video->v_aspect = 9; + break; + case HDMI_PICTURE_ASPECT_64_27: + video->h_aspect = 64; + video->v_aspect = 27; + break; + case HDMI_PICTURE_ASPECT_256_135: + video->h_aspect = 256; + video->v_aspect = 135; + break; + default: + video->h_aspect = 4; + video->v_aspect = 3; + break; + } + drm_dbg(it6162->drm, "aspect %d:%d", + video->h_aspect, video->v_aspect); + + video->pix_rep = avi_info->pixel_repeat + 1; + drm_dbg(it6162->drm, "pix_rep %d", video->pix_rep); + video->colorspace = avi_info->colorspace; + drm_dbg(it6162->drm, "colorspace %d", video->colorspace); +} + +static void it6162_pack_video_setting(struct it6162 *it6162, + struct it6162_video *video, + struct it6162_infoblock_msg *msg) +{ + msg->action = HOST_SETTING_VIDEO_INFO; + msg->len = 0x1C; + + msg->msg[0x00] = video->hdew & 0xFF; + msg->msg[0x01] = (video->hdew >> 8) & 0x3F; + msg->msg[0x02] = video->vdew & 0xFF; + msg->msg[0x03] = (video->vdew >> 8) & 0x3F; + msg->msg[0x04] = video->clock & 0xFF; + msg->msg[0x05] = (video->clock >> 8) & 0xFF; + msg->msg[0x06] = (video->clock >> 16) & 0xFF; + msg->msg[0x07] = (video->clock >> 24) & 0xFF; + msg->msg[0x08] = video->hfp & 0xFF; + msg->msg[0x09] = (video->hfp >> 8) & 0x3F; + msg->msg[0x0A] = video->hsw & 0xFF; + msg->msg[0x0B] = (video->hsw >> 8) & 0x3F; + msg->msg[0x0C] = video->hbp & 0xFF; + msg->msg[0x0D] = (video->hbp >> 8) & 0x3F; + msg->msg[0x0E] = video->vfp & 0xFF; + msg->msg[0x0F] = (video->vfp >> 8) & 0x3F; + msg->msg[0x10] = video->vsw & 0xFF; + msg->msg[0x11] = (video->vsw >> 8) & 0x3F; + msg->msg[0x12] = video->vbp & 0xFF; + msg->msg[0x13] = (video->vbp >> 8) & 0x3F; + msg->msg[0x14] = (video->prog << 2) | + (video->vpol << 1) | + video->hpol; + msg->msg[0x15] = video->v_aspect; + msg->msg[0x16] = video->h_aspect & 0xFF; + msg->msg[0x17] = video->h_aspect >> 8; + msg->msg[0x18] = video->pix_rep; + msg->msg[0x19] = video->vic; + msg->msg[0x1A] = video->colorspace; + msg->msg[0x1B] = 1; +} + +static void it6162_mipi_set_d2v_video_timing(struct it6162 *it6162, + struct videomode *vm, + struct hdmi_avi_infoframe *avi_info) +{ + struct it6162_video video; + struct it6162_infoblock_msg msg; + + it6162_get_video_setting(it6162, &video, vm, avi_info); + it6162_pack_video_setting(it6162, &video, &msg); + guard(mutex)(&it6162->lock); + it6162_infoblock_write_msg(it6162, &msg); +} + +static int it6162_hdcp_read_infomation(struct it6162 *it6162, + u8 *status, u8 *bksv) +{ + u8 buf[DATA_BUFFER_DEPTH]; + int ret; + + guard(mutex)(&it6162->lock); + ret = it6162_infoblock_request_data(it6162, + HOST_SETTING_HDCP_R, + 0x00, + buf); + + if (ret < 0) + return ret; + + if (status) + memcpy(status, buf, 2); + + if (bksv) + memcpy(bksv, buf + 2, 5); + + return 0; +} + +static int it6162_hdcp_read_list(struct it6162 *it6162, + u8 *ksv_list, + int dev_count) +{ + u8 buf[DATA_BUFFER_DEPTH]; + int i, j, ret; + + if (!ksv_list || dev_count <= 0) + return -EINVAL; + + guard(mutex)(&it6162->lock); + + for (i = 0; i < (dev_count / 6 + 1); i++) { + /* KsV lists */ + ret = it6162_infoblock_request_data(it6162, + HOST_SETTING_HDCP_R, + i + 1, + buf); + + if (ret < 0) + return ret; + + for (j = 0; j < 30; j += 5) { + if ((i * 6 + j / 5) >= dev_count) + break; + + memcpy(&ksv_list[(i * 6 + j / 5) * 5], &buf[j], 5); + } + } + + return dev_count; +} + +static void it6162_update_hdcp14(struct it6162 *it6162) +{ + struct drm_device *drm = it6162->drm; + int dev_count; + u8 bksv[5]; + u8 bstatus[2]; + u8 ksvlist[5 * 30]; + int i, ret; + + ret = it6162_hdcp_read_infomation(it6162, bstatus, bksv); + + if (ret < 0) { + drm_err(drm, "read Bstatus fail!!!!"); + return; + } + drm_dbg(drm, "status = 0x%02X%02X", bstatus[1], bstatus[0]); + + if (DRM_HDCP_MAX_DEVICE_EXCEEDED(bstatus[0]) || + DRM_HDCP_MAX_CASCADE_EXCEEDED(bstatus[1])) { + drm_err(drm, "HDCP14 Max Topology Limit Exceeded"); + return; + } + + dev_count = DRM_HDCP_NUM_DOWNSTREAM(bstatus[0]); + drm_dbg(drm, "dev_count %d", dev_count); + if (dev_count == 0) + return; + + dev_count = dev_count > 30 ? 30 : dev_count; + ret = it6162_hdcp_read_list(it6162, ksvlist, dev_count); + + if (ret < 0) { + drm_err(drm, "read KSV list fail!!!!"); + return; + } + + for (i = 0; i < dev_count; i++) { + drm_info(drm, "KSV %02X %02X %02X %02X %02X", + ksvlist[i * 5 + 0], ksvlist[i * 5 + 1], + ksvlist[i * 5 + 2], ksvlist[i * 5 + 3], + ksvlist[i * 5 + 4]); + } +} + +static void it6162_update_hdcp23(struct it6162 *it6162) +{ + struct drm_device *drm = it6162->drm; + int dev_count; + u8 rxid[5]; + u8 rxinfo[2]; + u8 rxid_list[5 * 30]; + int i, ret; + + ret = it6162_hdcp_read_infomation(it6162, rxinfo, rxid); + + if (ret < 0) { + drm_err(drm, "read Rxinfo fail!!!!"); + return; + } + drm_dbg(drm, "Rxinfo 0x%02X%02X", rxinfo[1], rxinfo[0]); + + if (HDCP_2_2_MAX_CASCADE_EXCEEDED(rxinfo[1]) || + HDCP_2_2_MAX_DEVS_EXCEEDED(rxinfo[1])) { + drm_err(drm, "Topology Max Size Exceeded"); + return; + } + + dev_count = (HDCP_2_2_DEV_COUNT_HI(rxinfo[0]) << 4 | + HDCP_2_2_DEV_COUNT_LO(rxinfo[1])); + + drm_dbg(drm, "dev_count %d", dev_count); + if (dev_count == 0) + return; + + dev_count = dev_count > 30 ? 30 : dev_count; + + ret = it6162_hdcp_read_list(it6162, rxid_list, dev_count); + + if (ret < 0) { + drm_err(drm, "read RxID list fail!!!!"); + return; + } + + for (i = 0; i < dev_count; i++) { + drm_info(drm, "RxID %02X %02X %02X %02X %02X", + rxid_list[i * 5 + 0], rxid_list[i * 5 + 1], + rxid_list[i * 5 + 2], rxid_list[i * 5 + 3], + rxid_list[i * 5 + 4]); + } +} + +static void it6162_update_hdcp(struct it6162 *it6162) +{ + if (it6162->hdcp_version == HDCP_23) + it6162_update_hdcp23(it6162); + else + it6162_update_hdcp14(it6162); +} + +static void it6162_hdcp_handler(struct it6162 *it6162) +{ + unsigned int tx_status, sink_cap; + enum hdcp_state hdcp_sts; + u8 hdcp_ver; + + drm_info(it6162->drm, "%s %d", __func__, it6162->en_hdcp); + if (!it6162->en_hdcp) + return; + + tx_status = it6162_infoblock_read(it6162, OFFSET_TX_STATUS); + sink_cap = it6162_infoblock_read(it6162, OFFSET_SINK_CAP); + drm_info(it6162->drm, "Tx status %x", tx_status); + drm_info(it6162->drm, "SINK capability %x", sink_cap); + + if (it6162->tx_out_set.hdcp_version != NO_HDCP && + it6162->hdcp_version != NO_HDCP) { + hdcp_sts = GET_TX_HDCP_STATUS(tx_status); + hdcp_ver = GET_SINK_CAP_HDCP_VER(sink_cap); + drm_info(it6162->drm, "hdcp %x->%x, ver %x-%x", + it6162->hdcp_sts, hdcp_sts, + it6162->hdcp_version, hdcp_ver); + + if (it6162->hdcp_sts != hdcp_sts || + it6162->hdcp_sts == NO_HDCP_STATE) { + it6162->hdcp_sts = hdcp_sts; + it6162->hdcp_version = hdcp_ver; + switch (hdcp_sts) { + case AUTH_DONE: + drm_info(it6162->drm, "HDCP AUTH DONE"); + it6162_update_hdcp(it6162); + break; + case AUTH_FAIL: + drm_info(it6162->drm, "HDCP AUTH FAIL"); + if (it6162->hdcp_version == HDCP_23) { + it6162->hdcp_version = HDCP_14; + it6162_tx_hdcp_setup(it6162, + it6162->hdcp_version, + true); + } else { + it6162_tx_hdcp_disable(it6162); + } + + break; + default: + drm_info(it6162->drm, "HDCP NO AUTH"); + it6162_tx_hdcp_setup(it6162, + it6162->hdcp_version, + true); + break; + } + } + } +} + +static void it6162_interrupt_handler(struct it6162 *it6162) +{ + unsigned int int_status, tx_status, mipi_status, sink_cap; + enum drm_connector_status connector_status; + + int_status = it6162_infoblock_read(it6162, OFFSET_EVENT_CHG); + it6162_infoblock_write(it6162, OFFSET_EVENT_CHG, 0xFF); + + if (!!GET_BUFFER_STATUS(int_status)) { + drm_info(it6162->drm, "IRQ int_status = %x", int_status); + it6162_infoblock_write(it6162, OFFSET_DRIVER_DATA, int_status); + it6162->data_buf_sts = GET_BUFFER_STATUS(int_status); + } + + if (!(int_status & EVENT_CHG)) + return; + + drm_info(it6162->drm, "evnet change"); + tx_status = it6162_infoblock_read(it6162, OFFSET_TX_STATUS); + drm_info(it6162->drm, "Tx status %x", tx_status); + + mipi_status = it6162_infoblock_read(it6162, OFFSET_MIPI_STATUS); + drm_info(it6162->drm, "MIPI status %x", mipi_status); + + sink_cap = it6162_infoblock_read(it6162, OFFSET_SINK_CAP); + drm_info(it6162->drm, "SINK capability %x", sink_cap); + + connector_status = GET_TX_HPD_STATUS(tx_status) ? + connector_status_connected : + connector_status_disconnected; + + if (it6162->connector_status != connector_status) { + it6162->connector_status = connector_status; + + if (it6162->bridge_hpd_enable) + drm_bridge_hpd_notify(&it6162->bridge, + it6162->connector_status); + } + + if (it6162->en_hdcp && GET_TX_VIDEO_STATUS(tx_status) && + connector_status == connector_status_connected) + queue_delayed_work(system_wq, + &it6162->hdcp_work, + msecs_to_jiffies(2500)); +} + +static bool it6162_wait_devices(struct it6162 *it6162) +{ + struct device *dev = &it6162->it6162_i2c->dev; + unsigned int status, i; + unsigned int regEF; + + for (i = 0; i < 10; i++) { + msleep(200); + regEF = it6162_infoblock_read(it6162, OFFSET_HOST_SETTING); + status = it6162_infoblock_read(it6162, OFFSET_EVENT_CHG); + dev_err(dev, "wait 6162 rdy %x %x %u", status, regEF, i); + + if (status & EVENT_READY) { + dev_info(dev, "IC status %01x", status); + return true; + } + it6162_infoblock_write(it6162, 0x00, 0x00); + } + + dev_err(dev, "-ENODEV %s %x", __func__, status); + return false; +} + +static void it6162_reset_init(struct it6162 *it6162) +{ + it6162_set_default_config(it6162); +} + +static int it6162_platform_set_power(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + int err; + + if (!it6162->ivdd && !it6162->pwr1833 && !it6162->ovdd) + return 0; + + if (it6162->ivdd) { + err = regulator_enable(it6162->ivdd); + if (err) { + dev_err(dev, "Failed to enable IVDD: %d", + err); + return err; + } + } + + if (it6162->pwr1833) { + err = regulator_enable(it6162->pwr1833); + if (err) { + dev_err(dev, "Failed to enable VDD1833: %d", + err); + return err; + } + } + + if (it6162->ovdd) { + err = regulator_enable(it6162->ovdd); + if (err) + return err; + } + + if (it6162->gpiod_reset) { + usleep_range(10000, 20000); + gpiod_set_value_cansleep(it6162->gpiod_reset, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(it6162->gpiod_reset, 0); + usleep_range(10000, 20000); + } + + return 0; +} + +static int it6162_platform_clear_power(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + int err; + + if (!it6162->ivdd && !it6162->pwr1833 && !it6162->ovdd) + return 0; + + if (it6162->ivdd) { + err = regulator_disable(it6162->ivdd); + if (err) { + dev_err(dev, "Failed to disable IVDD: %d", err); + return err; + } + usleep_range(2000, 3000); + } + + if (it6162->pwr1833) { + err = regulator_disable(it6162->pwr1833); + if (err) { + dev_err(dev, "Failed to disable VDD1833: %d", err); + return err; + } + } + + if (it6162->ovdd) { + err = regulator_disable(it6162->ovdd); + if (err) + return err; + } + return 0; +} + +static int it6162_detect_devices(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + const struct it6162_chip_info *chip_info; + u32 chip_id, version; + u8 buf[6]; + + it6162_platform_set_power(it6162); + + if (!it6162_wait_devices(it6162)) + return -ENODEV; + + chip_info = of_device_get_match_data(dev); + + it6162_infoblock_bulk_read(it6162, OFFSET_CHIP_ID_L, &buf[0], 6); + dev_info(dev, "chip id %02x %02x %02X", buf[0], buf[1], buf[2]); + dev_info(dev, "chip VER %02x %02x %02x", buf[3], buf[4], buf[5]); + + chip_id = (buf[0] << 16) | (buf[1] << 8) | (buf[2]); + version = (buf[3] << 16) | (buf[4] << 8) | (buf[5]); + dev_info(dev, "chip id 0x%06x, version 0x%06x", chip_id, version); + + if (chip_id != chip_info->chip_id || version < chip_info->version) { + dev_err(dev, "chip_id 0x%06x != 0x%06x", + chip_id, chip_info->chip_id); + dev_err(dev, "version 0x%06x != 0x%06x", + version, chip_info->version); + + return -ENODEV; + } + + it6162->chip_info.chip_id = chip_info->chip_id; + it6162->chip_info.version = chip_info->version; + + return 0; +} + +static int __maybe_unused it6162_poweron(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + int err; + + dev_dbg(dev, "powered on Start"); + if (!it6162->power_on) { + err = it6162_platform_set_power(it6162); + if (err < 0) + return err; + /*wait for info block ready after power-on-rest*/ + if (!it6162_wait_devices(it6162)) + return -ENODEV; + } + + it6162->connector_status = connector_status_disconnected; + it6162_reset_init(it6162); + + enable_irq(it6162->it6162_i2c->irq); + dev_dbg(dev, "enable irq %d", + it6162->it6162_i2c->irq); + + it6162->power_on = true; + dev_info(dev, "it6162 poweron end"); + return 0; +} + +static int __maybe_unused it6162_poweroff(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + int err; + + dev_dbg(dev, "powered off start"); + disable_irq(it6162->it6162_i2c->irq); + dev_dbg(dev, "disable irq %d", it6162->it6162_i2c->irq); + + if (it6162->power_on) { + err = it6162_platform_clear_power(it6162); + if (err < 0) + return err; + } + + it6162->power_on = false; + dev_dbg(dev, "it6162 poweroff"); + return 0; +} + +static void it6162_config_default(struct it6162 *it6162) +{ + struct it6162_mipi_cfg *mipi_cfg = &it6162->mipi_cfg; + struct it6162_audio *audio_config = &it6162->audio_config; + + mipi_cfg->lane_num = 4; + mipi_cfg->pn_swap = false; + mipi_cfg->lane_swap = false; + mipi_cfg->en_port[0] = false; + mipi_cfg->en_port[1] = false; + mipi_cfg->continuous_clk = true; + mipi_cfg->mode = SYNC_EVENT; + mipi_cfg->format = MIPI_DSI_FMT_RGB888; + mipi_cfg->mode_flags = MIPI_DSI_MODE_VIDEO; + + audio_config->select = I2S; + audio_config->sample_rate = SAMPLE_RATE_48K; + audio_config->type = LPCM; + audio_config->sample_width = WORD_LENGTH_16BIT; + audio_config->channel_number = 2; + + it6162->connector_status = connector_status_disconnected; +} + +static enum drm_connector_status it6162_detect(struct it6162 *it6162) +{ + unsigned int tx_status; + + tx_status = it6162_infoblock_read(it6162, OFFSET_TX_STATUS); + it6162->connector_status = GET_TX_HPD_STATUS(tx_status) ? + connector_status_connected : + connector_status_disconnected; + + drm_dbg(it6162->drm, "%s connector_status %x", __func__, + it6162->connector_status); + return it6162->connector_status; +} + +static int it6162_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + struct it6162 *it6162 = data; + unsigned int cnt; + unsigned int i; + u8 config; + + if (len > EDID_LENGTH) + return -EINVAL; + + guard(mutex)(&it6162->lock); + + cnt = 0; + for (i = 0; i < EDID_LENGTH; i += DATA_BUFFER_DEPTH, cnt++) { + config = (block << 2) | (cnt); + if (it6162_infoblock_request_data(it6162, + HOST_SETTING_EDID_R, + config, buf + i) < 0) + return -EIO; + } + + return 0; +} + +static void it6162_enable_audio(struct it6162 *it6162) +{ + struct it6162_audio *config = &it6162->audio_config; + + guard(mutex)(&it6162->lock); + it6162_infoblock_write(it6162, 0x3D, config->sample_rate); + it6162_infoblock_write(it6162, 0x3C, (config->channel_number) | + (config->select << 4) | + (config->type << 6)); + //Standard i2s 16bit, 24bit + it6162_infoblock_write(it6162, 0x3E, 0x80 | + config->sample_width << 5); + + it6162_infoblock_host_set(it6162, HOST_SETTING_AUDIO_INFO); +} + +static void it6162_disable_audio(struct it6162 *it6162) +{ + guard(mutex)(&it6162->lock); + it6162_infoblock_write(it6162, 0x3C, 0x00); + it6162_infoblock_host_set(it6162, HOST_SETTING_AUDIO_INFO); +} + +static irqreturn_t it6162_int_threaded_handler(int unused, void *data) +{ + struct it6162 *it6162 = data; + + it6162_interrupt_handler(it6162); + + return IRQ_HANDLED; +} + +static int it6162_audio_update_hw_params(struct it6162 *it6162, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct it6162_audio *config = &it6162->audio_config; + + hdmi_audio_infoframe_pack(&hparms->cea, &config->infoframe, + sizeof(config->infoframe)); + + memcpy(config->channel_status, &hparms->iec.status[0], + AES_IEC958_STATUS_SIZE); + + config->channel_number = hparms->channels; + + switch (hparms->sample_rate) { + case 32000: + config->sample_rate = SAMPLE_RATE_32K; + break; + case 44100: + config->sample_rate = SAMPLE_RATE_44_1K; + break; + case 48000: + config->sample_rate = SAMPLE_RATE_48K; + break; + case 88200: + config->sample_rate = SAMPLE_RATE_88_2K; + break; + case 96000: + config->sample_rate = SAMPLE_RATE_96K; + break; + case 176400: + config->sample_rate = SAMPLE_RATE_176_4K; + break; + case 192000: + config->sample_rate = SAMPLE_RATE_192K; + break; + default: + return -EINVAL; + } + + switch (hparms->sample_width) { + case 16: + config->sample_width = WORD_LENGTH_16BIT; + break; + case 24: + config->sample_width = WORD_LENGTH_18BIT; + break; + case 20: + config->sample_width = WORD_LENGTH_24BIT; + break; + default: + return -EINVAL; + } + + switch (fmt->fmt) { + case HDMI_I2S: + config->select = I2S; + break; + case HDMI_SPDIF: + config->select = SPDIF; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void it6162_hdcp_work(struct work_struct *work) +{ + struct it6162 *it6162 = container_of(work, + struct it6162, + hdcp_work.work); + + it6162_hdcp_handler(it6162); +} + +static struct mipi_dsi_host *it6162_of_get_dsi_host_by_port(struct it6162 *it6162, + int port) +{ + struct device_node *of = it6162->dev->of_node; + struct device_node *host_node; + struct device_node *endpoint; + struct mipi_dsi_host *dsi_host; + + endpoint = of_graph_get_endpoint_by_regs(of, port, -1); + if (!endpoint) + return ERR_PTR(-ENODEV); + + host_node = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!host_node) + return ERR_PTR(-ENODEV); + + dsi_host = of_find_mipi_dsi_host_by_node(host_node); + of_node_put(host_node); + + if (IS_ERR_OR_NULL(dsi_host)) + return ERR_PTR(-EPROBE_DEFER); + + return dsi_host; +} + +static int it6162_of_get_dsi_host(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + struct mipi_dsi_host *dsi_host; + int port, host_count = 0; + + for (port = 0; port < 2; port++) { + dsi_host = it6162_of_get_dsi_host_by_port(it6162, port); + + if (PTR_ERR(dsi_host) == -EPROBE_DEFER) { + dev_info(dev, + "DSI host for port %d not ready, defer probe", + port); + return -EPROBE_DEFER; + } + + if (IS_ERR(dsi_host)) { + dev_info(dev, "DSI host for port %d not found", port); + continue; + } + + host_count++; + } + + dev_info(dev, "%s = %d", __func__, host_count); + if (host_count == 0) + return -ENODEV; + + return 0; +} + +static void it6162_load_mipi_pars_from_port(struct it6162 *it6162, int port) +{ + struct device_node *of = it6162->dev->of_node; + struct device_node *endpoint; + struct device *dev = it6162->dev; + struct it6162_mipi_cfg *mipicfg = &it6162->mipi_cfg; + int dsi_lanes; + + endpoint = of_graph_get_endpoint_by_regs(of, port, -1); + + if (!endpoint) + return; + + dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4); + + if (dsi_lanes <= 0) + mipicfg->lane_num = 4; + else + mipicfg->lane_num = dsi_lanes; + + mipicfg->pn_swap = of_property_present(endpoint, + "ite,mipi-dsi-phy-pn-swap"); + mipicfg->lane_swap = of_property_present(endpoint, + "ite,mipi-dsi-phy-link-swap"); + + if (of_property_present(endpoint, "ite,mipi-dsi-mode-video-sync-pulse")) { + mipicfg->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + mipicfg->mode = SYNC_PULSE; + } + + if (of_property_present(endpoint, "ite,mipi-dsi-clock-non-continous")) { + mipicfg->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS; + mipicfg->continuous_clk = false; + } + + of_node_put(endpoint); + + dev_info(dev, "lanes: %d pn_swap: %d, lane_swap: %d, mode_flags: %lu", + mipicfg->lane_num, mipicfg->pn_swap, + mipicfg->lane_swap, mipicfg->mode_flags); +} + +static int it6162_attach_dsi(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + struct device_node *np = dev->of_node; + const struct mipi_dsi_device_info info = {"it6162-mipi", 0, np}; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *dsi_host; + struct it6162_mipi_cfg *mipi_cfg = &it6162->mipi_cfg; + int ret = 0; + + for (int port = 0; port < 2; port++) { + dsi_host = it6162_of_get_dsi_host_by_port(it6162, port); + if (IS_ERR(dsi_host)) + continue; + + mipi_cfg->en_port[port] = true; + + if (!it6162->dsi) { + dev_info(dev, "DSI host loaded paras for port %d", port); + it6162->dsi = dsi; + it6162_load_mipi_pars_from_port(it6162, port); + } + + dsi = devm_mipi_dsi_device_register_full(dev, dsi_host, &info); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to create dsi device"); + return PTR_ERR(dsi); + } + + dsi->lanes = mipi_cfg->lane_num; + dsi->format = mipi_cfg->format; + dsi->mode_flags = mipi_cfg->mode_flags; + dev_info(dev, "dsi lanes %d, format %d, mode_flags %lu", + dsi->lanes, dsi->format, dsi->mode_flags); + ret = devm_mipi_dsi_attach(dev, dsi); + + if (ret) { + dev_err(dev, "failed to attach dsi device %d", port); + return ret; + } + } + + it6162_poweron(it6162); + return 0; +} + +static unsigned int it6162_parse_dt(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + struct device_node *np = dev->of_node; + struct device_node *audio_port; + u16 reg_val; + int ret; + + /* get audio port*/ + audio_port = of_graph_get_port_by_id(np, 2); + if (audio_port) { + it6162->support_audio = true; + of_node_put(audio_port); + } + + /* get hdcp support*/ + ret = of_property_read_u16(np, "ite,hdcp-version", ®_val); + if (ret == 0) { + it6162->en_hdcp = true; + it6162->hdcp_version = reg_val; + dev_info(dev, "HDCP version %x", it6162->hdcp_version); + } else { + it6162->en_hdcp = false; + dev_info(dev, "HDCP not supported"); + } + + return 0; +} + +static int it6162_init_pdata(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + + it6162->ivdd = devm_regulator_get(dev, "ivdd"); + if (IS_ERR(it6162->ivdd)) { + dev_info(dev, "ivdd regulator not found"); + it6162->ivdd = NULL; + } + + it6162->pwr1833 = devm_regulator_get(dev, "ovdd1833"); + if (IS_ERR(it6162->pwr1833)) { + dev_info(dev, "pwr1833 regulator not found"); + it6162->pwr1833 = NULL; + } + + it6162->ovdd = devm_regulator_get(dev, "ovdd"); + if (IS_ERR(it6162->ovdd)) { + dev_info(dev, "ovdd regulator not found"); + it6162->ovdd = NULL; + } + + it6162->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(it6162->gpiod_reset)) { + dev_info(dev, "reset gpio not found"); + it6162->gpiod_reset = NULL; + } + + return 0; +} + +static int it6162_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + struct drm_device *drm = bridge->dev; + + it6162->drm = drm; + + if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { + drm_dbg(drm, + "it6162 driver only copes with atomic updates"); + return -EOPNOTSUPP; + } + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + struct drm_connector *connector; + int ret; + + connector = drm_bridge_connector_init(drm, bridge->encoder); + if (IS_ERR(connector)) { + drm_dbg(drm, "Unable to create bridge connector"); + return PTR_ERR(connector); + } + + ret = drm_connector_attach_encoder(connector, bridge->encoder); + if (ret < 0) + return ret; + } + + return 0; +} + +static enum drm_mode_status + it6162_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->clock > 300000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static enum drm_connector_status + it6162_bridge_detect(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162->power_on ? it6162_detect(it6162) : + connector_status_disconnected; +} + +static void it6162_bridge_hpd_enable(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + it6162->bridge_hpd_enable = true; +} + +static void it6162_bridge_hpd_disable(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + it6162->bridge_hpd_enable = false; +} + +static void it6162_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_state *state) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + struct drm_display_mode *mode; + struct drm_connector *connector; + struct videomode vm; + struct hdmi_avi_infoframe avi_info; + int ret; + + drm_dbg(it6162->drm, "it6162_bridge_atomic_enable"); + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + + if (!connector) + return; + + it6162->connector = *connector; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + mode = &crtc_state->mode; + + it6162->is_hdmi = connector->display_info.is_hdmi; + it6162->has_audio = connector->display_info.has_audio; + + drm_dbg(it6162->drm, "%s mode, monitor %ssupport audio", + it6162->is_hdmi ? "HDMI" : "DVI", + it6162->has_audio ? "" : "not "); + + if (it6162->is_hdmi) { + ret = drm_hdmi_avi_infoframe_from_display_mode(&avi_info, + connector, + mode); + if (ret) + drm_err(it6162->drm, "Failed to setup AVI infoframe: %d", ret); + } + + drm_display_mode_to_videomode(mode, &vm); + + it6162_mipi_set_d2v_video_timing(it6162, &vm, &avi_info); + + it6162_tx_enable(it6162); + it6162_mipi_enable(it6162); +} + +static int it6162_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *adj = &crtc_state->adjusted_mode; + struct drm_display_mode *mode = &crtc_state->mode; + struct videomode vm; + struct it6162 *it6162 = bridge_to_it6162(bridge); + u32 hfp, hsw, hbp; + u32 clock; + u32 hfp_check; + + drm_display_mode_to_videomode(mode, &vm); + clock = vm.pixelclock / 1000; + + hfp = vm.hfront_porch; + hsw = vm.hsync_len; + hbp = vm.hback_porch; + + hfp_check = DIV_ROUND_UP(65 * clock, 1000000) + 4; + if (hfp >= hfp_check) + return 0; + + drm_dbg(it6162->drm, "hfp_check %d", hfp_check); + if (hbp > hfp_check - hfp) { + adj->hsync_start = adj->hdisplay + hfp_check; + adj->hsync_end = adj->hsync_start + hsw; + } + + return 0; +} + +static void it6162_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_state *state) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + drm_dbg(it6162->drm, "it6162_bridge_atomic_disable"); + it6162_tx_disable(it6162); + it6162_mipi_disable(it6162); +} + +static const struct drm_edid + *it6162_bridge_read_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + const struct drm_edid *edid; + + drm_dbg(it6162->drm, "it6162_bridge_read_edid"); + edid = drm_edid_read_custom(connector, it6162_get_edid_block, it6162); + if (!edid) { + drm_err(it6162->drm, "failed to read EDID"); + return 0; + } + + return edid; +} + +static int it6162_bridge_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *params) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + it6162_audio_update_hw_params(it6162, fmt, params); + + return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, + ¶ms->cea); +} + +static int it6162_bridge_hdmi_audio_startup(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + it6162_enable_audio(it6162); + return 0; +} + +static void it6162_bridge_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + it6162_disable_audio(it6162); +} + +static const struct drm_bridge_funcs it6162_bridge_funcs = { + .attach = it6162_bridge_attach, + .mode_valid = it6162_bridge_mode_valid, + .detect = it6162_bridge_detect, + .hpd_enable = it6162_bridge_hpd_enable, + .hpd_disable = it6162_bridge_hpd_disable, + + .atomic_enable = it6162_bridge_atomic_enable, + .atomic_disable = it6162_bridge_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_check = it6162_bridge_atomic_check, + + .edid_read = it6162_bridge_read_edid, + + .hdmi_audio_prepare = it6162_bridge_hdmi_audio_prepare, + .hdmi_audio_startup = it6162_bridge_hdmi_audio_startup, + .hdmi_audio_shutdown = it6162_bridge_hdmi_audio_shutdown, +}; + +static int it6162_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; + struct it6162 *it6162; + int ret; + + it6162 = devm_drm_bridge_alloc(dev, struct it6162, bridge, + &it6162_bridge_funcs); + if (IS_ERR(it6162)) + return PTR_ERR(it6162); + + it6162->dev = dev; + + ret = it6162_of_get_dsi_host(it6162); + if (ret < 0) + return ret; + + ret = it6162_i2c_regmap_init(client, it6162); + if (ret != 0) + return ret; + + ret = it6162_init_pdata(it6162); + if (ret) { + dev_err(dev, "Failed to initialize pdata: %d", ret); + return ret; + } + + it6162_config_default(it6162); + it6162_parse_dt(it6162); + + if (it6162_detect_devices(it6162) < 0) + return -ENODEV; + + if (!client->irq) { + dev_err(dev, "Failed to get INTP IRQ"); + return -ENODEV; + } + + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + it6162_int_threaded_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT | + IRQF_NO_AUTOEN, + "it6162-intp", it6162); + if (ret) { + dev_err(dev, "Failed to request INTP threaded IRQ: %d", ret); + return ret; + } + + INIT_DELAYED_WORK(&it6162->hdcp_work, it6162_hdcp_work); + + mutex_init(&it6162->lock); + + it6162->bridge.of_node = np; + it6162->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_MODES; + + it6162->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + it6162->bridge.vendor = "ITE"; + it6162->bridge.product = "IT6162"; + + if (it6162->support_audio) { + /* enable audio support */ + it6162->bridge.ops |= DRM_BRIDGE_OP_HDMI_AUDIO; + it6162->bridge.hdmi_audio_dev = dev; + it6162->bridge.hdmi_audio_max_i2s_playback_channels = 8; + it6162->bridge.hdmi_audio_dai_port = 2; + } + + devm_drm_bridge_add(dev, &it6162->bridge); + + return it6162_attach_dsi(it6162); +} + +static void it6162_remove(struct i2c_client *client) +{ + struct it6162 *it6162 = i2c_get_clientdata(client); + + disable_irq(client->irq); + cancel_delayed_work_sync(&it6162->hdcp_work); + mutex_destroy(&it6162->lock); +} + +static const struct it6162_chip_info it6162_chip_info = { + .chip_id = 0x616200, + .version = 0x006500, +}; + +static const struct of_device_id it6162_dt_ids[] = { + { .compatible = "ite,it6162", .data = &it6162_chip_info}, + { } +}; +MODULE_DEVICE_TABLE(of, it6162_dt_ids); + +static const struct i2c_device_id it6162_i2c_ids[] = { + { "it6162", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, it6162_i2c_ids); + +static struct i2c_driver it6162_driver = { + .driver = { + .name = "it6162", + .of_match_table = it6162_dt_ids, + }, + .probe = it6162_probe, + .remove = it6162_remove, + .id_table = it6162_i2c_ids, +}; + +module_i2c_driver(it6162_driver); + +MODULE_AUTHOR("Pet Weng <pet.weng@ite.com.tw>"); +MODULE_AUTHOR("Hermes Wu <Hermes.Wu@ite.com.tw>"); +MODULE_DESCRIPTION("it6162 mipi to hdmi driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Claude review: drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver 2026-02-23 9:20 ` [PATCH 1/3] drm/bridge: " Hermes Wu via B4 Relay @ 2026-02-24 0:21 ` Claude Code Review Bot 0 siblings, 0 replies; 11+ messages in thread From: Claude Code Review Bot @ 2026-02-24 0:21 UTC (permalink / raw) To: dri-devel-reviews Patch Review This is the main driver patch. There are a number of issues, ranging from outright bugs to things worth cleaning up. **License header contradiction:** > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License as published by the > + * Free Software Foundation; either version 2 of the License, or (at your > + * option) any later version. The SPDX identifier says `GPL-2.0-only` but the boilerplate text says "version 2 of the License, or (at your option) any later version", which is `GPL-2.0-or-later`. These contradict each other. The SPDX identifier is what tools parse, so whichever is intended, the other should be made consistent. Probably simplest to just use the SPDX line and remove the boilerplate text entirely. **Duplicate register offset definition:** > +#define OFFSET_VERSION_L 0x03 > +#define OFFSET_VERSION_M 0x04 > +#define OFFSET_VERSION_H 0x03 `OFFSET_VERSION_L` and `OFFSET_VERSION_H` are both defined as 0x03. Given the bulk read of 6 bytes starting at 0x00 and the pattern of the chip ID offsets (0x00, 0x01, 0x02), `OFFSET_VERSION_H` should presumably be 0x05. Neither macro is actually used directly (the code does a bulk read), but this should still be fixed to avoid confusion. **`it6162_infoblock_read` return type mismatch:** > +static unsigned int it6162_infoblock_read(struct it6162 *it6162, > + unsigned int reg) > +{ > + ... > + if (err < 0) { > + dev_err(dev, "read failed rx reg[0x%x] err: %d", reg, err); > + return err; > + } This function returns `unsigned int` but returns a negative error code on failure. The negative value gets implicitly converted to a large unsigned value. Every caller treats the result as a valid register value -- for example in `it6162_interrupt_handler`, `it6162_detect`, `it6162_hdcp_handler`, etc. If the I2C read fails, all the bit-field extractions will produce garbage results silently. The function should either return `int` and have callers check for errors, or use an output parameter pattern like `regmap_read` does. **Uninitialized variable in `it6162_attach_dsi` -- this is a clear bug:** > + struct mipi_dsi_device *dsi; > + ... > + for (int port = 0; port < 2; port++) { > + dsi_host = it6162_of_get_dsi_host_by_port(it6162, port); > + if (IS_ERR(dsi_host)) > + continue; > + > + mipi_cfg->en_port[port] = true; > + > + if (!it6162->dsi) { > + dev_info(dev, "DSI host loaded paras for port %d", port); > + it6162->dsi = dsi; > + it6162_load_mipi_pars_from_port(it6162, port); > + } > + > + dsi = devm_mipi_dsi_device_register_full(dev, dsi_host, &info); On the first loop iteration (port 0), `dsi` is uninitialized when `it6162->dsi = dsi` executes. The `dsi` variable is only assigned a meaningful value by `devm_mipi_dsi_device_register_full` *after* this block. This stores a garbage pointer in `it6162->dsi`. It looks like the intent was for `it6162->dsi` to point to the first successfully registered DSI device, so the assignment should be moved after the register call. **Shallow copy of `struct drm_connector`:** > +static void it6162_bridge_atomic_enable(struct drm_bridge *bridge, > + struct drm_atomic_state *state) > +{ > + ... > + it6162->connector = *connector; This performs a shallow copy of the entire `struct drm_connector`, which contains mutexes, list heads, refcounted pointers, and many other fields that must not be copied. The driver should store a pointer to the connector instead, or better yet, just use the local `connector` pointer throughout this function since it doesn't appear to be needed outside of `atomic_enable`. **Uninitialized `avi_info` passed to timing function:** > + struct hdmi_avi_infoframe avi_info; > + ... > + if (it6162->is_hdmi) { > + ret = drm_hdmi_avi_infoframe_from_display_mode(&avi_info, > + connector, > + mode); > + if (ret) > + drm_err(it6162->drm, "Failed to setup AVI infoframe: %d", ret); > + } > + > + ... > + it6162_mipi_set_d2v_video_timing(it6162, &vm, &avi_info); When connecting to a DVI monitor (`is_hdmi` is false), `avi_info` is never initialized but is still passed to `it6162_mipi_set_d2v_video_timing`, which reads `avi_info->video_code`, `avi_info->picture_aspect`, `avi_info->pixel_repeat`, and `avi_info->colorspace`. This reads uninitialized stack data. The struct should be zeroed at declaration or the DVI case should be handled separately. **Missing `DRM_BRIDGE_OP_HPD`:** > + it6162->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | > + DRM_BRIDGE_OP_MODES; The driver implements `hpd_enable`, `hpd_disable` callbacks and calls `drm_bridge_hpd_notify` from its interrupt handler, but does not set `DRM_BRIDGE_OP_HPD` in the bridge ops. Without this flag, the DRM core will not call `hpd_enable`/`hpd_disable` and the HPD notification path is effectively dead code. `DRM_BRIDGE_OP_HPD` needs to be added. **Swapped sample width mappings:** > + switch (hparms->sample_width) { > + case 16: > + config->sample_width = WORD_LENGTH_16BIT; > + break; > + case 24: > + config->sample_width = WORD_LENGTH_18BIT; > + break; > + case 20: > + config->sample_width = WORD_LENGTH_24BIT; > + break; Is this intentional? 24-bit sample width maps to `WORD_LENGTH_18BIT` (0x1) and 20-bit maps to `WORD_LENGTH_24BIT` (0x3). This looks like the 20 and 24 cases are swapped. **Regulator error handling leaks enables:** > +static int it6162_platform_set_power(struct it6162 *it6162) > +{ > + ... > + if (it6162->ivdd) { > + err = regulator_enable(it6162->ivdd); > + if (err) { ... return err; } > + } > + if (it6162->pwr1833) { > + err = regulator_enable(it6162->pwr1833); > + if (err) { ... return err; } > + } > + if (it6162->ovdd) { > + err = regulator_enable(it6162->ovdd); > + if (err) > + return err; > + } If `pwr1833` enable fails, `ivdd` is left enabled. If `ovdd` enable fails, both `ivdd` and `pwr1833` are left enabled. Error paths should disable any regulators that were successfully enabled. **Regulator acquisition silently NULLs errors:** > + it6162->ivdd = devm_regulator_get(dev, "ivdd"); > + if (IS_ERR(it6162->ivdd)) { > + dev_info(dev, "ivdd regulator not found"); > + it6162->ivdd = NULL; > + } Since the DT binding lists `ivdd-supply`, `ovdd-supply`, and `ovdd1833-supply` as properties (not required, but present in the example), silently NULLing them hides probe-ordering issues. Consider using `devm_regulator_get_optional` if they're truly optional, or make them required and propagate errors (especially `-EPROBE_DEFER`). **`it6162_infoblock_host_set` ignores error from wait:** > +static int it6162_infoblock_host_set(struct it6162 *it6162, u8 setting) > +{ > + dev_info(it6162->dev, "%s %x", __func__, setting); > + it6162_infoblock_write(it6162, OFFSET_HOST_SETTING, setting); > + /*wait command complete*/ > + it6162_infoblock_wait_complete(it6162); > + > + return 0; > +} The return value of `it6162_infoblock_wait_complete` is ignored; the function always returns 0. Callers of `it6162_infoblock_host_set` also ignore its return value, so timeouts communicating with the firmware are silently swallowed. **`dev_info` noise:** The driver has many `dev_info`/`drm_info` calls for routine operations (entering functions, interrupt status, etc.). These should be `dev_dbg`/`drm_dbg` since they'll spam the kernel log during normal operation. For example: > + drm_info(it6162->drm, "evnet change"); Also a typo: "evnet" should be "event". > + dev_err(dev, "wait 6162 rdy %x %x %u", status, regEF, i); This uses `dev_err` for what is a normal wait loop iteration. Should be `dev_dbg`. **`it6162_bridge_read_edid` returns 0 instead of NULL:** > + if (!edid) { > + drm_err(it6162->drm, "failed to read EDID"); > + return 0; > + } The function returns `const struct drm_edid *`, so returning 0 works (it's NULL), but returning `NULL` explicitly would be clearer. **Vendor-specific DT properties used but not in binding:** The driver reads several properties from device tree endpoints (`ite,mipi-dsi-phy-pn-swap`, `ite,mipi-dsi-phy-link-swap`, `ite,mipi-dsi-mode-video-sync-pulse`, `ite,mipi-dsi-clock-non-continous`) and a node property (`ite,hdcp-version`), none of which are documented in the YAML binding. These need to be added to the binding, or preferably, standard DSI properties should be used where they exist (e.g., `clock-non-continuous` is a standard MIPI DSI property). **`__maybe_unused` on `it6162_poweron`/`it6162_poweroff`:** These functions are marked `__maybe_unused` but `it6162_poweron` is called directly from `it6162_attach_dsi`. The `__maybe_unused` annotation suggests these were intended for PM callbacks that aren't wired up. `it6162_poweroff` is indeed never called (the remove function doesn't call it), which means regulators are never disabled on removal. **`it6162_parse_dt` return type mismatch:** > +static unsigned int it6162_parse_dt(struct it6162 *it6162) Returns `unsigned int` but always returns 0 and the caller doesn't check the return value anyway. Should be `void` or `int`. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 2/3] dt-bindings: display: bridge: Add ITE IT6162 MIPI DSI to HDMI bridge 2026-02-23 9:20 [PATCH 0/3] Add ITE IT6162 MIPI DSI to HDMI bridge driver Hermes Wu via B4 Relay 2026-02-23 9:20 ` [PATCH 1/3] drm/bridge: " Hermes Wu via B4 Relay @ 2026-02-23 9:20 ` Hermes Wu via B4 Relay 2026-02-24 0:21 ` Claude review: " Claude Code Review Bot 2026-02-23 9:20 ` [PATCH 3/3] MAINTAINERS: Add entry for ITE IT6162 MIPI DSI to HDMI bridge driver Hermes Wu via B4 Relay 2026-02-24 0:21 ` Claude review: Add " Claude Code Review Bot 3 siblings, 1 reply; 11+ messages in thread From: Hermes Wu via B4 Relay @ 2026-02-23 9:20 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: Pet.Weng, Kenneth.Hung, Hermes Wu, dri-devel, devicetree, linux-kernel, Hermes Wu From: Hermes Wu <Hermes.wu@ite.com.tw> Add device tree binding documentation for the ITE IT6162 MIPI DSI to HDMI 2.0 bridge chip. The IT6162 is an I2C-controlled bridge that supports the following configurations: - Single MIPI DSI input: up to 4K @ 30Hz - Dual MIPI DSI input (combined): up to 4K @ 60Hz The chip also supports up to 8-channel audio output via 4 I2S data channels. Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw> --- .../bindings/display/bridge/ite,it6162.yaml | 156 +++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml b/Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml new file mode 100644 index 0000000000000000000000000000000000000000..31f809a83c305447a152e14b20cb39ef1f816911 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml @@ -0,0 +1,156 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/bridge/ite,it6162.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ITE IT6162 MIPI DSI to HDMI2.0 Bridge + +maintainers: + - Hermes Wu <Hermes.Wu@ite.com.tw> + +description: | + The ITE IT6162 is a high-performance, low-power HDMI bridge that converts + 2 MIPI DSI signal to 1 HDMI2.0. It supports 2 MIPI D-PHY 2.0 up to 10Gbps + each DSI port (20Gbps total), compatible with DSI-2 v2.0. + + The HDMI transmitter side supports up to 4Kx2K@60Hz resolutions, and is + compliant with HDMI2.0. + + For audio, the IT61620 supports up to 8-channel LPCM via I2S (multi-line or + TDM mode), with optional S/PDIF or DSD (for SACD). It supports audio + sampling rates up to 192kHz. + +allOf: + - $ref: /schemas/sound/dai-common.yaml# + +properties: + compatible: + const: ite,it6162 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + ivdd-supply: + description: core voltage + + ovdd-supply: + description: I/O voltage + + ovdd1833-supply: + description: flexible I/O voltage + + "#sound-dai-cells": + const: 0 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: Input port for MIPI DSI-0 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + required: + - data-lanes + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: Input port for MIPI DSI-1 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + required: + - data-lanes + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: Audio input port (I2S) + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: Output port for HDMI output + + required: + - port@1 + +required: + - compatible + - reg + - interrupts + - ports + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + bridge@58 { + compatible = "ite,it6162"; + reg = <0x58>; + #sound-dai-cells = <0>; + interrupt-parent = <&pio>; + interrupts = <128 IRQ_TYPE_LEVEL_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&it6162_pins>; + reset-gpios = <&pio 127 GPIO_ACTIVE_LOW>; + ivdd-supply = <&pp1000_hdmi_x>; + ovdd-supply = <&pp3300_vio28_x>; + ovdd1833-supply = <&pp1800_vcamio_x>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + it6162_dsi0: endpoint { + data-lanes = <0 1 2 3>; + remote-endpoint = <&dsi_0_out>; + }; + }; + + port@1 { + reg = <1>; + it6162_dsi1: endpoint { + data-lanes = <0 1 2 3>; + remote-endpoint = <&dsi_1_out>; + }; + }; + + port@2 { + reg = <2>; + it6162_audio_in: endpoint { + remote-endpoint = <&i2s0_out>; + }; + }; + + port@3 { + reg = <3>; + it6162_hdmi_out: endpoint { + remote-endpoint = <&hdmi_connector_in>; + }; + }; + }; + }; + }; -- 2.34.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Claude review: dt-bindings: display: bridge: Add ITE IT6162 MIPI DSI to HDMI bridge 2026-02-23 9:20 ` [PATCH 2/3] dt-bindings: display: bridge: Add ITE IT6162 MIPI DSI to HDMI bridge Hermes Wu via B4 Relay @ 2026-02-24 0:21 ` Claude Code Review Bot 0 siblings, 0 replies; 11+ messages in thread From: Claude Code Review Bot @ 2026-02-24 0:21 UTC (permalink / raw) To: dri-devel-reviews Patch Review > + For audio, the IT61620 supports up to 8-channel LPCM via I2S Minor: "IT61620" should be "IT6162" -- appears to be a typo in the description. > + required: > + - port@1 Only `port@1` (the second DSI input) is required, not `port@0`. This means a board could have only the second DSI port connected without the first, which seems odd. Should `port@0` also be required, or at least have a note explaining why only port@1 is mandatory? Looking at the driver, it iterates ports 0 and 1 and enables whichever are present, so requiring only port@1 seems like an arbitrary choice that should at least be documented. The binding is missing several vendor-specific properties used by the driver: - `ite,mipi-dsi-phy-pn-swap` - `ite,mipi-dsi-phy-link-swap` - `ite,mipi-dsi-mode-video-sync-pulse` - `ite,mipi-dsi-clock-non-continous` - `ite,hdcp-version` With `unevaluatedProperties: false`, any DT using these undocumented properties would fail schema validation. The supply properties (`ivdd-supply`, `ovdd-supply`, `ovdd1833-supply`) are documented but not listed as required, and the driver silently ignores their absence. This is fine if they're truly optional, but the example includes all three which suggests they are normally needed. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 3/3] MAINTAINERS: Add entry for ITE IT6162 MIPI DSI to HDMI bridge driver 2026-02-23 9:20 [PATCH 0/3] Add ITE IT6162 MIPI DSI to HDMI bridge driver Hermes Wu via B4 Relay 2026-02-23 9:20 ` [PATCH 1/3] drm/bridge: " Hermes Wu via B4 Relay 2026-02-23 9:20 ` [PATCH 2/3] dt-bindings: display: bridge: Add ITE IT6162 MIPI DSI to HDMI bridge Hermes Wu via B4 Relay @ 2026-02-23 9:20 ` Hermes Wu via B4 Relay 2026-02-24 0:21 ` Claude review: " Claude Code Review Bot 2026-02-24 0:21 ` Claude review: Add " Claude Code Review Bot 3 siblings, 1 reply; 11+ messages in thread From: Hermes Wu via B4 Relay @ 2026-02-23 9:20 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: Pet.Weng, Kenneth.Hung, Hermes Wu, dri-devel, devicetree, linux-kernel, Hermes Wu From: Hermes Wu <Hermes.wu@ite.com.tw> Add a MAINTAINERS entry for the newly introduced ITE IT6162 MIPI DSI to HDMI bridge driver, covering the driver source file and the device tree binding document. Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw> --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 6169bd4d7baccc6945363622b42e7286cbec7b88..8a6b5bf3f664247b45c66b37439d3debf6ee18b1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13485,6 +13485,14 @@ W: https://linuxtv.org Q: http://patchwork.linuxtv.org/project/linux-media/list/ F: drivers/media/tuners/it913x* +ITE IT6162 MIPI DSI TO HDMI BRIDGE DRIVER +M: Hermes Wu <Hermes.wu@ite.com.tw> +L: dri-devel@lists.freedesktop.org +S: Maintained +F: Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml +F: drivers/gpu/drm/bridge/ite-it6162.c + + ITE IT6263 LVDS TO HDMI BRIDGE DRIVER M: Liu Ying <victor.liu@nxp.com> L: dri-devel@lists.freedesktop.org -- 2.34.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Claude review: MAINTAINERS: Add entry for ITE IT6162 MIPI DSI to HDMI bridge driver 2026-02-23 9:20 ` [PATCH 3/3] MAINTAINERS: Add entry for ITE IT6162 MIPI DSI to HDMI bridge driver Hermes Wu via B4 Relay @ 2026-02-24 0:21 ` Claude Code Review Bot 0 siblings, 0 replies; 11+ messages in thread From: Claude Code Review Bot @ 2026-02-24 0:21 UTC (permalink / raw) To: dri-devel-reviews Patch Review > +ITE IT6162 MIPI DSI TO HDMI BRIDGE DRIVER > +M: Hermes Wu <Hermes.wu@ite.com.tw> > +L: dri-devel@lists.freedesktop.org > +S: Maintained > +F: Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml > +F: drivers/gpu/drm/bridge/ite-it6162.c > + > + There is an extra blank line (two blank lines between this entry and the next). Only one blank line is needed between MAINTAINERS entries. Otherwise no issues with this patch. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 11+ messages in thread
* Claude review: Add ITE IT6162 MIPI DSI to HDMI bridge driver 2026-02-23 9:20 [PATCH 0/3] Add ITE IT6162 MIPI DSI to HDMI bridge driver Hermes Wu via B4 Relay ` (2 preceding siblings ...) 2026-02-23 9:20 ` [PATCH 3/3] MAINTAINERS: Add entry for ITE IT6162 MIPI DSI to HDMI bridge driver Hermes Wu via B4 Relay @ 2026-02-24 0:21 ` Claude Code Review Bot 3 siblings, 0 replies; 11+ messages in thread From: Claude Code Review Bot @ 2026-02-24 0:21 UTC (permalink / raw) To: dri-devel-reviews Overall Series Review Subject: Add ITE IT6162 MIPI DSI to HDMI bridge driver Author: Hermes Wu via B4 Relay <devnull+Hermes.wu.ite.com.tw@kernel.org> Patches: 4 Reviewed: 2026-02-24T10:21:11.019014 --- This series adds support for the ITE IT6162 MIPI DSI to HDMI 2.0 bridge chip, consisting of a driver, DT binding, and MAINTAINERS entry. The IT6162 uses an I2C-based "infoblock" register interface to communicate with internal firmware that handles the actual DSI-to-HDMI conversion. The driver implements the DRM bridge framework including HPD, EDID reading, audio, and HDCP support. The architectural approach of an infoblock-based firmware interface is reasonable for this type of chip, but the driver has several significant bugs that need fixing before it can be merged. Most critically: an uninitialized variable use in DSI attach, a shallow copy of a large kernel structure, an uninitialized AVI infoframe in a common code path, and a missing bridge op flag that makes the HPD interrupt path dead code. The DT binding is missing documentation for several vendor-specific properties that the driver reads, and the binding needs to be submitted before or with the driver rather than after (the cover letter lists the binding as patch 2, but the ordering in the mbox has it before the driver which is correct). The driver also has a number of smaller issues: a register offset typo, swapped sample-width mappings, return-type mismatch in a read helper, and regulator error handling that leaks enables on failure. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 0/2] Add ITE IT6162 MIPI DSI to HDMI bridge driver
@ 2026-03-09 9:42 Hermes Wu via B4 Relay
2026-03-09 9:42 ` [PATCH v2 2/2] drm/bridge: " Hermes Wu via B4 Relay
0 siblings, 1 reply; 11+ messages in thread
From: Hermes Wu via B4 Relay @ 2026-03-09 9:42 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Pet.Weng, Kenneth.Hung, Hermes Wu, dri-devel, devicetree,
linux-kernel, Hermes Wu
This patch series adds support for the ITE IT6162 MIPI DSI to HDMI
bridge chip. The IT6162 is an I2C-controlled bridge that receives MIPI
DSI input and outputs HDMI signals.
The device supports the following configurations:
- Single MIPI DSI input: up to 4K @ 30Hz
- Dual MIPI DSI input (combined): up to 4K @ 60Hz
This series introduces:
- dt-bindings: Add YAML binding document for ITE IT6162
- drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver
Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw>
---
Changes in v2:
- dt-bindings:
* Add property "ite,spport-hdcp" to enable HDCP
* Add property "lane-polarities" and "clock-noncontinuous" for DSI
setting
- drm/bridge:
* Drop unused element in struct it6162
* Remove regmap wrappers
* Use FIELD_PREP for bitfield operations
* Update HDCP status with drm_hdcp_update_content_protection()
* Add AVI, AUDIO, and SPD infoframe control
* Remove conversion from drm_display_mode to videomode
* Fix regulator/gpio error handling in it6162_init_pdata() to return proper error codes
- MAINTAINERS
* squash to driver patch
- Link to v1: https://lore.kernel.org/r/20260223-upstream-6162-v1-0-ebcc66ccb1fe@ite.com.tw
---
Hermes Wu (2):
dt-bindings: display: bridge: Add ITE IT6162 MIPI DSI to HDMI bridge
drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver
.../bindings/display/bridge/ite,it6162.yaml | 216 +++
MAINTAINERS | 7 +
drivers/gpu/drm/bridge/Kconfig | 17 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/ite-it6162.c | 1847 ++++++++++++++++++++
5 files changed, 2088 insertions(+)
---
base-commit: 2622649ad6cdbb3e77bfafc8c0fe686090b77f70
change-id: 20260223-upstream-6162-3751e78dfcad
Best regards,
--
Hermes Wu <Hermes.wu@ite.com.tw>
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v2 2/2] drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver 2026-03-09 9:42 [PATCH v2 0/2] " Hermes Wu via B4 Relay @ 2026-03-09 9:42 ` Hermes Wu via B4 Relay 2026-03-10 2:31 ` Claude review: " Claude Code Review Bot 0 siblings, 1 reply; 11+ messages in thread From: Hermes Wu via B4 Relay @ 2026-03-09 9:42 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: Pet.Weng, Kenneth.Hung, Hermes Wu, dri-devel, devicetree, linux-kernel, Hermes Wu From: Hermes Wu <Hermes.wu@ite.com.tw> Add support for the ITE IT6162 MIPI DSI to HDMI 2.0 bridge chip. The IT6162 is an I2C-controlled bridge that supports the following configurations: - Single MIPI DSI input: up to 4K @ 30Hz - Dual MIPI DSI input (combined): up to 4K @ 60Hz The driver implements the DRM bridge and connector frameworks, including mode setting, EDID retrieval, and HPD support. Also add a MAINTAINERS entry for the newly introduced ITE IT6162 MIPI DSI to HDMI bridge driver, covering the driver source file and the device tree binding document. Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw> --- MAINTAINERS | 7 + drivers/gpu/drm/bridge/Kconfig | 17 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ite-it6162.c | 1847 +++++++++++++++++++++++++++++++++++ 4 files changed, 1872 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 30f3472c72933c93b9237d93ad35802d6dda75f1..9c6e7c02227e810f280d2bfaa73e0eb572db0119 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13640,6 +13640,13 @@ W: https://linuxtv.org Q: http://patchwork.linuxtv.org/project/linux-media/list/ F: drivers/media/tuners/it913x* +ITE IT6162 MIPI DSI TO HDMI BRIDGE DRIVER +M: Hermes Wu <Hermes.wu@ite.com.tw> +L: dri-devel@lists.freedesktop.org +S: Maintained +F: Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml +F: drivers/gpu/drm/bridge/ite-it6162.c + ITE IT6263 LVDS TO HDMI BRIDGE DRIVER M: Liu Ying <victor.liu@nxp.com> L: dri-devel@lists.freedesktop.org diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 1cabfa1d2b2efc6fca68619eea6050fe96a892e8..1a8fe59506cf60958babb48429b842ca3aecc755 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -107,6 +107,23 @@ config DRM_INNO_HDMI select DRM_DISPLAY_HELPER select DRM_KMS_HELPER +config DRM_ITE_IT6162 + tristate "iTE IT6162 DSI to HDMI bridge" + depends on OF + select REGMAP_I2C + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_HDMI_HELPER + select DRM_DISPLAY_HDMI_HELPER + select DRM_DISPLAY_HDCP_HELPER + select DRM_DISPLAY_HELPER + help + Driver for iTE IT6162 DSI to HDMI bridge + chip driver that converts DSI to HDMI signals + support up to 4k60 with 2 MIPI DSI + Please say Y if you have such hardware. + config DRM_ITE_IT6263 tristate "ITE IT6263 LVDS/HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index fb0cf0bf88756bed8323830c80f3f1d066b51e36..3a199b27b3bbe8294a5bef6c82cfee4cd7b8a34e 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -11,6 +11,7 @@ tda998x-y := tda998x_drv.o obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o obj-$(CONFIG_DRM_INNO_HDMI) += inno-hdmi.o +obj-$(CONFIG_DRM_ITE_IT6162) += ite-it6162.o obj-$(CONFIG_DRM_ITE_IT6263) += ite-it6263.o obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o diff --git a/drivers/gpu/drm/bridge/ite-it6162.c b/drivers/gpu/drm/bridge/ite-it6162.c new file mode 100644 index 0000000000000000000000000000000000000000..7491df8726e2af26478ab9784e76af1a124d5038 --- /dev/null +++ b/drivers/gpu/drm/bridge/ite-it6162.c @@ -0,0 +1,1847 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2024 ITE + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ +#include <linux/bitfield.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_irq.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <drm/display/drm_hdcp_helper.h> +#include <drm/display/drm_hdmi_helper.h> +#include <drm/display/drm_hdmi_state_helper.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_print.h> + +#include <video/videomode.h> +#include <sound/hdmi-codec.h> + +#define DATA_BUFFER_DEPTH 32 + +#define OFFSET_CHIP_ID_L 0x00 +#define OFFSET_CHIP_ID_M 0x01 +#define OFFSET_CHIP_ID_H 0x02 +#define OFFSET_VERSION_L 0x03 +#define OFFSET_VERSION_M 0x04 +#define OFFSET_VERSION_H 0x03 + +#define OFFSET_MIPI_CONFIG_L 0x06 +#define MIPI_CLOCK_MODE_MASK BIT(6) +#define MIPI_PORT1_EN_MASK BIT(5) +#define MIPI_PORT0_EN_MASK BIT(4) +#define MIPI_LANE_SWAP_MASK BIT(3) +#define MIPI_PN_SWAP_MASK BIT(2) +#define MIPI_LANE_NUM_MASK 0x3 + +#define OFFSET_MIPI_CONFIG_H 0x07 +#define OFFSET_TX_CONFIG 0x08 + +#define OFFSET_TX_SETTING 0x09 +#define TX_HDCP_VER_MASK 0xC0 +#define TX_HDCP_ENCRYP_MASK BIT(5) +#define TX_STREAM_TYPE_MASK BIT(4) + +#define OFFSET_MIPI_STATUS 0x0A +#define OFFSET_TX_STATUS 0x0C +#define B_TX_STATUS_HPD 7 +#define B_TX_STATUS_VIDEO_STB 6 +#define B_TX_STATUS_HDCP 4 +#define M_TX_STATUS_HDCP 0x30 + +#define TX_VIDEO_STB BIT(B_TX_STATUS_VIDEO_STB) +#define TX_STATUS_HPD BIT(B_TX_STATUS_HPD) + +#define GET_TX_HPD_STATUS(x) (((x) & TX_STATUS_HPD) >> B_TX_STATUS_HPD) +#define GET_TX_VIDEO_STATUS(x) (((x) & TX_VIDEO_STB) >> B_TX_STATUS_VIDEO_STB) +#define GET_TX_HDCP_STATUS(x) (((x) & M_TX_STATUS_HDCP) >> B_TX_STATUS_HDCP) + +#define OFFSET_SINK_CAP 0x0D +#define B_SINK_CAP_HDCP_VER 4 +#define M_SINK_CAP_HDCP_VER 0x30 + +#define GET_SINK_CAP_HDCP_VER(x) (((x) & M_SINK_CAP_HDCP_VER) >> B_SINK_CAP_HDCP_VER) + +#define OFFSET_DRIVER_DATA 0x0E + +#define OFFSET_DATA_TYPE_IDX 0x0F +#define OFFSET_DATA_BUFFER 0x20 + +#define OFFSET_AUDIO_CTRL0 0x3C +#define MASK_AUDIO_CHANNEL_NUM 0x0F +#define MASK_AUDIO_SELECT 0x30 +#define MASK_AUDIO_TYPE 0xC0 + +#define OFFSET_AUDIO_CTRL1 0x3D +#define OFFSET_AUDIO_CTRL2 0x3E +#define MASK_I2S_BUS_MODE 0x1F +#define MASK_I2S_WORD_LEN 0x60 +#define MASK_I2S_VALID 0x80 + +#define OFFSET_HOST_SET 0xFE +#define B_CONFIG_CHG BIT(7) +#define B_SET_CHG BIT(6) +#define HOST_SET_VIDEO_INFO (1) +#define HOST_SET_AUDIO_INFO (2) +#define HOST_SET_VIDEO_AUDIO_INFO (3) +#define HOST_SET_EDID_R (0x04) +#define HOST_SET_HDCP_R (0x05) +#define HOST_SET_CEA_INFOFRAME (0x0E) + +#define OFFSET_EVENT_CHG 0xFF +#define B_EVENT_CHG_BUFFER 4 +#define M_EVENT_CHG_BUFFER_STS (0x30) + +#define B_EVENT_CHG 1 +#define B_EVENT_IC_READY 0 + +#define EVENT_CHG BIT(B_EVENT_CHG) +#define EVENT_READY BIT(B_EVENT_IC_READY) + +#define GET_BUFFER_STATUS(x) (((x) & M_EVENT_CHG_BUFFER_STS) >> B_EVENT_CHG_BUFFER) + +#define TIMEOUT_INFOBLOCK_MS 1800 + +enum it6162_audio_select { + I2S = 0, + SPDIF, +}; + +enum it6162_audio_word_length { + WORD_LENGTH_16BIT = 0x0, + WORD_LENGTH_18BIT = 0x1, + WORD_LENGTH_20BIT = 0x2, + WORD_LENGTH_24BIT = 0x3, +}; + +enum it6162_audio_sample_rate { + SAMPLE_RATE_32K = 0x3, + SAMPLE_RATE_48K = 0x2, + SAMPLE_RATE_64K = 0xB, + SAMPLE_RATE_96K = 0xA, + SAMPLE_RATE_192K = 0xE, + SAMPLE_RATE_44_1K = 0x0, + SAMPLE_RATE_88_2K = 0x8, + SAMPLE_RATE_176_4K = 0xC, +}; + +enum it6162_audio_type { + LPCM = 0, + NLPCM, +}; + +enum data_buf_sts { + NO_STS = 0x00, + BUF_READY = 0x01, + BUF_FAIL = 0x02, +}; + +enum hdcp_state { + NO_HDCP_STATE = 0x00, + AUTH_DONE = 0x01, + AUTH_FAIL = 0x02, +}; + +enum hdcp_ver { + NO_HDCP = 0x0, + HDCP_14 = 0x1, + HDCP_23 = 0x2, +}; + +struct it6162_chip_info { + u32 chip_id; + u32 version; +}; + +struct it6162_audio { + enum it6162_audio_select select; + enum it6162_audio_type type; + enum it6162_audio_sample_rate sample_rate; + unsigned int audio_enable; + unsigned int sample_width; + unsigned int channel_number; + unsigned int user_cts; +}; + +struct it6162_video_settings { + u8 vic; + u32 clock; + u16 htotal; + u16 hfp; + u16 hsw; + u16 hbp; + u16 hdew; + u16 vtotal; + u16 vfp; + u16 vsw; + u16 vbp; + u16 vdew; + u8 hpol; + u8 vpol; + u8 prog; + u16 v_aspect; + u16 h_aspect; + u8 pix_rep; + u8 colorspace; +}; + +enum sync_mode { + SYNC_EVENT = 0x0, + SYNC_PULSE = 0x1, + SYNC_AUTO = 0x2, +}; + +struct it6162_mipi_cfg { + bool en_port[2]; + u8 lane_num; + bool pn_swap; + bool lane_swap; + bool continuous_clk; + enum sync_mode mode; + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; +}; + +struct it6162_hdcp_cfg { + enum hdcp_ver hdcp_version; + bool hdcp_encyption; + u8 stream_ID; +}; + +struct it6162_infoblock_msg { + u8 action; + int len; + u8 msg[32]; +}; + +struct it6162 { + struct drm_bridge bridge; + struct device *dev; + struct drm_connector *connector; + enum drm_connector_status connector_status; + struct drm_device *drm; + + struct i2c_client *it6162_i2c; + struct regmap *regmap; + + struct regulator *pwr1833; + struct regulator *ovdd; + struct regulator *ivdd; + struct gpio_desc *gpiod_reset; + + bool power_on; + bool en_hdcp; + + /*lock the infoblock write setting and buffer access*/ + struct mutex lock; + + enum data_buf_sts data_buf_sts; + + /* it6162 DSI RX related params */ + struct mipi_dsi_device *dsi; + struct it6162_mipi_cfg mipi_cfg; + + struct delayed_work hdcp_work; + enum hdcp_state hdcp_sts; + enum hdcp_ver hdcp_version; + struct it6162_hdcp_cfg hdcp_cfg; + + unsigned int content_protection; +}; + +static struct it6162 *bridge_to_it6162(struct drm_bridge *bridge) +{ + return container_of(bridge, struct it6162, bridge); +} + +static const struct regmap_config it6162_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, +}; + +static inline int it6162_i2c_regmap_init(struct i2c_client *client, + struct it6162 *it6162) +{ + it6162->it6162_i2c = client; + i2c_set_clientdata(client, it6162); + it6162->regmap = devm_regmap_init_i2c(it6162->it6162_i2c, + &it6162_regmap_config); + + if (IS_ERR(it6162->regmap)) + return PTR_ERR(it6162->regmap); + + return 0; +} + +static int it6162_infoblock_complete(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + struct regmap *regmap = it6162->regmap; + unsigned int val; + int ret; + + ret = regmap_read(regmap, OFFSET_HOST_SET, &val); + if (ret < 0) + return dev_err_probe(dev, ret, + "read failed rx reg[0x%x]", + OFFSET_HOST_SET); + + return val; +} + +static int it6162_infoblock_host_set(struct it6162 *it6162, u8 setting) +{ + struct device *dev = it6162->dev; + int status; + int val; + + regmap_write(it6162->regmap, OFFSET_HOST_SET, setting); + /*wait command complete*/ + status = readx_poll_timeout(it6162_infoblock_complete, + it6162, + val, + val <= 0, + 50 * 1000, + TIMEOUT_INFOBLOCK_MS * 1000); + + if (status < 0 || val != 0) { + dev_err(dev, "%s err status = %d, val = %d", __func__, + status, val); + return -ETIMEDOUT; + } + + return 0; +} + +static int it6162_infoblock_request_data(struct it6162 *it6162, + u8 setting, u8 index, u8 *buf) +{ + struct device *dev = it6162->dev; + int status; + bool val = 0; + + it6162->data_buf_sts = NO_STS; + regmap_write(it6162->regmap, OFFSET_DATA_TYPE_IDX, index); + regmap_write(it6162->regmap, OFFSET_HOST_SET, setting); + + status = readx_poll_timeout(it6162_infoblock_complete, + it6162, + val, + val <= 0 && it6162->data_buf_sts == BUF_READY, + 50 * 1000, + TIMEOUT_INFOBLOCK_MS * 1000); + + if (status < 0 || val != 0) { + dev_err(dev, + "%s err status = %d %d", + __func__, + status, + it6162->data_buf_sts); + return -ETIMEDOUT; + } + + dev_dbg(dev, "%s [0x%x] 0x%x", __func__, setting, it6162->data_buf_sts); + if (it6162->data_buf_sts != BUF_READY) + return -EIO; + + regmap_bulk_read(it6162->regmap, + OFFSET_DATA_BUFFER, buf, DATA_BUFFER_DEPTH); + return 0; +} + +static void it6162_infoblock_mipi_config(struct it6162 *it6162) +{ + struct it6162_mipi_cfg *cfg = &it6162->mipi_cfg; + u8 cfg_val = 0; + + cfg_val = FIELD_PREP(MIPI_CLOCK_MODE_MASK, cfg->continuous_clk) | + FIELD_PREP(MIPI_LANE_SWAP_MASK, cfg->lane_swap) | + FIELD_PREP(MIPI_PN_SWAP_MASK, cfg->pn_swap) | + FIELD_PREP(MIPI_LANE_NUM_MASK, cfg->lane_num - 1); + + dev_dbg(it6162->dev, "%s 0x%02x 0x%02x", __func__, + cfg_val, cfg->mode); + + regmap_write(it6162->regmap, OFFSET_MIPI_CONFIG_L, cfg_val); + regmap_write(it6162->regmap, OFFSET_MIPI_CONFIG_H, cfg->mode); +} + +static inline void it6162_infoblock_write_msg(struct it6162 *it6162, + struct it6162_infoblock_msg *msg) +{ + regmap_bulk_write(it6162->regmap, + OFFSET_DATA_BUFFER, msg->msg, msg->len); + it6162_infoblock_host_set(it6162, msg->action); +} + +static void it6162_set_default_config(struct it6162 *it6162) +{ + guard(mutex)(&it6162->lock); + it6162_infoblock_mipi_config(it6162); + regmap_write(it6162->regmap, OFFSET_TX_CONFIG, 0x00); + regmap_write(it6162->regmap, OFFSET_TX_SETTING, 0x00); + it6162_infoblock_host_set(it6162, B_CONFIG_CHG | B_SET_CHG); +} + +static void it6162_mipi_disable(struct it6162 *it6162) +{ + guard(mutex)(&it6162->lock); + regmap_update_bits(it6162->regmap, + OFFSET_MIPI_CONFIG_L, + MIPI_PORT1_EN_MASK | MIPI_PORT0_EN_MASK, + 0x00); + it6162_infoblock_host_set(it6162, B_CONFIG_CHG); +} + +static void it6162_mipi_enable(struct it6162 *it6162) +{ + unsigned int cfg_val = 0; + + cfg_val = FIELD_PREP(MIPI_PORT1_EN_MASK, it6162->mipi_cfg.en_port[1]) | + FIELD_PREP(MIPI_PORT0_EN_MASK, it6162->mipi_cfg.en_port[0]); + + guard(mutex)(&it6162->lock); + regmap_update_bits(it6162->regmap, + OFFSET_MIPI_CONFIG_L, + MIPI_PORT1_EN_MASK | + MIPI_PORT0_EN_MASK, + cfg_val); + it6162_infoblock_host_set(it6162, B_CONFIG_CHG); +} + +static void it6162_tx_hdcp_enable(struct it6162 *it6162, + struct it6162_hdcp_cfg *tx_out) +{ + int val; + struct regmap *regmap = it6162->regmap; + + guard(mutex)(&it6162->lock); + it6162->hdcp_sts = NO_HDCP_STATE; + regmap_read(regmap, OFFSET_TX_SETTING, &val); + val &= 0x0F; + + val |= FIELD_PREP(TX_HDCP_VER_MASK, tx_out->hdcp_version) | + FIELD_PREP(TX_HDCP_ENCRYP_MASK, tx_out->hdcp_encyption) | + FIELD_PREP(TX_STREAM_TYPE_MASK, tx_out->stream_ID); + + regmap_write(regmap, OFFSET_TX_SETTING, val); + it6162_infoblock_host_set(it6162, B_SET_CHG); + drm_dbg(it6162->drm, "%s 0x%02x", __func__, val); +} + +static void it6162_tx_hdcp_disable(struct it6162 *it6162) +{ + int val; + struct regmap *regmap = it6162->regmap; + + it6162->hdcp_sts = NO_HDCP_STATE; + it6162->hdcp_version = NO_HDCP; + + guard(mutex)(&it6162->lock); + regmap_read(regmap, OFFSET_TX_SETTING, &val); + regmap_write(it6162->regmap, OFFSET_TX_SETTING, val & 0x0F); + it6162_infoblock_host_set(it6162, B_SET_CHG); + + drm_dbg(it6162->drm, "%s 0x%02x", __func__, val); +} + +static void it6162_tx_hdcp_setup(struct it6162 *it6162, + u8 ver, + bool encription) +{ + struct it6162_hdcp_cfg tx_out; + + drm_dbg(it6162->drm, "%s ver %x enc %d", __func__, ver, encription); + + if (ver != NO_HDCP) { + tx_out.hdcp_version = ver; + tx_out.hdcp_encyption = encription; + tx_out.stream_ID = 0; + it6162_tx_hdcp_enable(it6162, &tx_out); + } else { + it6162_tx_hdcp_disable(it6162); + } +} + +static void it6162_tx_enable(struct it6162 *it6162) +{ + if (!it6162->en_hdcp) + it6162->hdcp_version = NO_HDCP; + else + it6162->hdcp_version = it6162->hdcp_cfg.hdcp_version; + + drm_dbg(it6162->drm, "%s %d", __func__, it6162->hdcp_version); +} + +static void it6162_tx_disable(struct it6162 *it6162) +{ + if (it6162->en_hdcp) { + cancel_delayed_work_sync(&it6162->hdcp_work); + it6162_tx_hdcp_setup(it6162, NO_HDCP, false); + } + drm_dbg(it6162->drm, "%s %d", __func__, it6162->hdcp_version); +} + +static inline void +it6162_pack_video_setting(struct it6162 *it6162, + struct it6162_video_settings *settings, + struct it6162_infoblock_msg *msg) +{ + msg->msg[0x00] = settings->hdew & 0xFF; + msg->msg[0x01] = (settings->hdew >> 8) & 0x3F; + msg->msg[0x02] = settings->vdew & 0xFF; + msg->msg[0x03] = (settings->vdew >> 8) & 0x3F; + msg->msg[0x04] = settings->clock & 0xFF; + msg->msg[0x05] = (settings->clock >> 8) & 0xFF; + msg->msg[0x06] = (settings->clock >> 16) & 0xFF; + msg->msg[0x07] = (settings->clock >> 24) & 0xFF; + msg->msg[0x08] = settings->hfp & 0xFF; + msg->msg[0x09] = (settings->hfp >> 8) & 0x3F; + msg->msg[0x0A] = settings->hsw & 0xFF; + msg->msg[0x0B] = (settings->hsw >> 8) & 0x3F; + msg->msg[0x0C] = settings->hbp & 0xFF; + msg->msg[0x0D] = (settings->hbp >> 8) & 0x3F; + msg->msg[0x0E] = settings->vfp & 0xFF; + msg->msg[0x0F] = (settings->vfp >> 8) & 0x3F; + msg->msg[0x10] = settings->vsw & 0xFF; + msg->msg[0x11] = (settings->vsw >> 8) & 0x3F; + msg->msg[0x12] = settings->vbp & 0xFF; + msg->msg[0x13] = (settings->vbp >> 8) & 0x3F; + msg->msg[0x14] = (settings->prog << 2) | + (settings->vpol << 1) | + settings->hpol; + + msg->msg[0x15] = settings->v_aspect; + msg->msg[0x16] = settings->h_aspect & 0xFF; + msg->msg[0x17] = settings->h_aspect >> 8; + msg->msg[0x18] = settings->pix_rep; + msg->msg[0x19] = settings->vic; + msg->msg[0x1A] = settings->colorspace; + msg->msg[0x1B] = 1; + + msg->len = 0x1C; + msg->action = HOST_SET_VIDEO_INFO; +} + +static void it6162_mipi_set_video_timing(struct it6162 *it6162, + struct it6162_video_settings *mode) +{ + struct it6162_infoblock_msg msg; + + it6162_pack_video_setting(it6162, mode, &msg); + guard(mutex)(&it6162->lock); + it6162_infoblock_write_msg(it6162, &msg); +} + +static int it6162_hdcp_read_infomation(struct it6162 *it6162, + u8 *status, u8 *bksv) +{ + u8 buf[DATA_BUFFER_DEPTH]; + int ret; + + guard(mutex)(&it6162->lock); + ret = it6162_infoblock_request_data(it6162, + HOST_SET_HDCP_R, + 0x00, + buf); + if (ret < 0) + return ret; + + if (status) + memcpy(status, buf, 2); + + if (bksv) + memcpy(bksv, buf + 2, 5); + + return 0; +} + +static int it6162_hdcp_read_list(struct it6162 *it6162, + u8 *ksv_list, + int dev_count) +{ + u8 buf[DATA_BUFFER_DEPTH]; + int i, j, ret; + + if (!ksv_list || dev_count <= 0) + return -EINVAL; + + guard(mutex)(&it6162->lock); + + for (i = 0; i < (dev_count / 6 + 1); i++) { + /* KsV lists */ + ret = it6162_infoblock_request_data(it6162, + HOST_SET_HDCP_R, + i + 1, + buf); + + if (ret < 0) + return ret; + + for (j = 0; j < 30; j += 5) { + if ((i * 6 + j / 5) >= dev_count) + break; + + memcpy(&ksv_list[(i * 6 + j / 5) * 5], &buf[j], 5); + } + } + + return dev_count; +} + +static void it6162_update_hdcp14(struct it6162 *it6162) +{ + struct drm_device *drm = it6162->drm; + int dev_count; + u8 bksv[5]; + u8 bstatus[2]; + u8 ksvlist[5 * 30]; + int ret; + + ret = it6162_hdcp_read_infomation(it6162, bstatus, bksv); + + if (ret < 0) { + drm_err(drm, "read Bstatus fail!!!!"); + return; + } + drm_dbg(drm, "status = 0x%02X%02X", bstatus[1], bstatus[0]); + + if (DRM_HDCP_MAX_DEVICE_EXCEEDED(bstatus[0]) || + DRM_HDCP_MAX_CASCADE_EXCEEDED(bstatus[1])) { + drm_err(drm, "HDCP14 Max Topology Limit Exceeded"); + return; + } + + dev_count = DRM_HDCP_NUM_DOWNSTREAM(bstatus[0]); + drm_dbg(drm, "dev_count %d", dev_count); + if (dev_count == 0) + return; + + dev_count = dev_count > 30 ? 30 : dev_count; + ret = it6162_hdcp_read_list(it6162, ksvlist, dev_count); + + if (ret < 0) { + drm_err(drm, "read KSV list fail!!!!"); + return; + } +} + +static void it6162_update_hdcp23(struct it6162 *it6162) +{ + struct drm_device *drm = it6162->drm; + int dev_count; + u8 rxid[5]; + u8 rxinfo[2]; + u8 rxid_list[5 * 30]; + int ret; + + ret = it6162_hdcp_read_infomation(it6162, rxinfo, rxid); + + if (ret < 0) { + drm_err(drm, "read Rxinfo fail!!!!"); + return; + } + drm_dbg(drm, "Rxinfo 0x%02X%02X", rxinfo[1], rxinfo[0]); + + if (HDCP_2_2_MAX_CASCADE_EXCEEDED(rxinfo[1]) || + HDCP_2_2_MAX_DEVS_EXCEEDED(rxinfo[1])) { + drm_err(drm, "Topology Max Size Exceeded"); + return; + } + + dev_count = (HDCP_2_2_DEV_COUNT_HI(rxinfo[0]) << 4 | + HDCP_2_2_DEV_COUNT_LO(rxinfo[1])); + dev_count = dev_count > 30 ? 30 : dev_count; + if (dev_count == 0) + return; + + ret = it6162_hdcp_read_list(it6162, rxid_list, dev_count); + if (ret < 0) { + drm_err(drm, "read RxID list fail!!!!"); + return; + } +} + +static void it6162_update_hdcp(struct it6162 *it6162) +{ + if (it6162->hdcp_version == HDCP_23) + it6162_update_hdcp23(it6162); + else + it6162_update_hdcp14(it6162); +} + +static void it6162_hdcp_handler(struct it6162 *it6162) +{ + struct regmap *regmap = it6162->regmap; + unsigned int tx_status, sink_cap; + enum hdcp_state hdcp_sts; + struct it6162_hdcp_cfg *hdcp_cfg = &it6162->hdcp_cfg; + u8 hdcp_ver; + u64 cp_status; + + if (hdcp_cfg->hdcp_version == NO_HDCP || !it6162->en_hdcp) { + drm_dbg(it6162->drm, "HDCP not enabled, skip hdcp check"); + return; + } + + regmap_read(regmap, OFFSET_TX_STATUS, &tx_status); + regmap_read(regmap, OFFSET_SINK_CAP, &sink_cap); + + drm_dbg(it6162->drm, "Tx status %x", tx_status); + drm_dbg(it6162->drm, "SINK capability %x", sink_cap); + + if (!GET_TX_VIDEO_STATUS(tx_status)) { + drm_dbg(it6162->drm, "video not stable, skip hdcp check"); + return; + } + + hdcp_sts = GET_TX_HDCP_STATUS(tx_status); + hdcp_ver = GET_SINK_CAP_HDCP_VER(sink_cap); + drm_dbg(it6162->drm, "hdcp status: %x->%x, version: %x-%x", + it6162->hdcp_sts, hdcp_sts, + it6162->hdcp_version, hdcp_ver); + + if (it6162->hdcp_version != NO_HDCP) { + if (it6162->hdcp_sts != hdcp_sts || + it6162->hdcp_sts == NO_HDCP_STATE) { + it6162->hdcp_sts = hdcp_sts; + cp_status = DRM_MODE_CONTENT_PROTECTION_DESIRED; + switch (hdcp_sts) { + case AUTH_DONE: + drm_dbg(it6162->drm, "HDCP AUTH DONE"); + it6162_update_hdcp(it6162); + cp_status = DRM_MODE_CONTENT_PROTECTION_ENABLED; + break; + case AUTH_FAIL: + drm_dbg(it6162->drm, "HDCP AUTH FAIL"); + if (hdcp_ver == HDCP_23) { + drm_dbg(it6162->drm, + "HDCP 2.3 auth fail, change to HDCP 1.4"); + it6162_tx_hdcp_setup(it6162, + HDCP_14, + true); + } else { + it6162_tx_hdcp_disable(it6162); + } + + break; + default: + drm_dbg(it6162->drm, "HDCP NO AUTH"); + it6162_tx_hdcp_setup(it6162, + it6162->hdcp_version, + true); + break; + } + } + drm_hdcp_update_content_protection(it6162->connector, cp_status); + } +} + +static void it6162_interrupt_handler(struct it6162 *it6162) +{ + unsigned int int_status, tx_status; + enum drm_connector_status connector_status; + struct device *dev = it6162->dev; + struct regmap *regmap = it6162->regmap; + int err; + + err = regmap_read(regmap, OFFSET_EVENT_CHG, &int_status); + if (err < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", + OFFSET_EVENT_CHG, err); + return; + } + + regmap_write(it6162->regmap, OFFSET_EVENT_CHG, 0xFF); + + if (!!GET_BUFFER_STATUS(int_status)) { + regmap_write(it6162->regmap, OFFSET_DRIVER_DATA, int_status); + it6162->data_buf_sts = GET_BUFFER_STATUS(int_status); + } + + if (!(int_status & EVENT_CHG)) + return; + + drm_dbg(it6162->drm, "evnet change 0x%X", int_status); + + err = regmap_read(regmap, OFFSET_TX_STATUS, &tx_status); + if (err < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", + OFFSET_TX_STATUS, err); + return; + } + drm_dbg(it6162->drm, "Tx status %x", tx_status); + + connector_status = GET_TX_HPD_STATUS(tx_status) ? + connector_status_connected : + connector_status_disconnected; + + if (it6162->connector_status != connector_status) { + it6162->connector_status = connector_status; + drm_bridge_hpd_notify(&it6162->bridge, + it6162->connector_status); + } + + if (it6162->en_hdcp && GET_TX_VIDEO_STATUS(tx_status) && + connector_status == connector_status_connected) + queue_delayed_work(system_wq, + &it6162->hdcp_work, + msecs_to_jiffies(2000)); +} + +static int it6162_wait_ready_event(struct it6162 *it6162) +{ + unsigned int val; + int ret; + struct device *dev = it6162->dev; + struct regmap *regmap = it6162->regmap; + + ret = regmap_read(regmap, OFFSET_EVENT_CHG, &val); + if (ret < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", + OFFSET_EVENT_CHG, ret); + return ret; + } + dev_dbg(dev, "%s val %x ret %d", __func__, val, ret); + if (val & EVENT_READY) + return EVENT_READY; + + ret = regmap_read(regmap, 0x00, &val); + if (ret < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", + OFFSET_EVENT_CHG, ret); + return ret; + } + + if (val == 0x61) { + dev_warn(dev, "MCU ready event missed, but reg[0x00] is 0x61, consider MCU ready"); + return EVENT_READY; + } + + return 0; +} + +static bool it6162_wait_mcu_ready(struct it6162 *it6162) +{ + struct device *dev = &it6162->it6162_i2c->dev; + unsigned int status; + int val; + + status = readx_poll_timeout(it6162_wait_ready_event, + it6162, + val, + val == EVENT_READY, + 100 * 1000, + 1500 * 1000); + + dev_dbg(dev, "%s status %d val %x", __func__, status, val); + return val == EVENT_READY ? true : false; +} + +static void it6162_reset_init(struct it6162 *it6162) +{ + it6162_set_default_config(it6162); +} + +static int it6162_platform_set_power(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + int err; + + err = regulator_enable(it6162->ivdd); + if (err) + return dev_err_probe(dev, err, "Failed to enable IVDD"); + + err = regulator_enable(it6162->pwr1833); + if (err) + return dev_err_probe(dev, err, "Failed to enable VDD1833"); + + err = regulator_enable(it6162->ovdd); + if (err) + return dev_err_probe(dev, err, "Failed to enable OVDD"); + + usleep_range(10000, 20000); + gpiod_set_value_cansleep(it6162->gpiod_reset, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(it6162->gpiod_reset, 0); + usleep_range(10000, 20000); + + return 0; +} + +static int it6162_platform_clear_power(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + int err; + + err = regulator_disable(it6162->ivdd); + if (err) + return dev_err_probe(dev, err, "Failed to disable IVDD"); + + usleep_range(2000, 3000); + + err = regulator_disable(it6162->pwr1833); + if (err) + return dev_err_probe(dev, err, "Failed to disable VDD1833"); + + err = regulator_disable(it6162->ovdd); + if (err) + return dev_err_probe(dev, err, "Failed to disable OVDD"); + + return 0; +} + +static int it6162_detect_devices(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + const struct it6162_chip_info *chip_info; + u32 chip_id, version; + u8 buf[6]; + + if (it6162_platform_set_power(it6162) < 0) + return -ENODEV; + /*wait MCU ready event after power on*/ + if (!it6162_wait_mcu_ready(it6162)) + return -ENODEV; + + chip_info = of_device_get_match_data(dev); + + regmap_bulk_read(it6162->regmap, + OFFSET_CHIP_ID_L, &buf[0], 6); + + dev_info(dev, "chip id %02x %02x %02X", buf[0], buf[1], buf[2]); + dev_info(dev, "chip VER %02x %02x %02x", buf[3], buf[4], buf[5]); + + chip_id = (buf[0] << 16) | (buf[1] << 8) | (buf[2]); + version = (buf[3] << 16) | (buf[4] << 8) | (buf[5]); + dev_info(dev, "chip id 0x%06x, version 0x%06x", chip_id, version); + + if (chip_id != chip_info->chip_id || version < chip_info->version) { + dev_err(dev, "chip_id 0x%06x != 0x%06x", + chip_id, chip_info->chip_id); + dev_err(dev, "version 0x%06x != 0x%06x", + version, chip_info->version); + + return -ENODEV; + } + + return 0; +} + +static int __maybe_unused it6162_poweron(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + int err; + + dev_dbg(dev, "powered on Start"); + + err = it6162_platform_set_power(it6162); + if (err < 0) + return err; + /*wait MCU ready event after power on*/ + if (!it6162_wait_mcu_ready(it6162)) + return -ENODEV; + + it6162->connector_status = connector_status_disconnected; + it6162_reset_init(it6162); + + enable_irq(it6162->it6162_i2c->irq); + dev_dbg(dev, "enable irq %d", + it6162->it6162_i2c->irq); + + it6162->power_on = true; + dev_info(dev, "it6162 poweron end"); + return 0; +} + +static int __maybe_unused it6162_poweroff(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + int err; + + dev_dbg(dev, "powered off start"); + disable_irq(it6162->it6162_i2c->irq); + dev_dbg(dev, "disable irq %d", it6162->it6162_i2c->irq); + + err = it6162_platform_clear_power(it6162); + if (err < 0) + return err; + + it6162->power_on = false; + dev_dbg(dev, "it6162 poweroff"); + return 0; +} + +static void it6162_config_default(struct it6162 *it6162) +{ + struct it6162_mipi_cfg *mipi_cfg = &it6162->mipi_cfg; + struct it6162_hdcp_cfg *hdcp_cfg = &it6162->hdcp_cfg; + + mipi_cfg->lane_num = 4; + mipi_cfg->pn_swap = false; + mipi_cfg->lane_swap = false; + mipi_cfg->en_port[0] = false; + mipi_cfg->en_port[1] = false; + mipi_cfg->continuous_clk = true; + mipi_cfg->mode = SYNC_EVENT; + mipi_cfg->format = MIPI_DSI_FMT_RGB888; + mipi_cfg->mode_flags = MIPI_DSI_MODE_VIDEO; + + it6162->en_hdcp = false; + hdcp_cfg->hdcp_version = HDCP_23; + hdcp_cfg->hdcp_encyption = true; + hdcp_cfg->stream_ID = 0; + + it6162->connector_status = connector_status_disconnected; +} + +static enum drm_connector_status it6162_detect(struct it6162 *it6162) +{ + struct regmap *regmap = it6162->regmap; + unsigned int tx_status; + + regmap_read(regmap, OFFSET_TX_STATUS, &tx_status); + + it6162->connector_status = GET_TX_HPD_STATUS(tx_status) ? + connector_status_connected : + connector_status_disconnected; + + drm_dbg(it6162->drm, "%s connector_status %x", __func__, + it6162->connector_status); + return it6162->connector_status; +} + +static int it6162_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + struct it6162 *it6162 = data; + unsigned int cnt; + unsigned int i; + u8 config; + + if (len > EDID_LENGTH) + return -EINVAL; + + guard(mutex)(&it6162->lock); + + cnt = 0; + for (i = 0; i < EDID_LENGTH; i += DATA_BUFFER_DEPTH, cnt++) { + config = (block << 2) | (cnt); + if (it6162_infoblock_request_data(it6162, + HOST_SET_EDID_R, + config, buf + i) < 0) + return -EIO; + } + + return 0; +} + +static void it6162_enable_audio(struct it6162 *it6162, + struct it6162_audio *config) +{ + unsigned int val; + + guard(mutex)(&it6162->lock); + val = FIELD_PREP(MASK_AUDIO_CHANNEL_NUM, config->channel_number) | + FIELD_PREP(MASK_AUDIO_SELECT, config->select) | + FIELD_PREP(MASK_AUDIO_TYPE, config->type); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL0, val); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL1, config->sample_rate); + /* Standard i2s 16bit, 24bit */ + val = FIELD_PREP(MASK_I2S_BUS_MODE, 0) | + FIELD_PREP(MASK_I2S_WORD_LEN, config->sample_width) | + FIELD_PREP(MASK_I2S_VALID, 1); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL2, val); + it6162_infoblock_host_set(it6162, HOST_SET_AUDIO_INFO); +} + +static void it6162_disable_audio(struct it6162 *it6162) +{ + guard(mutex)(&it6162->lock); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL0, 0x00); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL1, 0x00); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL2, 0x00); + it6162_infoblock_host_set(it6162, HOST_SET_AUDIO_INFO); +} + +static irqreturn_t it6162_int_threaded_handler(int unused, void *data) +{ + struct it6162 *it6162 = data; + + it6162_interrupt_handler(it6162); + + return IRQ_HANDLED; +} + +static void it6162_hdcp_work(struct work_struct *work) +{ + struct it6162 *it6162 = container_of(work, + struct it6162, + hdcp_work.work); + + it6162_hdcp_handler(it6162); +} + +static struct mipi_dsi_host *it6162_of_get_dsi_host_by_port(struct it6162 *it6162, + int port) +{ + struct device_node *of = it6162->dev->of_node; + struct device_node *host_node; + struct device_node *endpoint; + struct mipi_dsi_host *dsi_host; + + endpoint = of_graph_get_endpoint_by_regs(of, port, -1); + if (!endpoint) + return ERR_PTR(-ENODEV); + + host_node = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!host_node) + return ERR_PTR(-ENODEV); + + dsi_host = of_find_mipi_dsi_host_by_node(host_node); + of_node_put(host_node); + + if (!dsi_host) + return ERR_PTR(-EPROBE_DEFER); + + return dsi_host; +} + +static int it6162_of_get_dsi_host(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + struct mipi_dsi_host *dsi_host; + int port, host_count = 0; + + for (port = 0; port < 2; port++) { + dsi_host = it6162_of_get_dsi_host_by_port(it6162, port); + + if (PTR_ERR(dsi_host) == -EPROBE_DEFER) { + dev_info(dev, + "DSI host for port %d not ready, defer probe", + port); + return -EPROBE_DEFER; + } + + if (IS_ERR(dsi_host)) { + dev_info(dev, "DSI host for port %d not found", port); + continue; + } + + host_count++; + } + + dev_info(dev, "%s = %d", __func__, host_count); + if (host_count == 0) + return -ENODEV; + + return 0; +} + +static int it6162_load_mipi_pars_from_port(struct it6162 *it6162, int port) +{ + struct device_node *of = it6162->dev->of_node; + struct device_node *endpoint; + struct device *dev = it6162->dev; + struct it6162_mipi_cfg *mipicfg = &it6162->mipi_cfg; + u32 data_lanes[4] = {1}; + u32 lane_polarities[5] = {0}; + int dsi_lanes, i; + + endpoint = of_graph_get_endpoint_by_regs(of, port, -1); + + if (!endpoint) + return 0; + + dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4); + + if (dsi_lanes < 0) + return dsi_lanes; + + mipicfg->lane_num = dsi_lanes; + + of_property_read_u32_array(endpoint, "data-lanes", + data_lanes, dsi_lanes); + /* Support lane swapping [1 2 3 4] => [4 3 2 1] */ + if (data_lanes[0] == 4) + mipicfg->lane_swap = true; + + of_property_read_u32_array(endpoint, "lane-polarities", + lane_polarities, dsi_lanes + 1); + /* Support Dp/Dn swapping all data lanes and clock lane must have same polarity */ + if (lane_polarities[0] != 0) + mipicfg->pn_swap = true; + + for (i = 1; i <= dsi_lanes; i++) + if (lane_polarities[i] != lane_polarities[0]) + dev_warn_once(dev, "invalid lane-polarities configuration"); + + if (of_property_present(endpoint, "clock-noncontinuous")) { + mipicfg->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS; + mipicfg->continuous_clk = false; + } + + of_node_put(endpoint); + + dev_info(dev, "lanes: %d pn_swap: %d, lane_swap: %d, mode_flags: %lu", + mipicfg->lane_num, mipicfg->pn_swap, + mipicfg->lane_swap, mipicfg->mode_flags); + + return dsi_lanes; +} + +static int it6162_attach_dsi(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + struct device_node *np = dev->of_node; + const struct mipi_dsi_device_info info = {"it6162-mipi", 0, np}; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *dsi_host; + struct it6162_mipi_cfg *mipi_cfg = &it6162->mipi_cfg; + int ret = 0; + + for (int port = 0; port < 2; port++) { + dsi_host = it6162_of_get_dsi_host_by_port(it6162, port); + if (IS_ERR(dsi_host)) + continue; + + mipi_cfg->en_port[port] = true; + + if (!it6162->dsi) { + ret = it6162_load_mipi_pars_from_port(it6162, port); + if (ret <= 0) + return ret; + dev_info(dev, "DSI host loaded paras for port %d", port); + it6162->dsi = dsi; + } + + dsi = devm_mipi_dsi_device_register_full(dev, dsi_host, &info); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to create dsi device"); + return PTR_ERR(dsi); + } + + dsi->lanes = mipi_cfg->lane_num; + dsi->format = mipi_cfg->format; + dsi->mode_flags = mipi_cfg->mode_flags; + dev_info(dev, "dsi lanes %d, format %d, mode_flags %lu", + dsi->lanes, dsi->format, dsi->mode_flags); + ret = devm_mipi_dsi_attach(dev, dsi); + + if (ret) { + dev_err(dev, "failed to attach dsi device %d", port); + return ret; + } + } + + it6162_poweron(it6162); + return 0; +} + +static bool it6162_of_get_audio(struct it6162 *it6162) +{ + struct device_node *np = it6162->dev->of_node; + struct device_node *audio_port; + + audio_port = of_graph_get_port_by_id(np, 2); + if (audio_port) { + of_node_put(audio_port); + return true; + } + + return false; +} + +static void it6162_parse_dt(struct it6162 *it6162) +{ + struct device_node *np = it6162->dev->of_node; + + /* get hdcp support*/ + it6162->en_hdcp = of_property_present(np, "ite,support-hdcp"); +} + +static int it6162_init_pdata(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + + it6162->ivdd = devm_regulator_get(dev, "ivdd"); + if (IS_ERR(it6162->ivdd)) + return dev_err_probe(dev, PTR_ERR(it6162->ivdd), + "failed to get ivdd regulator"); + + it6162->pwr1833 = devm_regulator_get(dev, "ovdd1833"); + if (IS_ERR(it6162->pwr1833)) + return dev_err_probe(dev, PTR_ERR(it6162->pwr1833), + "failed to get ovdd1833 regulator"); + + it6162->ovdd = devm_regulator_get(dev, "ovdd"); + if (IS_ERR(it6162->ovdd)) + return dev_err_probe(dev, PTR_ERR(it6162->ovdd), + "failed to get ovdd regulator"); + + it6162->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(it6162->gpiod_reset)) + return dev_err_probe(dev, PTR_ERR(it6162->gpiod_reset), + "failed to get reset gpio\n"); + + return 0; +} + +static int it6162_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + struct drm_device *drm = bridge->dev; + + it6162->drm = drm; + + if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { + drm_dbg(drm, + "it6162 driver only copes with atomic updates"); + return -EOPNOTSUPP; + } + + return 0; +} + +static enum drm_mode_status +it6162_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + unsigned long long rate; + + rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB); + if (rate == 0) + return MODE_NOCLOCK; + + return bridge->funcs->hdmi_tmds_char_rate_valid(bridge, mode, rate); +} + +static enum drm_connector_status +it6162_bridge_detect(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162->power_on ? it6162_detect(it6162) : + connector_status_disconnected; +} + +static void +it6162_avi_to_video_setting(struct it6162 *it6162, + struct hdmi_avi_infoframe *avi_info, + struct it6162_video_settings *video) +{ + video->vic = avi_info->video_code; + + switch (avi_info->picture_aspect) { + case HDMI_PICTURE_ASPECT_4_3: + video->h_aspect = 4; + video->v_aspect = 3; + break; + case HDMI_PICTURE_ASPECT_16_9: + video->h_aspect = 16; + video->v_aspect = 9; + break; + case HDMI_PICTURE_ASPECT_64_27: + video->h_aspect = 64; + video->v_aspect = 27; + break; + case HDMI_PICTURE_ASPECT_256_135: + video->h_aspect = 256; + video->v_aspect = 135; + break; + default: + video->h_aspect = 4; + video->v_aspect = 3; + break; + } + + video->pix_rep = avi_info->pixel_repeat + 1; +} + +static void +it6162_display_mode_to_settings(struct it6162 *it6162, + struct drm_display_mode *mode, + struct it6162_video_settings *settings) +{ + struct drm_device *drm = it6162->drm; + + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + settings->hpol = 1; + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + settings->vpol = 1; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + settings->prog = 0; + + settings->clock = mode->clock; + settings->hdew = mode->hdisplay; + settings->hfp = mode->hsync_start - mode->hdisplay; + settings->hsw = mode->hsync_end - mode->hsync_start; + settings->hbp = mode->htotal - mode->hsync_end; + settings->htotal = mode->htotal; + + settings->vdew = mode->vdisplay; + settings->vfp = mode->vsync_start - mode->vdisplay; + settings->vsw = mode->vsync_end - mode->vsync_start; + settings->vbp = mode->vtotal - mode->vsync_end; + settings->vtotal = mode->vtotal; + + drm_dbg(drm, "HActive = %u", settings->hdew); + drm_dbg(drm, "VActive = %u", settings->vdew); + drm_dbg(drm, "HTotal = %u", settings->htotal); + drm_dbg(drm, "VTotal = %u", settings->vtotal); + drm_dbg(drm, "PCLK = %ukhz", settings->clock); + drm_dbg(drm, "HFP = %u", settings->hfp); + drm_dbg(drm, "HSW = %u", settings->hsw); + drm_dbg(drm, "HBP = %u", settings->hbp); + drm_dbg(drm, "VFP = %u", settings->vfp); + drm_dbg(drm, "VSW = %u", settings->vsw); + drm_dbg(drm, "VBP = %u", settings->vbp); + drm_dbg(drm, "HPOL = %d", settings->hpol); + drm_dbg(drm, "VPOL = %d", settings->vpol); + drm_dbg(drm, "Progressive = %d", settings->prog); +} + +static void it6162_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_state *state) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + struct drm_connector *connector; + struct hdmi_avi_infoframe *avi; + struct it6162_video_settings video_setting; + + drm_dbg(it6162->drm, "it6162_bridge_atomic_enable"); + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + + if (!connector) + return; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (!crtc_state) + return; + + it6162->connector = connector; + it6162->content_protection = conn_state->content_protection; + + drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); + avi = &conn_state->hdmi.infoframes.avi.data.avi; + video_setting.colorspace = conn_state->hdmi.output_format; + + it6162_avi_to_video_setting(it6162, avi, &video_setting); + it6162_display_mode_to_settings(it6162, + &crtc_state->mode, + &video_setting); + + it6162_mipi_set_video_timing(it6162, &video_setting); + + it6162_tx_enable(it6162); + it6162_mipi_enable(it6162); +} + +static int it6162_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *adj = &crtc_state->adjusted_mode; + struct drm_display_mode *mode = &crtc_state->mode; + struct it6162 *it6162 = bridge_to_it6162(bridge); + u32 hfp, hsw, hbp; + u32 clock; + u32 hfp_check; + + clock = mode->clock; + hfp = mode->hsync_start - mode->hdisplay; + hsw = mode->hsync_end - mode->hsync_start; + hbp = mode->htotal - mode->hsync_end; + + hfp_check = DIV_ROUND_UP(65 * clock, 1000000) + 4; + if (hfp >= hfp_check) + return 0; + + drm_dbg(it6162->drm, "hfp_check %d", hfp_check); + if (hbp > hfp_check - hfp) { + adj->hsync_start = adj->hdisplay + hfp_check; + adj->hsync_end = adj->hsync_start + hsw; + } + + return 0; +} + +static void it6162_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_state *state) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + drm_dbg(it6162->drm, "it6162_bridge_atomic_disable"); + it6162_tx_disable(it6162); + it6162_mipi_disable(it6162); +} + +static const struct drm_edid * +it6162_bridge_read_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return drm_edid_read_custom(connector, it6162_get_edid_block, it6162); +} + +static int it6162_audio_update_hw_params(struct it6162 *it6162, + struct it6162_audio *config, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + config->channel_number = hparms->channels; + config->type = LPCM; + + switch (hparms->sample_rate) { + case 32000: + config->sample_rate = SAMPLE_RATE_32K; + break; + case 44100: + config->sample_rate = SAMPLE_RATE_44_1K; + break; + case 48000: + config->sample_rate = SAMPLE_RATE_48K; + break; + case 88200: + config->sample_rate = SAMPLE_RATE_88_2K; + break; + case 96000: + config->sample_rate = SAMPLE_RATE_96K; + break; + case 176400: + config->sample_rate = SAMPLE_RATE_176_4K; + break; + case 192000: + config->sample_rate = SAMPLE_RATE_192K; + break; + default: + return -EINVAL; + } + + switch (hparms->sample_width) { + case 16: + config->sample_width = WORD_LENGTH_16BIT; + break; + case 24: + config->sample_width = WORD_LENGTH_18BIT; + break; + case 20: + config->sample_width = WORD_LENGTH_24BIT; + break; + default: + return -EINVAL; + } + + switch (fmt->fmt) { + case HDMI_I2S: + config->select = I2S; + break; + case HDMI_SPDIF: + config->select = SPDIF; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int it6162_bridge_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *params) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + struct it6162_audio config; + + it6162_audio_update_hw_params(it6162, &config, fmt, params); + it6162_enable_audio(it6162, &config); + + return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, + ¶ms->cea); +} + +static int it6162_bridge_hdmi_audio_startup(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + return 0; +} + +static void it6162_bridge_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + it6162_disable_audio(it6162); +} + +static enum drm_mode_status +it6162_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge, + const struct drm_display_mode *mode, + unsigned long long tmds_rate) +{ + /*IT6162 hdmi supports HDMI2.0 600Mhz*/ + if (tmds_rate > 600000000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static inline int it6162_write_infoframe(struct it6162 *it6162, + const u8 *buffer, size_t len) +{ + if (len > DATA_BUFFER_DEPTH) + return -EINVAL; + + regmap_bulk_write(it6162->regmap, + OFFSET_DATA_BUFFER, buffer, len); + regmap_write(it6162->regmap, OFFSET_DATA_TYPE_IDX, len); + it6162_infoblock_host_set(it6162, HOST_SET_CEA_INFOFRAME); + + return 0; +} + +static inline int it6162_clear_infoframe(struct it6162 *it6162, u8 type) +{ + regmap_write(it6162->regmap, OFFSET_DATA_TYPE_IDX, 3); + regmap_write(it6162->regmap, OFFSET_DATA_BUFFER, type); + regmap_write(it6162->regmap, OFFSET_DATA_BUFFER + 1, 0x00); + regmap_write(it6162->regmap, OFFSET_DATA_BUFFER + 2, 0x00); + it6162_infoblock_host_set(it6162, HOST_SET_CEA_INFOFRAME); + + return 0; +} + +static int +it6162_bridge_hdmi_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_write_infoframe(it6162, buffer, len); +} + +static int +it6162_bridge_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_clear_infoframe(it6162, HDMI_INFOFRAME_TYPE_AVI); +} + +static int +it6162_bridge_hdmi_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_write_infoframe(it6162, buffer, len); +} + +static int +it6162_bridge_hdmi_clear_audio_infoframe(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_clear_infoframe(it6162, HDMI_INFOFRAME_TYPE_AUDIO); +} + +static int +it6162_bridge_hdmi_write_spd_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_write_infoframe(it6162, buffer, len); +} + +static int +it6162_bridge_hdmi_clear_spd_infoframe(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_clear_infoframe(it6162, HDMI_INFOFRAME_TYPE_SPD); +} + +static const struct drm_bridge_funcs it6162_bridge_funcs = { + .attach = it6162_bridge_attach, + .mode_valid = it6162_bridge_mode_valid, + .detect = it6162_bridge_detect, + + .atomic_enable = it6162_bridge_atomic_enable, + .atomic_disable = it6162_bridge_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_check = it6162_bridge_atomic_check, + + .edid_read = it6162_bridge_read_edid, + + .hdmi_clear_avi_infoframe = it6162_bridge_hdmi_clear_avi_infoframe, + .hdmi_write_avi_infoframe = it6162_bridge_hdmi_write_avi_infoframe, + .hdmi_clear_spd_infoframe = it6162_bridge_hdmi_clear_spd_infoframe, + .hdmi_write_spd_infoframe = it6162_bridge_hdmi_write_spd_infoframe, + .hdmi_clear_audio_infoframe = it6162_bridge_hdmi_clear_audio_infoframe, + .hdmi_write_audio_infoframe = it6162_bridge_hdmi_write_audio_infoframe, + + .hdmi_tmds_char_rate_valid = it6162_hdmi_tmds_char_rate_valid, + .hdmi_audio_prepare = it6162_bridge_hdmi_audio_prepare, + .hdmi_audio_startup = it6162_bridge_hdmi_audio_startup, + .hdmi_audio_shutdown = it6162_bridge_hdmi_audio_shutdown, +}; + +static int it6162_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; + struct it6162 *it6162; + int ret; + + it6162 = devm_drm_bridge_alloc(dev, struct it6162, bridge, + &it6162_bridge_funcs); + if (IS_ERR(it6162)) + return PTR_ERR(it6162); + + it6162->dev = dev; + + ret = it6162_of_get_dsi_host(it6162); + if (ret < 0) + return ret; + + ret = it6162_i2c_regmap_init(client, it6162); + if (ret != 0) + return ret; + + ret = it6162_init_pdata(it6162); + if (ret) { + dev_err(dev, "Failed to init_pdata %d", ret); + return ret; + } + + it6162_config_default(it6162); + it6162_parse_dt(it6162); + + if (it6162_detect_devices(it6162) < 0) + return -ENODEV; + + if (!client->irq) { + dev_err(dev, "Failed to get INTP IRQ"); + return -ENODEV; + } + + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + it6162_int_threaded_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT | + IRQF_NO_AUTOEN, + "it6162-intp", it6162); + if (ret) + return dev_err_probe(dev, ret, + "Failed to request INTP threaded IRQ"); + + INIT_DELAYED_WORK(&it6162->hdcp_work, it6162_hdcp_work); + + mutex_init(&it6162->lock); + + it6162->bridge.of_node = np; + it6162->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_MODES; + + it6162->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + it6162->bridge.vendor = "ITE"; + it6162->bridge.product = "IT6162"; + + if (it6162_of_get_audio(it6162)) { + /* enable audio support */ + it6162->bridge.ops |= DRM_BRIDGE_OP_HDMI_AUDIO; + it6162->bridge.hdmi_audio_dev = dev; + it6162->bridge.hdmi_audio_max_i2s_playback_channels = 8; + it6162->bridge.hdmi_audio_dai_port = 2; + } + + devm_drm_bridge_add(dev, &it6162->bridge); + + return it6162_attach_dsi(it6162); +} + +static void it6162_remove(struct i2c_client *client) +{ + struct it6162 *it6162 = i2c_get_clientdata(client); + + disable_irq(client->irq); + cancel_delayed_work_sync(&it6162->hdcp_work); + mutex_destroy(&it6162->lock); +} + +static const struct it6162_chip_info it6162_chip_info = { + .chip_id = 0x616200, + .version = 0x006500, +}; + +static const struct of_device_id it6162_dt_ids[] = { + { .compatible = "ite,it6162", .data = &it6162_chip_info}, + { } +}; +MODULE_DEVICE_TABLE(of, it6162_dt_ids); + +static const struct i2c_device_id it6162_i2c_ids[] = { + { "it6162", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, it6162_i2c_ids); + +static struct i2c_driver it6162_driver = { + .driver = { + .name = "it6162", + .of_match_table = it6162_dt_ids, + }, + .probe = it6162_probe, + .remove = it6162_remove, + .id_table = it6162_i2c_ids, +}; + +module_i2c_driver(it6162_driver); + +MODULE_AUTHOR("Pet Weng <pet.weng@ite.com.tw>"); +MODULE_AUTHOR("Hermes Wu <Hermes.Wu@ite.com.tw>"); +MODULE_DESCRIPTION("it6162 MIPI to HDMI driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Claude review: drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver 2026-03-09 9:42 ` [PATCH v2 2/2] drm/bridge: " Hermes Wu via B4 Relay @ 2026-03-10 2:31 ` Claude Code Review Bot 0 siblings, 0 replies; 11+ messages in thread From: Claude Code Review Bot @ 2026-03-10 2:31 UTC (permalink / raw) To: dri-devel-reviews Patch Review **Critical bugs:** 1. **Use-before-init of `dsi` variable** in `it6162_attach_dsi()`: ```c if (!it6162->dsi) { ret = it6162_load_mipi_pars_from_port(it6162, port); if (ret <= 0) return ret; dev_info(dev, "DSI host loaded paras for port %d", port); it6162->dsi = dsi; // BUG: 'dsi' is uninitialized here } dsi = devm_mipi_dsi_device_register_full(dev, dsi_host, &info); ``` The assignment `it6162->dsi = dsi` happens before `dsi` is assigned by `devm_mipi_dsi_device_register_full()`. On the first iteration, `dsi` is an uninitialized local variable. The `dsi` registration and the `it6162->dsi` assignment need to be reordered. 2. **Missing `DRM_BRIDGE_OP_HDMI` flag**: The bridge sets ops for `DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_MODES` but implements HDMI-specific callbacks (`hdmi_write_avi_infoframe`, `hdmi_tmds_char_rate_valid`, etc.) that require `DRM_BRIDGE_OP_HDMI` to be set: ```c it6162->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_MODES; ``` Without `DRM_BRIDGE_OP_HDMI`, the HDMI connector framework won't use these callbacks. Similarly, `DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME` is needed for the SPD infoframe callbacks to be invoked. 3. **Missing `DRM_BRIDGE_OP_HPD` flag**: The driver calls `drm_bridge_hpd_notify()` in `it6162_interrupt_handler()` but never sets `DRM_BRIDGE_OP_HPD` in `bridge.ops`. Without this flag, `drm_bridge_hpd_notify()` will not function properly. 4. **Sample width mapping is swapped**: ```c case 24: config->sample_width = WORD_LENGTH_18BIT; // Should be WORD_LENGTH_24BIT break; case 20: config->sample_width = WORD_LENGTH_24BIT; // Should be WORD_LENGTH_20BIT break; ``` The 24-bit and 20-bit cases are assigned wrong enum values. **Significant issues:** 5. **Regulator disable order is wrong**: In `it6162_platform_clear_power()`, regulators are disabled in the same order they were enabled (`ivdd` → `pwr1833` → `ovdd`). Standard practice is to disable in reverse order of enable (`ovdd` → `pwr1833` → `ivdd`). Also, if `pwr1833` disable fails, `ovdd` will be left enabled (no cleanup on partial failure). 6. **`readx_poll_timeout` condition bug** in `it6162_infoblock_request_data()`: ```c bool val = 0; ... status = readx_poll_timeout(it6162_infoblock_complete, it6162, val, val <= 0 && it6162->data_buf_sts == BUF_READY, 50 * 1000, TIMEOUT_INFOBLOCK_MS * 1000); ``` `it6162_infoblock_complete()` returns an `int` but `val` is declared as `bool`. This will truncate the return value and break the `val <= 0` condition check. Should be `int val`. 7. **Duplicate register offset definition**: ```c #define OFFSET_VERSION_L 0x03 #define OFFSET_VERSION_M 0x04 #define OFFSET_VERSION_H 0x03 // BUG: same as OFFSET_VERSION_L ``` `OFFSET_VERSION_H` should presumably be `0x05`, not `0x03`. 8. **`it6162_bridge_atomic_check` modifies `adjusted_mode` but doesn't update `htotal`**: When HFP is adjusted, `hsync_start` and `hsync_end` are moved, but `htotal` is not updated. This means the effective HBP changes implicitly, and the adjustment may produce inconsistent timing. 9. **Missing `mutex_init` before first use**: `mutex_init(&it6162->lock)` is called in `it6162_probe()` after `it6162_detect_devices()`, which calls `it6162_platform_set_power()` and `it6162_wait_mcu_ready()`. But `it6162_set_default_config()` (called later via `it6162_poweron()` → `it6162_reset_init()`) uses `guard(mutex)(&it6162->lock)`. The mutex should be initialized earlier in the probe sequence, before any function that might acquire it. Looking more carefully, `it6162_detect_devices()` doesn't use the mutex, but `it6162_poweron()` (called from `it6162_attach_dsi()`) does, and the mutex IS initialized before that call. However, it's fragile — better to initialize it earlier. 10. **`it6162_poweron` / `it6162_poweroff` are marked `__maybe_unused`**: These are called directly from `it6162_attach_dsi()`. The `__maybe_unused` annotation is incorrect and misleading — these functions are definitely used. If the intent was for PM callbacks, they aren't actually wired up as PM ops. 11. **No power management (runtime PM or system PM)**: The driver powers on during probe/attach but has no suspend/resume support. `pm_runtime` is included but never used. 12. **`it6162_platform_clear_power` never called on normal remove**: `it6162_remove()` only disables IRQ and cancels work, but never powers down the chip. Regulators remain enabled after driver unbind. 13. **Copyright header inconsistency**: The SPDX tag says `GPL-2.0-only` but the comment block says "either version 2 of the License, or (at your option) any later version" which is `GPL-2.0-or-later`. Pick one and remove the redundant boilerplate text. 14. **Typo**: `"evnet change"` in the debug message at the interrupt handler should be `"event change"`. 15. **Excessive `dev_info` logging**: Many `dev_info` calls (chip ID, probe status, DSI host info, lane config, poweron end) should be `dev_dbg` to avoid spamming the kernel log during normal operation. 16. **`it6162_hdcp_handler` accesses `cp_status` potentially uninitialized**: If `it6162->hdcp_version != NO_HDCP` but `it6162->hdcp_sts == hdcp_sts` and `it6162->hdcp_sts != NO_HDCP_STATE`, the code falls through to `drm_hdcp_update_content_protection()` with `cp_status` uninitialized (it's only set inside the inner `if` block). 17. **Infoframe write/clear functions not protected by mutex**: `it6162_write_infoframe()` and `it6162_clear_infoframe()` access registers and call `it6162_infoblock_host_set()` but are not wrapped in `guard(mutex)`, unlike other infoblock operations. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3 0/2] Add ITE IT6162 MIPI DSI to HDMI bridge driver
@ 2026-03-13 6:15 Hermes Wu via B4 Relay
2026-03-13 6:16 ` [PATCH v3 2/2] drm/bridge: " Hermes Wu via B4 Relay
0 siblings, 1 reply; 11+ messages in thread
From: Hermes Wu via B4 Relay @ 2026-03-13 6:15 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Pet.Weng, Kenneth.Hung, Hermes Wu, dri-devel, devicetree,
linux-kernel, Hermes Wu
This patch series adds support for the ITE IT6162 MIPI DSI to HDMI
bridge chip. The IT6162 is an I2C-controlled bridge that receives MIPI
DSI input and outputs HDMI signals.
The device supports the following configurations:
- Single MIPI DSI input: up to 4K @ 30Hz
- Dual MIPI DSI input (combined): up to 4K @ 60Hz
This series introduces:
- dt-bindings: Add YAML binding document for ITE IT6162
- drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver
Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw>
---
Changes in v3:
- Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml:
* Fix lane-polarities: remove $ref redefinition, allow via video-interfaces.yaml
* Improve ite,support-hdcp description to clarify FW-based HDCP behavior
* Remove descriptions repeating schema constraints
* Remove stray comment in ports required section
* Fix data-lanes spacing in example: <1 2 3 4>
* Add missing newline at end of file
* Add missing change log that Reviewed-by tag was removed when V1->V2
- drivers/gpu/drm/bridge/ite-it6162.c:
* Fix OFFSET_VERSION_H register offset from 0x03 to 0x05
* Add MIPI_PORT_EN_MASK macro combining MIPI_PORT1_EN_MASK and MIPI_PORT0_EN_MASK
* Rename HDCP enums: NO_HDCP -> HDCP_DISABLE, NO_HDCP_STATE -> HDCP_STATE_IDLE,
AUTH_DONE -> HDCP_STATE_AUTH_DONE, AUTH_FAIL -> HDCP_STATE_AUTH_FAIL
* Rename it6162_infoblock_complete() to it6162_wait_command_complete()
* Rename it6162_infoblock_host_set() to it6162_infoblock_trigger()
* Remove it6162_infoblock_mipi_config() and it6162_infoblock_write_msg()
wrappers, inline into it6162_reset_init() and
it6162_mipi_set_video_timing() respectively
* Remove it6162_set_default_config() wrapper, inline into
it6162_reset_init()
* Fix typo: hdcp_encyption -> hdcp_encryption
* Fix typo: it6162_hdcp_read_infomation -> it6162_hdcp_read_information
* Remove dev_err_probe() usage outside of probe path
* Remove verbose success-path dev_info/dev_dbg logging throughout
* Replace __func__ usage in error messages with descriptive strings
* Fix double error printing in probe for it6162_init_pdata failure path
* Fix uninitialized variable warning: initialize cp_status to
DRM_MODE_CONTENT_PROTECTION_DESIRED at declaration; move
drm_hdcp_update_content_protection() inside the state-change block
* Fix audio sample width mapping: case 20 now maps to WORD_LENGTH_20BIT,
case 24 to WORD_LENGTH_24BIT
* Remove stray drm_dbg("it6162_bridge_atomic_disable") call
* Remove drm_dbg() calls from it6162_display_mode_to_settings()
* Drop unused struct it6162 * parameter from it6162_avi_to_video_setting()
and it6162_display_mode_to_settings()
* Fold it6162_set_default_config() body directly into it6162_reset_init(),
removing the wrapper
* it6162_infoblock_request_data(): split command complete polling and
buffer status check into two steps; use wait_event_timeout() for
data_buf_sts since it is updated asynchronously by the interrupt
handler; add wait_queue_head_t data_buf_wait to struct it6162 and
wake_up() in interrupt handler
- Link to v2: https://lore.kernel.org/r/20260309-upstream-6162-v2-0-debdb6c88030@ite.com.tw
Changes in v2:
- dt-bindings:
* Drop Reviewed-by tag due to below changes
* Add property "ite,spport-hdcp" to enable HDCP
* Add property "lane-polarities" and "clock-noncontinuous" for DSI
setting
- drm/bridge:
* Drop unused element in struct it6162
* Remove regmap wrappers
* Use FIELD_PREP for bitfield operations
* Update HDCP status with drm_hdcp_update_content_protection()
* Add AVI, AUDIO, and SPD infoframe control
* Remove conversion from drm_display_mode to videomode
* Fix regulator/gpio error handling in it6162_init_pdata() to return proper error codes
- MAINTAINERS
* squash to driver patch
- Link to v1: https://lore.kernel.org/r/20260223-upstream-6162-v1-0-ebcc66ccb1fe@ite.com.tw
---
Hermes Wu (2):
dt-bindings: display: bridge: Add ITE IT6162 MIPI DSI to HDMI bridge
drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver
.../bindings/display/bridge/ite,it6162.yaml | 178 +++
MAINTAINERS | 7 +
drivers/gpu/drm/bridge/Kconfig | 17 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/ite-it6162.c | 1631 ++++++++++++++++++++
5 files changed, 1834 insertions(+)
---
base-commit: 2622649ad6cdbb3e77bfafc8c0fe686090b77f70
change-id: 20260223-upstream-6162-3751e78dfcad
Best regards,
--
Hermes Wu <Hermes.wu@ite.com.tw>
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v3 2/2] drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver 2026-03-13 6:15 [PATCH v3 0/2] " Hermes Wu via B4 Relay @ 2026-03-13 6:16 ` Hermes Wu via B4 Relay 2026-03-13 21:27 ` Claude review: " Claude Code Review Bot 0 siblings, 1 reply; 11+ messages in thread From: Hermes Wu via B4 Relay @ 2026-03-13 6:16 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: Pet.Weng, Kenneth.Hung, Hermes Wu, dri-devel, devicetree, linux-kernel, Hermes Wu From: Hermes Wu <Hermes.wu@ite.com.tw> Add support for the ITE IT6162 MIPI DSI to HDMI 2.0 bridge chip. The IT6162 is an I2C-controlled bridge that supports the following configurations: - Single MIPI DSI input: up to 4K @ 30Hz - Dual MIPI DSI input (combined): up to 4K @ 60Hz The driver implements the DRM bridge and connector frameworks, including mode setting, EDID retrieval, and HPD support. Also add a MAINTAINERS entry for the newly introduced ITE IT6162 MIPI DSI to HDMI bridge driver, covering the driver source file and the device tree binding document. Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw> --- Changes in v3: * Fix OFFSET_VERSION_H register offset from 0x03 to 0x05 * Add MIPI_PORT_EN_MASK macro combining MIPI_PORT1_EN_MASK and MIPI_PORT0_EN_MASK * Rename HDCP enums: NO_HDCP -> HDCP_DISABLE, NO_HDCP_STATE -> HDCP_STATE_IDLE, AUTH_DONE -> HDCP_STATE_AUTH_DONE, AUTH_FAIL -> HDCP_STATE_AUTH_FAIL * Rename it6162_infoblock_complete() to it6162_wait_command_complete() * Rename it6162_infoblock_host_set() to it6162_infoblock_trigger() * Remove it6162_infoblock_mipi_config() and it6162_infoblock_write_msg() wrappers, inline into it6162_reset_init() and it6162_mipi_set_video_timing() respectively * Remove it6162_set_default_config() wrapper, inline into it6162_reset_init() * Fix typo: hdcp_encyption -> hdcp_encryption * Fix typo: it6162_hdcp_read_infomation -> it6162_hdcp_read_information * Remove dev_err_probe() usage outside of probe path * Remove verbose success-path dev_info/dev_dbg logging throughout * Replace __func__ usage in error messages with descriptive strings * Fix double error printing in probe for it6162_init_pdata failure path * Fix uninitialized variable warning: initialize cp_status to DRM_MODE_CONTENT_PROTECTION_DESIRED at declaration; move drm_hdcp_update_content_protection() inside the state-change block * Fix audio sample width mapping: case 20 now maps to WORD_LENGTH_20BIT, case 24 to WORD_LENGTH_24BIT * Remove stray drm_dbg("it6162_bridge_atomic_disable") call * Remove drm_dbg() calls from it6162_display_mode_to_settings() * Drop unused struct it6162 * parameter from it6162_avi_to_video_setting() and it6162_display_mode_to_settings() * Fold it6162_set_default_config() body directly into it6162_reset_init(), removing the wrapper * Fix it6162_infoblock_request_data(): split command complete polling and buffer status check into two steps; use wait_event_timeout() for data_buf_sts since it is updated asynchronously by the interrupt handler; add wait_queue_head_t data_buf_wait to struct it6162 and wake_up() in interrupt handler --- MAINTAINERS | 7 + drivers/gpu/drm/bridge/Kconfig | 17 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ite-it6162.c | 1631 +++++++++++++++++++++++++++++++++++ 4 files changed, 1656 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 30f3472c72933c93b9237d93ad35802d6dda75f1..9c6e7c02227e810f280d2bfaa73e0eb572db0119 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13640,6 +13640,13 @@ W: https://linuxtv.org Q: http://patchwork.linuxtv.org/project/linux-media/list/ F: drivers/media/tuners/it913x* +ITE IT6162 MIPI DSI TO HDMI BRIDGE DRIVER +M: Hermes Wu <Hermes.wu@ite.com.tw> +L: dri-devel@lists.freedesktop.org +S: Maintained +F: Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml +F: drivers/gpu/drm/bridge/ite-it6162.c + ITE IT6263 LVDS TO HDMI BRIDGE DRIVER M: Liu Ying <victor.liu@nxp.com> L: dri-devel@lists.freedesktop.org diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 1cabfa1d2b2efc6fca68619eea6050fe96a892e8..1a8fe59506cf60958babb48429b842ca3aecc755 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -107,6 +107,23 @@ config DRM_INNO_HDMI select DRM_DISPLAY_HELPER select DRM_KMS_HELPER +config DRM_ITE_IT6162 + tristate "iTE IT6162 DSI to HDMI bridge" + depends on OF + select REGMAP_I2C + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_HDMI_HELPER + select DRM_DISPLAY_HDMI_HELPER + select DRM_DISPLAY_HDCP_HELPER + select DRM_DISPLAY_HELPER + help + Driver for iTE IT6162 DSI to HDMI bridge + chip driver that converts DSI to HDMI signals + support up to 4k60 with 2 MIPI DSI + Please say Y if you have such hardware. + config DRM_ITE_IT6263 tristate "ITE IT6263 LVDS/HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index fb0cf0bf88756bed8323830c80f3f1d066b51e36..3a199b27b3bbe8294a5bef6c82cfee4cd7b8a34e 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -11,6 +11,7 @@ tda998x-y := tda998x_drv.o obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o obj-$(CONFIG_DRM_INNO_HDMI) += inno-hdmi.o +obj-$(CONFIG_DRM_ITE_IT6162) += ite-it6162.o obj-$(CONFIG_DRM_ITE_IT6263) += ite-it6263.o obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o diff --git a/drivers/gpu/drm/bridge/ite-it6162.c b/drivers/gpu/drm/bridge/ite-it6162.c new file mode 100644 index 0000000000000000000000000000000000000000..fc71de1863144deebc1d884629be5352643e6af8 --- /dev/null +++ b/drivers/gpu/drm/bridge/ite-it6162.c @@ -0,0 +1,1631 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2024 ITE Tech. Inc. + */ +#include <linux/bitfield.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_irq.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <drm/display/drm_hdcp_helper.h> +#include <drm/display/drm_hdmi_helper.h> +#include <drm/display/drm_hdmi_state_helper.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_print.h> +#include <video/videomode.h> +#include <sound/hdmi-codec.h> + +#define DATA_BUFFER_DEPTH 32 + +#define OFFSET_CHIP_ID_L 0x00 +#define OFFSET_CHIP_ID_M 0x01 +#define OFFSET_CHIP_ID_H 0x02 +#define OFFSET_VERSION_L 0x03 +#define OFFSET_VERSION_M 0x04 +#define OFFSET_VERSION_H 0x05 + +#define OFFSET_MIPI_CONFIG_L 0x06 +#define MIPI_CLOCK_MODE_MASK BIT(6) +#define MIPI_PORT1_EN_MASK BIT(5) +#define MIPI_PORT0_EN_MASK BIT(4) +#define MIPI_LANE_SWAP_MASK BIT(3) +#define MIPI_PN_SWAP_MASK BIT(2) +#define MIPI_LANE_NUM_MASK 0x3 +#define MIPI_PORT_EN_MASK (MIPI_PORT1_EN_MASK | MIPI_PORT0_EN_MASK) + +#define OFFSET_MIPI_CONFIG_H 0x07 +#define OFFSET_TX_CONFIG 0x08 + +#define OFFSET_TX_SETTING 0x09 +#define TX_HDCP_VER_MASK 0xC0 +#define TX_HDCP_ENCRYP_MASK BIT(5) +#define TX_STREAM_TYPE_MASK BIT(4) + +#define OFFSET_MIPI_STATUS 0x0A +#define OFFSET_TX_STATUS 0x0C +#define B_TX_STATUS_HPD 7 +#define B_TX_STATUS_VIDEO_STB 6 +#define B_TX_STATUS_HDCP 4 +#define M_TX_STATUS_HDCP 0x30 + +#define TX_VIDEO_STB BIT(B_TX_STATUS_VIDEO_STB) +#define TX_STATUS_HPD BIT(B_TX_STATUS_HPD) + +#define GET_TX_HPD_STATUS(x) (((x) & TX_STATUS_HPD) >> B_TX_STATUS_HPD) +#define GET_TX_VIDEO_STATUS(x) (((x) & TX_VIDEO_STB) >> B_TX_STATUS_VIDEO_STB) +#define GET_TX_HDCP_STATUS(x) (((x) & M_TX_STATUS_HDCP) >> B_TX_STATUS_HDCP) + +#define OFFSET_SINK_CAP 0x0D +#define B_SINK_CAP_HDCP_VER 4 +#define M_SINK_CAP_HDCP_VER 0x30 + +#define GET_SINK_CAP_HDCP_VER(x) (((x) & M_SINK_CAP_HDCP_VER) >> B_SINK_CAP_HDCP_VER) + +#define OFFSET_DRIVER_DATA 0x0E + +#define OFFSET_DATA_TYPE_IDX 0x0F +#define OFFSET_DATA_BUFFER 0x20 + +#define OFFSET_AUDIO_CTRL0 0x3C +#define MASK_AUDIO_CHANNEL_NUM 0x0F +#define MASK_AUDIO_SELECT 0x30 +#define MASK_AUDIO_TYPE 0xC0 + +#define OFFSET_AUDIO_CTRL1 0x3D +#define OFFSET_AUDIO_CTRL2 0x3E +#define MASK_I2S_BUS_MODE 0x1F +#define MASK_I2S_WORD_LEN 0x60 +#define MASK_I2S_VALID 0x80 + +#define OFFSET_HOST_SET 0xFE +#define B_CONFIG_CHG BIT(7) +#define B_SET_CHG BIT(6) +#define HOST_SET_VIDEO_INFO (1) +#define HOST_SET_AUDIO_INFO (2) +#define HOST_SET_VIDEO_AUDIO_INFO (3) +#define HOST_SET_EDID_R (0x04) +#define HOST_SET_HDCP_R (0x05) +#define HOST_SET_CEA_INFOFRAME (0x0E) + +#define OFFSET_EVENT_CHG 0xFF +#define B_EVENT_CHG_BUFFER 4 +#define M_EVENT_CHG_BUFFER_STS (0x30) + +#define B_EVENT_CHG 1 +#define B_EVENT_IC_READY 0 + +#define EVENT_CHG BIT(B_EVENT_CHG) +#define EVENT_READY BIT(B_EVENT_IC_READY) + +#define GET_BUFFER_STATUS(x) (((x) & M_EVENT_CHG_BUFFER_STS) >> B_EVENT_CHG_BUFFER) + +#define TIMEOUT_INFOBLOCK_MS 1800 + +enum it6162_audio_select { + I2S = 0, + SPDIF, +}; + +enum it6162_audio_word_length { + WORD_LENGTH_16BIT = 0x0, + WORD_LENGTH_18BIT = 0x1, + WORD_LENGTH_20BIT = 0x2, + WORD_LENGTH_24BIT = 0x3, +}; + +enum it6162_audio_sample_rate { + SAMPLE_RATE_32K = 0x3, + SAMPLE_RATE_48K = 0x2, + SAMPLE_RATE_64K = 0xB, + SAMPLE_RATE_96K = 0xA, + SAMPLE_RATE_192K = 0xE, + SAMPLE_RATE_44_1K = 0x0, + SAMPLE_RATE_88_2K = 0x8, + SAMPLE_RATE_176_4K = 0xC, +}; + +enum it6162_audio_type { + LPCM = 0, + NLPCM, +}; + +enum data_buf_sts { + NO_STS = 0x00, + BUF_READY = 0x01, + BUF_FAIL = 0x02, +}; + +enum hdcp_state { + HDCP_STATE_IDLE = 0x00, + HDCP_STATE_AUTH_DONE = 0x01, + HDCP_STATE_AUTH_FAIL = 0x02, +}; + +enum hdcp_ver { + HDCP_DISABLE = 0x0, + HDCP_14 = 0x1, + HDCP_23 = 0x2, +}; + +struct it6162_chip_info { + u32 chip_id; + u32 version; +}; + +struct it6162_audio { + enum it6162_audio_select select; + enum it6162_audio_type type; + enum it6162_audio_sample_rate sample_rate; + unsigned int audio_enable; + unsigned int sample_width; + unsigned int channel_number; + unsigned int user_cts; +}; + +struct it6162_video_settings { + u8 vic; + u32 clock; + u16 htotal; + u16 hfp; + u16 hsw; + u16 hbp; + u16 hdew; + u16 vtotal; + u16 vfp; + u16 vsw; + u16 vbp; + u16 vdew; + u8 hpol; + u8 vpol; + u8 prog; + u16 v_aspect; + u16 h_aspect; + u8 pix_rep; + u8 colorspace; +}; + +enum sync_mode { + SYNC_EVENT = 0x0, + SYNC_PULSE = 0x1, + SYNC_AUTO = 0x2, +}; + +struct it6162_mipi_cfg { + bool en_port[2]; + u8 lane_num; + bool pn_swap; + bool lane_swap; + bool continuous_clk; + enum sync_mode mode; + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; +}; + +struct it6162_hdcp_cfg { + enum hdcp_ver hdcp_version; + bool hdcp_encryption; + u8 stream_ID; +}; + +struct it6162_infoblock_msg { + u8 action; + int len; + u8 msg[32]; +}; + +struct it6162 { + struct drm_bridge bridge; + struct device *dev; + struct drm_connector *connector; + enum drm_connector_status connector_status; + struct drm_device *drm; + + struct i2c_client *it6162_i2c; + struct regmap *regmap; + + struct regulator *pwr1833; + struct regulator *ovdd; + struct regulator *ivdd; + struct gpio_desc *gpiod_reset; + + bool power_on; + bool en_hdcp; + + /*lock the infoblock write setting and buffer access*/ + struct mutex lock; + + enum data_buf_sts data_buf_sts; + wait_queue_head_t data_buf_wait; + + /* it6162 DSI RX related params */ + struct mipi_dsi_device *dsi; + struct it6162_mipi_cfg mipi_cfg; + + struct delayed_work hdcp_work; + enum hdcp_state hdcp_sts; + enum hdcp_ver hdcp_version; + struct it6162_hdcp_cfg hdcp_cfg; + + unsigned int content_protection; +}; + +static struct it6162 *bridge_to_it6162(struct drm_bridge *bridge) +{ + return container_of(bridge, struct it6162, bridge); +} + +static const struct regmap_config it6162_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, +}; + +static inline int it6162_i2c_regmap_init(struct i2c_client *client, + struct it6162 *it6162) +{ + it6162->it6162_i2c = client; + i2c_set_clientdata(client, it6162); + it6162->regmap = devm_regmap_init_i2c(it6162->it6162_i2c, + &it6162_regmap_config); + if (IS_ERR(it6162->regmap)) + return PTR_ERR(it6162->regmap); + + return 0; +} + +static int it6162_wait_command_complete(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + struct regmap *regmap = it6162->regmap; + unsigned int val; + int ret; + + ret = regmap_read(regmap, OFFSET_HOST_SET, &val); + if (ret < 0) { + dev_err(dev, "read failed rx reg[0x%x]", OFFSET_HOST_SET); + return ret; + } + return val; +} + +static int it6162_infoblock_trigger(struct it6162 *it6162, u8 setting) +{ + struct device *dev = it6162->dev; + int status; + int val; + + regmap_write(it6162->regmap, OFFSET_HOST_SET, setting); + /* OFFSET_HOST_SET is cleared by firmware when the command is processed */ + status = readx_poll_timeout(it6162_wait_command_complete, it6162, val, + val <= 0, 50 * 1000, + TIMEOUT_INFOBLOCK_MS * 1000); + if (status < 0 || val != 0) { + dev_err(dev, "infoblock command timeout: status=%d val=%d", + status, val); + return -ETIMEDOUT; + } + + return 0; +} + +static int it6162_infoblock_request_data(struct it6162 *it6162, + u8 request_type, u8 index, u8 *buf) +{ + struct device *dev = it6162->dev; + int status; + + it6162->data_buf_sts = NO_STS; + regmap_write(it6162->regmap, OFFSET_DATA_TYPE_IDX, index); + status = it6162_infoblock_trigger(it6162, request_type); + if (status < 0) + return status; + /* data_buf_sts is updated asynchronously by the interrupt handler */ + if (wait_event_timeout(it6162->data_buf_wait, + it6162->data_buf_sts != NO_STS, + msecs_to_jiffies(TIMEOUT_INFOBLOCK_MS)) == 0) { + dev_err(dev, "timeout waiting for data buffer"); + return -ETIMEDOUT; + } + + if (it6162->data_buf_sts != BUF_READY) + return -EIO; + + regmap_bulk_read(it6162->regmap, OFFSET_DATA_BUFFER, buf, + DATA_BUFFER_DEPTH); + return 0; +} + +static void it6162_mipi_disable(struct it6162 *it6162) +{ + guard(mutex)(&it6162->lock); + regmap_update_bits(it6162->regmap, OFFSET_MIPI_CONFIG_L, + MIPI_PORT_EN_MASK, 0x00); + it6162_infoblock_trigger(it6162, B_CONFIG_CHG); +} + +static void it6162_mipi_enable(struct it6162 *it6162) +{ + unsigned int cfg_val = 0; + + cfg_val = FIELD_PREP(MIPI_PORT1_EN_MASK, it6162->mipi_cfg.en_port[1]) | + FIELD_PREP(MIPI_PORT0_EN_MASK, it6162->mipi_cfg.en_port[0]); + + guard(mutex)(&it6162->lock); + regmap_update_bits(it6162->regmap, OFFSET_MIPI_CONFIG_L, + MIPI_PORT_EN_MASK, cfg_val); + it6162_infoblock_trigger(it6162, B_CONFIG_CHG); +} + +static void it6162_tx_hdcp_enable(struct it6162 *it6162, + struct it6162_hdcp_cfg *tx_out) +{ + int val; + struct regmap *regmap = it6162->regmap; + + guard(mutex)(&it6162->lock); + it6162->hdcp_sts = HDCP_STATE_IDLE; + + regmap_read(regmap, OFFSET_TX_SETTING, &val); + val &= 0x0F; + val |= FIELD_PREP(TX_HDCP_VER_MASK, tx_out->hdcp_version) | + FIELD_PREP(TX_HDCP_ENCRYP_MASK, tx_out->hdcp_encryption) | + FIELD_PREP(TX_STREAM_TYPE_MASK, tx_out->stream_ID); + + regmap_write(regmap, OFFSET_TX_SETTING, val); + it6162_infoblock_trigger(it6162, B_SET_CHG); +} + +static void it6162_tx_hdcp_disable(struct it6162 *it6162) +{ + int val; + struct regmap *regmap = it6162->regmap; + + it6162->hdcp_sts = HDCP_STATE_IDLE; + it6162->hdcp_version = HDCP_DISABLE; + + guard(mutex)(&it6162->lock); + regmap_read(regmap, OFFSET_TX_SETTING, &val); + regmap_write(it6162->regmap, OFFSET_TX_SETTING, val & 0x0F); + it6162_infoblock_trigger(it6162, B_SET_CHG); +} + +static void +it6162_tx_hdcp_setup(struct it6162 *it6162, u8 ver, bool encryption) +{ + struct it6162_hdcp_cfg tx_out; + + if (ver != HDCP_DISABLE) { + tx_out.hdcp_version = ver; + tx_out.hdcp_encryption = encryption; + tx_out.stream_ID = 0; + it6162_tx_hdcp_enable(it6162, &tx_out); + } else { + it6162_tx_hdcp_disable(it6162); + } +} + +static void it6162_tx_enable(struct it6162 *it6162) +{ + if (it6162->en_hdcp) + it6162->hdcp_version = it6162->hdcp_cfg.hdcp_version; + else + it6162->hdcp_version = HDCP_DISABLE; +} + +static void it6162_tx_disable(struct it6162 *it6162) +{ + cancel_delayed_work_sync(&it6162->hdcp_work); + it6162_tx_hdcp_setup(it6162, HDCP_DISABLE, false); +} + +static inline void +it6162_pack_video_setting(struct it6162 *it6162, + struct it6162_video_settings *settings, + struct it6162_infoblock_msg *msg) +{ + msg->msg[0x00] = settings->hdew & 0xFF; + msg->msg[0x01] = (settings->hdew >> 8) & 0x3F; + msg->msg[0x02] = settings->vdew & 0xFF; + msg->msg[0x03] = (settings->vdew >> 8) & 0x3F; + msg->msg[0x04] = settings->clock & 0xFF; + msg->msg[0x05] = (settings->clock >> 8) & 0xFF; + msg->msg[0x06] = (settings->clock >> 16) & 0xFF; + msg->msg[0x07] = (settings->clock >> 24) & 0xFF; + msg->msg[0x08] = settings->hfp & 0xFF; + msg->msg[0x09] = (settings->hfp >> 8) & 0x3F; + msg->msg[0x0A] = settings->hsw & 0xFF; + msg->msg[0x0B] = (settings->hsw >> 8) & 0x3F; + msg->msg[0x0C] = settings->hbp & 0xFF; + msg->msg[0x0D] = (settings->hbp >> 8) & 0x3F; + msg->msg[0x0E] = settings->vfp & 0xFF; + msg->msg[0x0F] = (settings->vfp >> 8) & 0x3F; + msg->msg[0x10] = settings->vsw & 0xFF; + msg->msg[0x11] = (settings->vsw >> 8) & 0x3F; + msg->msg[0x12] = settings->vbp & 0xFF; + msg->msg[0x13] = (settings->vbp >> 8) & 0x3F; + msg->msg[0x14] = (settings->prog << 2) | + (settings->vpol << 1) | + settings->hpol; + + msg->msg[0x15] = settings->v_aspect; + msg->msg[0x16] = settings->h_aspect & 0xFF; + msg->msg[0x17] = settings->h_aspect >> 8; + msg->msg[0x18] = settings->pix_rep; + msg->msg[0x19] = settings->vic; + msg->msg[0x1A] = settings->colorspace; + msg->msg[0x1B] = 1; + + msg->len = 0x1C; + msg->action = HOST_SET_VIDEO_INFO; +} + +static void it6162_mipi_set_video_timing(struct it6162 *it6162, + struct it6162_video_settings *mode) +{ + struct it6162_infoblock_msg msg; + + it6162_pack_video_setting(it6162, mode, &msg); + guard(mutex)(&it6162->lock); + regmap_bulk_write(it6162->regmap, OFFSET_DATA_BUFFER, &msg.msg, + msg.len); + it6162_infoblock_trigger(it6162, msg.action); +} + +static int +it6162_hdcp_read_information(struct it6162 *it6162, u8 *status, u8 *bksv) +{ + u8 buf[DATA_BUFFER_DEPTH]; + int ret; + + guard(mutex)(&it6162->lock); + ret = it6162_infoblock_request_data(it6162, HOST_SET_HDCP_R, 0x00, + buf); + if (ret < 0) + return ret; + + memcpy(status, buf, 2); + memcpy(bksv, buf + 2, 5); + return 0; +} + +static int it6162_hdcp_read_list(struct it6162 *it6162, + u8 *ksv_list, + int dev_count) +{ + u8 buf[DATA_BUFFER_DEPTH]; + int i, j, ret; + + if (!ksv_list || dev_count <= 0) + return -EINVAL; + + guard(mutex)(&it6162->lock); + for (i = 0; i < (dev_count / 6 + 1); i++) { + ret = it6162_infoblock_request_data(it6162, HOST_SET_HDCP_R, + i + 1, buf); + if (ret < 0) + return ret; + + for (j = 0; j < 30; j += 5) { + if ((i * 6 + j / 5) >= dev_count) + break; + + memcpy(&ksv_list[(i * 6 + j / 5) * 5], &buf[j], 5); + } + } + return dev_count; +} + +static void it6162_update_hdcp14(struct it6162 *it6162) +{ + struct drm_device *drm = it6162->drm; + int dev_count; + u8 bksv[5]; + u8 bstatus[2]; + u8 ksvlist[5 * 30]; + int ret; + + ret = it6162_hdcp_read_information(it6162, bstatus, bksv); + if (ret < 0) { + drm_err(drm, "fail to read Bstatus"); + return; + } + + dev_count = DRM_HDCP_NUM_DOWNSTREAM(bstatus[0]); + if (dev_count == 0) + return; + + if (DRM_HDCP_MAX_DEVICE_EXCEEDED(bstatus[0]) || + DRM_HDCP_MAX_CASCADE_EXCEEDED(bstatus[1])) { + drm_err(drm, "HDCP1.4 Max Topology Limit Exceeded"); + return; + } + + dev_count = dev_count > 30 ? 30 : dev_count; + ret = it6162_hdcp_read_list(it6162, ksvlist, dev_count); + if (ret < 0) { + drm_err(drm, "fail to read KSV list"); + return; + } +} + +static void it6162_update_hdcp23(struct it6162 *it6162) +{ + struct drm_device *drm = it6162->drm; + int dev_count; + u8 rxid[5]; + u8 rxinfo[2]; + u8 rxid_list[5 * 30]; + int ret; + + ret = it6162_hdcp_read_information(it6162, rxinfo, rxid); + if (ret < 0) { + drm_err(drm, "fail to read Rxinfo"); + return; + } + + dev_count = (HDCP_2_2_DEV_COUNT_HI(rxinfo[0]) << 4 | + HDCP_2_2_DEV_COUNT_LO(rxinfo[1])); + if (dev_count == 0) + return; + + if (HDCP_2_2_MAX_CASCADE_EXCEEDED(rxinfo[1]) || + HDCP_2_2_MAX_DEVS_EXCEEDED(rxinfo[1])) { + drm_err(drm, "HDCP2.3 Topology Max Size Exceeded"); + return; + } + + dev_count = dev_count > 30 ? 30 : dev_count; + ret = it6162_hdcp_read_list(it6162, rxid_list, dev_count); + if (ret < 0) { + drm_err(drm, "fail to read RxID list"); + return; + } +} + +static void it6162_update_hdcp(struct it6162 *it6162, u8 version) +{ + if (version == HDCP_23) + it6162_update_hdcp23(it6162); + else + it6162_update_hdcp14(it6162); +} + +static void it6162_hdcp_handler(struct it6162 *it6162) +{ + struct regmap *regmap = it6162->regmap; + unsigned int tx_status, sink_cap; + enum hdcp_state hdcp_sts; + struct it6162_hdcp_cfg *hdcp_cfg = &it6162->hdcp_cfg; + u8 hdcp_ver; + u64 cp_status = DRM_MODE_CONTENT_PROTECTION_DESIRED; + + if (hdcp_cfg->hdcp_version == HDCP_DISABLE || !it6162->en_hdcp) + return; + + if (it6162->hdcp_version != HDCP_DISABLE) { + regmap_read(regmap, OFFSET_TX_STATUS, &tx_status); + regmap_read(regmap, OFFSET_SINK_CAP, &sink_cap); + if (!GET_TX_VIDEO_STATUS(tx_status)) { + drm_dbg(it6162->drm, "video not stable, skip hdcp check"); + return; + } + + hdcp_sts = GET_TX_HDCP_STATUS(tx_status); + hdcp_ver = GET_SINK_CAP_HDCP_VER(sink_cap); + if (it6162->hdcp_sts != hdcp_sts || + it6162->hdcp_sts == HDCP_STATE_IDLE) { + it6162->hdcp_sts = hdcp_sts; + switch (hdcp_sts) { + case HDCP_STATE_AUTH_DONE: + it6162_update_hdcp(it6162, hdcp_ver); + cp_status = DRM_MODE_CONTENT_PROTECTION_ENABLED; + break; + case HDCP_STATE_AUTH_FAIL: + if (hdcp_ver == HDCP_23) + it6162_tx_hdcp_setup(it6162, HDCP_14, + true); + else + it6162_tx_hdcp_disable(it6162); + + break; + default: + it6162_tx_hdcp_setup(it6162, + it6162->hdcp_version, + true); + break; + } + drm_hdcp_update_content_protection(it6162->connector, + cp_status); + } + } +} + +static void it6162_interrupt_handler(struct it6162 *it6162) +{ + unsigned int int_status, tx_status; + enum drm_connector_status connector_status; + struct device *dev = it6162->dev; + struct regmap *regmap = it6162->regmap; + int err; + + err = regmap_read(regmap, OFFSET_EVENT_CHG, &int_status); + if (err < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", + OFFSET_EVENT_CHG, err); + return; + } + + regmap_write(it6162->regmap, OFFSET_EVENT_CHG, 0xFF); + + if (!!GET_BUFFER_STATUS(int_status)) { + regmap_write(it6162->regmap, OFFSET_DRIVER_DATA, int_status); + it6162->data_buf_sts = GET_BUFFER_STATUS(int_status); + wake_up(&it6162->data_buf_wait); + } + + if (!(int_status & EVENT_CHG)) + return; + + err = regmap_read(regmap, OFFSET_TX_STATUS, &tx_status); + if (err < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", + OFFSET_TX_STATUS, err); + return; + } + + connector_status = GET_TX_HPD_STATUS(tx_status) ? + connector_status_connected : + connector_status_disconnected; + + if (it6162->connector_status != connector_status) { + it6162->connector_status = connector_status; + drm_bridge_hpd_notify(&it6162->bridge, + it6162->connector_status); + } + + if (it6162->en_hdcp && GET_TX_VIDEO_STATUS(tx_status) && + connector_status == connector_status_connected) + queue_delayed_work(system_wq, &it6162->hdcp_work, + msecs_to_jiffies(2000)); +} + +static int it6162_wait_ready_event(struct it6162 *it6162) +{ + unsigned int val; + int ret; + struct device *dev = it6162->dev; + struct regmap *regmap = it6162->regmap; + + ret = regmap_read(regmap, OFFSET_EVENT_CHG, &val); + if (ret < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", + OFFSET_EVENT_CHG, ret); + return ret; + } + + return val & EVENT_READY; +} + +static bool it6162_wait_mcu_ready(struct it6162 *it6162) +{ + int ret, val; + + ret = readx_poll_timeout(it6162_wait_ready_event, it6162, val, + val == EVENT_READY, 100 * 1000, 1500 * 1000); + if (ret) { + dev_err(it6162->dev, "timeout waiting for MCU ready"); + return false; + } + + return true; +} + +static void it6162_reset_init(struct it6162 *it6162) +{ + struct it6162_mipi_cfg *cfg = &it6162->mipi_cfg; + u8 cfg_val = 0; + + cfg_val = FIELD_PREP(MIPI_CLOCK_MODE_MASK, cfg->continuous_clk) | + FIELD_PREP(MIPI_LANE_SWAP_MASK, cfg->lane_swap) | + FIELD_PREP(MIPI_PN_SWAP_MASK, cfg->pn_swap) | + FIELD_PREP(MIPI_LANE_NUM_MASK, cfg->lane_num - 1); + + guard(mutex)(&it6162->lock); + regmap_write(it6162->regmap, OFFSET_MIPI_CONFIG_L, cfg_val); + regmap_write(it6162->regmap, OFFSET_MIPI_CONFIG_H, cfg->mode); + regmap_write(it6162->regmap, OFFSET_TX_CONFIG, 0x00); + regmap_write(it6162->regmap, OFFSET_TX_SETTING, 0x00); + it6162_infoblock_trigger(it6162, B_CONFIG_CHG | B_SET_CHG); +} + +static int it6162_platform_set_power(struct it6162 *it6162) +{ + int err; + + err = regulator_enable(it6162->ivdd); + if (err) + return err; + + err = regulator_enable(it6162->pwr1833); + if (err) + return err; + + err = regulator_enable(it6162->ovdd); + if (err) + return err; + + usleep_range(10000, 20000); + gpiod_set_value_cansleep(it6162->gpiod_reset, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(it6162->gpiod_reset, 0); + usleep_range(10000, 20000); + + return 0; +} + +static int it6162_platform_clear_power(struct it6162 *it6162) +{ + int err; + + err = regulator_disable(it6162->ivdd); + if (err) + return err; + + usleep_range(2000, 3000); + + err = regulator_disable(it6162->pwr1833); + if (err) + return err; + + err = regulator_disable(it6162->ovdd); + if (err) + return err; + + return 0; +} + +static int it6162_detect_devices(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + const struct it6162_chip_info *chip_info; + u32 chip_id, version; + u8 buf[6]; + + if (it6162_platform_set_power(it6162) < 0) + return -ENODEV; + + /* Firmware must finish booting before register access is valid */ + if (!it6162_wait_mcu_ready(it6162)) + return -ENODEV; + + chip_info = of_device_get_match_data(dev); + regmap_bulk_read(it6162->regmap, OFFSET_CHIP_ID_L, &buf[0], 6); + chip_id = (buf[0] << 16) | (buf[1] << 8) | (buf[2]); + version = (buf[3] << 16) | (buf[4] << 8) | (buf[5]); + if (chip_id == chip_info->chip_id && version >= chip_info->version) + return 0; + + dev_err(dev, "chip mismatch: id=0x%06x ver=0x%06x", chip_id, version); + return -ENODEV; +} + +static int __maybe_unused it6162_poweron(struct it6162 *it6162) +{ + int err; + + err = it6162_platform_set_power(it6162); + if (err < 0) + return err; + + if (!it6162_wait_mcu_ready(it6162)) + return -ENODEV; + + it6162->connector_status = connector_status_disconnected; + it6162_reset_init(it6162); + enable_irq(it6162->it6162_i2c->irq); + it6162->power_on = true; + return 0; +} + +static int __maybe_unused it6162_poweroff(struct it6162 *it6162) +{ + int err; + + disable_irq(it6162->it6162_i2c->irq); + err = it6162_platform_clear_power(it6162); + if (err < 0) + return err; + + it6162->power_on = false; + return 0; +} + +static void it6162_config_default(struct it6162 *it6162) +{ + struct it6162_mipi_cfg *mipi_cfg = &it6162->mipi_cfg; + struct it6162_hdcp_cfg *hdcp_cfg = &it6162->hdcp_cfg; + + mipi_cfg->lane_num = 4; + mipi_cfg->pn_swap = false; + mipi_cfg->lane_swap = false; + mipi_cfg->en_port[0] = false; + mipi_cfg->en_port[1] = false; + mipi_cfg->continuous_clk = true; + mipi_cfg->mode = SYNC_EVENT; + mipi_cfg->format = MIPI_DSI_FMT_RGB888; + mipi_cfg->mode_flags = MIPI_DSI_MODE_VIDEO; + + it6162->en_hdcp = false; + hdcp_cfg->hdcp_version = HDCP_23; + hdcp_cfg->hdcp_encryption = true; + hdcp_cfg->stream_ID = 0; + + it6162->connector_status = connector_status_disconnected; +} + +static enum drm_connector_status it6162_detect(struct it6162 *it6162) +{ + struct regmap *regmap = it6162->regmap; + unsigned int tx_status; + + regmap_read(regmap, OFFSET_TX_STATUS, &tx_status); + it6162->connector_status = GET_TX_HPD_STATUS(tx_status) ? + connector_status_connected : + connector_status_disconnected; + return it6162->connector_status; +} + +static int it6162_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + struct it6162 *it6162 = data; + unsigned int i, cnt; + int ret; + u8 config; + + if (len > EDID_LENGTH) + return -EINVAL; + + guard(mutex)(&it6162->lock); + for (i = 0, cnt = 0; i < EDID_LENGTH; i += DATA_BUFFER_DEPTH, cnt++) { + config = (block << 2) | (cnt); + ret = it6162_infoblock_request_data(it6162, HOST_SET_EDID_R, + config, buf + i); + if (ret < 0) + return -EIO; + } + + return 0; +} + +static void it6162_enable_audio(struct it6162 *it6162, + struct it6162_audio *config) +{ + unsigned int val; + + guard(mutex)(&it6162->lock); + val = FIELD_PREP(MASK_AUDIO_CHANNEL_NUM, config->channel_number) | + FIELD_PREP(MASK_AUDIO_SELECT, config->select) | + FIELD_PREP(MASK_AUDIO_TYPE, config->type); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL0, val); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL1, config->sample_rate); + + val = FIELD_PREP(MASK_I2S_BUS_MODE, 0) | + FIELD_PREP(MASK_I2S_WORD_LEN, config->sample_width) | + FIELD_PREP(MASK_I2S_VALID, 1); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL2, val); + it6162_infoblock_trigger(it6162, HOST_SET_AUDIO_INFO); +} + +static void it6162_disable_audio(struct it6162 *it6162) +{ + guard(mutex)(&it6162->lock); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL0, 0x00); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL1, 0x00); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL2, 0x00); + it6162_infoblock_trigger(it6162, HOST_SET_AUDIO_INFO); +} + +static irqreturn_t it6162_int_threaded_handler(int unused, void *data) +{ + struct it6162 *it6162 = data; + + it6162_interrupt_handler(it6162); + return IRQ_HANDLED; +} + +static void it6162_hdcp_work(struct work_struct *work) +{ + struct it6162 *it6162 = container_of(work, struct it6162, + hdcp_work.work); + + it6162_hdcp_handler(it6162); +} + +static struct mipi_dsi_host * +it6162_of_get_dsi_host_by_port(struct it6162 *it6162, int port) +{ + struct device_node *of = it6162->dev->of_node; + struct device_node *host_node; + struct device_node *endpoint; + struct mipi_dsi_host *dsi_host; + + endpoint = of_graph_get_endpoint_by_regs(of, port, -1); + if (!endpoint) + return ERR_PTR(-ENODEV); + + host_node = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!host_node) + return ERR_PTR(-ENODEV); + + dsi_host = of_find_mipi_dsi_host_by_node(host_node); + of_node_put(host_node); + if (!dsi_host) + return ERR_PTR(-EPROBE_DEFER); + + return dsi_host; +} + +static int it6162_of_get_dsi_host(struct it6162 *it6162) +{ + struct mipi_dsi_host *dsi_host; + int port, host_count = 0; + + for (port = 0; port < 2; port++) { + dsi_host = it6162_of_get_dsi_host_by_port(it6162, port); + if (IS_ERR(dsi_host)) { + if (PTR_ERR(dsi_host) == -EPROBE_DEFER) + return -EPROBE_DEFER; + continue; + } + + host_count++; + } + + return host_count == 0 ? -ENODEV : 0; +} + +static int it6162_load_mipi_pars_from_port(struct it6162 *it6162, int port) +{ + struct device_node *of = it6162->dev->of_node; + struct device_node *endpoint; + struct it6162_mipi_cfg *mipicfg = &it6162->mipi_cfg; + u32 data_lanes[4] = {1}; + u32 lane_polarities[5] = {0}; + int dsi_lanes; + + endpoint = of_graph_get_endpoint_by_regs(of, port, -1); + if (!endpoint) + return 0; + + dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4); + if (dsi_lanes < 0) + return dsi_lanes; + + mipicfg->lane_num = dsi_lanes; + + of_property_read_u32_array(endpoint, "data-lanes", data_lanes, + dsi_lanes); + /* only supports full reversal of all lanes, not arbitrary remapping */ + if (data_lanes[0] == 4) + mipicfg->lane_swap = true; + + of_property_read_u32_array(endpoint, "lane-polarities", + lane_polarities, dsi_lanes + 1); + /* applies Dp/Dn swap globally; mixing polarities across lanes is invalid */ + if (lane_polarities[0] != 0) + mipicfg->pn_swap = true; + + if (of_property_present(endpoint, "clock-noncontinuous")) { + mipicfg->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS; + mipicfg->continuous_clk = false; + } + + of_node_put(endpoint); + return dsi_lanes; +} + +static int it6162_attach_dsi(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + struct device_node *np = dev->of_node; + const struct mipi_dsi_device_info info = {"it6162-mipi", 0, np}; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + struct it6162_mipi_cfg *mipi_cfg = &it6162->mipi_cfg; + int ret = 0; + + it6162->dsi = NULL; + for (int port = 0; port < 2; port++) { + host = it6162_of_get_dsi_host_by_port(it6162, port); + if (IS_ERR(host)) + continue; + + mipi_cfg->en_port[port] = true; + + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to create dsi device"); + return PTR_ERR(dsi); + } + /* uses a single DSI configuration for both ports in dual-link mode */ + if (!it6162->dsi) { + ret = it6162_load_mipi_pars_from_port(it6162, port); + if (ret <= 0) + return ret; + it6162->dsi = dsi; + } + + dsi->lanes = mipi_cfg->lane_num; + dsi->format = mipi_cfg->format; + dsi->mode_flags = mipi_cfg->mode_flags; + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret) { + dev_err(dev, "failed to attach dsi device %d", port); + return ret; + } + } + + it6162_poweron(it6162); + return 0; +} + +static bool it6162_of_get_audio(struct it6162 *it6162) +{ + struct device_node *np = it6162->dev->of_node; + struct device_node *audio_port; + + audio_port = of_graph_get_port_by_id(np, 2); + if (audio_port) { + of_node_put(audio_port); + return true; + } + + return false; +} + +static void it6162_parse_dt(struct it6162 *it6162) +{ + struct device_node *np = it6162->dev->of_node; + + it6162->en_hdcp = of_property_present(np, "ite,support-hdcp"); +} + +static int it6162_init_pdata(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + + it6162->ivdd = devm_regulator_get(dev, "ivdd"); + if (IS_ERR(it6162->ivdd)) + return dev_err_probe(dev, PTR_ERR(it6162->ivdd), + "failed to get ivdd regulator"); + + it6162->pwr1833 = devm_regulator_get(dev, "ovdd1833"); + if (IS_ERR(it6162->pwr1833)) + return dev_err_probe(dev, PTR_ERR(it6162->pwr1833), + "failed to get ovdd1833 regulator"); + + it6162->ovdd = devm_regulator_get(dev, "ovdd"); + if (IS_ERR(it6162->ovdd)) + return dev_err_probe(dev, PTR_ERR(it6162->ovdd), + "failed to get ovdd regulator"); + + it6162->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(it6162->gpiod_reset)) + return dev_err_probe(dev, PTR_ERR(it6162->gpiod_reset), + "failed to get reset gpio\n"); + + return 0; +} + +static int it6162_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + struct drm_device *drm = bridge->dev; + + it6162->drm = drm; + if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { + drm_dbg(drm, "it6162 driver only copes with atomic updates"); + return -EOPNOTSUPP; + } + + return 0; +} + +static enum drm_mode_status +it6162_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + unsigned long long rate; + + rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB); + if (rate == 0) + return MODE_NOCLOCK; + + return bridge->funcs->hdmi_tmds_char_rate_valid(bridge, mode, rate); +} + +static enum drm_connector_status +it6162_bridge_detect(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162->power_on ? it6162_detect(it6162) : + connector_status_disconnected; +} + +static inline void +it6162_avi_to_video_setting(struct hdmi_avi_infoframe *avi_info, + struct it6162_video_settings *video) +{ + video->vic = avi_info->video_code; + + switch (avi_info->picture_aspect) { + case HDMI_PICTURE_ASPECT_4_3: + video->h_aspect = 4; + video->v_aspect = 3; + break; + case HDMI_PICTURE_ASPECT_16_9: + video->h_aspect = 16; + video->v_aspect = 9; + break; + case HDMI_PICTURE_ASPECT_64_27: + video->h_aspect = 64; + video->v_aspect = 27; + break; + case HDMI_PICTURE_ASPECT_256_135: + video->h_aspect = 256; + video->v_aspect = 135; + break; + default: + video->h_aspect = 4; + video->v_aspect = 3; + break; + } + + video->pix_rep = avi_info->pixel_repeat + 1; +} + +static inline void +it6162_display_mode_to_settings(struct drm_display_mode *mode, + struct it6162_video_settings *settings) +{ + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + settings->hpol = 1; + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + settings->vpol = 1; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + settings->prog = 0; + + settings->clock = mode->clock; + settings->hdew = mode->hdisplay; + settings->hfp = mode->hsync_start - mode->hdisplay; + settings->hsw = mode->hsync_end - mode->hsync_start; + settings->hbp = mode->htotal - mode->hsync_end; + settings->htotal = mode->htotal; + settings->vdew = mode->vdisplay; + settings->vfp = mode->vsync_start - mode->vdisplay; + settings->vsw = mode->vsync_end - mode->vsync_start; + settings->vbp = mode->vtotal - mode->vsync_end; + settings->vtotal = mode->vtotal; +} + +static void it6162_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_state *state) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + struct drm_connector *connector; + struct hdmi_avi_infoframe *avi; + struct it6162_video_settings video_setting; + + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + if (!connector) + return; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (!crtc_state) + return; + + it6162->connector = connector; + it6162->content_protection = conn_state->content_protection; + + drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); + avi = &conn_state->hdmi.infoframes.avi.data.avi; + video_setting.colorspace = conn_state->hdmi.output_format; + it6162_avi_to_video_setting(avi, &video_setting); + it6162_display_mode_to_settings(&crtc_state->mode, &video_setting); + + it6162_mipi_set_video_timing(it6162, &video_setting); + it6162_tx_enable(it6162); + it6162_mipi_enable(it6162); +} + +static int it6162_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *adj = &crtc_state->adjusted_mode; + struct drm_display_mode *mode = &crtc_state->mode; + struct it6162 *it6162 = bridge_to_it6162(bridge); + u32 hfp, hsw, hbp; + u32 clock; + u32 hfp_check; + + clock = mode->clock; + hfp = mode->hsync_start - mode->hdisplay; + hsw = mode->hsync_end - mode->hsync_start; + hbp = mode->htotal - mode->hsync_end; + + hfp_check = DIV_ROUND_UP(65 * clock, 1000000) + 4; + if (hfp >= hfp_check) + return 0; + + if (hbp > hfp_check - hfp) { + adj->hsync_start = adj->hdisplay + hfp_check; + adj->hsync_end = adj->hsync_start + hsw; + drm_dbg(it6162->drm, "adjusted hsync on MIPI bus: %u, %u", + adj->hsync_start, adj->hsync_end); + } + + return 0; +} + +static void it6162_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_state *state) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + it6162_tx_disable(it6162); + it6162_mipi_disable(it6162); +} + +static const struct drm_edid * +it6162_bridge_read_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return drm_edid_read_custom(connector, it6162_get_edid_block, it6162); +} + +static int it6162_audio_update_hw_params(struct it6162 *it6162, + struct it6162_audio *config, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + config->channel_number = hparms->channels; + config->type = LPCM; + + switch (hparms->sample_rate) { + case 32000: + config->sample_rate = SAMPLE_RATE_32K; + break; + case 44100: + config->sample_rate = SAMPLE_RATE_44_1K; + break; + case 48000: + config->sample_rate = SAMPLE_RATE_48K; + break; + case 88200: + config->sample_rate = SAMPLE_RATE_88_2K; + break; + case 96000: + config->sample_rate = SAMPLE_RATE_96K; + break; + case 176400: + config->sample_rate = SAMPLE_RATE_176_4K; + break; + case 192000: + config->sample_rate = SAMPLE_RATE_192K; + break; + default: + return -EINVAL; + } + + switch (hparms->sample_width) { + case 16: + config->sample_width = WORD_LENGTH_16BIT; + break; + case 20: + config->sample_width = WORD_LENGTH_20BIT; + break; + case 24: + config->sample_width = WORD_LENGTH_24BIT; + break; + default: + return -EINVAL; + } + + switch (fmt->fmt) { + case HDMI_I2S: + config->select = I2S; + break; + case HDMI_SPDIF: + config->select = SPDIF; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int it6162_bridge_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *params) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + struct it6162_audio config; + + it6162_audio_update_hw_params(it6162, &config, fmt, params); + it6162_enable_audio(it6162, &config); + return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, + ¶ms->cea); +} + +static int it6162_bridge_hdmi_audio_startup(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + return 0; +} + +static void it6162_bridge_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + it6162_disable_audio(it6162); +} + +static enum drm_mode_status +it6162_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge, + const struct drm_display_mode *mode, + unsigned long long tmds_rate) +{ + /*IT6162 hdmi supports HDMI2.0 600Mhz*/ + if (tmds_rate > 600000000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static inline int +it6162_write_infoframe(struct it6162 *it6162, const u8 *buffer, size_t len) +{ + if (len > DATA_BUFFER_DEPTH) + return -EINVAL; + + regmap_bulk_write(it6162->regmap, OFFSET_DATA_BUFFER, buffer, len); + regmap_write(it6162->regmap, OFFSET_DATA_TYPE_IDX, len); + it6162_infoblock_trigger(it6162, HOST_SET_CEA_INFOFRAME); + return 0; +} + +static inline int it6162_clear_infoframe(struct it6162 *it6162, u8 type) +{ + regmap_write(it6162->regmap, OFFSET_DATA_TYPE_IDX, 3); + regmap_write(it6162->regmap, OFFSET_DATA_BUFFER, type); + regmap_write(it6162->regmap, OFFSET_DATA_BUFFER + 1, 0x00); + regmap_write(it6162->regmap, OFFSET_DATA_BUFFER + 2, 0x00); + it6162_infoblock_trigger(it6162, HOST_SET_CEA_INFOFRAME); + return 0; +} + +static int +it6162_bridge_hdmi_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_write_infoframe(it6162, buffer, len); +} + +static int +it6162_bridge_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_clear_infoframe(it6162, HDMI_INFOFRAME_TYPE_AVI); +} + +static int +it6162_bridge_hdmi_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_write_infoframe(it6162, buffer, len); +} + +static int +it6162_bridge_hdmi_clear_audio_infoframe(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_clear_infoframe(it6162, HDMI_INFOFRAME_TYPE_AUDIO); +} + +static int +it6162_bridge_hdmi_write_spd_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_write_infoframe(it6162, buffer, len); +} + +static int +it6162_bridge_hdmi_clear_spd_infoframe(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_clear_infoframe(it6162, HDMI_INFOFRAME_TYPE_SPD); +} + +static const struct drm_bridge_funcs it6162_bridge_funcs = { + .attach = it6162_bridge_attach, + .mode_valid = it6162_bridge_mode_valid, + .detect = it6162_bridge_detect, + + .atomic_enable = it6162_bridge_atomic_enable, + .atomic_disable = it6162_bridge_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_check = it6162_bridge_atomic_check, + + .edid_read = it6162_bridge_read_edid, + + .hdmi_clear_avi_infoframe = it6162_bridge_hdmi_clear_avi_infoframe, + .hdmi_write_avi_infoframe = it6162_bridge_hdmi_write_avi_infoframe, + .hdmi_clear_spd_infoframe = it6162_bridge_hdmi_clear_spd_infoframe, + .hdmi_write_spd_infoframe = it6162_bridge_hdmi_write_spd_infoframe, + .hdmi_clear_audio_infoframe = it6162_bridge_hdmi_clear_audio_infoframe, + .hdmi_write_audio_infoframe = it6162_bridge_hdmi_write_audio_infoframe, + + .hdmi_tmds_char_rate_valid = it6162_hdmi_tmds_char_rate_valid, + .hdmi_audio_prepare = it6162_bridge_hdmi_audio_prepare, + .hdmi_audio_startup = it6162_bridge_hdmi_audio_startup, + .hdmi_audio_shutdown = it6162_bridge_hdmi_audio_shutdown, +}; + +static int it6162_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; + struct it6162 *it6162; + int ret; + + it6162 = devm_drm_bridge_alloc(dev, struct it6162, bridge, + &it6162_bridge_funcs); + if (IS_ERR(it6162)) + return PTR_ERR(it6162); + + it6162->dev = dev; + + ret = it6162_of_get_dsi_host(it6162); + if (ret < 0) + return ret; + + ret = it6162_i2c_regmap_init(client, it6162); + if (ret != 0) + return ret; + + ret = it6162_init_pdata(it6162); + if (ret) + return ret; + + it6162_config_default(it6162); + it6162_parse_dt(it6162); + + if (it6162_detect_devices(it6162) < 0) + return -ENODEV; + + if (!client->irq) { + dev_err(dev, "Failed to get INTP IRQ"); + return -ENODEV; + } + + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + it6162_int_threaded_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT | + IRQF_NO_AUTOEN, + "it6162-intp", it6162); + if (ret) + return ret; + + INIT_DELAYED_WORK(&it6162->hdcp_work, it6162_hdcp_work); + init_waitqueue_head(&it6162->data_buf_wait); + + mutex_init(&it6162->lock); + + it6162->bridge.of_node = np; + it6162->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_MODES; + + it6162->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + it6162->bridge.vendor = "ITE"; + it6162->bridge.product = "IT6162"; + + if (it6162_of_get_audio(it6162)) { + it6162->bridge.ops |= DRM_BRIDGE_OP_HDMI_AUDIO; + it6162->bridge.hdmi_audio_dev = dev; + it6162->bridge.hdmi_audio_max_i2s_playback_channels = 8; + it6162->bridge.hdmi_audio_dai_port = 2; + } + + devm_drm_bridge_add(dev, &it6162->bridge); + + return it6162_attach_dsi(it6162); +} + +static void it6162_remove(struct i2c_client *client) +{ + struct it6162 *it6162 = i2c_get_clientdata(client); + + disable_irq(client->irq); + cancel_delayed_work_sync(&it6162->hdcp_work); + mutex_destroy(&it6162->lock); +} + +static const struct it6162_chip_info it6162_chip_info = { + .chip_id = 0x616200, + .version = 0x006500, +}; + +static const struct of_device_id it6162_dt_ids[] = { + { .compatible = "ite,it6162", .data = &it6162_chip_info}, + { } +}; +MODULE_DEVICE_TABLE(of, it6162_dt_ids); + +static const struct i2c_device_id it6162_i2c_ids[] = { + { "it6162", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, it6162_i2c_ids); + +static struct i2c_driver it6162_driver = { + .driver = { + .name = "it6162", + .of_match_table = it6162_dt_ids, + }, + .probe = it6162_probe, + .remove = it6162_remove, + .id_table = it6162_i2c_ids, +}; +module_i2c_driver(it6162_driver); + +MODULE_AUTHOR("Pet Weng <pet.weng@ite.com.tw>"); +MODULE_AUTHOR("Hermes Wu <Hermes.Wu@ite.com.tw>"); +MODULE_DESCRIPTION("it6162 MIPI to HDMI driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Claude review: drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver 2026-03-13 6:16 ` [PATCH v3 2/2] drm/bridge: " Hermes Wu via B4 Relay @ 2026-03-13 21:27 ` Claude Code Review Bot 0 siblings, 0 replies; 11+ messages in thread From: Claude Code Review Bot @ 2026-03-13 21:27 UTC (permalink / raw) To: dri-devel-reviews Patch Review **Critical / Functional Issues:** 1. **`DRM_BRIDGE_OP_MODES` set without `get_modes` callback** (line 1572 in driver, corresponding to mbox line ~1783): ```c it6162->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_MODES; ``` The `DRM_BRIDGE_OP_MODES` flag requires implementing the `get_modes` callback per the bridge documentation (`include/drm/drm_bridge.h:1027`). The driver doesn't implement `get_modes` in `it6162_bridge_funcs`. This flag should either be removed (since `DRM_BRIDGE_OP_EDID` is sufficient to enumerate modes from EDID), or a `get_modes` callback should be added. 2. **`DRM_BRIDGE_OP_HPD` not set**: The driver calls `drm_bridge_hpd_notify()` from the interrupt handler (line 694) but doesn't set `DRM_BRIDGE_OP_HPD` in `bridge.ops`. The HPD notification path may not work correctly without this flag, as the connector infrastructure checks for it. 3. **`it6162_poweroff` is dead code** (line 842): ```c static int __maybe_unused it6162_poweroff(struct it6162 *it6162) ``` `it6162_poweroff` is never called anywhere. The `__maybe_unused` annotation hides the warning, but the driver has no power-off path — not in `remove`, not in any PM ops, nowhere. This means once powered on, the chip is never cleanly powered down. The `remove` callback only disables the IRQ and cancels the HDCP work. 4. **`it6162_platform_set_power` missing regulator cleanup on error** (lines 753-776): If enabling `pwr1833` fails, `ivdd` is left enabled. If enabling `ovdd` fails, both `ivdd` and `pwr1833` are left enabled. Should use `goto` error unwind or `regulator_bulk_*` APIs. 5. **`it6162_detect_devices` powers on but never powers off** (lines 799-822): This is called during probe to verify the chip ID. It calls `it6162_platform_set_power()` but if the chip ID matches, it returns 0 without powering back down. Later, `it6162_poweron()` in `it6162_attach_dsi` calls `it6162_platform_set_power()` again, enabling the regulators a second time (bumping their reference counts), which means a single disable won't actually turn them off. **Moderate Issues:** 6. **`it6162_bridge_hdmi_audio_prepare` ignores error from `it6162_audio_update_hw_params`** (lines 1388-1393): ```c it6162_audio_update_hw_params(it6162, &config, fmt, params); it6162_enable_audio(it6162, &config); ``` The return value of `it6162_audio_update_hw_params` is discarded. If it returns `-EINVAL` (unsupported rate/width/format), the driver proceeds with uninitialized `config` fields. 7. **`it6162_display_mode_to_settings` uses conditional sets without zeroing first** (lines 1208-1230): The function checks `DRM_MODE_FLAG_PHSYNC` to set `hpol=1` and `DRM_MODE_FLAG_NVSYNC` to set `vpol=1`, but never sets them to 0 for the opposite case. The `video_setting` struct on the stack in `it6162_bridge_atomic_enable` is not zeroed (no `= {}` or `memset`). The `prog` field is never set at all — it defaults to whatever garbage is on the stack, but should be 1 for progressive. 8. **`it6162_write_infoframe` and `it6162_clear_infoframe` not holding the lock** (lines 1422-1441): These functions write to the regmap and call `it6162_infoblock_trigger` but don't take `it6162->lock`, unlike similar functions (`it6162_enable_audio`, `it6162_mipi_enable`, etc.). This risks racing with other infoblock operations. 9. **`!!GET_BUFFER_STATUS(int_status)` in interrupt handler** (line 672): ```c if (!!GET_BUFFER_STATUS(int_status)) { ``` The double negation is unnecessary. Just `if (GET_BUFFER_STATUS(int_status))` is clearer. 10. **`regmap_read` return values mostly unchecked**: Many `regmap_read` calls have their return value ignored (e.g., in `it6162_tx_hdcp_enable` line 380, `it6162_hdcp_handler` line 618, `it6162_detect` line 883). While regmap errors are relatively uncommon on I2C, inconsistent error checking is poor practice — the driver checks in some places but not others. **Minor / Style Issues:** 11. **`video/videomode.h` included but unused**: The header `<video/videomode.h>` is included at line 26 but no videomode types or functions are used in the driver. 12. **`drm_bridge_connector.h` included but unused**: `<drm/drm_bridge_connector.h>` is included but the driver doesn't use `drm_bridge_connector_*` APIs directly. 13. **`of_irq.h` included but unused**: `<linux/of_irq.h>` is included but not needed; IRQ is obtained via `client->irq`. 14. **`pm_runtime.h` included but unused**: `<linux/pm_runtime.h>` is included but no PM runtime APIs are used. 15. **`it6162_i2c_regmap_init` is `inline` unnecessarily** (line 275): This function is called once from probe; `inline` is pointless and the compiler will decide on its own. 16. **`it6162_avi_to_video_setting` and `it6162_display_mode_to_settings` are `inline` unnecessarily** (lines 1174, 1206): Same as above — these are called once from `atomic_enable`. 17. **Chip ID read byte ordering** (lines 815-816): ```c chip_id = (buf[0] << 16) | (buf[1] << 8) | (buf[2]); ``` The register names suggest `buf[0]` is `CHIP_ID_L` (low byte) and `buf[2]` is `CHIP_ID_H` (high byte), but the code places `buf[0]` in the MSB position. This is either an endianness confusion or the register naming is non-standard. Either way, a comment would help. Same for `version`. 18. **`content_protection` field stored but never read back**: The driver stores `conn_state->content_protection` in `it6162->content_protection` (line 1256) but never reads it. 19. **Kconfig help text style**: The help text (lines 185-189) is a bit terse and inconsistent with the capitalization of "iTE" vs "ITE" used elsewhere. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 0/2] Add ITE IT6162 MIPI DSI to HDMI bridge driver
@ 2026-03-19 6:37 Hermes Wu via B4 Relay
2026-03-19 6:37 ` [PATCH v4 2/2] drm/bridge: " Hermes Wu via B4 Relay
0 siblings, 1 reply; 11+ messages in thread
From: Hermes Wu via B4 Relay @ 2026-03-19 6:37 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Pet.Weng, Kenneth.Hung, Hermes Wu, dri-devel, devicetree,
linux-kernel, Hermes Wu
This patch series adds support for the ITE IT6162 MIPI DSI to HDMI
bridge chip. The IT6162 is an I2C-controlled bridge that receives MIPI
DSI input and outputs HDMI signals.
The device supports the following configurations:
- Single MIPI DSI input: up to 4K @ 30Hz
- Dual MIPI DSI input (combined): up to 4K @ 60Hz
This series introduces:
- dt-bindings: Add YAML binding document for ITE IT6162
- drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver
Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw>
---
Changes in v4:
- Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml:
* Drop ite,support-hdcp
* Drop endpoint properties already in video-interfaces.yaml
* Fix data-lanes in example with <0 1 2 3>
* Add blanking line in example before endpoint subnode starts
* Add reset-gpio and power controls to required
- drivers/gpu/drm/bridge/ite-it6162.c:
* Drop struc it6162_audio
* Add HDCP_AUTO in enum hdcp_ver{}, FW enable HDCP by sink capability.
* Call drm_atomic_helper_connector_hdmi_clear_audio_infoframe()
in it6162_bridge_hdmi_audio_shutdown()
* Drop it6162_bridge_hdmi_audio_startup()
* Add hdmi_clear_hdmi_infoframe and hdmi_write_hdmi_infoframe
* Drop it6162_parse_dt() due to "ite,support-hdcp" is removed from DT.
* add DRM_BRIDGE_OP_HDMI to it6162->bridge.ops
- Link to v3: https://lore.kernel.org/r/20260313-upstream-6162-v3-0-8497fb7c4406@ite.com.tw
Changes in v3:
- Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml:
* Fix lane-polarities: remove $ref redefinition, allow via video-interfaces.yaml
* Improve ite,support-hdcp description to clarify FW-based HDCP behavior
* Remove descriptions repeating schema constraints
* Remove stray comment in ports required section
* Fix data-lanes spacing in example: <1 2 3 4>
* Add missing newline at end of file
* Add missing change log that Reviewed-by tag was removed when V1->V2
- drivers/gpu/drm/bridge/ite-it6162.c:
* Fix OFFSET_VERSION_H register offset from 0x03 to 0x05
* Add MIPI_PORT_EN_MASK macro combining MIPI_PORT1_EN_MASK and MIPI_PORT0_EN_MASK
* Rename HDCP enums: NO_HDCP -> HDCP_DISABLE, NO_HDCP_STATE -> HDCP_STATE_IDLE,
AUTH_DONE -> HDCP_STATE_AUTH_DONE, AUTH_FAIL -> HDCP_STATE_AUTH_FAIL
* Rename it6162_infoblock_complete() to it6162_wait_command_complete()
* Rename it6162_infoblock_host_set() to it6162_infoblock_trigger()
* Remove it6162_infoblock_mipi_config() and it6162_infoblock_write_msg()
wrappers, inline into it6162_reset_init() and
it6162_mipi_set_video_timing() respectively
* Remove it6162_set_default_config() wrapper, inline into
it6162_reset_init()
* Fix typo: hdcp_encyption -> hdcp_encryption
* Fix typo: it6162_hdcp_read_infomation -> it6162_hdcp_read_information
* Remove dev_err_probe() usage outside of probe path
* Remove verbose success-path dev_info/dev_dbg logging throughout
* Replace __func__ usage in error messages with descriptive strings
* Fix double error printing in probe for it6162_init_pdata failure path
* Fix uninitialized variable warning: initialize cp_status to
DRM_MODE_CONTENT_PROTECTION_DESIRED at declaration; move
drm_hdcp_update_content_protection() inside the state-change block
* Fix audio sample width mapping: case 20 now maps to WORD_LENGTH_20BIT,
case 24 to WORD_LENGTH_24BIT
* Remove stray drm_dbg("it6162_bridge_atomic_disable") call
* Remove drm_dbg() calls from it6162_display_mode_to_settings()
* Drop unused struct it6162 * parameter from it6162_avi_to_video_setting()
and it6162_display_mode_to_settings()
* Fold it6162_set_default_config() body directly into it6162_reset_init(),
removing the wrapper
* it6162_infoblock_request_data(): split command complete polling and
buffer status check into two steps; use wait_event_timeout() for
data_buf_sts since it is updated asynchronously by the interrupt
handler; add wait_queue_head_t data_buf_wait to struct it6162 and
wake_up() in interrupt handler
- Link to v2: https://lore.kernel.org/r/20260309-upstream-6162-v2-0-debdb6c88030@ite.com.tw
Changes in v2:
- dt-bindings:
* Drop Reviewed-by tag due to below changes
* Add property "ite,spport-hdcp" to enable HDCP
* Add property "lane-polarities" and "clock-noncontinuous" for DSI
setting
- drm/bridge:
* Drop unused element in struct it6162
* Remove regmap wrappers
* Use FIELD_PREP for bitfield operations
* Update HDCP status with drm_hdcp_update_content_protection()
* Add AVI, AUDIO, and SPD infoframe control
* Remove conversion from drm_display_mode to videomode
* Fix regulator/gpio error handling in it6162_init_pdata() to return proper error codes
- MAINTAINERS
* squash to driver patch
- Link to v1: https://lore.kernel.org/r/20260223-upstream-6162-v1-0-ebcc66ccb1fe@ite.com.tw
---
Hermes Wu (2):
dt-bindings: display: bridge: Add ITE IT6162 MIPI DSI to HDMI bridge
drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver
.../bindings/display/bridge/ite,it6162.yaml | 169 ++
MAINTAINERS | 7 +
drivers/gpu/drm/bridge/Kconfig | 17 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/ite-it6162.c | 1628 ++++++++++++++++++++
5 files changed, 1822 insertions(+)
---
base-commit: 2622649ad6cdbb3e77bfafc8c0fe686090b77f70
change-id: 20260223-upstream-6162-3751e78dfcad
Best regards,
--
Hermes Wu <Hermes.wu@ite.com.tw>
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v4 2/2] drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver 2026-03-19 6:37 [PATCH v4 0/2] " Hermes Wu via B4 Relay @ 2026-03-19 6:37 ` Hermes Wu via B4 Relay 2026-03-21 18:39 ` Claude review: " Claude Code Review Bot 0 siblings, 1 reply; 11+ messages in thread From: Hermes Wu via B4 Relay @ 2026-03-19 6:37 UTC (permalink / raw) To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: Pet.Weng, Kenneth.Hung, Hermes Wu, dri-devel, devicetree, linux-kernel, Hermes Wu From: Hermes Wu <Hermes.wu@ite.com.tw> Add support for the ITE IT6162 MIPI DSI to HDMI 2.0 bridge chip. The IT6162 is an I2C-controlled bridge that supports the following configurations: - Single MIPI DSI input: up to 4K @ 30Hz - Dual MIPI DSI input (combined): up to 4K @ 60Hz The driver implements the DRM bridge and connector frameworks, including mode setting, EDID retrieval, and HPD support. Also add a MAINTAINERS entry for the newly introduced ITE IT6162 MIPI DSI to HDMI bridge driver, covering the driver source file and the device tree binding document. Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw> --- Changes in v4 * Drop struc it6162_audio * Add HDCP_AUTO in enum hdcp_ver{}, FW enable HDCP by sink capability. * Call drm_atomic_helper_connector_hdmi_clear_audio_infoframe() in it6162_bridge_hdmi_audio_shutdown() * Drop it6162_bridge_hdmi_audio_startup() * Add hdmi_clear_hdmi_infoframe and hdmi_write_hdmi_infoframe * Drop it6162_parse_dt() due to "ite,support-hdcp" is removed from DT. * Add DRM_BRIDGE_OP_HDMI to it6162->bridge.ops Changes in v3: * Fix OFFSET_VERSION_H register offset from 0x03 to 0x05 * Add MIPI_PORT_EN_MASK macro combining MIPI_PORT1_EN_MASK and MIPI_PORT0_EN_MASK * Rename HDCP enums: NO_HDCP -> HDCP_DISABLE, NO_HDCP_STATE -> HDCP_STATE_IDLE, AUTH_DONE -> HDCP_STATE_AUTH_DONE, AUTH_FAIL -> HDCP_STATE_AUTH_FAIL * Rename it6162_infoblock_complete() to it6162_wait_command_complete() * Rename it6162_infoblock_host_set() to it6162_infoblock_trigger() * Remove it6162_infoblock_mipi_config() and it6162_infoblock_write_msg() wrappers, inline into it6162_reset_init() and it6162_mipi_set_video_timing() respectively * Remove it6162_set_default_config() wrapper, inline into it6162_reset_init() * Fix typo: hdcp_encyption -> hdcp_encryption * Fix typo: it6162_hdcp_read_infomation -> it6162_hdcp_read_information * Remove dev_err_probe() usage outside of probe path * Remove verbose success-path dev_info/dev_dbg logging throughout * Replace __func__ usage in error messages with descriptive strings * Fix double error printing in probe for it6162_init_pdata failure path * Fix uninitialized variable warning: initialize cp_status to DRM_MODE_CONTENT_PROTECTION_DESIRED at declaration; move drm_hdcp_update_content_protection() inside the state-change block * Fix audio sample width mapping: case 20 now maps to WORD_LENGTH_20BIT, case 24 to WORD_LENGTH_24BIT * Remove stray drm_dbg("it6162_bridge_atomic_disable") call * Remove drm_dbg() calls from it6162_display_mode_to_settings() * Drop unused struct it6162 * parameter from it6162_avi_to_video_setting() and it6162_display_mode_to_settings() * Fold it6162_set_default_config() body directly into it6162_reset_init(), removing the wrapper * Fix it6162_infoblock_request_data(): split command complete polling and buffer status check into two steps; use wait_event_timeout() for data_buf_sts since it is updated asynchronously by the interrupt handler; add wait_queue_head_t data_buf_wait to struct it6162 and wake_up() in interrupt handler --- MAINTAINERS | 7 + drivers/gpu/drm/bridge/Kconfig | 17 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ite-it6162.c | 1628 +++++++++++++++++++++++++++++++++++ 4 files changed, 1653 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 30f3472c72933c93b9237d93ad35802d6dda75f1..9c6e7c02227e810f280d2bfaa73e0eb572db0119 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13640,6 +13640,13 @@ W: https://linuxtv.org Q: http://patchwork.linuxtv.org/project/linux-media/list/ F: drivers/media/tuners/it913x* +ITE IT6162 MIPI DSI TO HDMI BRIDGE DRIVER +M: Hermes Wu <Hermes.wu@ite.com.tw> +L: dri-devel@lists.freedesktop.org +S: Maintained +F: Documentation/devicetree/bindings/display/bridge/ite,it6162.yaml +F: drivers/gpu/drm/bridge/ite-it6162.c + ITE IT6263 LVDS TO HDMI BRIDGE DRIVER M: Liu Ying <victor.liu@nxp.com> L: dri-devel@lists.freedesktop.org diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 1cabfa1d2b2efc6fca68619eea6050fe96a892e8..1a8fe59506cf60958babb48429b842ca3aecc755 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -107,6 +107,23 @@ config DRM_INNO_HDMI select DRM_DISPLAY_HELPER select DRM_KMS_HELPER +config DRM_ITE_IT6162 + tristate "iTE IT6162 DSI to HDMI bridge" + depends on OF + select REGMAP_I2C + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_HDMI_HELPER + select DRM_DISPLAY_HDMI_HELPER + select DRM_DISPLAY_HDCP_HELPER + select DRM_DISPLAY_HELPER + help + Driver for iTE IT6162 DSI to HDMI bridge + chip driver that converts DSI to HDMI signals + support up to 4k60 with 2 MIPI DSI + Please say Y if you have such hardware. + config DRM_ITE_IT6263 tristate "ITE IT6263 LVDS/HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index fb0cf0bf88756bed8323830c80f3f1d066b51e36..3a199b27b3bbe8294a5bef6c82cfee4cd7b8a34e 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -11,6 +11,7 @@ tda998x-y := tda998x_drv.o obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o obj-$(CONFIG_DRM_INNO_HDMI) += inno-hdmi.o +obj-$(CONFIG_DRM_ITE_IT6162) += ite-it6162.o obj-$(CONFIG_DRM_ITE_IT6263) += ite-it6263.o obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o diff --git a/drivers/gpu/drm/bridge/ite-it6162.c b/drivers/gpu/drm/bridge/ite-it6162.c new file mode 100644 index 0000000000000000000000000000000000000000..09274468dc73f8fbe786dc983d9340c77b70ed48 --- /dev/null +++ b/drivers/gpu/drm/bridge/ite-it6162.c @@ -0,0 +1,1628 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2024 ITE Tech. Inc. + */ +#include <linux/bitfield.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_irq.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <drm/display/drm_hdcp_helper.h> +#include <drm/display/drm_hdmi_helper.h> +#include <drm/display/drm_hdmi_state_helper.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_print.h> +#include <video/videomode.h> +#include <sound/hdmi-codec.h> + +#define DATA_BUFFER_DEPTH 32 + +#define OFFSET_CHIP_ID_L 0x00 +#define OFFSET_CHIP_ID_M 0x01 +#define OFFSET_CHIP_ID_H 0x02 +#define OFFSET_VERSION_L 0x03 +#define OFFSET_VERSION_M 0x04 +#define OFFSET_VERSION_H 0x05 + +#define OFFSET_MIPI_CONFIG_L 0x06 +#define MIPI_CLOCK_MODE_MASK BIT(6) +#define MIPI_PORT1_EN_MASK BIT(5) +#define MIPI_PORT0_EN_MASK BIT(4) +#define MIPI_LANE_SWAP_MASK BIT(3) +#define MIPI_PN_SWAP_MASK BIT(2) +#define MIPI_LANE_NUM_MASK 0x3 +#define MIPI_PORT_EN_MASK (MIPI_PORT1_EN_MASK | MIPI_PORT0_EN_MASK) + +#define OFFSET_MIPI_CONFIG_H 0x07 +#define OFFSET_TX_CONFIG 0x08 + +#define OFFSET_TX_SETTING 0x09 +#define TX_HDCP_VER_MASK 0xC0 +#define TX_HDCP_ENCRYP_MASK BIT(5) +#define TX_STREAM_TYPE_MASK BIT(4) + +#define OFFSET_MIPI_STATUS 0x0A +#define OFFSET_TX_STATUS 0x0C +#define B_TX_STATUS_HPD 7 +#define B_TX_STATUS_VIDEO_STB 6 +#define B_TX_STATUS_HDCP 4 +#define M_TX_STATUS_HDCP 0x30 + +#define TX_VIDEO_STB BIT(B_TX_STATUS_VIDEO_STB) +#define TX_STATUS_HPD BIT(B_TX_STATUS_HPD) + +#define GET_TX_HPD_STATUS(x) (((x) & TX_STATUS_HPD) >> B_TX_STATUS_HPD) +#define GET_TX_VIDEO_STATUS(x) (((x) & TX_VIDEO_STB) >> B_TX_STATUS_VIDEO_STB) +#define GET_TX_HDCP_STATUS(x) (((x) & M_TX_STATUS_HDCP) >> B_TX_STATUS_HDCP) + +#define OFFSET_SINK_CAP 0x0D +#define B_SINK_CAP_HDCP_VER 4 +#define M_SINK_CAP_HDCP_VER 0x30 + +#define GET_SINK_CAP_HDCP_VER(x) (((x) & M_SINK_CAP_HDCP_VER) >> B_SINK_CAP_HDCP_VER) + +#define OFFSET_DRIVER_DATA 0x0E + +#define OFFSET_DATA_TYPE_IDX 0x0F +#define OFFSET_DATA_BUFFER 0x20 + +#define OFFSET_AUDIO_CTRL0 0x3C +#define MASK_AUDIO_CHANNEL_NUM 0x0F +#define MASK_AUDIO_SELECT 0x30 +#define MASK_AUDIO_TYPE 0xC0 + +#define OFFSET_AUDIO_CTRL1 0x3D +#define OFFSET_AUDIO_CTRL2 0x3E +#define MASK_I2S_BUS_MODE 0x1F +#define MASK_I2S_WORD_LEN 0x60 +#define MASK_I2S_VALID 0x80 + +#define OFFSET_HOST_SET 0xFE +#define B_CONFIG_CHG BIT(7) +#define B_SET_CHG BIT(6) +#define HOST_SET_VIDEO_INFO (1) +#define HOST_SET_AUDIO_INFO (2) +#define HOST_SET_VIDEO_AUDIO_INFO (3) +#define HOST_SET_EDID_R (0x04) +#define HOST_SET_HDCP_R (0x05) +#define HOST_SET_CEA_INFOFRAME (0x0E) + +#define OFFSET_EVENT_CHG 0xFF +#define B_EVENT_CHG_BUFFER 4 +#define M_EVENT_CHG_BUFFER_STS (0x30) + +#define B_EVENT_CHG 1 +#define B_EVENT_IC_READY 0 + +#define EVENT_CHG BIT(B_EVENT_CHG) +#define EVENT_READY BIT(B_EVENT_IC_READY) + +#define GET_BUFFER_STATUS(x) (((x) & M_EVENT_CHG_BUFFER_STS) >> B_EVENT_CHG_BUFFER) + +#define TIMEOUT_INFOBLOCK_MS 1800 + +enum it6162_audio_select { + I2S = 0, + SPDIF, +}; + +enum it6162_audio_word_length { + WORD_LENGTH_16BIT = 0x0, + WORD_LENGTH_18BIT = 0x1, + WORD_LENGTH_20BIT = 0x2, + WORD_LENGTH_24BIT = 0x3, +}; + +enum it6162_audio_sample_rate { + SAMPLE_RATE_32K = 0x3, + SAMPLE_RATE_48K = 0x2, + SAMPLE_RATE_64K = 0xB, + SAMPLE_RATE_96K = 0xA, + SAMPLE_RATE_192K = 0xE, + SAMPLE_RATE_44_1K = 0x0, + SAMPLE_RATE_88_2K = 0x8, + SAMPLE_RATE_176_4K = 0xC, +}; + +enum it6162_audio_type { + LPCM = 0, + NLPCM, +}; + +enum data_buf_sts { + NO_STS = 0x00, + BUF_READY = 0x01, + BUF_FAIL = 0x02, +}; + +enum hdcp_state { + HDCP_STATE_IDLE = 0x00, + HDCP_STATE_AUTH_DONE = 0x01, + HDCP_STATE_AUTH_FAIL = 0x02, +}; + +enum hdcp_ver { + HDCP_DISABLE = 0x0, + HDCP_14 = 0x1, + HDCP_23 = 0x2, + HDCP_AUTO = 0x3, +}; + +struct it6162_chip_info { + u32 chip_id; + u32 version; +}; + +struct it6162_video_settings { + u8 vic; + u32 clock; + u16 htotal; + u16 hfp; + u16 hsw; + u16 hbp; + u16 hdew; + u16 vtotal; + u16 vfp; + u16 vsw; + u16 vbp; + u16 vdew; + u8 hpol; + u8 vpol; + u8 prog; + u16 v_aspect; + u16 h_aspect; + u8 pix_rep; + u8 colorspace; +}; + +enum sync_mode { + SYNC_EVENT = 0x0, + SYNC_PULSE = 0x1, + SYNC_AUTO = 0x2, +}; + +struct it6162_mipi_cfg { + bool en_port[2]; + u8 lane_num; + bool pn_swap; + bool lane_swap; + bool continuous_clk; + enum sync_mode mode; + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; +}; + +struct it6162_hdcp_cfg { + enum hdcp_ver hdcp_version; + bool hdcp_encryption; + u8 stream_ID; +}; + +struct it6162_infoblock_msg { + u8 action; + int len; + u8 msg[32]; +}; + +struct it6162 { + struct drm_bridge bridge; + struct device *dev; + struct drm_connector *connector; + enum drm_connector_status connector_status; + struct drm_device *drm; + + struct i2c_client *it6162_i2c; + struct regmap *regmap; + + struct regulator *pwr1833; + struct regulator *ovdd; + struct regulator *ivdd; + struct gpio_desc *gpiod_reset; + + bool power_on; + bool en_hdcp; + + /*lock the infoblock write setting and buffer access*/ + struct mutex lock; + + enum data_buf_sts data_buf_sts; + wait_queue_head_t data_buf_wait; + + /* it6162 DSI RX related params */ + struct mipi_dsi_device *dsi; + struct it6162_mipi_cfg mipi_cfg; + + struct delayed_work hdcp_work; + enum hdcp_state hdcp_sts; + enum hdcp_ver hdcp_version; + struct it6162_hdcp_cfg hdcp_cfg; + + unsigned int content_protection; +}; + +static struct it6162 *bridge_to_it6162(struct drm_bridge *bridge) +{ + return container_of(bridge, struct it6162, bridge); +} + +static const struct regmap_config it6162_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, +}; + +static inline int it6162_i2c_regmap_init(struct i2c_client *client, + struct it6162 *it6162) +{ + it6162->it6162_i2c = client; + i2c_set_clientdata(client, it6162); + it6162->regmap = devm_regmap_init_i2c(it6162->it6162_i2c, + &it6162_regmap_config); + if (IS_ERR(it6162->regmap)) + return PTR_ERR(it6162->regmap); + + return 0; +} + +static int it6162_wait_command_complete(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + struct regmap *regmap = it6162->regmap; + unsigned int val; + int ret; + + ret = regmap_read(regmap, OFFSET_HOST_SET, &val); + if (ret < 0) { + dev_err(dev, "read failed rx reg[0x%x]", OFFSET_HOST_SET); + return ret; + } + return val; +} + +static int it6162_infoblock_trigger(struct it6162 *it6162, u8 setting) +{ + struct device *dev = it6162->dev; + int status; + int val; + + regmap_write(it6162->regmap, OFFSET_HOST_SET, setting); + /* OFFSET_HOST_SET is cleared by firmware when the command is processed */ + status = readx_poll_timeout(it6162_wait_command_complete, it6162, val, + val <= 0, 50 * 1000, + TIMEOUT_INFOBLOCK_MS * 1000); + if (status < 0 || val != 0) { + dev_err(dev, "infoblock command timeout: status=%d val=%d", + status, val); + return -ETIMEDOUT; + } + + return 0; +} + +static int it6162_infoblock_request_data(struct it6162 *it6162, + u8 request_type, u8 index, u8 *buf) +{ + struct device *dev = it6162->dev; + int status; + + it6162->data_buf_sts = NO_STS; + regmap_write(it6162->regmap, OFFSET_DATA_TYPE_IDX, index); + status = it6162_infoblock_trigger(it6162, request_type); + if (status < 0) + return status; + /* data_buf_sts is updated asynchronously by the interrupt handler */ + if (wait_event_timeout(it6162->data_buf_wait, + it6162->data_buf_sts != NO_STS, + msecs_to_jiffies(TIMEOUT_INFOBLOCK_MS)) == 0) { + dev_err(dev, "timeout waiting for data buffer"); + return -ETIMEDOUT; + } + + if (it6162->data_buf_sts != BUF_READY) { + dev_err(dev, "data buffer not ready: sts=%d", + it6162->data_buf_sts); + return -EIO; + } + + regmap_bulk_read(it6162->regmap, OFFSET_DATA_BUFFER, buf, + DATA_BUFFER_DEPTH); + return 0; +} + +static void it6162_mipi_disable(struct it6162 *it6162) +{ + guard(mutex)(&it6162->lock); + regmap_update_bits(it6162->regmap, OFFSET_MIPI_CONFIG_L, + MIPI_PORT_EN_MASK, 0x00); + it6162_infoblock_trigger(it6162, B_CONFIG_CHG); +} + +static void it6162_mipi_enable(struct it6162 *it6162) +{ + unsigned int cfg_val = 0; + + cfg_val = FIELD_PREP(MIPI_PORT1_EN_MASK, it6162->mipi_cfg.en_port[1]) | + FIELD_PREP(MIPI_PORT0_EN_MASK, it6162->mipi_cfg.en_port[0]); + + guard(mutex)(&it6162->lock); + regmap_update_bits(it6162->regmap, OFFSET_MIPI_CONFIG_L, + MIPI_PORT_EN_MASK, cfg_val); + it6162_infoblock_trigger(it6162, B_CONFIG_CHG); +} + +static void it6162_tx_hdcp_enable(struct it6162 *it6162, + struct it6162_hdcp_cfg *tx_out) +{ + int val; + struct regmap *regmap = it6162->regmap; + + guard(mutex)(&it6162->lock); + it6162->hdcp_sts = HDCP_STATE_IDLE; + + regmap_read(regmap, OFFSET_TX_SETTING, &val); + val &= 0x0F; + val |= FIELD_PREP(TX_HDCP_VER_MASK, tx_out->hdcp_version) | + FIELD_PREP(TX_HDCP_ENCRYP_MASK, tx_out->hdcp_encryption) | + FIELD_PREP(TX_STREAM_TYPE_MASK, tx_out->stream_ID); + + regmap_write(regmap, OFFSET_TX_SETTING, val); + it6162_infoblock_trigger(it6162, B_SET_CHG); +} + +static void it6162_tx_hdcp_disable(struct it6162 *it6162) +{ + int val; + struct regmap *regmap = it6162->regmap; + + it6162->hdcp_sts = HDCP_STATE_IDLE; + it6162->hdcp_version = HDCP_DISABLE; + + guard(mutex)(&it6162->lock); + regmap_read(regmap, OFFSET_TX_SETTING, &val); + regmap_write(it6162->regmap, OFFSET_TX_SETTING, val & 0x0F); + it6162_infoblock_trigger(it6162, B_SET_CHG); +} + +static void +it6162_tx_hdcp_setup(struct it6162 *it6162, u8 ver, bool encryption) +{ + struct it6162_hdcp_cfg tx_out; + + if (ver != HDCP_DISABLE) { + tx_out.hdcp_version = ver; + tx_out.hdcp_encryption = encryption; + tx_out.stream_ID = 0; + it6162_tx_hdcp_enable(it6162, &tx_out); + } else { + it6162_tx_hdcp_disable(it6162); + } +} + +static void it6162_tx_enable(struct it6162 *it6162) +{ + if (it6162->en_hdcp) + it6162->hdcp_version = it6162->hdcp_cfg.hdcp_version; + else + it6162->hdcp_version = HDCP_DISABLE; +} + +static void it6162_tx_disable(struct it6162 *it6162) +{ + cancel_delayed_work_sync(&it6162->hdcp_work); + it6162_tx_hdcp_setup(it6162, HDCP_DISABLE, false); +} + +static inline void +it6162_pack_video_setting(struct it6162 *it6162, + struct it6162_video_settings *settings, + struct it6162_infoblock_msg *msg) +{ + msg->msg[0x00] = settings->hdew & 0xFF; + msg->msg[0x01] = (settings->hdew >> 8) & 0x3F; + msg->msg[0x02] = settings->vdew & 0xFF; + msg->msg[0x03] = (settings->vdew >> 8) & 0x3F; + msg->msg[0x04] = settings->clock & 0xFF; + msg->msg[0x05] = (settings->clock >> 8) & 0xFF; + msg->msg[0x06] = (settings->clock >> 16) & 0xFF; + msg->msg[0x07] = (settings->clock >> 24) & 0xFF; + msg->msg[0x08] = settings->hfp & 0xFF; + msg->msg[0x09] = (settings->hfp >> 8) & 0x3F; + msg->msg[0x0A] = settings->hsw & 0xFF; + msg->msg[0x0B] = (settings->hsw >> 8) & 0x3F; + msg->msg[0x0C] = settings->hbp & 0xFF; + msg->msg[0x0D] = (settings->hbp >> 8) & 0x3F; + msg->msg[0x0E] = settings->vfp & 0xFF; + msg->msg[0x0F] = (settings->vfp >> 8) & 0x3F; + msg->msg[0x10] = settings->vsw & 0xFF; + msg->msg[0x11] = (settings->vsw >> 8) & 0x3F; + msg->msg[0x12] = settings->vbp & 0xFF; + msg->msg[0x13] = (settings->vbp >> 8) & 0x3F; + msg->msg[0x14] = (settings->prog << 2) | + (settings->vpol << 1) | + settings->hpol; + + msg->msg[0x15] = settings->v_aspect; + msg->msg[0x16] = settings->h_aspect & 0xFF; + msg->msg[0x17] = settings->h_aspect >> 8; + msg->msg[0x18] = settings->pix_rep; + msg->msg[0x19] = settings->vic; + msg->msg[0x1A] = settings->colorspace; + msg->msg[0x1B] = 1; + + msg->len = 0x1C; + msg->action = HOST_SET_VIDEO_INFO; +} + +static void it6162_mipi_set_video_timing(struct it6162 *it6162, + struct it6162_video_settings *mode) +{ + struct it6162_infoblock_msg msg; + + it6162_pack_video_setting(it6162, mode, &msg); + guard(mutex)(&it6162->lock); + regmap_bulk_write(it6162->regmap, OFFSET_DATA_BUFFER, &msg.msg, + msg.len); + it6162_infoblock_trigger(it6162, msg.action); +} + +static int +it6162_hdcp_read_information(struct it6162 *it6162, u8 *status, u8 *bksv) +{ + u8 buf[DATA_BUFFER_DEPTH]; + int ret; + + guard(mutex)(&it6162->lock); + ret = it6162_infoblock_request_data(it6162, HOST_SET_HDCP_R, 0x00, + buf); + if (ret < 0) + return ret; + + memcpy(status, buf, 2); + memcpy(bksv, buf + 2, 5); + return 0; +} + +static int it6162_hdcp_read_list(struct it6162 *it6162, + u8 *ksv_list, + int dev_count) +{ + u8 buf[DATA_BUFFER_DEPTH]; + int i, j, ret; + + if (!ksv_list || dev_count <= 0) + return -EINVAL; + + guard(mutex)(&it6162->lock); + for (i = 0; i < (dev_count / 6 + 1); i++) { + ret = it6162_infoblock_request_data(it6162, HOST_SET_HDCP_R, + i + 1, buf); + if (ret < 0) + return ret; + + for (j = 0; j < 30; j += 5) { + if ((i * 6 + j / 5) >= dev_count) + break; + + memcpy(&ksv_list[(i * 6 + j / 5) * 5], &buf[j], 5); + } + } + return dev_count; +} + +static void it6162_update_hdcp14(struct it6162 *it6162) +{ + struct drm_device *drm = it6162->drm; + int dev_count; + u8 bksv[5]; + u8 bstatus[2]; + u8 ksvlist[5 * 30]; + int ret; + + ret = it6162_hdcp_read_information(it6162, bstatus, bksv); + if (ret < 0) { + drm_err(drm, "fail to read Bstatus"); + return; + } + + dev_count = DRM_HDCP_NUM_DOWNSTREAM(bstatus[0]); + if (dev_count == 0) + return; + + if (DRM_HDCP_MAX_DEVICE_EXCEEDED(bstatus[0]) || + DRM_HDCP_MAX_CASCADE_EXCEEDED(bstatus[1])) { + drm_err(drm, "HDCP1.4 Max Topology Limit Exceeded"); + return; + } + + dev_count = dev_count > 30 ? 30 : dev_count; + ret = it6162_hdcp_read_list(it6162, ksvlist, dev_count); + if (ret < 0) { + drm_err(drm, "fail to read KSV list"); + return; + } +} + +static void it6162_update_hdcp23(struct it6162 *it6162) +{ + struct drm_device *drm = it6162->drm; + int dev_count; + u8 rxid[5]; + u8 rxinfo[2]; + u8 rxid_list[5 * 30]; + int ret; + + ret = it6162_hdcp_read_information(it6162, rxinfo, rxid); + if (ret < 0) { + drm_err(drm, "fail to read Rxinfo"); + return; + } + + dev_count = (HDCP_2_2_DEV_COUNT_HI(rxinfo[0]) << 4 | + HDCP_2_2_DEV_COUNT_LO(rxinfo[1])); + if (dev_count == 0) + return; + + if (HDCP_2_2_MAX_CASCADE_EXCEEDED(rxinfo[1]) || + HDCP_2_2_MAX_DEVS_EXCEEDED(rxinfo[1])) { + drm_err(drm, "HDCP2.3 Topology Max Size Exceeded"); + return; + } + + dev_count = dev_count > 30 ? 30 : dev_count; + ret = it6162_hdcp_read_list(it6162, rxid_list, dev_count); + if (ret < 0) { + drm_err(drm, "fail to read RxID list"); + return; + } +} + +static void it6162_update_hdcp(struct it6162 *it6162, u8 version) +{ + if (version == HDCP_23) + it6162_update_hdcp23(it6162); + else + it6162_update_hdcp14(it6162); +} + +static void it6162_hdcp_handler(struct it6162 *it6162) +{ + struct regmap *regmap = it6162->regmap; + unsigned int tx_status, sink_cap; + enum hdcp_state hdcp_sts; + struct it6162_hdcp_cfg *hdcp_cfg = &it6162->hdcp_cfg; + u8 hdcp_ver; + u64 cp_status = DRM_MODE_CONTENT_PROTECTION_DESIRED; + + if (hdcp_cfg->hdcp_version == HDCP_DISABLE || !it6162->en_hdcp) + return; + + if (it6162->hdcp_version != HDCP_DISABLE) { + regmap_read(regmap, OFFSET_TX_STATUS, &tx_status); + regmap_read(regmap, OFFSET_SINK_CAP, &sink_cap); + if (!GET_TX_VIDEO_STATUS(tx_status)) { + drm_dbg(it6162->drm, "video not stable, skip hdcp check"); + return; + } + + hdcp_sts = GET_TX_HDCP_STATUS(tx_status); + hdcp_ver = GET_SINK_CAP_HDCP_VER(sink_cap); + if (it6162->hdcp_sts != hdcp_sts || + it6162->hdcp_sts == HDCP_STATE_IDLE) { + it6162->hdcp_sts = hdcp_sts; + switch (hdcp_sts) { + case HDCP_STATE_AUTH_DONE: + it6162_update_hdcp(it6162, hdcp_ver); + cp_status = DRM_MODE_CONTENT_PROTECTION_ENABLED; + break; + case HDCP_STATE_AUTH_FAIL: + if (hdcp_ver == HDCP_23) + it6162_tx_hdcp_setup(it6162, HDCP_14, + true); + else + it6162_tx_hdcp_disable(it6162); + + break; + default: + it6162_tx_hdcp_setup(it6162, + it6162->hdcp_version, + true); + break; + } + drm_hdcp_update_content_protection(it6162->connector, + cp_status); + } + } +} + +static void it6162_interrupt_handler(struct it6162 *it6162) +{ + unsigned int int_status, tx_status; + enum drm_connector_status connector_status; + struct device *dev = it6162->dev; + struct regmap *regmap = it6162->regmap; + int err; + + err = regmap_read(regmap, OFFSET_EVENT_CHG, &int_status); + if (err < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", + OFFSET_EVENT_CHG, err); + return; + } + + regmap_write(it6162->regmap, OFFSET_EVENT_CHG, 0xFF); + + if (!!GET_BUFFER_STATUS(int_status)) { + regmap_write(it6162->regmap, OFFSET_DRIVER_DATA, int_status); + it6162->data_buf_sts = GET_BUFFER_STATUS(int_status); + wake_up(&it6162->data_buf_wait); + } + + if (!(int_status & EVENT_CHG)) + return; + + err = regmap_read(regmap, OFFSET_TX_STATUS, &tx_status); + if (err < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", + OFFSET_TX_STATUS, err); + return; + } + + connector_status = GET_TX_HPD_STATUS(tx_status) ? + connector_status_connected : + connector_status_disconnected; + + if (it6162->connector_status != connector_status) { + it6162->connector_status = connector_status; + drm_bridge_hpd_notify(&it6162->bridge, + it6162->connector_status); + } + + if (it6162->en_hdcp && GET_TX_VIDEO_STATUS(tx_status) && + connector_status == connector_status_connected) + queue_delayed_work(system_wq, &it6162->hdcp_work, + msecs_to_jiffies(2000)); +} + +static int it6162_wait_ready_event(struct it6162 *it6162) +{ + unsigned int val; + int ret; + struct device *dev = it6162->dev; + struct regmap *regmap = it6162->regmap; + + ret = regmap_read(regmap, OFFSET_EVENT_CHG, &val); + if (ret < 0) { + dev_err(dev, "read failed rx reg[0x%x] err: %d", + OFFSET_EVENT_CHG, ret); + return ret; + } + + return val & EVENT_READY; +} + +static bool it6162_wait_mcu_ready(struct it6162 *it6162) +{ + int ret, val; + + ret = readx_poll_timeout(it6162_wait_ready_event, it6162, val, + val == EVENT_READY, 100 * 1000, 1500 * 1000); + if (ret) { + dev_err(it6162->dev, "timeout waiting for MCU ready"); + return false; + } + + return true; +} + +static void it6162_reset_init(struct it6162 *it6162) +{ + struct it6162_mipi_cfg *cfg = &it6162->mipi_cfg; + u8 cfg_val = 0; + + cfg_val = FIELD_PREP(MIPI_CLOCK_MODE_MASK, cfg->continuous_clk) | + FIELD_PREP(MIPI_LANE_SWAP_MASK, cfg->lane_swap) | + FIELD_PREP(MIPI_PN_SWAP_MASK, cfg->pn_swap) | + FIELD_PREP(MIPI_LANE_NUM_MASK, cfg->lane_num - 1); + + guard(mutex)(&it6162->lock); + regmap_write(it6162->regmap, OFFSET_MIPI_CONFIG_L, cfg_val); + regmap_write(it6162->regmap, OFFSET_MIPI_CONFIG_H, cfg->mode); + regmap_write(it6162->regmap, OFFSET_TX_CONFIG, 0x00); + regmap_write(it6162->regmap, OFFSET_TX_SETTING, 0x00); + it6162_infoblock_trigger(it6162, B_CONFIG_CHG | B_SET_CHG); +} + +static int it6162_platform_set_power(struct it6162 *it6162) +{ + int err; + + err = regulator_enable(it6162->ivdd); + if (err) + return err; + + err = regulator_enable(it6162->pwr1833); + if (err) + return err; + + err = regulator_enable(it6162->ovdd); + if (err) + return err; + + usleep_range(10000, 20000); + gpiod_set_value_cansleep(it6162->gpiod_reset, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(it6162->gpiod_reset, 0); + usleep_range(10000, 20000); + + return 0; +} + +static int it6162_platform_clear_power(struct it6162 *it6162) +{ + int err; + + err = regulator_disable(it6162->ivdd); + if (err) + return err; + + usleep_range(2000, 3000); + + err = regulator_disable(it6162->pwr1833); + if (err) + return err; + + err = regulator_disable(it6162->ovdd); + if (err) + return err; + + return 0; +} + +static int it6162_detect_devices(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + const struct it6162_chip_info *chip_info; + u32 chip_id, version; + u8 buf[6]; + + if (it6162_platform_set_power(it6162) < 0) + return -ENODEV; + + /* Firmware must finish booting before register access is valid */ + if (!it6162_wait_mcu_ready(it6162)) + return -ENODEV; + + chip_info = of_device_get_match_data(dev); + regmap_bulk_read(it6162->regmap, OFFSET_CHIP_ID_L, &buf[0], 6); + chip_id = (buf[0] << 16) | (buf[1] << 8) | (buf[2]); + version = (buf[3] << 16) | (buf[4] << 8) | (buf[5]); + if (chip_id == chip_info->chip_id && version >= chip_info->version) + return 0; + + dev_err(dev, "chip mismatch: id=0x%06x ver=0x%06x", chip_id, version); + return -ENODEV; +} + +static int __maybe_unused it6162_poweron(struct it6162 *it6162) +{ + int err; + + err = it6162_platform_set_power(it6162); + if (err < 0) + return err; + + if (!it6162_wait_mcu_ready(it6162)) + return -ENODEV; + + it6162->connector_status = connector_status_disconnected; + it6162_reset_init(it6162); + enable_irq(it6162->it6162_i2c->irq); + it6162->power_on = true; + return 0; +} + +static int __maybe_unused it6162_poweroff(struct it6162 *it6162) +{ + int err; + + disable_irq(it6162->it6162_i2c->irq); + err = it6162_platform_clear_power(it6162); + if (err < 0) + return err; + + it6162->power_on = false; + return 0; +} + +static void it6162_config_default(struct it6162 *it6162) +{ + struct it6162_mipi_cfg *mipi_cfg = &it6162->mipi_cfg; + struct it6162_hdcp_cfg *hdcp_cfg = &it6162->hdcp_cfg; + + mipi_cfg->lane_num = 4; + mipi_cfg->pn_swap = false; + mipi_cfg->lane_swap = false; + mipi_cfg->en_port[0] = false; + mipi_cfg->en_port[1] = false; + mipi_cfg->continuous_clk = true; + mipi_cfg->mode = SYNC_EVENT; + mipi_cfg->format = MIPI_DSI_FMT_RGB888; + mipi_cfg->mode_flags = MIPI_DSI_MODE_VIDEO; + + it6162->en_hdcp = false; + hdcp_cfg->hdcp_version = HDCP_AUTO; + hdcp_cfg->hdcp_encryption = true; + hdcp_cfg->stream_ID = 0; + + it6162->connector_status = connector_status_disconnected; +} + +static enum drm_connector_status it6162_detect(struct it6162 *it6162) +{ + struct regmap *regmap = it6162->regmap; + unsigned int tx_status; + + regmap_read(regmap, OFFSET_TX_STATUS, &tx_status); + it6162->connector_status = GET_TX_HPD_STATUS(tx_status) ? + connector_status_connected : + connector_status_disconnected; + return it6162->connector_status; +} + +static int it6162_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + struct it6162 *it6162 = data; + unsigned int i, cnt; + int ret; + u8 config; + + if (len > EDID_LENGTH) + return -EINVAL; + + guard(mutex)(&it6162->lock); + for (i = 0, cnt = 0; i < EDID_LENGTH; i += DATA_BUFFER_DEPTH, cnt++) { + config = (block << 2) | (cnt); + ret = it6162_infoblock_request_data(it6162, HOST_SET_EDID_R, + config, buf + i); + if (ret < 0) + return -EIO; + } + + return 0; +} + +static int it6162_audio_update_hw_params(struct it6162 *it6162, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + enum it6162_audio_select select; + enum it6162_audio_sample_rate sample_rate; + unsigned int sample_width; + unsigned int val; + + switch (hparms->sample_rate) { + case 32000: + sample_rate = SAMPLE_RATE_32K; + break; + case 44100: + sample_rate = SAMPLE_RATE_44_1K; + break; + case 48000: + sample_rate = SAMPLE_RATE_48K; + break; + case 88200: + sample_rate = SAMPLE_RATE_88_2K; + break; + case 96000: + sample_rate = SAMPLE_RATE_96K; + break; + case 176400: + sample_rate = SAMPLE_RATE_176_4K; + break; + case 192000: + sample_rate = SAMPLE_RATE_192K; + break; + default: + return -EINVAL; + } + + switch (hparms->sample_width) { + case 16: + sample_width = WORD_LENGTH_16BIT; + break; + case 18: + sample_width = WORD_LENGTH_18BIT; + break; + case 20: + sample_width = WORD_LENGTH_20BIT; + break; + case 24: + sample_width = WORD_LENGTH_24BIT; + break; + default: + return -EINVAL; + } + + switch (fmt->fmt) { + case HDMI_I2S: + select = I2S; + break; + case HDMI_SPDIF: + select = SPDIF; + break; + default: + return -EINVAL; + } + + guard(mutex)(&it6162->lock); + val = FIELD_PREP(MASK_AUDIO_CHANNEL_NUM, hparms->channels) | + FIELD_PREP(MASK_AUDIO_SELECT, select) | + FIELD_PREP(MASK_AUDIO_TYPE, LPCM); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL0, val); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL1, sample_rate); + + val = FIELD_PREP(MASK_I2S_BUS_MODE, 0) | + FIELD_PREP(MASK_I2S_WORD_LEN, sample_width) | + FIELD_PREP(MASK_I2S_VALID, 1); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL2, val); + return it6162_infoblock_trigger(it6162, HOST_SET_AUDIO_INFO); +} + +static void it6162_disable_audio(struct it6162 *it6162) +{ + guard(mutex)(&it6162->lock); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL0, 0x00); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL1, 0x00); + regmap_write(it6162->regmap, OFFSET_AUDIO_CTRL2, 0x00); + it6162_infoblock_trigger(it6162, HOST_SET_AUDIO_INFO); +} + +static irqreturn_t it6162_int_threaded_handler(int unused, void *data) +{ + struct it6162 *it6162 = data; + + it6162_interrupt_handler(it6162); + return IRQ_HANDLED; +} + +static void it6162_hdcp_work(struct work_struct *work) +{ + struct it6162 *it6162 = container_of(work, struct it6162, + hdcp_work.work); + + it6162_hdcp_handler(it6162); +} + +static struct mipi_dsi_host * +it6162_of_get_dsi_host_by_port(struct it6162 *it6162, int port) +{ + struct device_node *of = it6162->dev->of_node; + struct device_node *host_node; + struct device_node *endpoint; + struct mipi_dsi_host *dsi_host; + + endpoint = of_graph_get_endpoint_by_regs(of, port, -1); + if (!endpoint) + return ERR_PTR(-ENODEV); + + host_node = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!host_node) + return ERR_PTR(-ENODEV); + + dsi_host = of_find_mipi_dsi_host_by_node(host_node); + of_node_put(host_node); + if (!dsi_host) + return ERR_PTR(-EPROBE_DEFER); + + return dsi_host; +} + +static int it6162_of_get_dsi_host(struct it6162 *it6162) +{ + struct mipi_dsi_host *dsi_host; + int port, host_count = 0; + + for (port = 0; port < 2; port++) { + dsi_host = it6162_of_get_dsi_host_by_port(it6162, port); + if (IS_ERR(dsi_host)) { + if (PTR_ERR(dsi_host) == -EPROBE_DEFER) + return -EPROBE_DEFER; + continue; + } + + host_count++; + } + + return host_count == 0 ? -ENODEV : 0; +} + +static int it6162_load_mipi_pars_from_port(struct it6162 *it6162, int port) +{ + struct device_node *of = it6162->dev->of_node; + struct device_node *endpoint; + struct it6162_mipi_cfg *mipicfg = &it6162->mipi_cfg; + u32 data_lanes[4] = {1}; + u32 lane_polarities[5] = {0}; + int dsi_lanes; + + endpoint = of_graph_get_endpoint_by_regs(of, port, -1); + if (!endpoint) + return 0; + + dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4); + if (dsi_lanes < 0) + return dsi_lanes; + + mipicfg->lane_num = dsi_lanes; + + of_property_read_u32_array(endpoint, "data-lanes", data_lanes, + dsi_lanes); + /* only supports full reversal of all lanes, not arbitrary remapping */ + if (data_lanes[0] == 3) + mipicfg->lane_swap = true; + + of_property_read_u32_array(endpoint, "lane-polarities", + lane_polarities, dsi_lanes + 1); + /* applies Dp/Dn swap globally; mixing polarities across lanes is invalid */ + if (lane_polarities[0] != 0) + mipicfg->pn_swap = true; + + if (of_property_present(endpoint, "clock-noncontinuous")) { + mipicfg->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS; + mipicfg->continuous_clk = false; + } + + of_node_put(endpoint); + return dsi_lanes; +} + +static int it6162_attach_dsi(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + struct device_node *np = dev->of_node; + const struct mipi_dsi_device_info info = {"it6162-mipi", 0, np}; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + struct it6162_mipi_cfg *mipi_cfg = &it6162->mipi_cfg; + int ret = 0; + + it6162->dsi = NULL; + for (int port = 0; port < 2; port++) { + host = it6162_of_get_dsi_host_by_port(it6162, port); + if (IS_ERR(host)) + continue; + + mipi_cfg->en_port[port] = true; + + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to create dsi device"); + return PTR_ERR(dsi); + } + /* uses a single DSI configuration for both ports in dual-link mode */ + if (!it6162->dsi) { + ret = it6162_load_mipi_pars_from_port(it6162, port); + if (ret <= 0) + return ret; + it6162->dsi = dsi; + } + + dsi->lanes = mipi_cfg->lane_num; + dsi->format = mipi_cfg->format; + dsi->mode_flags = mipi_cfg->mode_flags; + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret) { + dev_err(dev, "failed to attach dsi device %d", port); + return ret; + } + } + + it6162_poweron(it6162); + return 0; +} + +static bool it6162_of_get_audio(struct it6162 *it6162) +{ + struct device_node *np = it6162->dev->of_node; + struct device_node *audio_port; + + audio_port = of_graph_get_port_by_id(np, 2); + if (audio_port) { + of_node_put(audio_port); + return true; + } + + return false; +} + +static int it6162_init_pdata(struct it6162 *it6162) +{ + struct device *dev = it6162->dev; + + it6162->ivdd = devm_regulator_get(dev, "ivdd"); + if (IS_ERR(it6162->ivdd)) + return dev_err_probe(dev, PTR_ERR(it6162->ivdd), + "failed to get ivdd regulator"); + + it6162->pwr1833 = devm_regulator_get(dev, "ovdd1833"); + if (IS_ERR(it6162->pwr1833)) + return dev_err_probe(dev, PTR_ERR(it6162->pwr1833), + "failed to get ovdd1833 regulator"); + + it6162->ovdd = devm_regulator_get(dev, "ovdd"); + if (IS_ERR(it6162->ovdd)) + return dev_err_probe(dev, PTR_ERR(it6162->ovdd), + "failed to get ovdd regulator"); + + it6162->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(it6162->gpiod_reset)) + return dev_err_probe(dev, PTR_ERR(it6162->gpiod_reset), + "failed to get reset gpio\n"); + + return 0; +} + +static int it6162_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + struct drm_device *drm = bridge->dev; + + it6162->drm = drm; + if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { + drm_dbg(drm, "it6162 driver only copes with atomic updates"); + return -EOPNOTSUPP; + } + + return 0; +} + +static enum drm_mode_status +it6162_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + unsigned long long rate; + + rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB); + if (rate == 0) + return MODE_NOCLOCK; + + return bridge->funcs->hdmi_tmds_char_rate_valid(bridge, mode, rate); +} + +static enum drm_connector_status +it6162_bridge_detect(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162->power_on ? it6162_detect(it6162) : + connector_status_disconnected; +} + +static inline void +it6162_avi_to_video_setting(struct hdmi_avi_infoframe *avi_info, + struct it6162_video_settings *video) +{ + video->vic = avi_info->video_code; + + switch (avi_info->picture_aspect) { + case HDMI_PICTURE_ASPECT_4_3: + video->h_aspect = 4; + video->v_aspect = 3; + break; + case HDMI_PICTURE_ASPECT_16_9: + video->h_aspect = 16; + video->v_aspect = 9; + break; + case HDMI_PICTURE_ASPECT_64_27: + video->h_aspect = 64; + video->v_aspect = 27; + break; + case HDMI_PICTURE_ASPECT_256_135: + video->h_aspect = 256; + video->v_aspect = 135; + break; + default: + video->h_aspect = 4; + video->v_aspect = 3; + break; + } + + video->pix_rep = avi_info->pixel_repeat + 1; +} + +static inline void +it6162_display_mode_to_settings(struct drm_display_mode *mode, + struct it6162_video_settings *settings) +{ + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + settings->hpol = 1; + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + settings->vpol = 1; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + settings->prog = 0; + + settings->clock = mode->clock; + settings->hdew = mode->hdisplay; + settings->hfp = mode->hsync_start - mode->hdisplay; + settings->hsw = mode->hsync_end - mode->hsync_start; + settings->hbp = mode->htotal - mode->hsync_end; + settings->htotal = mode->htotal; + settings->vdew = mode->vdisplay; + settings->vfp = mode->vsync_start - mode->vdisplay; + settings->vsw = mode->vsync_end - mode->vsync_start; + settings->vbp = mode->vtotal - mode->vsync_end; + settings->vtotal = mode->vtotal; +} + +static void it6162_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_state *state) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + struct drm_connector *connector; + struct hdmi_avi_infoframe *avi; + struct it6162_video_settings video_setting; + + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + if (!connector) + return; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (!crtc_state) + return; + + it6162->connector = connector; + it6162->content_protection = conn_state->content_protection; + + drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); + avi = &conn_state->hdmi.infoframes.avi.data.avi; + video_setting.colorspace = conn_state->hdmi.output_format; + it6162_avi_to_video_setting(avi, &video_setting); + it6162_display_mode_to_settings(&crtc_state->mode, &video_setting); + + it6162_mipi_set_video_timing(it6162, &video_setting); + it6162_tx_enable(it6162); + it6162_mipi_enable(it6162); +} + +static int it6162_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *adj = &crtc_state->adjusted_mode; + struct drm_display_mode *mode = &crtc_state->mode; + struct it6162 *it6162 = bridge_to_it6162(bridge); + u32 hfp, hsw, hbp; + u32 clock; + u32 hfp_check; + + clock = mode->clock; + hfp = mode->hsync_start - mode->hdisplay; + hsw = mode->hsync_end - mode->hsync_start; + hbp = mode->htotal - mode->hsync_end; + + hfp_check = DIV_ROUND_UP(65 * clock, 1000000) + 4; + if (hfp >= hfp_check) + return 0; + + if (hbp > hfp_check - hfp) { + adj->hsync_start = adj->hdisplay + hfp_check; + adj->hsync_end = adj->hsync_start + hsw; + drm_dbg(it6162->drm, "adjusted hsync on MIPI bus: %u, %u", + adj->hsync_start, adj->hsync_end); + } + + return 0; +} + +static void it6162_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_state *state) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + it6162_tx_disable(it6162); + it6162_mipi_disable(it6162); +} + +static const struct drm_edid * +it6162_bridge_read_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return drm_edid_read_custom(connector, it6162_get_edid_block, it6162); +} + +static int it6162_bridge_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *params) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + int ret; + + ret = it6162_audio_update_hw_params(it6162, fmt, params); + if (ret < 0) + return ret; + + return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, + ¶ms->cea); +} + +static void it6162_bridge_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector); + it6162_disable_audio(it6162); +} + +static enum drm_mode_status +it6162_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge, + const struct drm_display_mode *mode, + unsigned long long tmds_rate) +{ + /*IT6162 hdmi supports HDMI2.0 600Mhz*/ + if (tmds_rate > 600000000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static inline int +it6162_write_infoframe(struct it6162 *it6162, const u8 *buffer, size_t len) +{ + if (len > DATA_BUFFER_DEPTH) + return -EINVAL; + + regmap_bulk_write(it6162->regmap, OFFSET_DATA_BUFFER, buffer, len); + regmap_write(it6162->regmap, OFFSET_DATA_TYPE_IDX, len); + it6162_infoblock_trigger(it6162, HOST_SET_CEA_INFOFRAME); + return 0; +} + +static inline int it6162_clear_infoframe(struct it6162 *it6162, u8 type) +{ + regmap_write(it6162->regmap, OFFSET_DATA_TYPE_IDX, 3); + regmap_write(it6162->regmap, OFFSET_DATA_BUFFER, type); + regmap_write(it6162->regmap, OFFSET_DATA_BUFFER + 1, 0x00); + regmap_write(it6162->regmap, OFFSET_DATA_BUFFER + 2, 0x00); + it6162_infoblock_trigger(it6162, HOST_SET_CEA_INFOFRAME); + return 0; +} + +static int +it6162_bridge_hdmi_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_write_infoframe(it6162, buffer, len); +} + +static int +it6162_bridge_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_clear_infoframe(it6162, HDMI_INFOFRAME_TYPE_AVI); +} + +static int +it6162_bridge_hdmi_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_write_infoframe(it6162, buffer, len); +} + +static int +it6162_bridge_hdmi_clear_audio_infoframe(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_clear_infoframe(it6162, HDMI_INFOFRAME_TYPE_AUDIO); +} + +static int +it6162_bridge_hdmi_write_spd_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_write_infoframe(it6162, buffer, len); +} + +static int +it6162_bridge_hdmi_clear_spd_infoframe(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_clear_infoframe(it6162, HDMI_INFOFRAME_TYPE_SPD); +} + +static int +it6162_bridge_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_write_infoframe(it6162, buffer, len); +} + +static int +it6162_bridge_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + struct it6162 *it6162 = bridge_to_it6162(bridge); + + return it6162_clear_infoframe(it6162, HDMI_INFOFRAME_TYPE_VENDOR); +} + +static const struct drm_bridge_funcs it6162_bridge_funcs = { + .attach = it6162_bridge_attach, + .mode_valid = it6162_bridge_mode_valid, + .detect = it6162_bridge_detect, + + .atomic_enable = it6162_bridge_atomic_enable, + .atomic_disable = it6162_bridge_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_check = it6162_bridge_atomic_check, + + .edid_read = it6162_bridge_read_edid, + + .hdmi_clear_hdmi_infoframe = it6162_bridge_hdmi_clear_hdmi_infoframe, + .hdmi_write_hdmi_infoframe = it6162_bridge_hdmi_write_hdmi_infoframe, + .hdmi_clear_avi_infoframe = it6162_bridge_hdmi_clear_avi_infoframe, + .hdmi_write_avi_infoframe = it6162_bridge_hdmi_write_avi_infoframe, + .hdmi_clear_spd_infoframe = it6162_bridge_hdmi_clear_spd_infoframe, + .hdmi_write_spd_infoframe = it6162_bridge_hdmi_write_spd_infoframe, + .hdmi_clear_audio_infoframe = it6162_bridge_hdmi_clear_audio_infoframe, + .hdmi_write_audio_infoframe = it6162_bridge_hdmi_write_audio_infoframe, + + .hdmi_tmds_char_rate_valid = it6162_hdmi_tmds_char_rate_valid, + .hdmi_audio_prepare = it6162_bridge_hdmi_audio_prepare, + .hdmi_audio_shutdown = it6162_bridge_hdmi_audio_shutdown, +}; + +static int it6162_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; + struct it6162 *it6162; + int ret; + + it6162 = devm_drm_bridge_alloc(dev, struct it6162, bridge, + &it6162_bridge_funcs); + if (IS_ERR(it6162)) + return PTR_ERR(it6162); + + it6162->dev = dev; + + ret = it6162_of_get_dsi_host(it6162); + if (ret < 0) + return ret; + + ret = it6162_i2c_regmap_init(client, it6162); + if (ret != 0) + return ret; + + ret = it6162_init_pdata(it6162); + if (ret) + return ret; + + it6162_config_default(it6162); + + if (it6162_detect_devices(it6162) < 0) + return -ENODEV; + + if (!client->irq) { + dev_err(dev, "Failed to get INTP IRQ"); + return -ENODEV; + } + + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + it6162_int_threaded_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT | + IRQF_NO_AUTOEN, + "it6162-intp", it6162); + if (ret) + return ret; + + INIT_DELAYED_WORK(&it6162->hdcp_work, it6162_hdcp_work); + init_waitqueue_head(&it6162->data_buf_wait); + + mutex_init(&it6162->lock); + + it6162->bridge.of_node = np; + it6162->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HDMI | DRM_BRIDGE_OP_MODES; + + it6162->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + it6162->bridge.vendor = "ITE"; + it6162->bridge.product = "IT6162"; + + if (it6162_of_get_audio(it6162)) { + it6162->bridge.ops |= DRM_BRIDGE_OP_HDMI_AUDIO; + it6162->bridge.hdmi_audio_dev = dev; + it6162->bridge.hdmi_audio_max_i2s_playback_channels = 8; + it6162->bridge.hdmi_audio_dai_port = 2; + } + + devm_drm_bridge_add(dev, &it6162->bridge); + + return it6162_attach_dsi(it6162); +} + +static void it6162_remove(struct i2c_client *client) +{ + struct it6162 *it6162 = i2c_get_clientdata(client); + + disable_irq(client->irq); + cancel_delayed_work_sync(&it6162->hdcp_work); + mutex_destroy(&it6162->lock); +} + +static const struct it6162_chip_info it6162_chip_info = { + .chip_id = 0x616200, + .version = 0x006500, +}; + +static const struct of_device_id it6162_dt_ids[] = { + { .compatible = "ite,it6162", .data = &it6162_chip_info}, + { } +}; +MODULE_DEVICE_TABLE(of, it6162_dt_ids); + +static const struct i2c_device_id it6162_i2c_ids[] = { + { "it6162", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, it6162_i2c_ids); + +static struct i2c_driver it6162_driver = { + .driver = { + .name = "it6162", + .of_match_table = it6162_dt_ids, + }, + .probe = it6162_probe, + .remove = it6162_remove, + .id_table = it6162_i2c_ids, +}; +module_i2c_driver(it6162_driver); + +MODULE_AUTHOR("Pet Weng <pet.weng@ite.com.tw>"); +MODULE_AUTHOR("Hermes Wu <Hermes.Wu@ite.com.tw>"); +MODULE_DESCRIPTION("it6162 MIPI to HDMI driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Claude review: drm/bridge: Add ITE IT6162 MIPI DSI to HDMI bridge driver 2026-03-19 6:37 ` [PATCH v4 2/2] drm/bridge: " Hermes Wu via B4 Relay @ 2026-03-21 18:39 ` Claude Code Review Bot 0 siblings, 0 replies; 11+ messages in thread From: Claude Code Review Bot @ 2026-03-21 18:39 UTC (permalink / raw) To: dri-devel-reviews Patch Review This is a ~1600-line new bridge driver. There are several issues: **Critical / High:** 1. **Uninitialized `video_setting` struct on stack** in `it6162_bridge_atomic_enable`: ```c struct it6162_video_settings video_setting; ... it6162_avi_to_video_setting(avi, &video_setting); it6162_display_mode_to_settings(&crtc_state->mode, &video_setting); ``` `it6162_display_mode_to_settings` only conditionally sets `hpol`, `vpol`, and `prog`: ```c if (mode->flags & DRM_MODE_FLAG_PHSYNC) settings->hpol = 1; if (mode->flags & DRM_MODE_FLAG_NVSYNC) settings->vpol = 1; if (mode->flags & DRM_MODE_FLAG_INTERLACE) settings->prog = 0; ``` If none of those flags are set, these fields contain stack garbage. The struct should be zero-initialized: `struct it6162_video_settings video_setting = {};` or use `memset`. The `prog` field is particularly problematic — for progressive modes (the common case), it's never explicitly set to 1. 2. **Device-tree node reference leak** in `it6162_load_mipi_pars_from_port`: ```c endpoint = of_graph_get_endpoint_by_regs(of, port, -1); if (!endpoint) return 0; dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4); if (dsi_lanes < 0) return dsi_lanes; // <-- leaks endpoint refcount ``` The `of_node_put(endpoint)` at line 1831 is never reached if an early return fires. This needs a cleanup path or use of `__free` cleanup attributes. 3. **Missing locking in infoframe write/clear helpers**: `it6162_write_infoframe` and `it6162_clear_infoframe` call `it6162_infoblock_trigger` and do regmap writes, but do **not** take `it6162->lock`. All other callers of `it6162_infoblock_trigger` take the mutex. These are called from the bridge HDMI infoframe callbacks which can race with interrupt-driven activity: ```c static inline int it6162_write_infoframe(struct it6162 *it6162, const u8 *buffer, size_t len) { // No lock taken! regmap_bulk_write(it6162->regmap, OFFSET_DATA_BUFFER, buffer, len); regmap_write(it6162->regmap, OFFSET_DATA_TYPE_IDX, len); it6162_infoblock_trigger(it6162, HOST_SET_CEA_INFOFRAME); ... } ``` 4. **Chip ID byte order appears reversed** in `it6162_detect_devices`: ```c regmap_bulk_read(it6162->regmap, OFFSET_CHIP_ID_L, &buf[0], 6); chip_id = (buf[0] << 16) | (buf[1] << 8) | (buf[2]); ``` Register 0x00 is `OFFSET_CHIP_ID_L` (low byte), 0x01 is `_M` (mid), 0x02 is `_H` (high). So `buf[0]` is the low byte, but the code puts it in the most significant position. This should be: ```c chip_id = (buf[2] << 16) | (buf[1] << 8) | buf[0]; ``` Same issue for `version`. (Unless the expected `chip_info` value is byte-swapped to match, in which case this is just confusing and should be documented.) 5. **Regulator error handling in `it6162_platform_set_power`** — if enabling `pwr1833` fails, `ivdd` is left enabled. If enabling `ovdd` fails, both `ivdd` and `pwr1833` are left enabled: ```c err = regulator_enable(it6162->ivdd); if (err) return err; err = regulator_enable(it6162->pwr1833); if (err) return err; // ivdd still enabled err = regulator_enable(it6162->ovdd); if (err) return err; // ivdd and pwr1833 still enabled ``` Use goto-based unwinding to disable previously enabled regulators on failure. **Medium:** 6. **Missing `DRM_BRIDGE_OP_HPD`**: The interrupt handler calls `drm_bridge_hpd_notify()`, but the bridge ops don't include `DRM_BRIDGE_OP_HPD`: ```c it6162->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HDMI | DRM_BRIDGE_OP_MODES; ``` Without `DRM_BRIDGE_OP_HPD`, the connector infrastructure won't enable HPD polling/notification support, making the IRQ-based HPD path likely non-functional. 7. **`it6162_poweroff` is never called**: The function is defined with `__maybe_unused` but is genuinely never called anywhere — not from `remove`, not from any disable path. This means once powered on, the chip is never powered down. The `remove` callback only disables the IRQ and cancels the work, but doesn't power down: ```c static void it6162_remove(struct i2c_client *client) { struct it6162 *it6162 = i2c_get_clientdata(client); disable_irq(client->irq); cancel_delayed_work_sync(&it6162->hdcp_work); mutex_destroy(&it6162->lock); } ``` 8. **Double power-on**: `it6162_detect_devices` calls `it6162_platform_set_power`, then `it6162_attach_dsi` calls `it6162_poweron` which calls `it6162_platform_set_power` again. The regulators get `enable` called twice without a matching `disable` in between (unless reference counting saves it, but the GPIO reset also fires twice). 9. **`__maybe_unused` on `it6162_poweron`**: This function IS used (called from `it6162_attach_dsi`), so the annotation is wrong. 10. **`it6162_wait_command_complete` error conflation**: The function returns either a negative error (I2C read failure) or the register value. When used with `readx_poll_timeout(..., val, val <= 0, ...)`, an I2C read error (negative value) satisfies the poll condition, but is then treated as timeout rather than a bus error: ```c if (status < 0 || val != 0) { dev_err(dev, "infoblock command timeout: status=%d val=%d", status, val); return -ETIMEDOUT; } ``` An I2C failure should return the actual error, not `-ETIMEDOUT`. **Low / Style:** 11. **`!!GET_BUFFER_STATUS(int_status)`** — the double negation is unnecessary, a simple `GET_BUFFER_STATUS(int_status) != 0` or just `GET_BUFFER_STATUS(int_status)` would be clearer. 12. **`CACHE_TYPE_NONE`** — for a register map that's mostly write-then-poll, this is fine, but worth noting. 13. **`i2c_device_id` table** — the `{ "it6162", 0 }` style with explicit zero is slightly outdated; newer drivers use `{ "it6162" }`, but this is cosmetic. 14. **Copyright year 2024** for a driver first submitted in 2026 — should probably be 2024-2026 or just 2026. --- Generated by Claude Code Patch Reviewer ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-03-21 18:39 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-02-23 9:20 [PATCH 0/3] Add ITE IT6162 MIPI DSI to HDMI bridge driver Hermes Wu via B4 Relay 2026-02-23 9:20 ` [PATCH 1/3] drm/bridge: " Hermes Wu via B4 Relay 2026-02-24 0:21 ` Claude review: " Claude Code Review Bot 2026-02-23 9:20 ` [PATCH 2/3] dt-bindings: display: bridge: Add ITE IT6162 MIPI DSI to HDMI bridge Hermes Wu via B4 Relay 2026-02-24 0:21 ` Claude review: " Claude Code Review Bot 2026-02-23 9:20 ` [PATCH 3/3] MAINTAINERS: Add entry for ITE IT6162 MIPI DSI to HDMI bridge driver Hermes Wu via B4 Relay 2026-02-24 0:21 ` Claude review: " Claude Code Review Bot 2026-02-24 0:21 ` Claude review: Add " Claude Code Review Bot -- strict thread matches above, loose matches on Subject: below -- 2026-03-09 9:42 [PATCH v2 0/2] " Hermes Wu via B4 Relay 2026-03-09 9:42 ` [PATCH v2 2/2] drm/bridge: " Hermes Wu via B4 Relay 2026-03-10 2:31 ` Claude review: " Claude Code Review Bot 2026-03-13 6:15 [PATCH v3 0/2] " Hermes Wu via B4 Relay 2026-03-13 6:16 ` [PATCH v3 2/2] drm/bridge: " Hermes Wu via B4 Relay 2026-03-13 21:27 ` Claude review: " Claude Code Review Bot 2026-03-19 6:37 [PATCH v4 0/2] " Hermes Wu via B4 Relay 2026-03-19 6:37 ` [PATCH v4 2/2] drm/bridge: " Hermes Wu via B4 Relay 2026-03-21 18:39 ` Claude review: " Claude Code Review Bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox