On Thu, Dec 05, 2024 at 12:45:17PM +0100, AngeloGioacchino Del Regno wrote: > Add support for the newer HDMI-TX (Encoder) v2 and DDC v2 IPs > found in MediaTek's MT8195, MT8188 SoC and their variants, and > including support for display modes up to 4k60 and for HDMI > Audio, as per the HDMI 2.0 spec. > > HDCP and CEC functionalities are also supported by this hardware, > but are not included in this commit and that also poses a slight > difference between the V2 and V1 controllers in how they handle > Hotplug Detection (HPD). > > While the v1 controller was using the CEC controller to check > HDMI cable connection and disconnection, in this driver the v2 > one does not. > > This is due to the fact that on parts with v2 designs, like the > MT8195 SoC, there is one CEC controller shared between the HDMI > Transmitter (HDMI-TX) and Receiver (HDMI-RX): before eventually > adding support to use the CEC HW to wake up the HDMI controllers > it is necessary to have support for one TX, one RX *and* for both > at the same time. > > Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@xxxxxxxxxxxxx> > --- > drivers/gpu/drm/mediatek/Kconfig | 8 + > drivers/gpu/drm/mediatek/Makefile | 4 + > drivers/gpu/drm/mediatek/mtk_hdmi_common.c | 5 + > drivers/gpu/drm/mediatek/mtk_hdmi_common.h | 1 + > drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c | 403 +++++ > drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h | 263 ++++ > drivers/gpu/drm/mediatek/mtk_hdmi_v2.c | 1488 +++++++++++++++++++ > 7 files changed, 2172 insertions(+) > create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c > create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h > create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_v2.c > > diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig > index 73459f07dae3..8127576be1eb 100644 > --- a/drivers/gpu/drm/mediatek/Kconfig > +++ b/drivers/gpu/drm/mediatek/Kconfig > @@ -50,3 +50,11 @@ config DRM_MEDIATEK_HDMI > select PHY_MTK_HDMI > help > DRM/KMS HDMI driver for Mediatek SoCs > + > +config DRM_MEDIATEK_HDMI_V2 > + tristate "DRM HDMI v2 IP support for MediaTek SoCs" > + depends on DRM_MEDIATEK > + select DRM_MEDIATEK_HDMI_COMMON > + select PHY_MTK_HDMI > + help > + DRM/KMS HDMI driver for MediaTek SoCs with HDMIv2 IP > diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile > index 8973d7ba37d3..928a850f707f 100644 > --- a/drivers/gpu/drm/mediatek/Makefile > +++ b/drivers/gpu/drm/mediatek/Makefile > @@ -25,7 +25,11 @@ mediatek-drm-hdmi-objs := mtk_cec.o \ > mtk_hdmi.o \ > mtk_hdmi_ddc.o > > +mediatek-drm-hdmi-v2-objs := mtk_hdmi_ddc_v2.o \ > + mtk_hdmi_v2.o > + > obj-$(CONFIG_DRM_MEDIATEK_HDMI_COMMON) += mtk_hdmi_common.o > obj-$(CONFIG_DRM_MEDIATEK_HDMI) += mediatek-drm-hdmi.o > +obj-$(CONFIG_DRM_MEDIATEK_HDMI_V2) += mediatek-drm-hdmi-v2.o > > obj-$(CONFIG_DRM_MEDIATEK_DP) += mtk_dp.o > diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_common.c b/drivers/gpu/drm/mediatek/mtk_hdmi_common.c > index 0f60842462b0..90f603f34cde 100644 > --- a/drivers/gpu/drm/mediatek/mtk_hdmi_common.c > +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_common.c > @@ -295,6 +295,11 @@ static int mtk_hdmi_dt_parse_pdata(struct mtk_hdmi *hdmi, struct platform_device > if (IS_ERR(hdmi->regs)) > return PTR_ERR(hdmi->regs); > > + /* Populate HDMI sub-devices if present */ > + ret = devm_of_platform_populate(&pdev->dev); > + if (ret) > + return ret; > + > remote = of_graph_get_remote_node(np, 1, 0); > if (!remote) > return dev_err_probe(dev, -EINVAL, "Cannot find HDMI input port\n"); > diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_common.h b/drivers/gpu/drm/mediatek/mtk_hdmi_common.h > index 15121e8548f3..8cfff39b0d90 100644 > --- a/drivers/gpu/drm/mediatek/mtk_hdmi_common.h > +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_common.h > @@ -176,6 +176,7 @@ struct mtk_hdmi_conf { > bool tz_disabled; > bool cea_modes_only; > unsigned long max_mode_clock; > + u32 reg_hdmi_tx_cfg; > }; > > static inline struct mtk_hdmi *hdmi_ctx_from_bridge(struct drm_bridge *b) > diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c > new file mode 100644 > index 000000000000..297b8b6f668c > --- /dev/null > +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c > @@ -0,0 +1,403 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * MediaTek HDMI v2 Display Data Channel Driver > + * > + * Copyright (c) 2021 MediaTek Inc. > + * Copyright (c) 2021 BayLibre, SAS > + * Copyright (c) 2024 Collabora Ltd. > + * AngeloGioacchino Del Regno <angelogioacchino.delregno@xxxxxxxxxxxxx> > + */ > + > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/err.h> > +#include <linux/errno.h> > +#include <linux/i2c.h> > +#include <linux/io.h> > +#include <linux/iopoll.h> > +#include <linux/kernel.h> > +#include <linux/mfd/syscon.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/of_address.h> > +#include <linux/of_irq.h> > +#include <linux/of_platform.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/regmap.h> > +#include <linux/semaphore.h> > +#include <linux/slab.h> > +#include <linux/time.h> > +#include <linux/types.h> > + > +#include <drm/drm_edid.h> > + > +#include "mtk_hdmi_common.h" > +#include "mtk_hdmi_regs_v2.h" > + > +#define DDC2_DLY_CNT 572 /* BIM=208M/(v*4) = 90Khz */ > +#define DDC2_DLY_CNT_EDID 832 /* BIM=208M/(v*4) = 62.5Khz */ > +#define SI2C_ADDR_READ 0xf4 > +#define SCDC_I2C_SLAVE_ADDRESS 0x54 > + > +struct mtk_hdmi_ddc { > + struct device *dev; > + void __iomem *regs; > + struct clk *clk; > + struct i2c_adapter adap; > + /* Serialize read/write operations */ > + struct mutex mtx; > +}; > + > +static int mtk_ddc_check_and_rise_low_bus(struct mtk_hdmi_ddc *ddc) > +{ > + u32 val; > + > + regmap_read(ddc->regs, HDCP2X_DDCM_STATUS, &val); > + if (val & DDC_I2C_BUS_LOW) { > + regmap_update_bits(ddc->regs, DDC_CTRL, DDC_CTRL_CMD, > + FIELD_PREP(DDC_CTRL_CMD, DDC_CMD_CLOCK_SCL)); > + usleep_range(250, 300); > + } > + > + if (val & DDC_I2C_NO_ACK) { > + u32 ddc_ctrl, hpd_ddc_ctrl, hpd_ddc_status; > + > + regmap_read(ddc->regs, DDC_CTRL, &ddc_ctrl); > + regmap_read(ddc->regs, HPD_DDC_CTRL, &hpd_ddc_ctrl); > + regmap_read(ddc->regs, HPD_DDC_STATUS, &hpd_ddc_status); > + } > + > + if (val & DDC_I2C_NO_ACK) > + return -EIO; > + > + return 0; > +} > + > +static int mtk_ddc_wr_one(struct mtk_hdmi_ddc *ddc, u16 addr_id, > + u16 offset_id, u8 wr_data) > +{ > + u32 val; > + int ret; > + > + /* If down, rise bus for write operation */ > + mtk_ddc_check_and_rise_low_bus(ddc); > + > + regmap_update_bits(ddc->regs, HPD_DDC_CTRL, HPD_DDC_DELAY_CNT, > + FIELD_PREP(HPD_DDC_DELAY_CNT, DDC2_DLY_CNT)); > + > + regmap_write(ddc->regs, SI2C_CTRL, > + FIELD_PREP(SI2C_ADDR, SI2C_ADDR_READ) | > + FIELD_PREP(SI2C_WDATA, wr_data) | > + SI2C_WR); > + > + regmap_write(ddc->regs, DDC_CTRL, > + FIELD_PREP(DDC_CTRL_CMD, DDC_CMD_SEQ_WRITE) | > + FIELD_PREP(DDC_CTRL_DIN_CNT, 1) | > + FIELD_PREP(DDC_CTRL_OFFSET, offset_id) | > + FIELD_PREP(DDC_CTRL_ADDR, addr_id)); > + usleep_range(1000, 1250); > + > + ret = regmap_read_poll_timeout(ddc->regs, HPD_DDC_STATUS, val, > + !(val & DDC_I2C_IN_PROG), 500, 1000); > + if (ret) { > + dev_err(ddc->dev, "DDC I2C write timeout\n"); > + return ret; > + } > + > + /* The I2C bus might be down after WR operation: rise it again */ > + ret = mtk_ddc_check_and_rise_low_bus(ddc); > + if (ret) { > + dev_err(ddc->dev, "Error during write operation: No ACK\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int mtk_ddcm_read_hdmi(struct mtk_hdmi_ddc *ddc, u16 dly_cnt, u16 uc_dev, > + u8 addr, u8 *puc_value, u16 data_cnt) > +{ > + u16 i, loop_counter, temp_length, uc_idx; > + u32 rem, uc_read_count, val; > + int ret; > + > + if (!puc_value || !data_cnt || !dly_cnt) { > + dev_err(ddc->dev, "Bad DDCM read request\n"); > + return 0; > + } > + > + mtk_ddc_check_and_rise_low_bus(ddc); > + > + regmap_update_bits(ddc->regs, DDC_CTRL, DDC_CTRL_CMD, > + FIELD_PREP(DDC_CTRL_CMD, DDC_CMD_CLEAR_FIFO)); > + > + if (data_cnt >= 16) { > + temp_length = 16; > + loop_counter = data_cnt; > + > + rem = do_div(loop_counter, temp_length); > + if (rem) > + loop_counter++; > + } else { > + temp_length = data_cnt; > + loop_counter = 1; > + } > + > + if (uc_dev >= DDC_ADDR && dly_cnt < DDC2_DLY_CNT_EDID) > + dly_cnt = DDC2_DLY_CNT_EDID; > + > + regmap_update_bits(ddc->regs, HPD_DDC_CTRL, HPD_DDC_DELAY_CNT, > + FIELD_PREP(HPD_DDC_DELAY_CNT, dly_cnt)); > + > + for (i = 0; i < loop_counter; i++) { > + rem = data_cnt % 16; > + > + if (i > 0 && i == (loop_counter - 1) && rem) > + temp_length = rem; > + > + /* 0x51 - 0x53: Flow control */ > + if (uc_dev > DDC_ADDR && uc_dev <= 0x53) { > + regmap_update_bits(ddc->regs, SCDC_CTRL, SCDC_DDC_SEGMENT, > + FIELD_PREP(SCDC_DDC_SEGMENT, uc_dev - DDC_ADDR)); > + > + regmap_write(ddc->regs, DDC_CTRL, > + FIELD_PREP(DDC_CTRL_CMD, DDC_CMD_ENH_READ_NOACK) | > + FIELD_PREP(DDC_CTRL_DIN_CNT, temp_length) | > + FIELD_PREP(DDC_CTRL_OFFSET, addr + i * temp_length) | > + FIELD_PREP(DDC_CTRL_ADDR, DDC_ADDR)); > + } else { > + u16 offset; > + > + if (addr != 0x43) > + offset = i * 16; > + else > + offset = 0; > + > + regmap_write(ddc->regs, DDC_CTRL, > + FIELD_PREP(DDC_CTRL_CMD, DDC_CMD_SEQ_READ_NOACK) | > + FIELD_PREP(DDC_CTRL_DIN_CNT, temp_length) | > + FIELD_PREP(DDC_CTRL_OFFSET, addr + offset) | > + FIELD_PREP(DDC_CTRL_ADDR, uc_dev)); > + } > + usleep_range(5000, 5500); > + > + ret = regmap_read_poll_timeout(ddc->regs, HPD_DDC_STATUS, val, > + !(val & DDC_I2C_IN_PROG), 1000, > + 500 * (temp_length + 5)); > + if (ret) { > + dev_err(ddc->dev, "Timeout waiting for DDC I2C\n"); > + return ret; > + } > + > + ret = mtk_ddc_check_and_rise_low_bus(ddc); > + if (ret) { > + dev_err(ddc->dev, "Error during read operation: No ACK\n"); > + return ret; > + } > + > + for (uc_idx = 0; uc_idx < temp_length; uc_idx++) { > + unsigned int read_idx = i * 16 + uc_idx; > + > + regmap_write(ddc->regs, SI2C_CTRL, > + FIELD_PREP(SI2C_ADDR, SI2C_ADDR_READ) | > + SI2C_RD); > + > + regmap_read(ddc->regs, HPD_DDC_STATUS, &val); > + puc_value[read_idx] = FIELD_GET(DDC_DATA_OUT, val); > + > + regmap_write(ddc->regs, SI2C_CTRL, > + FIELD_PREP(SI2C_ADDR, SI2C_ADDR_READ) | > + SI2C_CONFIRM_READ); > + > + /* > + * If HDMI IP gets reset during EDID read, DDC read > + * operation will fail and its delay counter will be > + * reset to 400. > + */ > + regmap_read(ddc->regs, HPD_DDC_CTRL, &val); > + if (FIELD_GET(HPD_DDC_DELAY_CNT, val) < DDC2_DLY_CNT) > + return 0; > + > + uc_read_count = read_idx + 1; > + } > + } > + if (uc_read_count > U8_MAX) > + dev_warn(ddc->dev, "Invalid read data count %u\n", uc_read_count); > + > + return uc_read_count; > +} > + > +static int mtk_hdmi_fg_ddc_data_read(struct mtk_hdmi_ddc *ddc, u16 b_dev, > + u8 data_addr, u16 data_cnt, u8 *pr_data) > +{ > + int read_data_cnt; > + u16 req_data_cnt; > + > + if (!pr_data || !data_cnt) > + return -EINVAL; > + > + req_data_cnt = U8_MAX - data_addr + 1; > + if (req_data_cnt > data_cnt) > + req_data_cnt = data_cnt; > + > + mutex_lock(&ddc->mtx); > + > + regmap_set_bits(ddc->regs, HDCP2X_POL_CTRL, HDCP2X_DIS_POLL_EN); > + > + read_data_cnt = mtk_ddcm_read_hdmi(ddc, DDC2_DLY_CNT, b_dev, data_addr, > + pr_data, req_data_cnt); > + > + mutex_unlock(&ddc->mtx); > + > + if (read_data_cnt < 0) > + return read_data_cnt; > + else if (read_data_cnt != req_data_cnt) > + return -EINVAL; > + > + return 0; > +} > + > +static int mtk_hdmi_ddc_fg_data_write(struct mtk_hdmi_ddc *ddc, u16 b_dev, > + u8 data_addr, u16 data_cnt, u8 *pr_data) > +{ > + int i = 0, ret; > + > + mutex_lock(&ddc->mtx); > + > + regmap_set_bits(ddc->regs, HDCP2X_POL_CTRL, HDCP2X_DIS_POLL_EN); > + do { > + ret = mtk_ddc_wr_one(ddc, b_dev, data_addr + i, pr_data[i]); > + if (ret) > + break; > + } while (++i < data_cnt); > + > + mutex_unlock(&ddc->mtx); > + > + return ret; > +} > + > +static int mtk_hdmi_ddc_v2_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num) > +{ > + struct mtk_hdmi_ddc *ddc; > + u8 offset = 0; > + int i, ret; > + > + if (!adapter || !adapter->algo_data || !msgs) > + return -EINVAL; > + > + ddc = adapter->algo_data; > + > + for (i = 0; i < num; i++) { > + struct i2c_msg *msg = &msgs[i]; > + > + if (!msg->buf) { > + pr_err("XFER: No message buffer\n"); > + return -EINVAL; > + } > + > + if (msg->flags & I2C_M_RD) { > + /* > + * The underlying DDC hardware always issues a write request > + * that assigns the read offset as part of the read operation, > + * therefore, use the `offset` value assigned in the previous > + * write request from drm_edid > + */ > + ret = mtk_hdmi_fg_ddc_data_read(ddc, msg->addr, offset, > + msg->len, &msg->buf[0]); > + if (ret) > + return ret; > + } else { > + ret = mtk_hdmi_ddc_fg_data_write(ddc, msg->addr, msg->buf[0], > + msg->len - 1, &msg->buf[1]); > + if (ret) > + return ret; > + > + /* > + * Store the offset value requested by drm_edid or by > + * scdc to use in subsequent read requests. > + */ > + if ((msg->addr == DDC_ADDR || msg->addr == SCDC_I2C_SLAVE_ADDRESS) && > + msg->len == 1) { > + offset = msg->buf[0]; > + } > + } > + } > + > + return i; > +} > + > +static u32 mtk_hdmi_ddc_v2_func(struct i2c_adapter *adapter) > +{ > + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; > +} > + > +static const struct i2c_algorithm mtk_hdmi_ddc_v2_algorithm = { > + .master_xfer = mtk_hdmi_ddc_v2_xfer, > + .functionality = mtk_hdmi_ddc_v2_func, > +}; > + > +static int mtk_hdmi_ddc_v2_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct mtk_hdmi_ddc *ddc; > + int ret; > + > + ddc = devm_kzalloc(dev, sizeof(*ddc), GFP_KERNEL); > + if (!ddc) > + return -ENOMEM; > + > + ddc->dev = dev; > + ddc->regs = device_node_to_regmap(dev->parent->of_node); > + if (IS_ERR_OR_NULL(ddc->regs)) > + return dev_err_probe(dev, > + IS_ERR(ddc->regs) ? PTR_ERR(ddc->regs) : -EINVAL, > + "Cannot get regmap\n"); > + > + ddc->clk = devm_clk_get_enabled(dev, NULL); > + if (IS_ERR(ddc->clk)) > + return dev_err_probe(dev, PTR_ERR(ddc->clk), "Cannot get DDC clock\n"); > + > + mutex_init(&ddc->mtx); > + > + strscpy(ddc->adap.name, "mediatek-hdmi-ddc-v2", sizeof(ddc->adap.name)); > + ddc->adap.owner = THIS_MODULE; > + ddc->adap.algo = &mtk_hdmi_ddc_v2_algorithm; > + ddc->adap.retries = 3; > + ddc->adap.dev.of_node = dev->of_node; > + ddc->adap.algo_data = ddc; > + ddc->adap.dev.parent = &pdev->dev; > + > + ret = devm_pm_runtime_enable(&pdev->dev); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, "Cannot enable Runtime PM\n"); > + > + pm_runtime_get_sync(dev); > + > + ret = devm_i2c_add_adapter(dev, &ddc->adap); > + if (ret < 0) > + return dev_err_probe(dev, ret, "Cannot add DDC I2C adapter\n"); > + > + platform_set_drvdata(pdev, ddc); > + return 0; > +} > + > +static const struct of_device_id mtk_hdmi_ddc_v2_match[] = { > + { .compatible = "mediatek,mt8195-hdmi-ddc" }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, mtk_hdmi_ddc_v2_match) > + > +struct platform_driver mtk_hdmi_ddc_v2_driver = { > + .probe = mtk_hdmi_ddc_v2_probe, > + .driver = { > + .name = "mediatek-hdmi-ddc-v2", > + .of_match_table = mtk_hdmi_ddc_v2_match, > + }, > +}; > +module_platform_driver(mtk_hdmi_ddc_v2_driver); > + > +MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@xxxxxxxxxxxxx>"); > +MODULE_AUTHOR("Can Zeng <can.zeng@xxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("MediaTek HDMIv2 DDC Driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h > new file mode 100644 > index 000000000000..521b35c7e14d > --- /dev/null > +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h > @@ -0,0 +1,263 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (c) 2021 MediaTek Inc. > + * Copyright (c) 2021 BayLibre, SAS > + * Copyright (c) 2024 Collabora Ltd. > + * AngeloGioacchino Del Regno <angelogioacchino.delregno@xxxxxxxxxxxxx> > + */ > + > +#ifndef _MTK_HDMI_REGS_H > +#define _MTK_HDMI_REGS_H > + > +/* HDMI_TOP Config */ > +#define TOP_CFG00 0x000 > +#define HDMI2_ON BIT(2) > +#define HDMI_MODE_HDMI BIT(3) > +#define SCR_ON BIT(4) > +#define TMDS_PACK_MODE GENMASK(9, 8) > +#define TMDS_PACK_MODE_8BPP 0 > +#define TMDS_PACK_MODE_10BPP 1 > +#define TMDS_PACK_MODE_12BPP 2 > +#define TMDS_PACK_MODE_16BPP 3 > +#define DEEPCOLOR_PKT_EN BIT(12) > +#define HDMI_ABIST_VIDEO_FORMAT GENMASK(21, 16) > +#define HDMI_ABIST_ENABLE BIT(31) > +#define TOP_CFG01 0x004 > +#define CP_SET_MUTE_EN BIT(0) > +#define CP_CLR_MUTE_EN BIT(1) > +#define NULL_PKT_EN BIT(2) > +#define NULL_PKT_VSYNC_HIGH_EN BIT(3) > + > +/* HDMI_TOP Audio: Channel Mapping */ > +#define TOP_AUD_MAP 0x00c > +#define SD0_MAP GENMASK(2, 0) > +#define SD1_MAP GENMASK(6, 4) > +#define SD2_MAP GENMASK(10, 8) > +#define SD3_MAP GENMASK(14, 12) > +#define SD4_MAP GENMASK(18, 16) > +#define SD5_MAP GENMASK(22, 20) > +#define SD6_MAP GENMASK(26, 24) > +#define SD7_MAP GENMASK(30, 28) > + > +/* Auxiliary Video Information (AVI) Infoframe */ > +#define TOP_AVI_HEADER 0x024 > +#define TOP_AVI_PKT00 0x028 > +#define TOP_AVI_PKT01 0x02C > +#define TOP_AVI_PKT02 0x030 > +#define TOP_AVI_PKT03 0x034 > +#define TOP_AVI_PKT04 0x038 > +#define TOP_AVI_PKT05 0x03C > + > +/* Audio Interface Infoframe */ > +#define TOP_AIF_HEADER 0x040 > +#define TOP_AIF_PKT00 0x044 > +#define TOP_AIF_PKT01 0x048 > +#define TOP_AIF_PKT02 0x04c > +#define TOP_AIF_PKT03 0x050 > + > +/* Audio SPDIF Infoframe */ > +#define TOP_SPDIF_HEADER 0x054 > +#define TOP_SPDIF_PKT00 0x058 > +#define TOP_SPDIF_PKT01 0x05c > +#define TOP_SPDIF_PKT02 0x060 > +#define TOP_SPDIF_PKT03 0x064 > +#define TOP_SPDIF_PKT04 0x068 > +#define TOP_SPDIF_PKT05 0x06c > +#define TOP_SPDIF_PKT06 0x070 > +#define TOP_SPDIF_PKT07 0x074 > + > +/* Infoframes Configuration */ > +#define TOP_INFO_EN 0x01c > +#define AVI_EN BIT(0) > +#define SPD_EN BIT(1) > +#define AUD_EN BIT(2) > +#define CP_EN BIT(5) > +#define VSIF_EN BIT(11) > +#define AVI_EN_WR BIT(16) > +#define SPD_EN_WR BIT(17) > +#define AUD_EN_WR BIT(18) > +#define CP_EN_WR BIT(21) > +#define VSIF_EN_WR BIT(27) > +#define TOP_INFO_RPT 0x020 > +#define AVI_RPT_EN BIT(0) > +#define SPD_RPT_EN BIT(1) > +#define AUD_RPT_EN BIT(2) > +#define CP_RPT_EN BIT(5) > +#define VSIF_RPT_EN BIT(11) > + > +/* Vendor Specific Infoframe */ > +#define TOP_VSIF_HEADER 0x174 > +#define TOP_VSIF_PKT00 0x178 > +#define TOP_VSIF_PKT01 0x17c > +#define TOP_VSIF_PKT02 0x180 > +#define TOP_VSIF_PKT03 0x184 > +#define TOP_VSIF_PKT04 0x188 > +#define TOP_VSIF_PKT05 0x18c > +#define TOP_VSIF_PKT06 0x190 > +#define TOP_VSIF_PKT07 0x194 > + > +/* HDMI_TOP Misc */ > +#define TOP_MISC_CTLR 0x1a4 > +#define DEEP_COLOR_ADD BIT(4) > + > +/* Hardware interrupts */ > +#define TOP_INT_STA00 0x1a8 > +#define TOP_INT_ENABLE00 0x1b0 > +#define HTPLG_R_INT BIT(0) > +#define HTPLG_F_INT BIT(1) > +#define PORD_R_INT BIT(2) > +#define PORD_F_INT BIT(3) > +#define HDMI_VSYNC_INT BIT(4) > +#define HDMI_AUDIO_INT BIT(5) > +#define HDCP2X_RX_REAUTH_REQ_DDCM_INT BIT(25) > +#define TOP_INT_ENABLE01 0x1b4 > +#define TOP_INT_CLR00 0x1b8 > +#define TOP_INT_CLR01 0x1bc > + > + > +/* Video Mute */ > +#define TOP_VMUTE_CFG1 0x1c8 > +#define REG_VMUTE_EN BIT(16) > + > +/* HDMI Audio IP */ > +#define AIP_CTRL 0x400 > +#define CTS_SW_SEL BIT(0) > +#define CTS_REQ_EN BIT(1) > +#define MCLK_EN BIT(2) > +#define NO_MCLK_CTSGEN_SEL BIT(3) > +#define AUD_IN_EN BIT(8) > +#define AUD_SEL_OWRT BIT(9) > +#define SPDIF_EN BIT(13) > +#define HBRA_ON BIT(14) > +#define DSD_EN BIT(15) > +#define I2S_EN GENMASK(19, 16) > +#define HBR_FROM_SPDIF BIT(20) > +#define CTS_CAL_N4 BIT(23) > +#define SPDIF_INTERNAL_MODULE BIT(24) > +#define AIP_N_VAL 0x404 > +#define AIP_CTS_SVAL 0x408 > +#define AIP_SPDIF_CTRL 0x40c > +#define WR_1UI_LOCK BIT(0) > +#define FS_OVERRIDE_WRITE BIT(1) > +#define WR_2UI_LOCK BIT(2) > +#define MAX_1UI_WRITE GENMASK(15, 8) > +#define MAX_2UI_SPDIF_WRITE GENMASK(23, 16) > +#define MAX_2UI_I2S_HI_WRITE GENMASK(23, 20) > +#define MAX_2UI_I2S_LFE_CC_SWAP BIT(1) > +#define MAX_2UI_I2S_LO_WRITE GENMASK(19, 16) > +#define AUD_ERR_THRESH GENMASK(29, 24) > +#define I2S2DSD_EN BIT(30) > +#define AIP_I2S_CTRL 0x410 > +#define FIFO0_MAP GENMASK(1, 0) > +#define FIFO1_MAP GENMASK(3, 2) > +#define FIFO2_MAP GENMASK(5, 4) > +#define FIFO3_MAP GENMASK(7, 6) > +#define I2S_1ST_BIT_NOSHIFT BIT(8) > +#define I2S_DATA_DIR_LSB BIT(9) > +#define JUSTIFY_RIGHT BIT(10) > +#define WS_HIGH BIT(11) > +#define VBIT_COMPRESSED BIT(12) > +#define CBIT_ORDER_SAME BIT(13) > +#define SCK_EDGE_RISE BIT(14) > +#define AIP_I2S_CHST0 0x414 > +#define AIP_I2S_CHST1 0x418 > +#define AIP_TXCTRL 0x424 > +#define RST4AUDIO BIT(0) > +#define RST4AUDIO_FIFO BIT(1) > +#define RST4AUDIO_ACR BIT(2) > +#define AUD_LAYOUT_1 BIT(4) > +#define AUD_MUTE_FIFO_EN BIT(5) > +#define AUD_PACKET_DROP BIT(6) > +#define DSD_MUTE_EN BIT(7) > +#define AIP_TPI_CTRL 0x428 > +#define TPI_AUDIO_LOOKUP_EN BIT(2) > + > +/* Video downsampling configuration */ > +#define VID_DOWNSAMPLE_CONFIG 0x8d0 > +#define C444_C422_CONFIG_ENABLE BIT(0) > +#define C422_C420_CONFIG_ENABLE BIT(4) > +#define C422_C420_CONFIG_BYPASS BIT(5) > +#define C422_C420_CONFIG_OUT_CB_OR_CR BIT(6) > +#define VID_OUT_FORMAT 0x8fc > +#define OUTPUT_FORMAT_DEMUX_420_ENABLE BIT(10) > + > +/* HDCP registers */ > +#define HDCP_TOP_CTRL 0xc00 > +#define HDCP2X_CTRL_0 0xc20 > +#define HDCP2X_EN BIT(0) > +#define HDCP2X_ENCRYPT_EN BIT(7) > +#define HDCP2X_HPD_OVR BIT(10) > +#define HDCP2X_HPD_SW BIT(11) > +#define HDCP2X_POL_CTRL 0xc54 > +#define HDCP2X_DIS_POLL_EN BIT(16) > +#define HDCP1X_CTRL 0xcd0 > +#define HDCP1X_ENC_EN BIT(6) > + > +/* HDMI DDC registers */ > +#define HPD_DDC_CTRL 0xc08 > +#define HPD_DDC_DELAY_CNT GENMASK(31, 16) > +#define HPD_DDC_HPD_DBNC_EN BIT(2) > +#define HPD_DDC_PORD_DBNC_EN BIT(3) > +#define DDC_CTRL 0xc10 > +#define DDC_CTRL_ADDR GENMASK(7, 1) > +#define DDC_CTRL_OFFSET GENMASK(15, 8) > +#define DDC_CTRL_DIN_CNT GENMASK(25, 16) > +#define DDC_CTRL_CMD GENMASK(31, 28) > +#define SCDC_CTRL 0xc18 > +#define SCDC_DDC_SEGMENT GENMASK(15, 8) > +#define HPD_DDC_STATUS 0xc60 > +#define HPD_STATE GENMASK(1, 0) > +#define HPD_STATE_CONNECTED 2 > +#define HPD_PIN_STA BIT(4) > +#define PORD_PIN_STA BIT(5) > +#define DDC_I2C_IN_PROG BIT(13) > +#define DDC_DATA_OUT GENMASK(23, 16) > +#define SI2C_CTRL 0xcac > +#define SI2C_WR BIT(0) > +#define SI2C_RD BIT(1) > +#define SI2C_CONFIRM_READ BIT(2) > +#define SI2C_WDATA GENMASK(15, 8) > +#define SI2C_ADDR GENMASK(23, 16) > + > +/* HDCP DDC registers */ > +#define HDCP2X_DDCM_STATUS 0xc68 > +#define DDC_I2C_NO_ACK BIT(10) > +#define DDC_I2C_BUS_LOW BIT(11) > + > +/* HDMI TX registers */ > +#define HDMITX_CONFIG_MT8188 0xea0 > +#define HDMITX_CONFIG_MT8195 0x900 > +#define HDMI_YUV420_MODE BIT(10) > +#define HDMITX_SW_HPD BIT(29) > +#define HDMITX_SW_RSTB BIT(31) > + > +/** > + * enum mtk_hdmi_ddc_v2_cmds - DDC_CMD register commands > + * @DDC_CMD_READ_NOACK: Current address read with no ACK on last byte > + * @DDC_CMD_READ: Current address read with ACK on last byte > + * @DDC_CMD_SEQ_READ_NOACK: Sequential read with no ACK on last byte > + * @DDC_CMD_SEQ_READ: Sequential read with ACK on last byte > + * @DDC_CMD_ENH_READ_NOACK: Enhanced read with no ACK on last byte > + * @DDC_CMD_ENH_READ: Enhanced read with ACK on last byte > + * @DDC_CMD_SEQ_WRITE_NOACK: Sequential write ignoring ACK on last byte > + * @DDC_CMD_SEQ_WRITE: Sequential write requiring ACK on last byte > + * @DDC_CMD_RSVD: Reserved for future use > + * @DDC_CMD_CLEAR_FIFO: Clear DDC I2C FIFO > + * @DDC_CMD_CLOCK_SCL: Start clocking DDC I2C SCL > + * @DDC_CMD_ABORT_XFER: Abort DDC I2C transaction > + */ > +enum mtk_hdmi_ddc_v2_cmds { > + DDC_CMD_READ_NOACK = 0x0, > + DDC_CMD_READ, > + DDC_CMD_SEQ_READ_NOACK, > + DDC_CMD_SEQ_READ, > + DDC_CMD_ENH_READ_NOACK, > + DDC_CMD_ENH_READ, > + DDC_CMD_SEQ_WRITE_NOACK, > + DDC_CMD_SEQ_WRITE = 0x07, > + DDC_CMD_CLEAR_FIFO = 0x09, > + DDC_CMD_CLOCK_SCL = 0x0a, > + DDC_CMD_ABORT_XFER = 0x0f > +}; > + > +#endif /* _MTK_HDMI_REGS_H */ > diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c > new file mode 100644 > index 000000000000..05cbfc45be54 > --- /dev/null > +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c > @@ -0,0 +1,1488 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * MediaTek HDMI v2 IP driver > + * > + * Copyright (c) 2022 MediaTek Inc. > + * Copyright (c) 2022 BayLibre, SAS > + * Copyright (c) 2024 Collabora Ltd. > + * AngeloGioacchino Del Regno <angelogioacchino.delregno@xxxxxxxxxxxxx> > + */ > + > +#include <linux/arm-smccc.h> > +#include <linux/clk-provider.h> > +#include <linux/clk.h> > +#include <linux/debugfs.h> > +#include <linux/delay.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/mfd/syscon.h> > +#include <linux/of.h> > +#include <linux/of_gpio.h> > +#include <linux/of_graph.h> > +#include <linux/of_irq.h> > +#include <linux/of_platform.h> > +#include <linux/pm_runtime.h> > +#include <linux/suspend.h> > +#include <linux/timer.h> > +#include <linux/units.h> > + > +#include <drm/display/drm_hdmi_helper.h> > +#include <drm/display/drm_hdmi_state_helper.h> > +#include <drm/display/drm_scdc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drm_print.h> > +#include <drm/drm_probe_helper.h> > + > +#include "mtk_hdmi_common.h" > +#include "mtk_hdmi_regs_v2.h" > + > +#define MTK_HDMI_V2_CLOCK_MIN 27000 > +#define MTK_HDMI_V2_CLOCK_MAX 594000 > + > +#define HPD_PORD_HWIRQS (HTPLG_R_INT | HTPLG_F_INT | PORD_F_INT | PORD_R_INT) > + > +enum mtk_hdmi_v2_clk_id { > + MTK_HDMI_V2_CLK_HDCP_SEL, > + MTK_HDMI_V2_CLK_HDCP_24M_SEL, > + MTK_HDMI_V2_CLK_VPP_SPLIT_HDMI, > + MTK_HDMI_V2_CLK_HDMI_APB_SEL, > + MTK_HDMI_V2_CLK_COUNT, > +}; > + > +const char *const mtk_hdmi_v2_clk_names[MTK_HDMI_V2_CLK_COUNT] = { > + [MTK_HDMI_V2_CLK_HDMI_APB_SEL] = "bus", > + [MTK_HDMI_V2_CLK_HDCP_SEL] = "hdcp", > + [MTK_HDMI_V2_CLK_HDCP_24M_SEL] = "hdcp24m", > + [MTK_HDMI_V2_CLK_VPP_SPLIT_HDMI] = "hdmi-split", > +}; > + > +static inline void mtk_hdmi_v2_hwirq_disable(struct mtk_hdmi *hdmi) > +{ > + regmap_write(hdmi->regs, TOP_INT_ENABLE00, 0); > + regmap_write(hdmi->regs, TOP_INT_ENABLE01, 0); > +} > + > +static inline void mtk_hdmi_v2_enable_hpd_pord_irq(struct mtk_hdmi *hdmi, bool enable) > +{ > + if (enable) > + regmap_set_bits(hdmi->regs, TOP_INT_ENABLE00, HPD_PORD_HWIRQS); > + else > + regmap_clear_bits(hdmi->regs, TOP_INT_ENABLE00, HPD_PORD_HWIRQS); > +} > + > +static inline void mtk_hdmi_v2_clear_hpd_pord_irq(struct mtk_hdmi *hdmi) > +{ > + regmap_set_bits(hdmi->regs, TOP_INT_CLR00, HPD_PORD_HWIRQS); > + regmap_clear_bits(hdmi->regs, TOP_INT_CLR00, HPD_PORD_HWIRQS); > +} > + > +static inline void mtk_hdmi_v2_set_sw_hpd(struct mtk_hdmi *hdmi, bool enable) > +{ > + if (enable) { > + regmap_set_bits(hdmi->regs, hdmi->conf->reg_hdmi_tx_cfg, HDMITX_SW_HPD); > + regmap_set_bits(hdmi->regs, HDCP2X_CTRL_0, HDCP2X_HPD_OVR); > + regmap_set_bits(hdmi->regs, HDCP2X_CTRL_0, HDCP2X_HPD_SW); > + } else { > + regmap_clear_bits(hdmi->regs, HDCP2X_CTRL_0, HDCP2X_HPD_OVR); > + regmap_clear_bits(hdmi->regs, HDCP2X_CTRL_0, HDCP2X_HPD_SW); > + regmap_clear_bits(hdmi->regs, hdmi->conf->reg_hdmi_tx_cfg, HDMITX_SW_HPD); > + } > +} > + > +static void mtk_hdmi_v2_disable_hdcp_encrypt(struct mtk_hdmi *hdmi) > +{ > + regmap_clear_bits(hdmi->regs, HDCP2X_CTRL_0, HDCP2X_ENCRYPT_EN); > + regmap_clear_bits(hdmi->regs, HDCP1X_CTRL, HDCP1X_ENC_EN); > +} > + > +static inline void mtk_hdmi_v2_enable_scrambling(struct mtk_hdmi *hdmi, bool enable) > +{ > + struct drm_scdc *scdc = &hdmi->curr_conn->display_info.hdmi.scdc; > + > + if (enable) > + regmap_set_bits(hdmi->regs, TOP_CFG00, SCR_ON | HDMI2_ON); > + else > + regmap_clear_bits(hdmi->regs, TOP_CFG00, SCR_ON | HDMI2_ON); > + > + if (scdc->supported) { > + if (scdc->scrambling.supported) > + drm_scdc_set_scrambling(hdmi->curr_conn, enable); > + drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, enable); > + } > +} > + > +static void mtk_hdmi_v2_hw_vid_mute(struct mtk_hdmi *hdmi, bool enable) > +{ > + /* If enabled, sends a black image */ > + if (enable) > + regmap_set_bits(hdmi->regs, TOP_VMUTE_CFG1, REG_VMUTE_EN); > + else > + regmap_clear_bits(hdmi->regs, TOP_VMUTE_CFG1, REG_VMUTE_EN); > +} > + > +static void mtk_hdmi_v2_hw_aud_mute(struct mtk_hdmi *hdmi, bool enable) > +{ > + u32 aip, val; > + > + if (!enable) { > + regmap_clear_bits(hdmi->regs, AIP_TXCTRL, AUD_MUTE_FIFO_EN); > + return; > + } > + > + regmap_read(hdmi->regs, AIP_CTRL, &aip); > + > + val = AUD_MUTE_FIFO_EN; > + if (aip & DSD_EN) > + val |= DSD_MUTE_EN; > + > + regmap_update_bits(hdmi->regs, AIP_TXCTRL, val, val); > +} > + > +static void mtk_hdmi_v2_hw_reset(struct mtk_hdmi *hdmi) > +{ > + regmap_clear_bits(hdmi->regs, hdmi->conf->reg_hdmi_tx_cfg, HDMITX_SW_RSTB); > + udelay(5); > + regmap_set_bits(hdmi->regs, hdmi->conf->reg_hdmi_tx_cfg, HDMITX_SW_RSTB); > +} > + > +static inline u32 mtk_hdmi_v2_format_hw_packet(const u8 *buffer, u8 len) > +{ > + unsigned short i; > + u32 val = 0; > + > + for (i = 0; i < len; i++) > + val |= buffer[i] << (i * 8); > + > + return val; > +} > + > +static void mtk_hdmi_v2_hw_write_audio_infoframe(struct mtk_hdmi *hdmi, const u8 *buffer) > +{ > + regmap_clear_bits(hdmi->regs, TOP_INFO_EN, AUD_EN | AUD_EN_WR); > + regmap_clear_bits(hdmi->regs, TOP_INFO_RPT, AUD_RPT_EN); > + > + regmap_write(hdmi->regs, TOP_AIF_HEADER, mtk_hdmi_v2_format_hw_packet(&buffer[0], 3)); > + regmap_write(hdmi->regs, TOP_AIF_PKT00, mtk_hdmi_v2_format_hw_packet(&buffer[3], 3)); > + regmap_write(hdmi->regs, TOP_AIF_PKT01, mtk_hdmi_v2_format_hw_packet(&buffer[7], 2)); > + regmap_write(hdmi->regs, TOP_AIF_PKT02, 0); > + regmap_write(hdmi->regs, TOP_AIF_PKT03, 0); > + > + regmap_set_bits(hdmi->regs, TOP_INFO_RPT, AUD_RPT_EN); > + regmap_set_bits(hdmi->regs, TOP_INFO_EN, AUD_EN | AUD_EN_WR); > +} > + > +static void mtk_hdmi_v2_hw_write_avi_infoframe(struct mtk_hdmi *hdmi, const u8 *buffer) > +{ > + regmap_clear_bits(hdmi->regs, TOP_INFO_EN, AVI_EN_WR | AVI_EN); > + regmap_clear_bits(hdmi->regs, TOP_INFO_RPT, AVI_RPT_EN); > + > + regmap_write(hdmi->regs, TOP_AVI_HEADER, mtk_hdmi_v2_format_hw_packet(&buffer[0], 3)); > + regmap_write(hdmi->regs, TOP_AVI_PKT00, mtk_hdmi_v2_format_hw_packet(&buffer[3], 4)); > + regmap_write(hdmi->regs, TOP_AVI_PKT01, mtk_hdmi_v2_format_hw_packet(&buffer[7], 3)); > + regmap_write(hdmi->regs, TOP_AVI_PKT02, mtk_hdmi_v2_format_hw_packet(&buffer[10], 4)); > + regmap_write(hdmi->regs, TOP_AVI_PKT03, mtk_hdmi_v2_format_hw_packet(&buffer[14], 3)); > + regmap_write(hdmi->regs, TOP_AVI_PKT04, 0); > + regmap_write(hdmi->regs, TOP_AVI_PKT05, 0); > + > + regmap_set_bits(hdmi->regs, TOP_INFO_RPT, AVI_RPT_EN); > + regmap_set_bits(hdmi->regs, TOP_INFO_EN, AVI_EN_WR | AVI_EN); > +} > + > +static void mtk_hdmi_v2_hw_write_spd_infoframe(struct mtk_hdmi *hdmi, const u8 *buffer) > +{ > + regmap_clear_bits(hdmi->regs, TOP_INFO_EN, SPD_EN_WR | SPD_EN); > + regmap_clear_bits(hdmi->regs, TOP_INFO_RPT, SPD_RPT_EN); > + > + regmap_write(hdmi->regs, TOP_SPDIF_HEADER, mtk_hdmi_v2_format_hw_packet(&buffer[0], 3)); > + regmap_write(hdmi->regs, TOP_SPDIF_PKT00, mtk_hdmi_v2_format_hw_packet(&buffer[3], 4)); > + regmap_write(hdmi->regs, TOP_SPDIF_PKT01, mtk_hdmi_v2_format_hw_packet(&buffer[7], 3)); > + regmap_write(hdmi->regs, TOP_SPDIF_PKT02, mtk_hdmi_v2_format_hw_packet(&buffer[10], 4)); > + regmap_write(hdmi->regs, TOP_SPDIF_PKT03, mtk_hdmi_v2_format_hw_packet(&buffer[14], 3)); > + regmap_write(hdmi->regs, TOP_SPDIF_PKT04, mtk_hdmi_v2_format_hw_packet(&buffer[17], 4)); > + regmap_write(hdmi->regs, TOP_SPDIF_PKT05, mtk_hdmi_v2_format_hw_packet(&buffer[21], 3)); > + regmap_write(hdmi->regs, TOP_SPDIF_PKT06, mtk_hdmi_v2_format_hw_packet(&buffer[24], 4)); > + regmap_write(hdmi->regs, TOP_SPDIF_PKT07, buffer[28]); > + > + regmap_set_bits(hdmi->regs, TOP_INFO_EN, SPD_EN_WR | SPD_EN); > + regmap_set_bits(hdmi->regs, TOP_INFO_RPT, SPD_RPT_EN); > +} > + > +static void mtk_hdmi_v2_hw_write_vendor_infoframe(struct mtk_hdmi *hdmi, const u8 *buffer) > +{ > + regmap_clear_bits(hdmi->regs, TOP_INFO_EN, VSIF_EN_WR | VSIF_EN); > + regmap_clear_bits(hdmi->regs, TOP_INFO_RPT, VSIF_RPT_EN); > + > + regmap_write(hdmi->regs, TOP_VSIF_HEADER, mtk_hdmi_v2_format_hw_packet(&buffer[0], 3)); > + regmap_write(hdmi->regs, TOP_VSIF_PKT00, mtk_hdmi_v2_format_hw_packet(&buffer[3], 4)); > + regmap_write(hdmi->regs, TOP_VSIF_PKT01, mtk_hdmi_v2_format_hw_packet(&buffer[7], 2)); > + regmap_write(hdmi->regs, TOP_VSIF_PKT02, 0); > + regmap_write(hdmi->regs, TOP_VSIF_PKT03, 0); > + regmap_write(hdmi->regs, TOP_VSIF_PKT04, 0); > + regmap_write(hdmi->regs, TOP_VSIF_PKT05, 0); > + regmap_write(hdmi->regs, TOP_VSIF_PKT06, 0); > + regmap_write(hdmi->regs, TOP_VSIF_PKT07, 0); > + > + regmap_set_bits(hdmi->regs, TOP_INFO_EN, VSIF_EN_WR | VSIF_EN); > + regmap_set_bits(hdmi->regs, TOP_INFO_RPT, VSIF_RPT_EN); > +} > + > +static void mtk_hdmi_yuv420_downsampling(struct mtk_hdmi *hdmi, bool enable) > +{ > + u32 val; > + > + regmap_read(hdmi->regs, VID_DOWNSAMPLE_CONFIG, &val); > + > + if (enable) { > + regmap_set_bits(hdmi->regs, hdmi->conf->reg_hdmi_tx_cfg, > + HDMI_YUV420_MODE | HDMITX_SW_HPD); > + > + val |= C444_C422_CONFIG_ENABLE | C422_C420_CONFIG_ENABLE; > + val |= C422_C420_CONFIG_OUT_CB_OR_CR; > + val &= ~C422_C420_CONFIG_BYPASS; > + regmap_write(hdmi->regs, VID_DOWNSAMPLE_CONFIG, val); > + > + regmap_set_bits(hdmi->regs, VID_OUT_FORMAT, OUTPUT_FORMAT_DEMUX_420_ENABLE); > + } else { > + regmap_update_bits(hdmi->regs, hdmi->conf->reg_hdmi_tx_cfg, > + HDMI_YUV420_MODE | HDMITX_SW_HPD, HDMITX_SW_HPD); > + > + val &= ~(C444_C422_CONFIG_ENABLE | C422_C420_CONFIG_ENABLE); > + val &= ~C422_C420_CONFIG_OUT_CB_OR_CR; > + val |= C422_C420_CONFIG_BYPASS; > + regmap_write(hdmi->regs, VID_DOWNSAMPLE_CONFIG, val); > + > + regmap_clear_bits(hdmi->regs, VID_OUT_FORMAT, OUTPUT_FORMAT_DEMUX_420_ENABLE); > + } > +} > + > +static int mtk_hdmi_v2_setup_audio_infoframe(struct mtk_hdmi *hdmi) > +{ > + struct hdmi_codec_params *params = &hdmi->aud_param.codec_params; > + struct hdmi_audio_infoframe frame; > + u8 buffer[14]; > + ssize_t ret; > + > + memcpy(&frame, ¶ms->cea, sizeof(frame)); > + > + ret = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); > + if (ret < 0) > + return ret; > + > + mtk_hdmi_v2_hw_write_audio_infoframe(hdmi, buffer); This should be handled via HDMI Connector framework too. There is already an interface to set / clear audio infoframes. als I have been working on an interface to make HDMI codec implementation more generic, see [1]. Your comments are appreciated. [1] https://patchwork.freedesktop.org/series/134927/ > + > + return 0; > +} > + > +static inline void mtk_hdmi_v2_hw_reset_av_mute_regs(struct mtk_hdmi *hdmi) > +{ > + /* GCP packet */ > + regmap_clear_bits(hdmi->regs, TOP_CFG01, CP_CLR_MUTE_EN | CP_SET_MUTE_EN); > + regmap_clear_bits(hdmi->regs, TOP_INFO_RPT, CP_RPT_EN); > + regmap_clear_bits(hdmi->regs, TOP_INFO_EN, CP_EN | CP_EN_WR); > +} > + > +static inline void mtk_hdmi_v2_hw_av_mute(struct mtk_hdmi *hdmi, bool mute) > +{ > + mtk_hdmi_v2_hw_reset_av_mute_regs(hdmi); > + > + if (mute) > + regmap_set_bits(hdmi->regs, TOP_CFG01, CP_SET_MUTE_EN); > + else > + regmap_set_bits(hdmi->regs, TOP_CFG01, CP_CLR_MUTE_EN); > + > + regmap_set_bits(hdmi->regs, TOP_INFO_RPT, CP_RPT_EN); > + regmap_set_bits(hdmi->regs, TOP_INFO_EN, CP_EN | CP_EN_WR); > +} > + > +static void mtk_hdmi_v2_hw_ncts_enable(struct mtk_hdmi *hdmi, bool enable) > +{ > + if (enable) > + regmap_set_bits(hdmi->regs, AIP_CTRL, CTS_SW_SEL); > + else > + regmap_clear_bits(hdmi->regs, AIP_CTRL, CTS_SW_SEL); > +} > + > +static void mtk_hdmi_v2_hw_aud_set_channel_status(struct mtk_hdmi *hdmi, u8 *ch_status) > +{ > + /* Only the first 5 to 7 bytes of Channel Status contain useful information */ > + regmap_write(hdmi->regs, AIP_I2S_CHST0, mtk_hdmi_v2_format_hw_packet(&ch_status[0], 4)); > + regmap_write(hdmi->regs, AIP_I2S_CHST1, mtk_hdmi_v2_format_hw_packet(&ch_status[4], 3)); > +} > + > +static void mtk_hdmi_v2_hw_aud_set_ncts(struct mtk_hdmi *hdmi, > + unsigned int sample_rate, > + unsigned int clock) > +{ > + unsigned int n, cts; > + int ret; > + > + ret = mtk_hdmi_get_ncts(sample_rate, clock, &n, &cts); > + if (ret) { > + dev_err(hdmi->dev, "Invalid sample rate: %u\n", sample_rate); > + return; > + } > + > + regmap_write(hdmi->regs, AIP_N_VAL, n); > + regmap_write(hdmi->regs, AIP_CTS_SVAL, cts); > +} > + > +static void mtk_hdmi_v2_hw_aud_enable(struct mtk_hdmi *hdmi, bool enable) > +{ > + if (enable) > + regmap_clear_bits(hdmi->regs, AIP_TXCTRL, AUD_PACKET_DROP); > + else > + regmap_set_bits(hdmi->regs, AIP_TXCTRL, AUD_PACKET_DROP); > +} > + > +static u32 mtk_hdmi_v2_aud_output_channel_map(u8 sd0, u8 sd1, u8 sd2, u8 sd3, > + u8 sd4, u8 sd5, u8 sd6, u8 sd7) > +{ > + u32 val; > + > + /* > + * Each of the Output Channels (0-7) can be mapped to get their input > + * from any of the available Input Channels (0-7): this function > + * takes input channel numbers and formats a value that must then > + * be written to the TOP_AUD_MAP hardware register by the caller. > + */ > + val = FIELD_PREP(SD0_MAP, sd0) | FIELD_PREP(SD1_MAP, sd1); > + val |= FIELD_PREP(SD2_MAP, sd2) | FIELD_PREP(SD3_MAP, sd3); > + val |= FIELD_PREP(SD4_MAP, sd4) | FIELD_PREP(SD5_MAP, sd5); > + val |= FIELD_PREP(SD6_MAP, sd6) | FIELD_PREP(SD7_MAP, sd7); > + > + return val; > +} > + > +static void mtk_hdmi_audio_dsd_config(struct mtk_hdmi *hdmi, > + unsigned char chnum, bool dsd_bypass) > +{ > + u32 channel_map; > + > + regmap_update_bits(hdmi->regs, AIP_CTRL, SPDIF_EN | DSD_EN | HBRA_ON, DSD_EN); > + regmap_set_bits(hdmi->regs, AIP_TXCTRL, DSD_MUTE_EN); > + > + if (dsd_bypass) > + channel_map = mtk_hdmi_v2_aud_output_channel_map(0, 2, 4, 6, 1, 3, 5, 7); > + else > + channel_map = mtk_hdmi_v2_aud_output_channel_map(0, 5, 1, 0, 3, 2, 4, 0); > + > + regmap_write(hdmi->regs, TOP_AUD_MAP, channel_map); > + regmap_clear_bits(hdmi->regs, AIP_SPDIF_CTRL, I2S2DSD_EN); > +} > + > +static inline void mtk_hdmi_v2_hw_i2s_fifo_map(struct mtk_hdmi *hdmi, u32 fifo_mapping) > +{ > + regmap_update_bits(hdmi->regs, AIP_I2S_CTRL, > + FIFO0_MAP | FIFO1_MAP | FIFO2_MAP | FIFO3_MAP, fifo_mapping); > +} > + > +static inline void mtk_hdmi_v2_hw_i2s_ch_number(struct mtk_hdmi *hdmi, u8 chnum) > +{ > + regmap_update_bits(hdmi->regs, AIP_CTRL, I2S_EN, FIELD_PREP(I2S_EN, chnum)); > +} > + > +static void mtk_hdmi_v2_hw_i2s_ch_mapping(struct mtk_hdmi *hdmi, u8 chnum, u8 mapping) > +{ > + u32 fifo_map; > + u8 bdata; > + > + switch (chnum) { > + default: > + case 2: > + bdata = 0x1; > + break; > + case 3: > + bdata = 0x3; > + break; > + case 6: > + if (mapping == 0x0e) { > + bdata = 0xf; > + break; > + } > + fallthrough; > + case 5: > + bdata = 0x7; > + break; > + case 7: > + case 8: > + bdata = 0xf; > + break; > + } > + > + /* Assign default FIFO mapping: SD0 to FIFO0, SD1 to FIFO1, etc. */ > + fifo_map = FIELD_PREP(FIFO0_MAP, 0) | FIELD_PREP(FIFO1_MAP, 1); > + fifo_map |= FIELD_PREP(FIFO2_MAP, 2) | FIELD_PREP(FIFO3_MAP, 3); > + mtk_hdmi_v2_hw_i2s_fifo_map(hdmi, fifo_map); > + mtk_hdmi_v2_hw_i2s_ch_number(hdmi, bdata); > + > + /* > + * Set HDMI Audio packet layout indicator: > + * Layout 0 is for two channels > + * Layout 1 is for up to eight channels > + */ > + if (chnum == 2) > + regmap_set_bits(hdmi->regs, AIP_TXCTRL, AUD_LAYOUT_1); > + else > + regmap_clear_bits(hdmi->regs, AIP_TXCTRL, AUD_LAYOUT_1); > +} > + > +static void mtk_hdmi_i2s_data_fmt(struct mtk_hdmi *hdmi, unsigned char fmt) > +{ > + u32 val; > + > + regmap_read(hdmi->regs, AIP_I2S_CTRL, &val); > + val &= ~(WS_HIGH | I2S_1ST_BIT_NOSHIFT | JUSTIFY_RIGHT); > + > + switch (fmt) { > + case HDMI_I2S_MODE_RJT_24BIT: > + case HDMI_I2S_MODE_RJT_16BIT: > + val |= (WS_HIGH | I2S_1ST_BIT_NOSHIFT | JUSTIFY_RIGHT); > + break; > + case HDMI_I2S_MODE_LJT_24BIT: > + case HDMI_I2S_MODE_LJT_16BIT: > + val |= (WS_HIGH | I2S_1ST_BIT_NOSHIFT); > + break; > + case HDMI_I2S_MODE_I2S_24BIT: > + case HDMI_I2S_MODE_I2S_16BIT: > + default: > + break; > + } > + > + regmap_write(hdmi->regs, AIP_I2S_CTRL, val); > +} > + > +static inline void mtk_hdmi_i2s_sck_edge_rise(struct mtk_hdmi *hdmi, bool rise) > +{ > + if (rise) > + regmap_set_bits(hdmi->regs, AIP_I2S_CTRL, SCK_EDGE_RISE); > + else > + regmap_clear_bits(hdmi->regs, AIP_I2S_CTRL, SCK_EDGE_RISE); > +} > + > +static inline void mtk_hdmi_i2s_cbit_order(struct mtk_hdmi *hdmi, unsigned int cbit) > +{ > + regmap_update_bits(hdmi->regs, AIP_I2S_CTRL, CBIT_ORDER_SAME, cbit); > +} > + > +static inline void mtk_hdmi_i2s_vbit(struct mtk_hdmi *hdmi, unsigned int vbit) > +{ > + /* V bit: 0 for PCM, 1 for Compressed data */ > + regmap_update_bits(hdmi->regs, AIP_I2S_CTRL, VBIT_COMPRESSED, vbit); > +} > + > +static inline void mtk_hdmi_i2s_data_direction(struct mtk_hdmi *hdmi, unsigned int is_lsb) > +{ > + regmap_update_bits(hdmi->regs, AIP_I2S_CTRL, I2S_DATA_DIR_LSB, is_lsb); > +} > + > +static inline void mtk_hdmi_v2_hw_audio_type(struct mtk_hdmi *hdmi, unsigned int spdif_i2s) > +{ > + regmap_update_bits(hdmi->regs, AIP_CTRL, SPDIF_EN, FIELD_PREP(SPDIF_EN, spdif_i2s)); > +} > + > +static u8 mtk_hdmi_v2_get_i2s_ch_mapping(struct mtk_hdmi *hdmi, u8 channel_type) > +{ > + switch (channel_type) { > + case HDMI_AUD_CHAN_TYPE_1_1: > + case HDMI_AUD_CHAN_TYPE_2_1: > + return 0x01; > + case HDMI_AUD_CHAN_TYPE_3_0: > + return 0x02; > + case HDMI_AUD_CHAN_TYPE_3_1: > + return 0x03; > + case HDMI_AUD_CHAN_TYPE_3_0_LRS: > + case HDMI_AUD_CHAN_TYPE_4_0: > + return 0x08; > + case HDMI_AUD_CHAN_TYPE_5_1: > + return 0x0b; > + case HDMI_AUD_CHAN_TYPE_4_1_CLRS: > + case HDMI_AUD_CHAN_TYPE_6_0: > + case HDMI_AUD_CHAN_TYPE_6_0_CS: > + case HDMI_AUD_CHAN_TYPE_6_0_CH: > + case HDMI_AUD_CHAN_TYPE_6_0_OH: > + case HDMI_AUD_CHAN_TYPE_6_0_CHR: > + return 0x0e; > + case HDMI_AUD_CHAN_TYPE_1_0: > + case HDMI_AUD_CHAN_TYPE_2_0: > + case HDMI_AUD_CHAN_TYPE_3_1_LRS: > + case HDMI_AUD_CHAN_TYPE_4_1: > + case HDMI_AUD_CHAN_TYPE_5_0: > + case HDMI_AUD_CHAN_TYPE_4_0_CLRS: > + case HDMI_AUD_CHAN_TYPE_6_1: > + case HDMI_AUD_CHAN_TYPE_6_1_CS: > + case HDMI_AUD_CHAN_TYPE_6_1_CH: > + case HDMI_AUD_CHAN_TYPE_6_1_OH: > + case HDMI_AUD_CHAN_TYPE_6_1_CHR: > + case HDMI_AUD_CHAN_TYPE_7_0: > + case HDMI_AUD_CHAN_TYPE_7_0_LH_RH: > + case HDMI_AUD_CHAN_TYPE_7_0_LSR_RSR: > + case HDMI_AUD_CHAN_TYPE_7_0_LC_RC: > + case HDMI_AUD_CHAN_TYPE_7_0_LW_RW: > + case HDMI_AUD_CHAN_TYPE_7_0_LSD_RSD: > + case HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS: > + case HDMI_AUD_CHAN_TYPE_7_0_LHS_RHS: > + case HDMI_AUD_CHAN_TYPE_7_0_CS_CH: > + case HDMI_AUD_CHAN_TYPE_7_0_CS_OH: > + case HDMI_AUD_CHAN_TYPE_7_0_CS_CHR: > + case HDMI_AUD_CHAN_TYPE_7_0_CH_OH: > + case HDMI_AUD_CHAN_TYPE_7_0_CH_CHR: > + case HDMI_AUD_CHAN_TYPE_7_0_OH_CHR: > + case HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS_LSR_RSR: > + case HDMI_AUD_CHAN_TYPE_8_0_LH_RH_CS: > + case HDMI_AUD_CHAN_TYPE_7_1: > + case HDMI_AUD_CHAN_TYPE_7_1_LH_RH: > + case HDMI_AUD_CHAN_TYPE_7_1_LSR_RSR: > + case HDMI_AUD_CHAN_TYPE_7_1_LC_RC: > + case HDMI_AUD_CHAN_TYPE_7_1_LW_RW: > + case HDMI_AUD_CHAN_TYPE_7_1_LSD_RSD: > + case HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS: > + case HDMI_AUD_CHAN_TYPE_7_1_LHS_RHS: > + case HDMI_AUD_CHAN_TYPE_7_1_CS_CH: > + case HDMI_AUD_CHAN_TYPE_7_1_CS_OH: > + case HDMI_AUD_CHAN_TYPE_7_1_CS_CHR: > + case HDMI_AUD_CHAN_TYPE_7_1_CH_OH: > + case HDMI_AUD_CHAN_TYPE_7_1_CH_CHR: > + case HDMI_AUD_CHAN_TYPE_7_1_OH_CHR: > + case HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS_LSR_RSR: > + default: > + return 0; > + } > + > + return 0; > +} > + > +static inline void mtk_hdmi_v2_hw_i2s_ch_swap(struct mtk_hdmi *hdmi, unsigned char swapbit) > +{ > + regmap_update_bits(hdmi->regs, AIP_SPDIF_CTRL, MAX_2UI_I2S_HI_WRITE, > + FIELD_PREP(MAX_2UI_I2S_HI_WRITE, swapbit)); > +} > + > +static void mtk_hdmi_hbr_config(struct mtk_hdmi *hdmi, bool dsd_bypass) > +{ > + const u32 hbr_mask = SPDIF_EN | DSD_EN | HBRA_ON; > + > + if (dsd_bypass) { > + regmap_update_bits(hdmi->regs, AIP_CTRL, hbr_mask, HBRA_ON); > + regmap_set_bits(hdmi->regs, AIP_CTRL, I2S_EN); > + } else { > + regmap_update_bits(hdmi->regs, AIP_CTRL, hbr_mask, SPDIF_EN); > + regmap_set_bits(hdmi->regs, AIP_CTRL, SPDIF_INTERNAL_MODULE); > + regmap_set_bits(hdmi->regs, AIP_CTRL, HBR_FROM_SPDIF); > + regmap_set_bits(hdmi->regs, AIP_CTRL, CTS_CAL_N4); > + } > +} > + > +static inline void mtk_hdmi_v2_hw_spdif_config(struct mtk_hdmi *hdmi) > +{ > + regmap_clear_bits(hdmi->regs, AIP_SPDIF_CTRL, WR_1UI_LOCK); > + regmap_clear_bits(hdmi->regs, AIP_SPDIF_CTRL, FS_OVERRIDE_WRITE); > + regmap_clear_bits(hdmi->regs, AIP_SPDIF_CTRL, WR_2UI_LOCK); > + > + regmap_update_bits(hdmi->regs, AIP_SPDIF_CTRL, MAX_1UI_WRITE, > + FIELD_PREP(MAX_1UI_WRITE, 4)); > + regmap_update_bits(hdmi->regs, AIP_SPDIF_CTRL, MAX_2UI_SPDIF_WRITE, > + FIELD_PREP(MAX_2UI_SPDIF_WRITE, 9)); > + regmap_update_bits(hdmi->regs, AIP_SPDIF_CTRL, AUD_ERR_THRESH, > + FIELD_PREP(AUD_ERR_THRESH, 4)); > + > + regmap_set_bits(hdmi->regs, AIP_SPDIF_CTRL, I2S2DSD_EN); > +} > + > +static void mtk_hdmi_v2_aud_set_input(struct mtk_hdmi *hdmi) > +{ > + struct hdmi_audio_param *aud_param = &hdmi->aud_param; > + u8 i2s_ch_map; > + u32 out_ch_map; > + > + /* Write the default output channel map. CH0 maps to SD0, CH1 maps to SD1, etc */ > + out_ch_map = mtk_hdmi_v2_aud_output_channel_map(0, 1, 2, 3, 4, 5, 6, 7); > + regmap_write(hdmi->regs, TOP_AUD_MAP, out_ch_map); > + > + regmap_update_bits(hdmi->regs, AIP_SPDIF_CTRL, MAX_2UI_I2S_HI_WRITE, 0); > + regmap_clear_bits(hdmi->regs, AIP_CTRL, > + SPDIF_EN | DSD_EN | HBRA_ON | CTS_CAL_N4 | > + HBR_FROM_SPDIF | SPDIF_INTERNAL_MODULE); > + regmap_clear_bits(hdmi->regs, AIP_TXCTRL, DSD_MUTE_EN | AUD_LAYOUT_1); > + > + if (aud_param->aud_input_type == HDMI_AUD_INPUT_I2S) { > + switch (aud_param->aud_codec) { > + case HDMI_AUDIO_CODING_TYPE_DTS_HD: > + case HDMI_AUDIO_CODING_TYPE_MLP: > + mtk_hdmi_i2s_data_fmt(hdmi, aud_param->aud_i2s_fmt); > + mtk_hdmi_hbr_config(hdmi, true); > + break; > + case HDMI_AUDIO_CODING_TYPE_DSD: > + mtk_hdmi_audio_dsd_config(hdmi, aud_param->codec_params.channels, 0); > + mtk_hdmi_v2_hw_i2s_ch_mapping(hdmi, aud_param->codec_params.channels, 1); > + break; > + default: > + mtk_hdmi_i2s_data_fmt(hdmi, aud_param->aud_i2s_fmt); > + mtk_hdmi_i2s_sck_edge_rise(hdmi, true); > + mtk_hdmi_i2s_cbit_order(hdmi, CBIT_ORDER_SAME); > + mtk_hdmi_i2s_vbit(hdmi, 0); /* PCM data */ > + mtk_hdmi_i2s_data_direction(hdmi, 0); /* MSB first */ > + mtk_hdmi_v2_hw_audio_type(hdmi, HDMI_AUD_INPUT_I2S); > + i2s_ch_map = mtk_hdmi_v2_get_i2s_ch_mapping(hdmi, > + aud_param->aud_input_chan_type); > + mtk_hdmi_v2_hw_i2s_ch_mapping(hdmi, > + aud_param->codec_params.channels, i2s_ch_map); > + mtk_hdmi_v2_hw_i2s_ch_swap(hdmi, MAX_2UI_I2S_LFE_CC_SWAP); > + } > + } else { > + if (aud_param->codec_params.sample_rate == 768000 && > + (aud_param->aud_codec == HDMI_AUDIO_CODING_TYPE_DTS_HD || > + aud_param->aud_codec == HDMI_AUDIO_CODING_TYPE_MLP)) { > + mtk_hdmi_hbr_config(hdmi, false); > + } else { > + mtk_hdmi_v2_hw_spdif_config(hdmi); > + mtk_hdmi_v2_hw_i2s_ch_mapping(hdmi, 2, 0); > + } > + } > +} > + > +static void mtk_hdmi_v2_aud_set_sw_ncts(struct mtk_hdmi *hdmi, > + struct drm_display_mode *display_mode) > +{ > + mtk_hdmi_v2_hw_ncts_enable(hdmi, false); > + mtk_hdmi_v2_hw_aud_set_ncts(hdmi, hdmi->aud_param.codec_params.sample_rate, > + display_mode->clock); > +} > + > +static inline void mtk_hdmi_v2_hw_audio_input_enable(struct mtk_hdmi *hdmi, > + unsigned int enable) > +{ > + if (enable) > + regmap_set_bits(hdmi->regs, AIP_CTRL, AUD_IN_EN); > + else > + regmap_clear_bits(hdmi->regs, AIP_CTRL, AUD_IN_EN); > +} > + > +static void mtk_hdmi_v2_aip_ctrl_init(struct mtk_hdmi *hdmi) > +{ > + regmap_set_bits(hdmi->regs, AIP_CTRL, > + AUD_SEL_OWRT | NO_MCLK_CTSGEN_SEL | MCLK_EN | CTS_REQ_EN); > + regmap_clear_bits(hdmi->regs, AIP_TPI_CTRL, TPI_AUDIO_LOOKUP_EN); > +} > + > +static void mtk_hdmi_v2_audio_reset(struct mtk_hdmi *hdmi, bool reset) > +{ > + const u32 arst_bits = RST4AUDIO | RST4AUDIO_FIFO | RST4AUDIO_ACR; > + > + if (reset) > + regmap_set_bits(hdmi->regs, AIP_TXCTRL, arst_bits); > + else > + regmap_clear_bits(hdmi->regs, AIP_TXCTRL, arst_bits); > +} > + > +static void mtk_hdmi_v2_aud_output_config(struct mtk_hdmi *hdmi, > + struct drm_display_mode *display_mode) > +{ > + mtk_hdmi_v2_hw_aud_mute(hdmi, true); > + mtk_hdmi_v2_hw_aud_enable(hdmi, false); > + mtk_hdmi_v2_audio_reset(hdmi, true); > + mtk_hdmi_v2_aip_ctrl_init(hdmi); > + mtk_hdmi_v2_aud_set_input(hdmi); > + mtk_hdmi_v2_hw_aud_set_channel_status(hdmi, hdmi->aud_param.codec_params.iec.status); > + mtk_hdmi_v2_setup_audio_infoframe(hdmi); > + mtk_hdmi_v2_hw_audio_input_enable(hdmi, true); > + mtk_hdmi_v2_audio_reset(hdmi, false); > + mtk_hdmi_v2_aud_set_sw_ncts(hdmi, display_mode); > + > + /* Wait for the HW to apply settings */ > + usleep_range(25, 50); > + > + mtk_hdmi_v2_hw_ncts_enable(hdmi, true); > + mtk_hdmi_v2_hw_aud_enable(hdmi, true); > + mtk_hdmi_v2_hw_aud_mute(hdmi, false); > +} > + > +static void mtk_hdmi_v2_change_video_resolution(struct mtk_hdmi *hdmi) > +{ > + mtk_hdmi_v2_hw_reset(hdmi); > + mtk_hdmi_v2_set_sw_hpd(hdmi, true); > + udelay(2); > + > + regmap_write(hdmi->regs, HDCP_TOP_CTRL, 0); > + > + /* Enable HDCP reauthentication interrupt */ > + regmap_set_bits(hdmi->regs, TOP_INT_ENABLE00, HDCP2X_RX_REAUTH_REQ_DDCM_INT); > + > + /* Enable hotplug and pord interrupts */ > + mtk_hdmi_v2_enable_hpd_pord_irq(hdmi, true); > + > + /* Force enabling HDCP HPD */ > + regmap_set_bits(hdmi->regs, HDCP2X_CTRL_0, HDCP2X_HPD_OVR); > + regmap_set_bits(hdmi->regs, HDCP2X_CTRL_0, HDCP2X_HPD_SW); > + > + /* Set 8 bits per pixel */ > + regmap_update_bits(hdmi->regs, TOP_CFG00, TMDS_PACK_MODE, > + FIELD_PREP(TMDS_PACK_MODE, TMDS_PACK_MODE_8BPP)); > + /* Disable generating deepcolor packets */ > + regmap_clear_bits(hdmi->regs, TOP_CFG00, DEEPCOLOR_PKT_EN); > + /* Disable adding deepcolor information to the general packet */ > + regmap_clear_bits(hdmi->regs, TOP_MISC_CTLR, DEEP_COLOR_ADD); > + > + if (hdmi->dvi_mode) > + regmap_clear_bits(hdmi->regs, TOP_CFG00, HDMI_MODE_HDMI); > + else > + regmap_set_bits(hdmi->regs, TOP_CFG00, HDMI_MODE_HDMI); > + > + udelay(5); > + mtk_hdmi_v2_hw_vid_mute(hdmi, true); > + mtk_hdmi_v2_hw_aud_mute(hdmi, true); > + mtk_hdmi_v2_hw_av_mute(hdmi, false); > + > + regmap_update_bits(hdmi->regs, TOP_CFG01, > + NULL_PKT_VSYNC_HIGH_EN | NULL_PKT_EN, NULL_PKT_VSYNC_HIGH_EN); > + usleep_range(100, 150); > + > + /* Enable scrambling if tmds clock is 340MHz or more */ > + mtk_hdmi_v2_enable_scrambling(hdmi, hdmi->mode.clock >= 340 * KILO); > + > + /* Disable YUV420 downsampling */ > + mtk_hdmi_yuv420_downsampling(hdmi, false); > +} > + > +static void mtk_hdmi_v2_output_set_display_mode(struct mtk_hdmi *hdmi, > + struct drm_display_mode *mode) > +{ > + union phy_configure_opts opts = { > + .dp = { .link_rate = hdmi->mode.clock * KILO } > + }; > + int ret; > + > + ret = phy_configure(hdmi->phy, &opts); > + if (ret) > + dev_err(hdmi->dev, "Setting clock=%d failed: %d", mode->clock, ret); > + > + mtk_hdmi_v2_change_video_resolution(hdmi); > + mtk_hdmi_v2_aud_output_config(hdmi, mode); > +} > + > +static int mtk_hdmi_v2_clk_enable(struct mtk_hdmi *hdmi) > +{ > + int ret; > + > + ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_V2_CLK_HDCP_SEL]); > + if (ret) > + return ret; > + > + ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_V2_CLK_HDCP_24M_SEL]); > + if (ret) > + goto disable_hdcp_clk; > + > + ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_V2_CLK_HDMI_APB_SEL]); > + if (ret) > + goto disable_hdcp_24m_clk; > + > + ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_V2_CLK_VPP_SPLIT_HDMI]); > + if (ret) > + goto disable_bus_clk; > + > + return 0; > + > +disable_bus_clk: > + clk_disable_unprepare(hdmi->clk[MTK_HDMI_V2_CLK_HDMI_APB_SEL]); > +disable_hdcp_24m_clk: > + clk_disable_unprepare(hdmi->clk[MTK_HDMI_V2_CLK_HDCP_24M_SEL]); > +disable_hdcp_clk: > + clk_disable_unprepare(hdmi->clk[MTK_HDMI_V2_CLK_HDCP_SEL]); > + > + return ret; > +} > + > +static void mtk_hdmi_v2_clk_disable(struct mtk_hdmi *hdmi) > +{ > + clk_disable_unprepare(hdmi->clk[MTK_HDMI_V2_CLK_VPP_SPLIT_HDMI]); > + clk_disable_unprepare(hdmi->clk[MTK_HDMI_V2_CLK_HDMI_APB_SEL]); > + clk_disable_unprepare(hdmi->clk[MTK_HDMI_V2_CLK_HDCP_24M_SEL]); > + clk_disable_unprepare(hdmi->clk[MTK_HDMI_V2_CLK_HDCP_SEL]); > +} > + > +static void mtk_hdmi_hpd_event(enum hdmi_hpd_state hpd, struct device *dev) > +{ > + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); > + > + if (hdmi && hdmi->bridge.encoder && hdmi->bridge.encoder->dev) > + drm_helper_hpd_irq_event(hdmi->bridge.encoder->dev); > +} > + > +static enum hdmi_hpd_state mtk_hdmi_v2_hpd_pord_status(struct mtk_hdmi *hdmi) > +{ > + u8 hpd_pin_sta, pord_pin_sta; > + u32 hpd_status; > + > + regmap_read(hdmi->regs, HPD_DDC_STATUS, &hpd_status); > + hpd_pin_sta = FIELD_GET(HPD_PIN_STA, hpd_status); > + pord_pin_sta = FIELD_GET(PORD_PIN_STA, hpd_status); > + > + if (hpd_pin_sta && pord_pin_sta) > + return HDMI_PLUG_IN_AND_SINK_POWER_ON; > + else if (hpd_pin_sta) > + return HDMI_PLUG_IN_ONLY; > + else > + return HDMI_PLUG_OUT; > +} > + > +static irqreturn_t mtk_hdmi_v2_isr(int irq, void *arg) > +{ > + struct mtk_hdmi *hdmi = arg; > + unsigned int irq_sta; > + int ret = IRQ_HANDLED; > + > + regmap_read(hdmi->regs, TOP_INT_STA00, &irq_sta); > + > + /* Handle Hotplug Detection interrupt */ > + if (irq_sta & (HTPLG_R_INT | HTPLG_F_INT | PORD_F_INT | PORD_R_INT)) { > + mtk_hdmi_v2_enable_hpd_pord_irq(hdmi, false); > + ret = IRQ_WAKE_THREAD; > + } > + > + /* > + * Clear all 32 + 19 interrupts in CLR00 and CLR01: this is important > + * to avoid unwanted retriggering of any interrupts > + */ > + regmap_write(hdmi->regs, TOP_INT_CLR00, GENMASK(31, 0)); > + regmap_write(hdmi->regs, TOP_INT_CLR01, GENMASK(18, 0)); > + > + /* Restore interrupt clearing registers to zero */ > + regmap_write(hdmi->regs, TOP_INT_CLR00, 0); > + regmap_write(hdmi->regs, TOP_INT_CLR01, 0); > + > + return ret; > +} > + > +static irqreturn_t __mtk_hdmi_v2_isr_thread(struct mtk_hdmi *hdmi) > +{ > + enum hdmi_hpd_state hpd; > + > + hpd = mtk_hdmi_v2_hpd_pord_status(hdmi); > + if (hpd != hdmi->hpd) { > + hdmi->hpd = hpd; > + mtk_hdmi_hpd_event(hpd, hdmi->dev); > + } > + > + mtk_hdmi_v2_enable_hpd_pord_irq(hdmi, true); > + return IRQ_HANDLED; > +} > + > +static irqreturn_t mtk_hdmi_v2_isr_thread(int irq, void *arg) > +{ > + struct mtk_hdmi *hdmi = arg; > + > + /* > + * Debounce HDMI monitor HPD status. > + * Empirical testing shows that 30ms is enough wait > + */ > + msleep(30); > + > + return __mtk_hdmi_v2_isr_thread(hdmi); > +} > + > +static int mtk_hdmi_v2_enable(struct mtk_hdmi *hdmi) > +{ > + int ret; > + > + ret = pm_runtime_resume_and_get(hdmi->dev); > + if (ret) { > + dev_err(hdmi->dev, "Cannot resume HDMI\n"); > + return ret; > + } > + > + mtk_hdmi_v2_clk_enable(hdmi); > + mtk_hdmi_v2_hw_reset(hdmi); > + mtk_hdmi_v2_set_sw_hpd(hdmi, true); > + > + return 0; > +} > + > +/* > + * Bridge callbacks > + */ > + > +static int mtk_hdmi_v2_bridge_attach(struct drm_bridge *bridge, > + enum drm_bridge_attach_flags flags) > +{ > + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); > + int ret; > + > + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { > + DRM_ERROR("The flag DRM_BRIDGE_ATTACH_NO_CONNECTOR must be supplied\n"); > + return -EINVAL; > + } > + if (hdmi->next_bridge) { > + ret = drm_bridge_attach(bridge->encoder, hdmi->next_bridge, bridge, flags); > + if (ret) > + return ret; > + } > + > + /* Enable the controller at attach time for HPD/Pord feedback */ > + ret = mtk_hdmi_v2_enable(hdmi); > + if (ret) > + return ret; This looks like a hack. Please use .hpd_enable() / .hpd_disable() callbacks. > + > + /* Enable Hotplug and Pord pins internal debouncing */ > + regmap_set_bits(hdmi->regs, HPD_DDC_CTRL, > + HPD_DDC_HPD_DBNC_EN | HPD_DDC_PORD_DBNC_EN); > + > + irq_clear_status_flags(hdmi->irq, IRQ_NOAUTOEN); > + enable_irq(hdmi->irq); > + > + /* > + * Check if any HDMI monitor was connected before probing this driver > + * and/or attaching the bridge, without debouncing: if so, we want to > + * notify the DRM so that we start outputting an image ASAP. > + * Note that calling the ISR thread function will also perform a HW > + * registers write that enables both the HPD and Pord interrupts. > + */ > + __mtk_hdmi_v2_isr_thread(hdmi); > + > + return 0; > +} > + > +static void mtk_hdmi_v2_bridge_detach(struct drm_bridge *bridge) > +{ > + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); > + > + /* Disable interrupts */ > + mtk_hdmi_v2_hwirq_disable(hdmi); > + disable_irq(hdmi->irq); > + pm_runtime_put_sync(hdmi->dev); > +} > + > +static void mtk_hdmi_v2_handle_plugged_change(struct mtk_hdmi *hdmi, bool plugged) > +{ > + mutex_lock(&hdmi->update_plugged_status_lock); > + if (hdmi->plugged_cb && hdmi->codec_dev) > + hdmi->plugged_cb(hdmi->codec_dev, plugged); > + mutex_unlock(&hdmi->update_plugged_status_lock); > +} > + > +static void mtk_hdmi_v2_bridge_pre_enable(struct drm_bridge *bridge, > + struct drm_bridge_state *old_state) > +{ > + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); > + struct drm_atomic_state *state = old_state->base.state; > + struct drm_connector_state *conn_state; > + union phy_configure_opts opts = { > + .dp = { .link_rate = hdmi->mode.clock * KILO } > + }; > + > + /* Retrieve the connector through the atomic state */ > + hdmi->curr_conn = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); > + > + conn_state = drm_atomic_get_new_connector_state(state, hdmi->curr_conn); > + if (WARN_ON(!conn_state)) > + return; > + > + /* > + * Preconfigure the HDMI controller and the HDMI PHY at pre_enable > + * stage to make sure that this IP is ready and clocked before the > + * mtk_dpi gets powered on and before it enables the output. > + */ > + hdmi->dvi_mode = !hdmi->curr_conn->display_info.is_hdmi; Can you access display_info directly instead? > + mtk_hdmi_v2_output_set_display_mode(hdmi, &hdmi->mode); > + > + /* Reconfigure phy clock link with appropriate rate */ > + phy_configure(hdmi->phy, &opts); > + > + /* Power on the PHY here to make sure that DPI_HDMI is clocked */ > + phy_power_on(hdmi->phy); > + > + hdmi->powered = true; > +} > + > +static void mtk_hdmi_v2_bridge_enable(struct drm_bridge *bridge, > + struct drm_bridge_state *old_state) > +{ > + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); > + struct drm_atomic_state *state = old_state->base.state; > + int ret; > + > + ret = drm_atomic_helper_connector_hdmi_update_infoframes(hdmi->curr_conn, state); > + if (ret) > + dev_err(hdmi->dev, "Could not update infoframes: %d\n", ret); > + > + mtk_hdmi_v2_hw_vid_mute(hdmi, false); > + mtk_hdmi_v2_hw_aud_mute(hdmi, false); > + > + /* signal the connect event to audio codec */ > + mtk_hdmi_v2_handle_plugged_change(hdmi, true); I think it was agreed that this should be called from the hotplug path, see the (linked) HDMI codec infrastructure patchset and dicussions for the previous revisions. > + > + hdmi->enabled = true; > +} > + > +static void mtk_hdmi_v2_bridge_disable(struct drm_bridge *bridge, > + struct drm_bridge_state *old_bridge_state) > +{ > + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); > + > + if (!hdmi->enabled) > + return; > + > + mtk_hdmi_v2_hw_av_mute(hdmi, true); > + msleep(50); > + mtk_hdmi_v2_hw_vid_mute(hdmi, true); > + mtk_hdmi_v2_hw_aud_mute(hdmi, true); > + mtk_hdmi_v2_disable_hdcp_encrypt(hdmi); > + msleep(50); > + > + hdmi->enabled = false; > +} > + > +static void mtk_hdmi_v2_bridge_post_disable(struct drm_bridge *bridge, > + struct drm_bridge_state *old_state) > +{ > + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); > + > + if (!hdmi->powered) > + return; > + > + /* Disable VSync interrupt */ > + regmap_clear_bits(hdmi->regs, TOP_INT_ENABLE00, HDMI_VSYNC_INT); > + > + phy_power_off(hdmi->phy); > + hdmi->powered = false; > + > + /* signal the disconnect event to audio codec */ > + mtk_hdmi_v2_handle_plugged_change(hdmi, false); > +} > + > +static enum drm_connector_status mtk_hdmi_v2_bridge_detect(struct drm_bridge *bridge) > +{ > + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); > + > + return hdmi->hpd != HDMI_PLUG_OUT ? > + connector_status_connected : connector_status_disconnected; > +} > + > +static const struct drm_edid *mtk_hdmi_v2_bridge_edid_read(struct drm_bridge *bridge, > + struct drm_connector *connector) > +{ > + return drm_edid_read(connector); > +} > + > +static int mtk_hdmi_v2_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge, > + const struct drm_display_mode *mode, > + unsigned long long tmds_rate) > +{ > + if (mode->clock < MTK_HDMI_V2_CLOCK_MIN) > + return MODE_CLOCK_LOW; > + else if (mode->clock > MTK_HDMI_V2_CLOCK_MAX) > + return MODE_CLOCK_HIGH; > + else > + return MODE_OK; > +} > + > +static enum drm_mode_status > +mtk_hdmi_v2_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); > + return mtk_hdmi_v2_hdmi_tmds_char_rate_valid(bridge, mode, rate); > +} Please rebase on top of https://patchwork.freedesktop.org/series/140193/ There should be no need to do this manually. > + > +static int mtk_hdmi_v2_hdmi_clear_infoframe(struct drm_bridge *bridge, > + enum hdmi_infoframe_type type) > +{ > + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); > + u32 reg_start, reg_end; > + > + switch (type) { > + case HDMI_INFOFRAME_TYPE_AUDIO: > + regmap_clear_bits(hdmi->regs, TOP_INFO_EN, AUD_EN | AUD_EN_WR); > + regmap_clear_bits(hdmi->regs, TOP_INFO_RPT, AUD_RPT_EN); > + reg_start = TOP_AIF_HEADER; > + reg_end = TOP_AIF_PKT03; > + break; > + case HDMI_INFOFRAME_TYPE_AVI: > + regmap_clear_bits(hdmi->regs, TOP_INFO_EN, AVI_EN_WR | AVI_EN); > + regmap_clear_bits(hdmi->regs, TOP_INFO_RPT, AVI_RPT_EN); > + reg_start = TOP_AVI_HEADER; > + reg_end = TOP_AVI_PKT05; > + break; > + case HDMI_INFOFRAME_TYPE_SPD: > + regmap_clear_bits(hdmi->regs, TOP_INFO_EN, SPD_EN_WR | SPD_EN); > + regmap_clear_bits(hdmi->regs, TOP_INFO_RPT, SPD_RPT_EN); > + reg_start = TOP_SPDIF_HEADER; > + reg_end = TOP_SPDIF_PKT07; > + break; > + case HDMI_INFOFRAME_TYPE_VENDOR: > + regmap_clear_bits(hdmi->regs, TOP_INFO_EN, VSIF_EN_WR | VSIF_EN); > + regmap_clear_bits(hdmi->regs, TOP_INFO_RPT, VSIF_RPT_EN); > + reg_start = TOP_VSIF_HEADER; > + reg_end = TOP_VSIF_PKT07; > + break; > + case HDMI_INFOFRAME_TYPE_DRM: > + default: > + return 0; > + }; > + > + for (; reg_start <= reg_end; reg_start += 4) > + regmap_write(hdmi->regs, reg_start, 0); Interesting. Usually sending of the infoframe is controlled by a register of a kind. > + > + return 0; > +} > + > +static int mtk_hdmi_v2_hdmi_write_infoframe(struct drm_bridge *bridge, > + enum hdmi_infoframe_type type, > + const u8 *buffer, size_t len) > +{ > + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); > + > + switch (type) { > + case HDMI_INFOFRAME_TYPE_AUDIO: > + mtk_hdmi_v2_hw_write_audio_infoframe(hdmi, buffer); > + break; > + case HDMI_INFOFRAME_TYPE_AVI: > + mtk_hdmi_v2_hw_write_avi_infoframe(hdmi, buffer); > + break; > + case HDMI_INFOFRAME_TYPE_SPD: > + mtk_hdmi_v2_hw_write_spd_infoframe(hdmi, buffer); > + break; > + case HDMI_INFOFRAME_TYPE_VENDOR: > + mtk_hdmi_v2_hw_write_vendor_infoframe(hdmi, buffer); > + break; > + case HDMI_INFOFRAME_TYPE_DRM: > + default: > + dev_err(hdmi->dev, "Unsupported HDMI infoframe type %u\n", type); > + break; > + }; > + > + return 0; > +} > + > +static int mtk_hdmi_v2_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) > +{ > + return drm_atomic_helper_connector_hdmi_check(conn_state->connector, > + conn_state->state); > +} Note to myself, probably we can move this to drm_bridge_connector too. > + > +static int mtk_hdmi_v2_set_abist(struct mtk_hdmi *hdmi, bool enable) > +{ > + struct drm_display_mode *mode = &hdmi->mode; > + int abist_format = -EINVAL; > + bool interlaced; > + > + if (!enable) { > + regmap_clear_bits(hdmi->regs, TOP_CFG00, HDMI_ABIST_ENABLE); > + return 0; > + } > + > + if (!mode->hdisplay || !mode->vdisplay) > + return -EINVAL; > + > + interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; The interlaced modes should be filtered, unless you also set bridge->interlace_allowed to true. > + > + switch (mode->hdisplay) { > + case 720: > + if (mode->vdisplay == 480) > + abist_format = 2; > + else if (mode->vdisplay == 576) > + abist_format = 11; > + break; > + case 1280: > + if (mode->vdisplay == 720) > + abist_format = 3; > + break; > + case 1440: > + if (mode->vdisplay == 480) > + abist_format = interlaced ? 5 : 9; > + else if (mode->vdisplay == 576) > + abist_format = interlaced ? 14 : 18; > + break; > + case 1920: > + if (mode->vdisplay == 1080) > + abist_format = interlaced ? 4 : 10; > + break; > + case 3840: > + if (mode->vdisplay == 2160) > + abist_format = 25; > + break; > + case 4096: > + if (mode->vdisplay == 2160) > + abist_format = 26; > + break; > + default: > + break; > + } > + if (!abist_format) > + return -EINVAL; > + > + regmap_update_bits(hdmi->regs, TOP_CFG00, HDMI_ABIST_VIDEO_FORMAT, > + FIELD_PREP(HDMI_ABIST_VIDEO_FORMAT, abist_format)); > + regmap_set_bits(hdmi->regs, TOP_CFG00, HDMI_ABIST_ENABLE); > + return 0; > +} > + > +static int mtk_hdmi_v2_debug_abist_show(struct seq_file *m, void *arg) > +{ > + struct mtk_hdmi *hdmi = m->private; > + bool en; > + u32 val; > + int ret; > + > + if (!hdmi) > + return -EINVAL; > + > + ret = regmap_read(hdmi->regs, TOP_CFG00, &val); > + if (ret) > + return ret; > + > + en = FIELD_GET(HDMI_ABIST_ENABLE, val); > + > + seq_printf(m, "HDMI Automated Built-In Self Test: %s\n", > + en ? "Enabled" : "Disabled"); > + > + return 0; > +} > + > +static ssize_t mtk_hdmi_v2_debug_abist_write(struct file *file, > + const char __user *ubuf, > + size_t len, loff_t *offp) > +{ > + struct seq_file *m = file->private_data; > + int ret; > + u32 en; > + > + if (!m || !m->private || *offp) > + return -EINVAL; > + > + ret = kstrtouint_from_user(ubuf, len, 0, &en); > + if (ret) > + return ret; > + > + if (en < 0 || en > 1) > + return -EINVAL; > + > + mtk_hdmi_v2_set_abist((struct mtk_hdmi *)m->private, en); > + return len; > +} > + > +static int mtk_hdmi_v2_debug_abist_open(struct inode *inode, struct file *file) > +{ > + return single_open(file, mtk_hdmi_v2_debug_abist_show, inode->i_private); > +} > + > +static const struct file_operations mtk_hdmi_debug_abist_fops = { > + .owner = THIS_MODULE, > + .open = mtk_hdmi_v2_debug_abist_open, > + .read = seq_read, > + .write = mtk_hdmi_v2_debug_abist_write, > + .llseek = seq_lseek, > + .release = single_release, > +}; > + > +static void mtk_hdmi_v2_debugfs_init(struct drm_bridge *bridge, struct dentry *root) > +{ > + struct mtk_hdmi *dpi = hdmi_ctx_from_bridge(bridge); > + > + debugfs_create_file("hdmi_abist", 0640, root, dpi, &mtk_hdmi_debug_abist_fops); > +} > + > +static const struct drm_bridge_funcs mtk_v2_hdmi_bridge_funcs = { > + .attach = mtk_hdmi_v2_bridge_attach, > + .detach = mtk_hdmi_v2_bridge_detach, > + .mode_fixup = mtk_hdmi_bridge_mode_fixup, > + .mode_set = mtk_hdmi_bridge_mode_set, > + .mode_valid = mtk_hdmi_v2_bridge_mode_valid, > + .atomic_pre_enable = mtk_hdmi_v2_bridge_pre_enable, > + .atomic_enable = mtk_hdmi_v2_bridge_enable, > + .atomic_disable = mtk_hdmi_v2_bridge_disable, > + .atomic_post_disable = mtk_hdmi_v2_bridge_post_disable, > + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, > + .atomic_check = mtk_hdmi_v2_bridge_atomic_check, > + .atomic_reset = drm_atomic_helper_bridge_reset, > + .detect = mtk_hdmi_v2_bridge_detect, > + .edid_read = mtk_hdmi_v2_bridge_edid_read, > + .hdmi_tmds_char_rate_valid = mtk_hdmi_v2_hdmi_tmds_char_rate_valid, > + .hdmi_clear_infoframe = mtk_hdmi_v2_hdmi_clear_infoframe, > + .hdmi_write_infoframe = mtk_hdmi_v2_hdmi_write_infoframe, > + .debugfs_init = mtk_hdmi_v2_debugfs_init, > +}; > + > +/* > + * HDMI audio codec callbacks > + */ > +static int mtk_hdmi_v2_audio_hook_plugged_cb(struct device *dev, void *data, > + hdmi_codec_plugged_cb fn, > + struct device *codec_dev) > +{ > + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); > + bool plugged; > + > + if (!hdmi) > + return -ENODEV; > + > + mtk_hdmi_audio_set_plugged_cb(hdmi, fn, codec_dev); > + plugged = (hdmi->hpd == HDMI_PLUG_IN_AND_SINK_POWER_ON); > + mtk_hdmi_v2_handle_plugged_change(hdmi, plugged); > + > + return 0; > +} > + > +static int mtk_hdmi_v2_audio_hw_params(struct device *dev, void *data, > + struct hdmi_codec_daifmt *daifmt, > + struct hdmi_codec_params *params) > +{ > + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); > + > + if (hdmi->audio_enable) { > + mtk_hdmi_audio_params(hdmi, daifmt, params); > + mtk_hdmi_v2_aud_output_config(hdmi, &hdmi->mode); > + } > + return 0; > +} > + > +static int mtk_hdmi_v2_audio_startup(struct device *dev, void *data) > +{ > + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); > + > + mtk_hdmi_v2_hw_aud_enable(hdmi, true); > + hdmi->audio_enable = true; > + > + return 0; > +} > + > +static void mtk_hdmi_v2_audio_shutdown(struct device *dev, void *data) > +{ > + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); > + > + hdmi->audio_enable = false; > + mtk_hdmi_v2_hw_aud_enable(hdmi, false); > +} > + > +static int mtk_hdmi_v2_audio_mute(struct device *dev, void *data, bool enable, int dir) > +{ > + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); > + > + mtk_hdmi_v2_hw_aud_mute(hdmi, enable); > + > + return 0; > +} > + > +static const struct hdmi_codec_ops mtk_hdmi_v2_audio_codec_ops = { > + .hw_params = mtk_hdmi_v2_audio_hw_params, > + .audio_startup = mtk_hdmi_v2_audio_startup, > + .audio_shutdown = mtk_hdmi_v2_audio_shutdown, > + .mute_stream = mtk_hdmi_v2_audio_mute, > + .get_eld = mtk_hdmi_audio_get_eld, > + .hook_plugged_cb = mtk_hdmi_v2_audio_hook_plugged_cb, > +}; > + > +static __maybe_unused int mtk_hdmi_v2_suspend(struct device *dev) > +{ > + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); > + > + mtk_hdmi_v2_hwirq_disable(hdmi); > + mtk_hdmi_v2_clk_disable(hdmi); > + pm_runtime_put_sync(dev); > + > + return 0; > +} > + > +static __maybe_unused int mtk_hdmi_v2_resume(struct device *dev) > +{ > + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); > + int ret; > + > + pm_runtime_get_sync(dev); > + > + ret = mtk_hdmi_v2_clk_enable(hdmi); > + if (ret) > + return ret; > + > + mtk_hdmi_v2_enable_hpd_pord_irq(hdmi, true); > + > + return 0; > +} > + > +static SIMPLE_DEV_PM_OPS(mtk_hdmi_v2_pm_ops, mtk_hdmi_v2_suspend, mtk_hdmi_v2_resume); > + > +static const struct mtk_hdmi_ver_conf mtk_hdmi_conf_v2 = { > + .bridge_funcs = &mtk_v2_hdmi_bridge_funcs, > + .codec_ops = &mtk_hdmi_v2_audio_codec_ops, > + .mtk_hdmi_clock_names = mtk_hdmi_v2_clk_names, > + .num_clocks = MTK_HDMI_V2_CLK_COUNT > +}; > + > +static const struct mtk_hdmi_conf mtk_hdmi_conf_mt8188 = { > + .ver_conf = &mtk_hdmi_conf_v2, > + .reg_hdmi_tx_cfg = HDMITX_CONFIG_MT8188 > +}; > + > +static const struct mtk_hdmi_conf mtk_hdmi_conf_mt8195 = { > + .ver_conf = &mtk_hdmi_conf_v2, > + .reg_hdmi_tx_cfg = HDMITX_CONFIG_MT8195, > +}; > + > +static int mtk_hdmi_v2_probe(struct platform_device *pdev) > +{ > + struct mtk_hdmi *hdmi; > + int ret; > + > + hdmi = mtk_hdmi_common_probe(pdev); > + if (IS_ERR(hdmi)) > + return PTR_ERR(hdmi); > + > + hdmi->hpd = HDMI_PLUG_OUT; > + > + /* > + * Disable all HW interrupts at probe stage and install the ISR > + * but keep it disabled, as the rest of the interrupts setup is > + * done in the .bridge_attach() callback, which will enable both > + * the right HW IRQs and the ISR. > + */ > + mtk_hdmi_v2_hwirq_disable(hdmi); > + irq_set_status_flags(hdmi->irq, IRQ_NOAUTOEN); > + ret = devm_request_threaded_irq(&pdev->dev, hdmi->irq, mtk_hdmi_v2_isr, > + mtk_hdmi_v2_isr_thread, > + IRQ_TYPE_LEVEL_HIGH, > + dev_name(&pdev->dev), hdmi); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, "Cannot request IRQ\n"); > + > + ret = devm_pm_runtime_enable(&pdev->dev); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, "Cannot enable Runtime PM\n"); > + > + return 0; > +} > + > +static void mtk_hdmi_v2_remove(struct platform_device *pdev) > +{ > + struct mtk_hdmi *hdmi = platform_get_drvdata(pdev); > + > + pm_runtime_disable(&pdev->dev); > + i2c_put_adapter(hdmi->ddc_adpt); > +} > + > +static const struct of_device_id mtk_drm_hdmi_v2_of_ids[] = { > + { .compatible = "mediatek,mt8188-hdmi-tx", .data = &mtk_hdmi_conf_mt8188 }, > + { .compatible = "mediatek,mt8195-hdmi-tx", .data = &mtk_hdmi_conf_mt8195 }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, mtk_drm_hdmi_v2_of_ids); > + > +static struct platform_driver mtk_hdmi_v2_driver = { > + .probe = mtk_hdmi_v2_probe, > + .remove = mtk_hdmi_v2_remove, > + .driver = { > + .name = "mediatek-drm-hdmi-v2", > + .of_match_table = mtk_drm_hdmi_v2_of_ids, > + .pm = &mtk_hdmi_v2_pm_ops, > + }, > +}; > +module_platform_driver(mtk_hdmi_v2_driver); > + > +MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@xxxxxxxxxxxxx>>"); > +MODULE_AUTHOR("Guillaume Ranquet <granquet@xxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("MediaTek HDMIv2 Driver"); > +MODULE_LICENSE("GPL"); > -- > 2.47.0 > -- With best wishes Dmitry