On Thu, Oct 09, 2025 at 04:02:35PM +0800, Pet Weng wrote:
> This adds support for the ITE IT61620 bridge chip which converts
> MIPI DSI input to HDMI output. The Driver implements the basic
> bridge functions and integrates with the DRM bridge and connector
> frameworks.
>
> Supported fetures include:
> MIPI DSI input handling
> HDMI output setup
> Basic mode configuration
> I2C-based control and initialization
> HDCP 1.4 handling
>
> This driver will be used on platforms embedding the IT61620 for
> video output via HDMI from SoCs with MIPI DSI output.
>
> Signed-off-by: Pet Weng <[email protected]>
> ---
> drivers/gpu/drm/bridge/Kconfig | 18 +
> drivers/gpu/drm/bridge/Makefile | 1 +
> drivers/gpu/drm/bridge/ite-it61620.c | 2998
> ++++++++++++++++++++++++++++++++++
> 3 files changed, 3017 insertions(+)
>
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index
> a250afd8d662200c250f8e7c1b968a956b9c180c..69430734faabae6e5eb8aa6c50cdc47eca3545e1
> 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -111,6 +111,24 @@ config DRM_ITE_IT6263
> help
> ITE IT6263 LVDS to HDMI bridge chip driver.
>
> +config DRM_ITE_IT61620
> + tristate "ITE IT61620 DSI/HDMI bridge"
> + depends on OF
> + select DRM_DISPLAY_CONNECTOR
> + select DRM_DISPLAY_HDMI_HELPER
> + select DRM_DISPLAY_HDCP_HELPER
> + select DRM_DISPLAY_HELPER
> + select DRM_MIPI_DSI
> + select DRM_KMS_HELPER
> + select DRM_HDMI_HELPER
> + select CRYPTO_LIB_SHA1
> + help
> + Driver for ITE IT61620 MIPI DSI to HDMI bridge
> + chip driver.
> +
> + It enables display output through HDMI when connected to a MIPI
> + DSI source. The bridge translates the video signals for HDMI monitors.
> +
> config DRM_ITE_IT6505
> tristate "ITE IT6505 DisplayPort bridge"
> depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index
> c7dc03182e59273f52250ecd158d90ac3d29439c..41668786a909a217872ef2222b545d2b5bf87b51
> 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_ITE_IT6263) += ite-it6263.o
> +obj-$(CONFIG_DRM_ITE_IT61620) += ite-it61620.o
> obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
> obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
> obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> diff --git a/drivers/gpu/drm/bridge/ite-it61620.c
> b/drivers/gpu/drm/bridge/ite-it61620.c
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..079cb0ea264586a34c2b90858dcf6c1e0c66078c
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ite-it61620.c
> @@ -0,0 +1,2998 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2025 ITE Tech. Inc.
> + */
> +
> +#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 <linux/pm_runtime.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_of.h>
> +#include <drm/display/drm_hdcp_helper.h>
> +#include <drm/display/drm_hdmi_helper.h>
> +#include <drm/display/drm_hdmi_state_helper.h>
> +#include <video/videomode.h>
> +#include <sound/hdmi-codec.h>
> +#include <crypto/sha1.h>
> +
> +#define EDID_R_BURST_NUM 16
> +#define DDC_FIFO_DEPTH 32
> +
> +#define MIPIRX_I2C_ADDRESS (0x78 >> 1)
> +#define TX_I2C_ADDRESS (0xC0 >> 1)
> +
> +#define REG_CTRL_PW 0xFF
> +
> +#define REG_VENDER_ID_L 0x00
> +#define REG_VENDER_ID_H 0x01
> +#define REG_DEVICE_ID_L 0x02
> +#define REG_DEVICE_ID_H 0x03
> +#define REG_DEV_VERSION 0x04
> +#define DEV_VERSION_A0 0xA0
> +#define DEV_VERSION_C0 0xC0
> +
> +#define RX_REG_BANK 0x0F
> +#define M_MIPIRX_BANK 0x01
> +
> +#define RX_REG_RESET_CTRL 0x05
> +#define B_REF_CLOCK_RESET BIT(3)
> +#define RX_REG_CLK_CTRL 0x10
> +#define B_MIPI_INT_STATUS BIT(7)
> +#define RX_REG_D_RST 0x1A
> +#define B_D_RST BIT(0)
> +#define RX_REG_INT_STATUS_01 0x0A
> +#define B_INI_V_CHG BIT(0)
> +#define RX_REG_INT_POL 0x11
> +#define RX_REG_INT_MASK 0x114
> +#define B_V_CHG BIT(0)
> +#define RX_REG_INT_STATUS_02 0x22
> +#define B_INI_V_STB BIT(5)
> +#define RX_REG_MPPCLKI 0x12
> +
> +#define RX_REG_HFP_L 0x30
> +#define RX_REG_HFP_H 0x31
> +#define RX_REG_HSW_L 0x32
> +#define RX_REG_HSW_H 0x33
> +#define RX_REG_HBP_L 0x34
> +#define RX_REG_HBP_H 0x35
> +#define RX_REG_HDEW_L 0x36
> +#define RX_REG_HDEW_H 0x37
> +#define RX_REG_HTOTAL_L 0x48
> +#define RX_REG_HTOTAL_H 0x49
> +
> +#define RX_REG_SELDCLK 0x24
> +#define RX_REG_VFP_L 0x3A
> +#define RX_REG_VFP_H 0x3B
> +#define RX_REG_VSW_L 0x3C
> +#define RX_REG_VSW_H 0x3D
> +#define RX_REG_VBP_L 0x3E
> +#define RX_REG_VBP_H 0x3F
> +#define RX_REG_VDEW_L 0x40
> +#define RX_REG_VDEW_H 0x41
> +#define RX_REG_POL 0x4E
> +#define B_MIPI_H_POL BIT(0)
> +#define B_MIPI_V_POL BIT(1)
> +
> +#define RX_REG_DSCCTRL 0x25
> +#define RX_REG_SYNC_NUM 0x4D
> +#define RX_REG_BSP_SEL 0x1F
> +#define RX_REG_PWD_CTRL 0xE0
> +#define RX_REG_ENVRR 0x1AC
> +#define RX_REG_ENVBLK 0x1AD
> +
> +#define RX_REG_D2P_RESET 0xA0
> +#define RX_REG_D2P_CTRL 0xA1
> +#define RX_REG_AUTO_D2P_RESET 0xA2
> +#define RX_REG_TUNEOPT 0xA9
> +#define RX_REG_TUNETHRE 0xAA
> +#define RX_REG_DSC_RESET 0xAB
> +#define RX_REG_DSC_VFRD 0xAC
> +
> +#define RX_REG_MIPI_CONFIG 0x112
> +#define B_MIPI_LANE_NUM 0x03
> +#define B_MIPI_PN_SWAP BIT(2)
> +#define B_MIPI_LANE_SWAP BIT(3)
> +#define RX_REG_HS_CTRL 0x118
> +#define RX_REG_LP_CTRL 0x119
> +#define RX_REG_MIPI_CTRL01 0x44
> +#define RX_REG_AUTO_SYNC 0x144
> +#define RX_REG_MV_MAX 0x146
> +#define RX_REG_FORCE_M 0x147
> +#define B_FORCE_MHT_STB BIT(0)
> +#define B_FORCE_MVT_STB BIT(3)
> +#define RX_REG_MIPI_CTRL02 0x14E
> +#define B_FIFO_RST BIT(5)
> +
> +#define TX_REG_STATUS01 0x07
> +#define B_INT_STATUS BIT(0)
> +#define B_HPD_STATUS BIT(1)
> +#define B_VIDEO_STB BIT(2)
> +
> +#define TX_REG_HDMITX_BANK 0x0F
> +#define M_HDMITX_BANK 0x03
> +#define B_INT_EVENT_HDMI BIT(4)
> +#define TX_REG_RESET_1_CTRL 0x05
> +#define B_REFERENCE_CLOCK_RESET BIT(0)
> +#define B_VIDEO_RESET BIT(1)
> +#define B_AUDIO_RESET BIT(2)
> +#define B_AUX_RESET BIT(3)
> +#define B_IPCLK_RESET BIT(4)
> +#define B_SDM_RESET BIT(5)
> +#define B_TCLK_RESET BIT(6)
> +
> +#define TX_REG_RESET_2_CTRL 0x06
> +
> +#define TX_REG_V_STS 0x09
> +#define B_RXSEN BIT(1)
> +#define B_V_STABLE BIT(2)
> +#define B_TMDS_STABLE BIT(3)
> +
> +#define TX_REG_SYS_CONFIG 0x0C
> +#define B_EN_HDMI BIT(6)
> +#define B_INT_POL BIT(0)
> +#define B_INT_MODE BIT(1)
> +#define B_INT_OUT_STATUS BIT(2)
> +#define B_INT_OUTPUT BIT(3)
> +
> +#define BIT_OFFSET(x) ((x) * BITS_PER_BYTE)
> +
> +#define TX_REG_INT1 0x10
> +#define TX_REG_INT_MASK1 0x18
> +#define INT_HPD_CHG 0
> +#define B_HPD_CHG BIT(INT_HPD_CHG)
> +#define BIT_HPD_CHG (BIT_OFFSET(0) + INT_HPD_CHG)
> +#define INT_RXSEN_CHANGE 2
> +#define B_RXSEN_CHANGE BIT(INT_RXSEN_CHANGE)
> +#define BIT_RXSEN_CHANGE (BIT_OFFSET(0) + INT_RXSEN_CHANGE)
> +#define INT_AUTH_F 3
> +#define B_INT_AUTH_F BIT(INT_AUTH_F)
> +#define BIT_INT_AUTH_F (BIT_OFFSET(0) + INT_AUTH_F)
> +#define INT_AUTH_D 4
> +#define B_INT_AUTH_D BIT(INT_AUTH_D)
> +#define BIT_INT_AUTH_D (BIT_OFFSET(0) + INT_AUTH_D)
> +
> +#define TX_REG_INT2 0x11
> +#define TX_REG_INT_MASK2 0x19
> +#define INT_KSV_CHECK 1
> +#define B_KSV_CHECK BIT(INT_KSV_CHECK)
> +#define BIT_KSV_CHECK (BIT_OFFSET(1) + INT_KSV_CHECK)
> +
> +#define TX_REG_INT3 0x16
> +#define TX_REG_INT_MASK3 0x1E
> +#define INT_TMDS_STB_CHG 6
> +#define B_TMDS_STB_CHG BIT(INT_TMDS_STB_CHG)
> +#define BIT_TMDS_STB_CHG (BIT_OFFSET(2) + INT_TMDS_STB_CHG)
> +
> +#define REG_TX_INT_CTRL 0x21
> +#define B_DIS_INT_OUTPUT BIT(5)
> +#define TX_REG_CLOCK_PWD_CTRL 0x2B
> +
> +#define TX_REG_AFE0E 0x0E
> +#define TX_REG_AFE30 0x30
> +#define TX_REG_AFE33 0x33
> +#define TX_REG_AFE34 0x34
> +#define TX_REG_AFE35 0x35
> +#define TX_REG_AFEE9 0xE9
> +#define TX_REG_AFE_XP 0x219
> +#define TX_REG_AFE_XLC1 0x220
> +#define TX_REG_AFE_XLC2 0x223
> +#define TX_REG_AFE_XLC3 0x226
> +#define TX_REG_AFE_DRV 0x23B
> +
> +#define TX_REG_CR_1_CTRL 0x3A
> +#define TX_REG_CR_2_CTRL 0x3F
> +
> +#define TX_REG_R0TM 0x5A
> +#define TX_REG_SHA_SEL 0x5D
> +
> +#define TX_REG_HDCP_CTRL1 0x60
> +#define B_CPDESIRED BIT(0)
> +#define TX_REG_HDCP_PRO 0x61
> +#define B_AUTH_FIRE BIT(0)
> +#define B_LIST_CHK_DONE BIT(4)
> +#define B_LIST_CHK_FAIL BIT(5)
> +#define TX_REG_HDCP_CTRL2 0x62
> +#define TX_REG_AUTOMUTE 0x65
> +#define B_AN_SEL BIT(0)
> +#define B_EN_AN_GEN BIT(1)
> +#define B_EN_M0_RD BIT(5)
> +#define TX_REG_HDCP_AUTH_CS 0x66
> +#define M_AUTH_CH 0x7F
> +#define CS_AUTH_DONE 0x4E
> +#define CS_AUTH_FAIL 0x02
> +#define CS_KSVLIST_CHK 0x19
> +#define TX_REG_HDCP_CTRL3 0x1BA
> +#define B_ENC_DIS BIT(6)
> +#define B_PAUSE BIT(7)
> +
> +#define TX_REG_VD_CTRL1 0xA8
> +#define TX_REG_VD_CTRL2 0xA4
> +#define B_VIDEO_FIFO_REST BIT(0)
> +#define TX_REG_VD_CTRL3 0xB7
> +
> +#define TX_REG_AUD_SPDIF 0x23
> +#define TX_REG_AUD_CTRL 0xB8
> +#define TX_REG_AUD_FMT 0xBA
> +#define TX_REG_EN_AUDIO 0xBB
> +#define TX_REG_AUD_FIFO1 0xBC
> +#define TX_REG_AUD_FIFO2 0xBD
> +#define TX_REG_AUD_CTS 0x1BC
> +#define TX_REG_AUD_STS1 0x1F0
> +#define B_EN_AUD_NLPCM BIT(1)
> +#define TX_REG_AUD_STS2 0x1F3
> +#define TX_REG_AUD_STS3 0x1F4
> +
> +#define TX_REG_LINK_CTRL0 0xD3
> +#define B_EN_AUDIO_MUTE BIT(5)
> +
> +#define TX_REG_V_QUEUE 0xE7
> +#define TX_REG_V_TU 0xEB
> +
> +#define TX_REG_HPD_CONFIG 0xF6
> +
> +#define TX_REG_CEC_CONFIG 0xFA
> +#define B_EN_CEC BIT(0)
> +
> +#define TX_REG_DDC_CTRL1 0x1A0
> +#define TX_REG_DDC_ADDR 0x1A1
> +#define DDC_HDCP_ADDR (DRM_HDCP_DDC_ADDR << 1)
> +#define DDC_EDID_ADDR (DDC_ADDR << 1)
> +#define TX_REG_DDC_OFFSET 0x1A2
> +#define TX_REG_DDC_NUM_L 0x1A3
> +#define TX_REG_DDC_NUM_H 0x1A4
> +#define TX_REG_DDC_SEGMENT 0x1A5
> +#define TX_REG_DDC_COMMAND 0x1A6
> +#define DDC_COMMAND_BURST_R 0X00
> +#define DDC_COMMAND_BURST_W 0X01
> +#define DDC_COMMAND_EDID_RD 0X03
> +#define DDC_COMMAND_FIFO_CLR 0X09
> +#define DDC_COMMAND_ABORT 0X0F
> +#define TX_REG_DDC_CTRL2 0x1AD
> +#define B_DDC_REST BIT(4)
> +#define TX_REG_DDC_STATUS 0x1A7
> +#define B_DDC_TX_DONE BIT(7)
> +#define B_DDC_NOACK BIT(5)
> +#define B_DDC_FULL BIT(2)
> +#define TX_REG_DDC_FIFO 0x1A8
> +#define TX_REG_DDC_FIFO_STS 0x1AE
> +#define M_DDC_STAGE_NUM 0x3F
> +
> +#define TX_REG_HDMI_CTRL1 0x1B8
> +#define B_EN_HDMI_MODE BIT(0)
> +#define TX_REG_HDMI_CTRL2 0x1B9
> +#define B_EN_AVMUTE BIT(0)
> +#define TX_REG_EN_PKT1 0x1BF
> +#define B_EN_AVI BIT(0)
> +#define B_AVI_RP BIT(1)
> +#define B_EN_AUD BIT(2)
> +#define B_EN_AUD_RP BIT(3)
> +#define B_EN_VSIF BIT(6)
> +#define B_EN_VSIF_RP BIT(7)
> +#define TX_REG_EN_PKT2 0x1C0
> +#define B_EN_NULL BIT(0)
> +#define B_EN_NULL_RP BIT(1)
> +#define B_EN_GEN BIT(4)
> +#define B_GEN_RP BIT(5)
> +
> +#define TX_REG_VH_TIME 0x165
> +#define TX_REG_PG_HFP_L 0x150
> +#define TX_REG_PG_HFP_H 0x151
> +#define TX_REG_PG_HSW_L 0x152
> +#define TX_REG_PG_HSW_H 0x153
> +#define TX_REG_PG_HBP_L 0x154
> +#define TX_REG_PG_HBP_H 0x155
> +#define TX_REG_PG_DEW_L 0x156
> +#define TX_REG_PG_DEW_H 0x157
> +#define TX_REG_PG_HVR2_L 0x158
> +#define TX_REG_PG_HVR2_H 0x159
> +#define TX_REG_PG_VFP_L 0x15A
> +#define TX_REG_PG_VFP_H 0x15B
> +#define TX_REG_PG_VSW_L 0x15C
> +#define TX_REG_PG_VSW_H 0x15D
> +#define TX_REG_PG_VBP_L 0x15E
> +#define TX_REG_PG_VBP_H 0x15F
> +#define TX_REG_PG_VDEW_L 0x160
> +#define TX_REG_PG_HDEW_H 0x161
> +#define TX_REG_PG_VFP2_L 0x162
> +#define TX_REG_PG_VFP2_H 0x163
> +#define TX_REG_PG_POL 0x164
> +
> +#define TX_REG_AVIINFO_DB00 0x1D0
> +#define TX_REG_AVIINFO_DB01 0x1D1
> +#define TX_REG_AVIINFO_DB02 0x1D2
> +#define TX_REG_AVIINFO_DB03 0x1D3
> +#define TX_REG_AVIINFO_DB04 0x1D4
> +#define TX_REG_AVIINFO_DB05 0x1D5
> +#define TX_REG_AVIINFO_DB06 0x1D6
> +#define TX_REG_AVIINFO_DB07 0x1D7
> +#define TX_REG_AVIINFO_DB08 0x1D8
> +#define TX_REG_AVIINFO_DB09 0x1D9
> +#define TX_REG_AVIINFO_DB10 0x1DA
> +#define TX_REG_AVIINFO_DB11 0x1DB
> +#define TX_REG_AVIINFO_DB12 0x1DC
> +#define TX_REG_AVIINFO_DB13 0x1DD
> +#define TX_REG_AVIINFO_DB14 0x1DE
> +
> +#define TX_REG_AUDINFO_DB01 0x1E0
> +#define TX_REG_AUDINFO_DB02 0x1E1
> +#define TX_REG_AUDINFO_DB03 0x1E2
> +#define TX_REG_AUDINFO_DB04 0x1E3
> +#define TX_REG_AUDINFO_DB05 0x1E4
> +#define TX_REG_AUDINFO_DB06 0x1E5
> +#define TX_REG_AUDINFO_DB07 0x1E6
> +#define TX_REG_AUDINFO_DB08 0x1E7
> +#define TX_REG_AUDINFO_DB09 0x1E8
> +#define TX_REG_AUDINFO_DB10 0x1E9
> +
> +#define TX_REG_NULLPKT_HB00 0x310
> +#define TX_REG_NULLPKT_HB01 0x311
> +#define TX_REG_NULLPKT_HB02 0x312
> +#define TX_REG_NULLPKT_PB00 0x314
> +#define TX_REG_NULLPKT_PB27 0x32F
> +
> +#define TX_REG_VSIFPKT_HB02 0x37E
> +#define TX_REG_VSIFPKT_PB00 0x380
> +#define TX_REG_VSIFPKT_PB01 0x381
> +
> +#define TX_REG_SSC_PD 0x211
> +
> +#define TX_REG_TXPLL_CTRL 0x218
> +#define TX_REG_XLC_7_CTRL 0x227
> +
> +#define TX_REG_TXDRV_PD_CTRL 0x23D
> +#define TX_REG_TXDRV_CTRL 0x240
> +#define TX_REG_AUX_CTRL 0x244
> +#define TX_REG_IPLL_CTRL 0x248
> +#define TX_REG_RDROM_TRG 0x271
> +
> +#define TX_REG_HDCP_ARI_L 0x63
> +#define TX_REG_HDCP_ARI_H 0x64
> +
> +#define TX_REG_HDCP_AUTH_STS 0x67
> +
> +#define TX_REG_HDCP_BKSV1 0x68
> +#define TX_REG_HDCP_BKSV2 0x69
> +#define TX_REG_HDCP_BKSV3 0x6A
> +#define TX_REG_HDCP_BKSV4 0x6B
> +#define TX_REG_HDCP_BKSV5 0x6C
> +
> +#define TX_REG_HDCP_BRI_L 0x6D
> +#define TX_REG_HDCP_BRI_H 0x6E
> +
> +#define TX_REG_HDCP_AKSV1 0x6F
> +#define TX_REG_HDCP_AKSV2 0x70
> +#define TX_REG_HDCP_AKSV3 0x71
> +#define TX_REG_HDCP_AKSV4 0x72
> +#define TX_REG_HDCP_AKSV5 0x73
> +
> +#define TX_REG_ANM0V1 0x74
> +#define TX_REG_ANM0V2 0x75
> +#define TX_REG_ANM0V3 0x76
> +#define TX_REG_HDCP_AN_M0_V4 0x77
> +#define TX_REG_HDCP_AN_M05 0x78
> +#define TX_REG_HDCP_AN_M06 0x79
> +#define TX_REG_HDCP_AN_M07 0x7A
> +#define TX_REG_HDCP_AN_M08 0x7B
> +
> +#define TX_REG_HDCP_BCAPS 0x7C
> +#define B_KSV_READY BIT(5)
> +#define TX_REG_HDCP_BSTS_L 0x7D
> +#define TX_REG_HDCP_BSTS_H 0x7E
> +
> +#define TX_REG_AUDPKT_CTS_0 0x3A8
> +#define TX_REG_AUDPKT_CTS_1 0x3A9
> +#define TX_REG_AUDPKT_CTS_2 0x3AA
> +
> +#define TX_REG_AUDPKT_N_0 0x3AB
> +#define TX_REG_AUDPKT_N_1 0x3AC
> +#define TX_REG_AUDPKT_N_2 0x3AD
> +
> +#define SOFT_DDC_TIMEOUT_MS 100
> +#define HDCP_KSV_LIST_TIMEOUT_MS 5000
> +
> +#define MAX_HDCP_DOWN_STREAM_COUNT 127
> +#define HDCP_SHA1_FIFO_LEN (MAX_HDCP_DOWN_STREAM_COUNT * DRM_HDCP_KSV_LEN +
> 10)
> +
> +#define HI_BYTE(x) (((x) >> 8) & 0xFF)
> +#define LO_BYTE(x) ((x) & 0xFF)
> +
> +enum video_state {
> + it61620_VIDEO_OFF = 0x00,
> + it61620_VIDEO_WAIT,
> + it61620_VIDEO_ON,
> +};
> +
> +enum hdcp_state {
> + CP_NONE = 0x00,
> + CP_GOING = 0x01,
> +};
> +
> +enum it61620_audio_select {
> + I2S = 0,
> + SPDIF,
> +};
> +
> +enum it61620_audio_word_length {
> + WORD_LENGTH_16BIT = 0x0,
> + WORD_LENGTH_18BIT = 0x1,
> + WORD_LENGTH_20BIT = 0x2,
> + WORD_LENGTH_24BIT = 0x3,
> +};
> +
> +enum it61620_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 it61620_audio_type {
> + LPCM = 0,
> + NLPCM,
> +};
> +
> +enum it61620_audio_i2s_input_format {
> + I2S_INPUT_FORMAT_STANDARD = 0,
> + I2S_INPUT_FORMAT_32BIT = 1,
> +};
> +
> +struct it6162_chip_info {
> + u16 vid;
> + u16 pid;
> +};
> +
> +struct it61620_audio {
> + enum it61620_audio_select select;
> + enum it61620_audio_type type;
> + enum it61620_audio_sample_rate sample_rate;
> + enum it61620_audio_i2s_input_format i2s_input_format;
> + u8 word_length;
> + u8 channel_count;
> + unsigned int sample_width;
> + unsigned int channel_number;
> + unsigned char channel_status[AES_IEC958_STATUS_SIZE];
> +};
> +
> +struct it61620_mipirx {
> + u8 lane_num;
> + u8 pn_swap;
> + u8 lane_swap;
> +};
> +
> +struct it61620_hdmi_afe_setting {
> + unsigned int clock;
> + unsigned int h2on_pll;
> + unsigned int hs;
> + unsigned int afe_val[24];
> +};
> +
> +struct it61620 {
> + struct drm_bridge bridge;
> + struct drm_connector *connector;
> + struct device *dev;
> + enum drm_connector_status connector_status;
> + struct drm_device *drm;
> + struct drm_bridge *next_bridge;
> +
> + struct i2c_client *it61620_i2c;
> + struct i2c_client *mipirx_i2c;
> + struct i2c_client *tx_i2c;
> + struct regmap *it61620_regmap;
> + struct regmap *mipirx_regmap;
> + struct regmap *tx_regmap;
> +
> + struct delayed_work hdcp_work;
> + struct wait_queue_head wq;
> +
> + struct regulator *ovdd1833;
> + struct regulator *ivdd;
> + struct regulator *ovdd33;
> + struct gpio_desc *gpiod_reset;
> +
> + bool powered;
> + bool is_hdmi;
> + bool en_audio;
> + bool hpd;
> + u8 dev_ver;
> +
> + /* operations can only be served one at the time */
> + struct mutex ddc_lock;
> +
> + enum video_state video_state;
> +
> + struct it61620_audio audio_config;
> +
> + /* it61620 DSI RX related params */
> + struct mipi_dsi_device *dsi;
> +
> + struct it61620_mipirx mipirx_config;
> +
> + struct i2c_adapter ddc;
> +
> + unsigned long pixelclock;
> +
> + enum hdcp_state hdcp_state;
> + int hdcp_cp;
> + u8 sha1_input[HDCP_SHA1_FIFO_LEN];
> + const struct it6162_chip_info *chip_info;
> +};
> +
> +static inline struct it61620 *bridge_to_it61620(struct drm_bridge *bridge)
> +{
> + return container_of(bridge, struct it61620, bridge);
> +}
> +
> +static const struct regmap_config it61620_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .max_register = 0xff,
> + .cache_type = REGCACHE_NONE,
> +};
> +
> +static const struct regmap_range it61620_tx_volatile_ranges[] = {
> + { .range_min = 0, .range_max = 0x3ff},
> +};
> +
> +static const struct regmap_access_table it61620_tx_volatile_table = {
> + .yes_ranges = it61620_tx_volatile_ranges,
> + .n_yes_ranges = ARRAY_SIZE(it61620_tx_volatile_ranges),
> +};
> +
> +static const struct regmap_range_cfg it61620_tx_regmap_ranges[] = {
> + {
> + .range_min = 0,
> + .range_max = 0x3ff,
> + .selector_reg = TX_REG_HDMITX_BANK,
> + .selector_mask = M_HDMITX_BANK,
> + .selector_shift = 0,
> + .window_start = 0x00,
> + .window_len = 0x100,
> + },
> +};
> +
> +static const struct regmap_config it61620_tx_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .volatile_table = &it61620_tx_volatile_table,
> + .ranges = it61620_tx_regmap_ranges,
> + .num_ranges = ARRAY_SIZE(it61620_tx_regmap_ranges),
> + .max_register = 0x3FF,
> + .cache_type = REGCACHE_NONE,
> +};
> +
> +static const struct regmap_range it61620_mipirx_volatile_ranges[] = {
> + { .range_min = 0, .range_max = 0x1ff },
> +};
> +
> +static const struct regmap_access_table it61620_mipirx_volatile_table = {
> + .yes_ranges = it61620_mipirx_volatile_ranges,
> + .n_yes_ranges = ARRAY_SIZE(it61620_mipirx_volatile_ranges),
> +};
> +
> +static const struct regmap_range_cfg it61620_mipirx_regmap_ranges[] = {
> + {
> + .range_min = 0,
> + .range_max = 0x1ff,
> + .selector_reg = RX_REG_BANK,
> + .selector_mask = M_MIPIRX_BANK,
> + .selector_shift = 0,
> + .window_start = 0x00,
> + .window_len = 0x100,
> + },
> +};
> +
> +static const struct regmap_config it61620_mipi_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .volatile_table = &it61620_mipirx_volatile_table,
> + .ranges = it61620_mipirx_regmap_ranges,
> + .num_ranges = ARRAY_SIZE(it61620_mipirx_regmap_ranges),
> + .max_register = 0x1FF,
> + .cache_type = REGCACHE_NONE,
> +};
> +
> +static unsigned int it61620_mipi_reg_read(struct it61620 *it61620,
> + unsigned int reg)
> +{
> + unsigned int val;
> + int err;
> + struct device *dev = it61620->dev;
> +
> + if (!it61620->powered)
> + return -ENODEV;
Do you actually need all these wrappers if you know that the driver will
only access these registers if the bridge is powered on?
BTW: Can the powered off bridge cause any issues if it shared the I2C
bus with other devices? Will it prevent communication with any other
devices on the same bus?
> +
> +static void it61620_mipi_set_d2v_video_timing(struct it61620 *it61620,
> + struct drm_display_mode *mode)
> +{
> + struct videomode vm;
> + u8 d2vffrd_adr_dly;
> + u32 htotal, hfp, hsw, hbp, hdew;
> + u32 vfp, vsw, vbp, vdew;
> + u32 clock;
> + bool hpol_high = 0, vpol_high = 0;
> +
> + drm_display_mode_to_videomode(mode, &vm);
> + it61620_show_drm_video_mode(it61620, &vm);
> +
> + /*
> + * Keep the video pixel clock for later N/CTS calculation and
> + * HDMI AFE configuration
> + */
> + it61620->pixelclock = vm.pixelclock;
Well, no. I think you should be using conn_state->hdmi.tmds_char_rate,
especially for N/CTS.
> +
> + if (vm.flags & DISPLAY_FLAGS_HSYNC_HIGH)
> + hpol_high = true;
> +
> + if (vm.flags & DISPLAY_FLAGS_VSYNC_HIGH)
> + vpol_high = true;
> +
> + clock = vm.pixelclock / 1000;
> + hdew = vm.hactive;
> +
> + hfp = vm.hfront_porch;
> + hsw = vm.hsync_len;
> + hbp = vm.hback_porch;
> + htotal = vm.hactive + vm.hfront_porch + vm.hsync_len +
> + vm.hback_porch;
> +
> + vdew = vm.vactive;
> + vfp = vm.vfront_porch;
> + vsw = vm.vsync_len;
> + vbp = vm.vback_porch;
> +
> + if (it61620->dev_ver != DEV_VERSION_A0)
> + it61620_mipi_reg_set(it61620, RX_REG_SELDCLK, 0x1C, 0x04);
> +
> + if (hdew > 1920) {
> + if (htotal < 4272) {
> + d2vffrd_adr_dly = abs(htotal - 2880) / 24;
> + it61620_mipi_reg_write(it61620, RX_REG_DSC_VFRD,
> + d2vffrd_adr_dly);
> + } else {
> + it61620_mipi_reg_write(it61620, RX_REG_DSC_VFRD, 0x50);
> + }
> + } else {
> + it61620_mipi_reg_write(it61620, RX_REG_DSC_VFRD, hdew / 36);
> + }
> +
[...]
> +
> +static void it61620_hdmi_irq_hpd(struct it61620 *it61620)
> +{
> + it61620->hpd = it61620_hdmi_get_hpd_status(it61620);
So... There is an HPD interrupt. Please call drm_bridge_hpd_notify().
> +
> + if (!it61620->hpd) {
> + wake_up(&it61620->wq);
> + it61620_stop_hdcp_work(it61620);
> + it61620_hdmi_disable_afe(it61620);
> + } else if (it61620->video_state == it61620_VIDEO_ON) {
> + it61620_hdmi_fire_afe(it61620);
> + it61620_start_hdcp_work(it61620);
> + }
> +}
> +
> +static void it61620_hdmi_irq_rxsen_chg(struct it61620 *it61620)
> +{
> + unsigned int rxsen;
> +
> + if (!it61620_hdmi_get_hpd_status(it61620))
> + return;
> +
> + rxsen = it61620_hdmi_reg_read(it61620, TX_REG_V_STS) & B_RXSEN;
> + if (it61620->video_state == it61620_VIDEO_ON) {
> + if (rxsen) {
> + it61620_hdmi_fire_afe(it61620);
> + it61620_start_hdcp_work(it61620);
> + } else {
> + it61620_stop_hdcp_work(it61620);
> + it61620_hdmi_disable_afe(it61620);
> + }
> + }
> +}
> +
> +static void it61620_hdmi_irq_TMDS_stb_change(struct it61620 *it61620)
tmds
> +{
> + unsigned int video_status;
> +
> + video_status = it61620_hdmi_reg_read(it61620, TX_REG_V_STS);
> +
> + if (video_status & B_TMDS_STABLE)
> + it61620_start_hdcp_work(it61620);
> +}
> +
> +static bool it61620_test_bit(unsigned int bit, const unsigned int *addr)
> +{
> + return 1 & (addr[bit / BITS_PER_BYTE] >> (bit % BITS_PER_BYTE));
Use existing function for this, please.
> +}
> +
> +
> +static enum drm_mode_status it61620_mode_valid(struct it61620 *it61620,
> + const struct drm_display_mode
> *mode)
> +{
> + if (mode->clock > 300000)
> + return MODE_CLOCK_HIGH;
Use tmds_char_rate_valid callback and drop this function?
> +
> + return MODE_OK;
> +}
> +
> +static int it61620_get_edid_block(void *data, u8 *buf, unsigned int block,
> + size_t len)
> +{
> + struct it61620 *it61620 = data;
> + unsigned int edid_offset;
> + unsigned int cnt;
> + unsigned int i;
> + int ret = 0;
> +
> + if (len > EDID_LENGTH)
> + return -EINVAL;
> +
> + guard(mutex)(&it61620->ddc_lock);
> +
> + it61620_hdmi_reg_write(it61620, TX_REG_DDC_COMMAND,
> DDC_COMMAND_FIFO_CLR);
> +
> + it61620_hdmi_reg_write(it61620, TX_REG_DDC_ADDR, DDC_EDID_ADDR);
> + it61620_hdmi_reg_write(it61620, TX_REG_DDC_SEGMENT, block / 2);
Is this necessary? Can't we use generic code that reads EDID through the
provided DDC bus?
> +
> + cnt = 0;
> + edid_offset = block * 128;
> +
> + for (i = 0; i < EDID_LENGTH; i += EDID_R_BURST_NUM,
> + edid_offset += EDID_R_BURST_NUM,
> + cnt += EDID_R_BURST_NUM) {
> + it61620_hdmi_reg_write(it61620, TX_REG_DDC_OFFSET, edid_offset);
> + it61620_hdmi_reg_write(it61620, TX_REG_DDC_SEGMENT, block >> 1);
> + it61620_hdmi_reg_write(it61620, TX_REG_DDC_NUM_L,
> + EDID_R_BURST_NUM);
> + it61620_hdmi_reg_write(it61620, TX_REG_DDC_NUM_H,
> + (EDID_R_BURST_NUM >> 8));
> + it61620_hdmi_reg_write(it61620, TX_REG_DDC_COMMAND,
> + DDC_COMMAND_EDID_RD);
> +
> + if (it61620_hdmi_ddc_wait(it61620) < 0) {
> + it61620_hdmi_ddc_abort(it61620);
> + ret = -EIO;
> + break;
> + }
> +
> + it61620_hdmi_get_ddc_fifo(it61620, &buf[cnt], EDID_R_BURST_NUM);
> + }
> +
> + return ret;
> +}
> +
> +static void it61620_set_capability_from_edid_parse(struct it61620 *it61620,
> + const struct edid *edid)
> +{
> + struct drm_device *drm = it61620->drm;
> +
> + it61620->is_hdmi = drm_detect_hdmi_monitor(edid);
> + it61620->en_audio = drm_detect_monitor_audio(edid);
> +
> + drm_dbg(drm, "%s mode, monitor %ssupport audio",
> + it61620->is_hdmi ? "HDMI" : "DVI",
> + it61620->en_audio ? "" : "not ");
> +}
> +
> +
> +static int it61620_audio_update_hw_params(struct it61620 *it61620,
> + struct hdmi_codec_daifmt *fmt,
> + struct hdmi_codec_params *hparms)
> +{
> + struct it61620_audio *config = &it61620->audio_config;
> +
> + 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 18:
> + config->sample_width = WORD_LENGTH_20BIT;
> + 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;
> + }
> +
> + it61620_hdmi_audio_set_ncts(it61620, hparms->sample_rate);
> + it61620_config_audio(it61620);
> + return 0;
> +}
> +
> +static void it61620_hdcp_work(struct work_struct *work)
> +{
> + struct it61620 *it61620 = container_of(work, struct it61620,
> + hdcp_work.work);
> + it61620_hdmi_reset_hdcp(it61620);
> + if (!it61620_hdmi_start_hdcp(it61620) &&
> + it61620->hdcp_state == CP_GOING) {
> + it61620_hdmi_disable_hdcp(it61620);
> + it61620_start_hdcp_work(it61620);
> + }
> +}
> +
> +static int it61620_hdmi_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct it61620 *it61620 = i2c_get_adapdata(adap);
> + struct device *dev = it61620->dev;
> + int i, j;
> +
> + for (i = 0 ; i < num ; i++) {
> + dev_dbg(dev,
> + " msg [%d] addr = %X, flag = %X, len = %d\n",
> + i, msgs[i].addr, msgs[i].flags, msgs[i].len);
> +
> + if ((msgs[i].flags & I2C_M_RD) == 0)
> + for (j = 0 ; j < msgs[i].len ; j++)
> + dev_dbg(dev,
> + "buf[%d] = %x\n", j, msgs[i].buf[j]);
> +
> + num = it61620_ddc_xfer(it61620, &msgs[i]);
> + }
> +
> + return num;
> +}
> +
> +static u32 it61620_hdmi_i2c_func(struct i2c_adapter *adapter)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm hdmi_ddc_algorithm = {
> + .master_xfer = it61620_hdmi_i2c_xfer,
> + .functionality = it61620_hdmi_i2c_func,
> +};
> +
> +static struct i2c_adapter *it61620_add_ddc_i2c_adapter(struct it61620
> *it61620)
> +{
> + struct i2c_adapter *adap = &it61620->ddc;
> + struct device *dev = it61620->dev;
> + int ret;
> +
> + adap->owner = THIS_MODULE;
> + adap->dev.parent = dev;
> + adap->algo = &hdmi_ddc_algorithm;
> + strscpy(adap->name, "ITE 61620 HDMI", sizeof(adap->name));
> + i2c_set_adapdata(adap, it61620);
> +
> + ret = i2c_add_adapter(adap);
> + if (ret) {
> + dev_err(dev,
> + "cannot add %s I2C adapter\n", adap->name);
> + return ERR_PTR(ret);
> + }
> +
> + return 0;
> +}
> +
> +static int it61620_i2c_and_regmap_init(struct i2c_client *client,
> + struct it61620 *it61620)
> +{
> + struct device *dev = it61620->dev;
> +
> + it61620->it61620_i2c = client;
> +
> + it61620->tx_i2c = devm_i2c_new_dummy_device(dev,
> + client->adapter,
> + TX_I2C_ADDRESS);
> + if (IS_ERR(it61620->tx_i2c))
> + return dev_err_probe(dev, PTR_ERR(it61620->tx_i2c),
> + "failed to create TX dummy i2c device at
> 0x%02x\n",
> + TX_I2C_ADDRESS);
> +
> + it61620->mipirx_i2c = devm_i2c_new_dummy_device(dev,
> + client->adapter,
> + MIPIRX_I2C_ADDRESS);
> + if (IS_ERR(it61620->mipirx_i2c))
> + return dev_err_probe(dev, PTR_ERR(it61620->mipirx_i2c),
> + "failed to create MIPI dummy i2c device at
> 0x%02x\n",
> + MIPIRX_I2C_ADDRESS);
> +
> + it61620->it61620_regmap = devm_regmap_init_i2c(it61620->it61620_i2c,
> + &it61620_regmap_config);
> + if (IS_ERR(it61620->it61620_regmap))
> + return dev_err_probe(dev, PTR_ERR(it61620->it61620_regmap),
> + "failed to init I2C regmap for it61620\n");
> +
> + it61620->tx_regmap = devm_regmap_init_i2c(it61620->tx_i2c,
> + &it61620_tx_regmap_config);
> + if (IS_ERR(it61620->tx_regmap))
> + return dev_err_probe(dev, PTR_ERR(it61620->tx_regmap),
> + "failed to init I2C regmap for TX\n");
> +
> + it61620->mipirx_regmap = devm_regmap_init_i2c(it61620->mipirx_i2c,
> +
> &it61620_mipi_regmap_config);
> + if (IS_ERR(it61620->mipirx_regmap))
> + return dev_err_probe(dev, PTR_ERR(it61620->mipirx_regmap),
> + "failed to init I2C regmap for MIPI\n");
> + return 0;
> +}
> +
> +static int it61620_attach_dsi(struct it61620 *it61620,
> + struct mipi_dsi_host *host)
> +{
> + struct device *dev = it61620->dev;
> + struct mipi_dsi_device *dsi;
> + const struct mipi_dsi_device_info info = {"it61620",
> + 0,
> + dev->of_node};
> +
> + dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
> + if (IS_ERR(dsi))
> + return dev_err_probe(dev, PTR_ERR(dsi), "failed to create dsi
> device\n");
> +
> + it61620->dsi = dsi;
> + dsi->lanes = 4;
> + dsi->format = MIPI_DSI_FMT_RGB888;
> + dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
> + MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
> +
> + return devm_mipi_dsi_attach(dev, dsi);
> +}
> +
> +static void it61620_detach_dsi(struct it61620 *it61620)
> +{
> + if (!it61620->dsi)
> + return;
> +
> + mipi_dsi_detach(it61620->dsi);
> +}
> +
> +static unsigned int it61620_parse_dt(struct it61620 *it61620)
> +{
> + struct device *dev = it61620->dev;
> + struct device_node *np = it61620->dev->of_node;
> + int num_lanes;
> +
> + if (!dev->of_node)
> + return -EINVAL;
> +
> + num_lanes = drm_of_get_data_lanes_count_ep(np, 0, -1, 1, 4);
> + if (num_lanes < 0)
> + num_lanes = 4;
> + it61620->mipirx_config.lane_num = num_lanes;
> +
> + it61620->next_bridge = devm_drm_of_get_bridge(dev, np, 1, -1);
> + if (IS_ERR(it61620->next_bridge))
> + return dev_err_probe(dev, PTR_ERR(it61620->next_bridge),
> + "failed to get next bridge\n");
> +
> + return 0;
> +}
> +
> +static int it61620_init_power(struct it61620 *it61620)
> +{
> + struct device *dev = it61620->dev;
> +
> + it61620->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(it61620->gpiod_reset))
> + return dev_err_probe(dev, PTR_ERR(it61620->gpiod_reset),
> + "gpiod_reset not found\n");
> +
> + it61620->ivdd = devm_regulator_get(dev, "ivdd");
> + if (IS_ERR(it61620->ivdd))
> + return dev_err_probe(dev, PTR_ERR(it61620->ivdd),
> + "ivdd regulator not found\n");
> +
> + it61620->ovdd1833 = devm_regulator_get(dev, "ovdd1833");
> + if (IS_ERR(it61620->ovdd1833))
> + return dev_err_probe(dev, PTR_ERR(it61620->ovdd1833),
> + "ovdd1833 regulator not found\n");
> +
> + it61620->ovdd33 = devm_regulator_get(dev, "ovdd");
> + if (IS_ERR(it61620->ovdd33))
> + return dev_err_probe(dev, PTR_ERR(it61620->ovdd33),
> + "ovdd33 regulator not found\n");
> +
> + return 0;
> +}
> +
> +static inline int __maybe_unused it61620_pm_bridge_suspend(struct device
> *dev)
> +{
> + struct it61620 *it61620 = dev_get_drvdata(dev);
> +
> + it61620_poweroff(it61620);
> +
> + return 0;
> +}
> +
> +static inline int __maybe_unused it61620_pm_bridge_resume(struct device *dev)
> +{
> + struct it61620 *it61620 = dev_get_drvdata(dev);
> +
> + return it61620_poweron(it61620);
> +}
> +
> +static DEFINE_RUNTIME_DEV_PM_OPS(it61620_bridge_pm_ops,
> + it61620_pm_bridge_suspend,
> + it61620_pm_bridge_resume, NULL);
> +
> +static int it61620_bridge_attach(struct drm_bridge *bridge,
> + struct drm_encoder *encoder,
> + enum drm_bridge_attach_flags flags)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> + struct drm_device *drm = bridge->dev;
> + int ret;
> +
> + it61620->drm = drm;
> +
> + if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) {
> + drm_err(drm,
> + "it61620 driver only copes with atomic updates\n");
> + return -EOPNOTSUPP;
> + }
No need to.
> +
> + ret = drm_bridge_attach(bridge->encoder, it61620->next_bridge,
> + bridge, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
pass flags directly.
> + if (ret < 0)
> + return ret;
> +
> + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
> + drm_dbg(drm,
> + "DRM_BRIDGE_ATTACH_NO_CONNECTOR must be supplied");
> + return -EINVAL;
> + }
Check this before calling attach.
> + return 0;
> +}
> +
> +static inline enum
> +drm_mode_status it61620_bridge_mode_valid(struct drm_bridge *bridge,
> + const struct drm_display_info *info,
> + const struct drm_display_mode *mode)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> +
> + return it61620_mode_valid(it61620, mode);
> +}
> +
> +static void it61620_bridge_hpd_notify(struct drm_bridge *bridge,
> + enum drm_connector_status status)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> + struct drm_device *drm = it61620->drm;
> + struct device *dev = it61620->dev;
> + int hpd, ret;
> +
> + hpd = (status == connector_status_connected) ? 1 : 0;
> +
> + if (it61620->connector_status != status) {
if (status == status) {
dev_dbg();
return;
}
> + drm_dbg(drm, "GPIO hpd status change %d->%d",
> + !hpd, hpd);
> +
> + it61620->connector_status = status;
> + if (hpd) {
> + drm_dbg(drm, "HPD_GPIO get to wake up");
> + ret = pm_runtime_get_sync(dev);
> + if (ret < 0)
> + dev_err(dev,
> + "pm_runtime_get_sync error %d", ret);
> + } else {
> + drm_dbg(drm, "HPD_GPIO put to sleep");
> + pm_runtime_mark_last_busy(dev);
> + pm_runtime_put_autosuspend(dev);
> + }
> +
> + } else {
> + drm_dbg(drm, "GPIO hpd status NO change %d", hpd);
> + }
> +}
> +
> +static void it61620_bridge_atomic_pre_enable(struct drm_bridge *bridge,
> + struct drm_atomic_state *state)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> + struct device *dev = it61620->dev;
> +
> + pm_runtime_get_sync(dev);
> +}
> +
> +static void it61620_bridge_atomic_enable(struct drm_bridge *bridge,
> + struct drm_atomic_state *state)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> + struct drm_crtc_state *crtc_state;
> + struct drm_connector_state *conn_state;
> + struct drm_display_mode *adj_mode;
> + struct drm_connector *connector;
> +
> + connector = drm_atomic_get_new_connector_for_encoder(state,
> + bridge->encoder);
> +
> + if (!connector)
> + return;
> + it61620->connector = connector;
> +
> + conn_state = drm_atomic_get_new_connector_state(state, connector);
> + if (WARN_ON(!conn_state))
> + return;
It can't be NULL here.
> + it61620->hdcp_cp = conn_state->content_protection;
> +
> + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
> + if (WARN_ON(!crtc_state))
> + return;
It can't be NULL here.
> +
> + adj_mode = &crtc_state->adjusted_mode;
> + if (WARN_ON(!adj_mode))
> + return;
How can it be NULL here? The previous checks are theoretically
possible. This one is guaranteed by the source code.
> +
> + drm_atomic_helper_connector_hdmi_update_infoframes(connector, state);
> + it61620_mipi_set_d2v_video_timing(it61620, adj_mode);
> + it61620_hdmi_config_output(it61620);
> +}
> +
> +static void it61620_bridge_atomic_disable(struct drm_bridge *bridge,
> + struct drm_atomic_state *state)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> +
> + if (!it61620->powered)
> + return;
> +
> + it61620_stop_hdcp_work(it61620);
> + if (it61620->hdcp_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED) {
> + drm_hdcp_update_content_protection(it61620->connector,
> +
> DRM_MODE_CONTENT_PROTECTION_DESIRED);
> + it61620->hdcp_cp = DRM_MODE_CONTENT_PROTECTION_UNDESIRED;
> + }
> + it61620_hdmi_disable_afe(it61620);
> + it61620_hdmi_powerdown(it61620);
> + it61620->video_state = it61620_VIDEO_OFF;
> +}
> +
> +static void it61620_bridge_atomic_post_disable(struct drm_bridge *bridge,
> + struct drm_atomic_state *state)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> + struct device *dev = it61620->dev;
> +
> + pm_runtime_mark_last_busy(dev);
> + pm_runtime_put_autosuspend(dev);
> +}
> +
> +static const struct drm_edid *it61620_bridge_edid_read(struct drm_bridge
> *bridge,
> + struct drm_connector
> *connector)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> + struct device *dev = it61620->dev;
> + const struct drm_edid *cached_edid;
> +
> + cached_edid = drm_edid_read_custom(connector, it61620_get_edid_block,
> + it61620);
It's no longer cached.
> +
> + if (!cached_edid) {
> + dev_dbg(dev, "failed to get edid!");
> + return NULL;
> + }
> +
> + it61620_set_capability_from_edid_parse(it61620,
> + drm_edid_raw(cached_edid));
The DRM framework will call drm_atomic_helper_connector_hdmi_update()
for you, which in turn will update connector->display_info via
drm_edid_connector_update(). After that you should be able to use
display_info.is_hdmi and display_info.has_audio.
> + return cached_edid;
> +}
> +
> +static int it61620_bridge_hdmi_clear_infoframe(struct drm_bridge *bridge,
> + enum hdmi_infoframe_type type)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> + struct drm_device *drm = it61620->drm;
> +
> + switch (type) {
> + case HDMI_INFOFRAME_TYPE_AVI:
> + it61620_hdmi_disable_avi_infoframe(it61620);
> + break;
> + case HDMI_INFOFRAME_TYPE_AUDIO:
> + it61620_hdmi_disable_audio_infoframe(it61620);
> + break;
> + case HDMI_INFOFRAME_TYPE_SPD:
> + it61620_hdmi_reg_set(it61620, TX_REG_EN_PKT2,
> + (B_EN_NULL | B_EN_NULL_RP), 0x00);
> + break;
> + case HDMI_INFOFRAME_TYPE_VENDOR:
> + it61620_hdmi_reg_set(it61620, TX_REG_EN_PKT1,
> + (B_EN_VSIF | B_EN_VSIF_RP), 0x00);
> + break;
> + default:
> + drm_dbg(drm, "Error: hdmi infoframe_type %x!!!\n", type);
> + }
> + return 0;
> +}
> +
> +static int it61620_bridge_hdmi_write_infoframe(struct drm_bridge *bridge,
> + enum hdmi_infoframe_type type,
> + const u8 *buffer, size_t len)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> + struct drm_device *drm = it61620->drm;
> +
> + switch (type) {
> + case HDMI_INFOFRAME_TYPE_AVI:
> + it61620_hdmi_avi_infoframe_set(it61620, buffer, len);
> + break;
> + case HDMI_INFOFRAME_TYPE_AUDIO:
> + it61620_hdmi_audio_infoframe_set(it61620, buffer, len);
> + break;
> + case HDMI_INFOFRAME_TYPE_SPD:
> + it61620_hdmi_spd_infoframe_set(it61620, buffer, len);
> + break;
> + case HDMI_INFOFRAME_TYPE_VENDOR:
> + it61620_hdmi_vender_infoframe_set(it61620, buffer, len);
> + break;
> + default:
> + drm_dbg(drm, "Error: hdmi infoframe_type %x!!!\n", type);
> + }
> + return 0;
> +}
> +
> +static int it61620_bridge_hdmi_audio_startup(struct drm_bridge *bridge,
> + struct drm_connector *connector)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> +
> + it61620_enable_audio(it61620);
> + return 0;
> +}
> +
> +static int it61620_bridge_hdmi_audio_prepare(struct drm_bridge *bridge,
> + struct drm_connector *connector,
> + struct hdmi_codec_daifmt *fmt,
> + struct hdmi_codec_params *hparms)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> +
> + switch (hparms->sample_rate) {
> + case 32000:
> + case 44100:
> + case 48000:
> + case 88200:
> + case 96000:
> + case 176400:
> + case 192000:
> + case 768000:
I think I have asked and got no response. What does this bring on top of
the checks implemented by hdmi-codec?
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + switch (hparms->sample_width) {
> + case 16:
> + case 24:
> + case 18:
18 bits are not allowed by hdmi-codec, so this is strange.
> + case 20:
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + switch (fmt->fmt) {
> + case HDMI_I2S:
> + case HDMI_SPDIF:
This is definitely an overkill. What else do we expect to get here?
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + it61620_audio_update_hw_params(it61620, fmt, hparms);
Wait... You've checked the params (needlessly), here is a function which
can return an error if any of the values is unuspported, but... you just
ignore it's return value. Why?
> +
> + return
> drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector,
> +
> &hparms->cea);
> +}
> +
> +static void it61620_bridge_hdmi_audio_shutdown(struct drm_bridge *bridge,
> + struct drm_connector *connector)
> +{
> + struct it61620 *it61620 = bridge_to_it61620(bridge);
> +
> + it61620_disable_audio(it61620);
> +}
> +
> +static const struct drm_bridge_funcs it61620_bridge_funcs = {
> + .attach = it61620_bridge_attach,
> + .mode_valid = it61620_bridge_mode_valid,
> + .hpd_notify = it61620_bridge_hpd_notify,
> +
> + .atomic_pre_enable = it61620_bridge_atomic_pre_enable,
> + .atomic_enable = it61620_bridge_atomic_enable,
> + .atomic_disable = it61620_bridge_atomic_disable,
> + .atomic_post_disable = it61620_bridge_atomic_post_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,
> +
> + .edid_read = it61620_bridge_edid_read,
> +
> + .hdmi_clear_infoframe = it61620_bridge_hdmi_clear_infoframe,
> + .hdmi_write_infoframe = it61620_bridge_hdmi_write_infoframe,
> + .hdmi_audio_startup = it61620_bridge_hdmi_audio_startup,
> + .hdmi_audio_prepare = it61620_bridge_hdmi_audio_prepare,
> + .hdmi_audio_shutdown = it61620_bridge_hdmi_audio_shutdown,
> +};
> +
> +static int it61620_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct device_node *np = dev->of_node;
> + struct mipi_dsi_host *host;
> + struct it61620 *it61620;
> + int ret = 0;
> +
> + it61620 = devm_drm_bridge_alloc(dev, struct it61620, bridge,
> + &it61620_bridge_funcs);
> + if (IS_ERR(it61620))
> + return PTR_ERR(it61620);
> +
> + it61620->dev = dev;
> + it61620->chip_info = of_device_get_match_data(dev);
> +
> + host = drm_of_get_dsi_bus(dev);
> + if (IS_ERR(host))
> + return dev_err_probe(dev, PTR_ERR(host),
> + "failed to find dsi host\n");
> +
> + ret = it61620_i2c_and_regmap_init(client, it61620);
> + if (ret < 0)
> + return ret;
> +
> + i2c_set_clientdata(client, it61620);
> +
> + ret = it61620_init_power(it61620);
> + if (ret < 0)
> + return ret;
> +
> + it61620_config_default(it61620);
> +
> + ret = it61620_parse_dt(it61620);
> + if (ret < 0)
> + return ret;
> +
> + if (!client->irq)
> + return dev_err_probe(dev, -ENODEV,
> + "Failed to get INTP IRQ\n");
> +
> + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> + it61620_int_threaded_handler,
> + IRQF_TRIGGER_LOW | IRQF_ONESHOT |
> + IRQF_NO_AUTOEN,
> + "it61620-intp", it61620);
> + if (ret < 0)
> + return dev_err_probe(dev, ret,
> + "failed to request INTP threaded IRQ\n");
> +
> + INIT_DELAYED_WORK(&it61620->hdcp_work, it61620_hdcp_work);
> + init_waitqueue_head(&it61620->wq);
> +
> + mutex_init(&it61620->ddc_lock);
> +
> + it61620_add_ddc_i2c_adapter(it61620);
> +
> + pm_runtime_enable(dev);
> + pm_runtime_set_autosuspend_delay(dev, 1000);
> + pm_runtime_use_autosuspend(dev);
> +
> + it61620->bridge.funcs = &it61620_bridge_funcs;
> + it61620->bridge.of_node = np;
> + it61620->bridge.ops = DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HDMI |
> + DRM_BRIDGE_OP_HDMI_AUDIO;
Having no OP_DETECT / OP_HPD is surprising
> + it61620->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
> + it61620->bridge.support_hdcp = true;
> + it61620->bridge.vendor = "ITE";
> + it61620->bridge.product = "IT61620";
> + it61620->bridge.hdmi_audio_dev = dev;
> + it61620->bridge.hdmi_audio_max_i2s_playback_channels = 8;
> + it61620->bridge.hdmi_audio_spdif_playback = false;
> + it61620->bridge.hdmi_audio_dai_port = 2;
> +
> + ret = devm_drm_bridge_add(dev, &it61620->bridge);
> + if (ret < 0)
> + return dev_err_probe(dev, ret,
> + "failed to add drm bridge\n");
> +
> + ret = it61620_attach_dsi(it61620, host);
> + if (ret < 0)
> + return dev_err_probe(dev, ret,
> + "failed to attach to DSI host\n");
> +
> + return 0;
> +}
> +
> +static void it61620_remove(struct i2c_client *client)
> +{
> + struct it61620 *it61620 = i2c_get_clientdata(client);
> + struct device *dev = it61620->dev;
> +
> + disable_irq(client->irq);
> + pm_runtime_disable(dev);
> + it61620_detach_dsi(it61620);
> +
> + mutex_destroy(&it61620->ddc_lock);
> +}
> +
> +static const struct it6162_chip_info it61620_chip_info = {
> + .vid = 0x4954,
> + .pid = 0x6152,
> +};
> +
> +static const struct of_device_id it61620_dt_ids[] = {
> + { .compatible = "ite,it61620", .data = &it61620_chip_info},
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, it61620_dt_ids);
> +
> +static const struct i2c_device_id it61620_i2c_ids[] = {
> + { "it61620", 0 },
> + { },
> +};
> +MODULE_DEVICE_TABLE(i2c, it61620_i2c_ids);
> +
> +static struct i2c_driver it61620_driver = {
> + .driver = {
> + .name = "it61620",
> + .of_match_table = it61620_dt_ids,
> + .pm = &it61620_bridge_pm_ops,
> + },
> + .probe = it61620_probe,
> + .remove = it61620_remove,
> + .id_table = it61620_i2c_ids,
> +};
> +
> +module_i2c_driver(it61620_driver);
> +
> +MODULE_AUTHOR("Pet Weng <[email protected]>");
> +MODULE_AUTHOR("Hermes Wu <[email protected]>");
> +MODULE_DESCRIPTION("it61620 mipi to hdmi driver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.34.1
>
--
With best wishes
Dmitry