drm/mediatek: Add HDMI support
authorJie Qiu <jie.qiu@mediatek.com>
Mon, 4 Jan 2016 17:36:38 +0000 (18:36 +0100)
committerPhilipp Zabel <p.zabel@pengutronix.de>
Thu, 16 Jun 2016 14:30:38 +0000 (16:30 +0200)
This patch adds drivers for the HDMI bridge connected to the DPI0
display subsystem function block, for the HDMI DDC block, and for
the HDMI PHY to support HDMI output.
This includes an interface to the generic hdmi-codec driver to start
or stop audio playback and to retrieve ELD (EDID like data) to limit the
supported audio formats to the HDMI sink capabilities.

Signed-off-by: Jie Qiu <jie.qiu@mediatek.com>
Signed-off-by: Junzhi Zhao <junzhi.zhao@mediatek.com>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
drivers/gpu/drm/mediatek/Kconfig
drivers/gpu/drm/mediatek/Makefile
drivers/gpu/drm/mediatek/mtk_cec.c [new file with mode: 0644]
drivers/gpu/drm/mediatek/mtk_cec.h [new file with mode: 0644]
drivers/gpu/drm/mediatek/mtk_hdmi.c [new file with mode: 0644]
drivers/gpu/drm/mediatek/mtk_hdmi.h [new file with mode: 0644]
drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c [new file with mode: 0644]
drivers/gpu/drm/mediatek/mtk_hdmi_regs.h [new file with mode: 0644]
drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c [new file with mode: 0644]

index eeefc971801a7e069f6891279ee6d6e186e625ed..a6898d5dcc989cb3ffdcc9ce76c29a31d23eae32 100644 (file)
@@ -14,3 +14,11 @@ config DRM_MEDIATEK
          The module will be called mediatek-drm
          This driver provides kernel mode setting and
          buffer management to userspace.
+
+config DRM_MEDIATEK_HDMI
+       tristate "DRM HDMI Support for Mediatek SoCs"
+       depends on DRM_MEDIATEK
+       select SND_SOC_HDMI_CODEC if SND_SOC
+       select GENERIC_PHY
+       help
+         DRM/KMS HDMI driver for Mediatek SoCs
index 5fcf58e877860974c4d0194c8af23c4dd3e39f14..bf2e5be1ab30df7699b99512b3e67048036726f9 100644 (file)
@@ -12,3 +12,10 @@ mediatek-drm-y := mtk_disp_ovl.o \
                  mtk_dpi.o
 
 obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
+
+mediatek-drm-hdmi-objs := mtk_cec.o \
+                         mtk_hdmi.o \
+                         mtk_hdmi_ddc.o \
+                         mtk_mt8173_hdmi_phy.o
+
+obj-$(CONFIG_DRM_MEDIATEK_HDMI) += mediatek-drm-hdmi.o
diff --git a/drivers/gpu/drm/mediatek/mtk_cec.c b/drivers/gpu/drm/mediatek/mtk_cec.c
new file mode 100644 (file)
index 0000000..7a3eb8c
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include "mtk_cec.h"
+
+#define TR_CONFIG              0x00
+#define CLEAR_CEC_IRQ                  BIT(15)
+
+#define CEC_CKGEN              0x04
+#define CEC_32K_PDN                    BIT(19)
+#define PDN                            BIT(16)
+
+#define RX_EVENT               0x54
+#define HDMI_PORD                      BIT(25)
+#define HDMI_HTPLG                     BIT(24)
+#define HDMI_PORD_INT_EN               BIT(9)
+#define HDMI_HTPLG_INT_EN              BIT(8)
+
+#define RX_GEN_WD              0x58
+#define HDMI_PORD_INT_32K_STATUS       BIT(26)
+#define RX_RISC_INT_32K_STATUS         BIT(25)
+#define HDMI_HTPLG_INT_32K_STATUS      BIT(24)
+#define HDMI_PORD_INT_32K_CLR          BIT(18)
+#define RX_INT_32K_CLR                 BIT(17)
+#define HDMI_HTPLG_INT_32K_CLR         BIT(16)
+#define HDMI_PORD_INT_32K_STA_MASK     BIT(10)
+#define RX_RISC_INT_32K_STA_MASK       BIT(9)
+#define HDMI_HTPLG_INT_32K_STA_MASK    BIT(8)
+#define HDMI_PORD_INT_32K_EN           BIT(2)
+#define RX_INT_32K_EN                  BIT(1)
+#define HDMI_HTPLG_INT_32K_EN          BIT(0)
+
+#define NORMAL_INT_CTRL                0x5C
+#define HDMI_HTPLG_INT_STA             BIT(0)
+#define HDMI_PORD_INT_STA              BIT(1)
+#define HDMI_HTPLG_INT_CLR             BIT(16)
+#define HDMI_PORD_INT_CLR              BIT(17)
+#define HDMI_FULL_INT_CLR              BIT(20)
+
+struct mtk_cec {
+       void __iomem *regs;
+       struct clk *clk;
+       int irq;
+       bool hpd;
+       void (*hpd_event)(bool hpd, struct device *dev);
+       struct device *hdmi_dev;
+       spinlock_t lock;
+};
+
+static void mtk_cec_clear_bits(struct mtk_cec *cec, unsigned int offset,
+                              unsigned int bits)
+{
+       void __iomem *reg = cec->regs + offset;
+       u32 tmp;
+
+       tmp = readl(reg);
+       tmp &= ~bits;
+       writel(tmp, reg);
+}
+
+static void mtk_cec_set_bits(struct mtk_cec *cec, unsigned int offset,
+                            unsigned int bits)
+{
+       void __iomem *reg = cec->regs + offset;
+       u32 tmp;
+
+       tmp = readl(reg);
+       tmp |= bits;
+       writel(tmp, reg);
+}
+
+static void mtk_cec_mask(struct mtk_cec *cec, unsigned int offset,
+                        unsigned int val, unsigned int mask)
+{
+       u32 tmp = readl(cec->regs + offset) & ~mask;
+
+       tmp |= val & mask;
+       writel(val, cec->regs + offset);
+}
+
+void mtk_cec_set_hpd_event(struct device *dev,
+                          void (*hpd_event)(bool hpd, struct device *dev),
+                          struct device *hdmi_dev)
+{
+       struct mtk_cec *cec = dev_get_drvdata(dev);
+       unsigned long flags;
+
+       spin_lock_irqsave(&cec->lock, flags);
+       cec->hdmi_dev = hdmi_dev;
+       cec->hpd_event = hpd_event;
+       spin_unlock_irqrestore(&cec->lock, flags);
+}
+
+bool mtk_cec_hpd_high(struct device *dev)
+{
+       struct mtk_cec *cec = dev_get_drvdata(dev);
+       unsigned int status;
+
+       status = readl(cec->regs + RX_EVENT);
+
+       return (status & (HDMI_PORD | HDMI_HTPLG)) == (HDMI_PORD | HDMI_HTPLG);
+}
+
+static void mtk_cec_htplg_irq_init(struct mtk_cec *cec)
+{
+       mtk_cec_mask(cec, CEC_CKGEN, 0 | CEC_32K_PDN, PDN | CEC_32K_PDN);
+       mtk_cec_set_bits(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR |
+                        RX_INT_32K_CLR | HDMI_HTPLG_INT_32K_CLR);
+       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR | RX_INT_32K_CLR |
+                    HDMI_HTPLG_INT_32K_CLR | HDMI_PORD_INT_32K_EN |
+                    RX_INT_32K_EN | HDMI_HTPLG_INT_32K_EN);
+}
+
+static void mtk_cec_htplg_irq_enable(struct mtk_cec *cec)
+{
+       mtk_cec_set_bits(cec, RX_EVENT, HDMI_PORD_INT_EN | HDMI_HTPLG_INT_EN);
+}
+
+static void mtk_cec_htplg_irq_disable(struct mtk_cec *cec)
+{
+       mtk_cec_clear_bits(cec, RX_EVENT, HDMI_PORD_INT_EN | HDMI_HTPLG_INT_EN);
+}
+
+static void mtk_cec_clear_htplg_irq(struct mtk_cec *cec)
+{
+       mtk_cec_set_bits(cec, TR_CONFIG, CLEAR_CEC_IRQ);
+       mtk_cec_set_bits(cec, NORMAL_INT_CTRL, HDMI_HTPLG_INT_CLR |
+                        HDMI_PORD_INT_CLR | HDMI_FULL_INT_CLR);
+       mtk_cec_set_bits(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR |
+                        RX_INT_32K_CLR | HDMI_HTPLG_INT_32K_CLR);
+       usleep_range(5, 10);
+       mtk_cec_clear_bits(cec, NORMAL_INT_CTRL, HDMI_HTPLG_INT_CLR |
+                          HDMI_PORD_INT_CLR | HDMI_FULL_INT_CLR);
+       mtk_cec_clear_bits(cec, TR_CONFIG, CLEAR_CEC_IRQ);
+       mtk_cec_clear_bits(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR |
+                          RX_INT_32K_CLR | HDMI_HTPLG_INT_32K_CLR);
+}
+
+static void mtk_cec_hpd_event(struct mtk_cec *cec, bool hpd)
+{
+       void (*hpd_event)(bool hpd, struct device *dev);
+       struct device *hdmi_dev;
+       unsigned long flags;
+
+       spin_lock_irqsave(&cec->lock, flags);
+       hpd_event = cec->hpd_event;
+       hdmi_dev = cec->hdmi_dev;
+       spin_unlock_irqrestore(&cec->lock, flags);
+
+       if (hpd_event)
+               hpd_event(hpd, hdmi_dev);
+}
+
+static irqreturn_t mtk_cec_htplg_isr_thread(int irq, void *arg)
+{
+       struct device *dev = arg;
+       struct mtk_cec *cec = dev_get_drvdata(dev);
+       bool hpd;
+
+       mtk_cec_clear_htplg_irq(cec);
+       hpd = mtk_cec_hpd_high(dev);
+
+       if (cec->hpd != hpd) {
+               dev_dbg(dev, "hotplug event! cur hpd = %d, hpd = %d\n",
+                       cec->hpd, hpd);
+               cec->hpd = hpd;
+               mtk_cec_hpd_event(cec, hpd);
+       }
+       return IRQ_HANDLED;
+}
+
+static int mtk_cec_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct mtk_cec *cec;
+       struct resource *res;
+       int ret;
+
+       cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL);
+       if (!cec)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, cec);
+       spin_lock_init(&cec->lock);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       cec->regs = devm_ioremap_resource(dev, res);
+       if (IS_ERR(cec->regs)) {
+               ret = PTR_ERR(cec->regs);
+               dev_err(dev, "Failed to ioremap cec: %d\n", ret);
+               return ret;
+       }
+
+       cec->clk = devm_clk_get(dev, NULL);
+       if (IS_ERR(cec->clk)) {
+               ret = PTR_ERR(cec->clk);
+               dev_err(dev, "Failed to get cec clock: %d\n", ret);
+               return ret;
+       }
+
+       cec->irq = platform_get_irq(pdev, 0);
+       if (cec->irq < 0) {
+               dev_err(dev, "Failed to get cec irq: %d\n", cec->irq);
+               return cec->irq;
+       }
+
+       ret = devm_request_threaded_irq(dev, cec->irq, NULL,
+                                       mtk_cec_htplg_isr_thread,
+                                       IRQF_SHARED | IRQF_TRIGGER_LOW |
+                                       IRQF_ONESHOT, "hdmi hpd", dev);
+       if (ret) {
+               dev_err(dev, "Failed to register cec irq: %d\n", ret);
+               return ret;
+       }
+
+       ret = clk_prepare_enable(cec->clk);
+       if (ret) {
+               dev_err(dev, "Failed to enable cec clock: %d\n", ret);
+               return ret;
+       }
+
+       mtk_cec_htplg_irq_init(cec);
+       mtk_cec_htplg_irq_enable(cec);
+
+       return 0;
+}
+
+static int mtk_cec_remove(struct platform_device *pdev)
+{
+       struct mtk_cec *cec = platform_get_drvdata(pdev);
+
+       mtk_cec_htplg_irq_disable(cec);
+       clk_disable_unprepare(cec->clk);
+       return 0;
+}
+
+static const struct of_device_id mtk_cec_of_ids[] = {
+       { .compatible = "mediatek,mt8173-cec", },
+       {}
+};
+
+struct platform_driver mtk_cec_driver = {
+       .probe = mtk_cec_probe,
+       .remove = mtk_cec_remove,
+       .driver = {
+               .name = "mediatek-cec",
+               .of_match_table = mtk_cec_of_ids,
+       },
+};
diff --git a/drivers/gpu/drm/mediatek/mtk_cec.h b/drivers/gpu/drm/mediatek/mtk_cec.h
new file mode 100644 (file)
index 0000000..10057b7
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _MTK_CEC_H
+#define _MTK_CEC_H
+
+#include <linux/types.h>
+
+struct device;
+
+void mtk_cec_set_hpd_event(struct device *dev,
+                          void (*hotplug_event)(bool hpd, struct device *dev),
+                          struct device *hdmi_dev);
+bool mtk_cec_hpd_high(struct device *dev);
+
+#endif /* _MTK_CEC_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c
new file mode 100644 (file)
index 0000000..8ec1ea4
--- /dev/null
@@ -0,0 +1,1816 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/hdmi.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_platform.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_graph.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <sound/hdmi-codec.h>
+#include "mtk_cec.h"
+#include "mtk_hdmi.h"
+#include "mtk_hdmi_regs.h"
+
+#define NCTS_BYTES     7
+
+enum mtk_hdmi_clk_id {
+       MTK_HDMI_CLK_HDMI_PIXEL,
+       MTK_HDMI_CLK_HDMI_PLL,
+       MTK_HDMI_CLK_AUD_BCLK,
+       MTK_HDMI_CLK_AUD_SPDIF,
+       MTK_HDMI_CLK_COUNT
+};
+
+enum hdmi_aud_input_type {
+       HDMI_AUD_INPUT_I2S = 0,
+       HDMI_AUD_INPUT_SPDIF,
+};
+
+enum hdmi_aud_i2s_fmt {
+       HDMI_I2S_MODE_RJT_24BIT = 0,
+       HDMI_I2S_MODE_RJT_16BIT,
+       HDMI_I2S_MODE_LJT_24BIT,
+       HDMI_I2S_MODE_LJT_16BIT,
+       HDMI_I2S_MODE_I2S_24BIT,
+       HDMI_I2S_MODE_I2S_16BIT
+};
+
+enum hdmi_aud_mclk {
+       HDMI_AUD_MCLK_128FS,
+       HDMI_AUD_MCLK_192FS,
+       HDMI_AUD_MCLK_256FS,
+       HDMI_AUD_MCLK_384FS,
+       HDMI_AUD_MCLK_512FS,
+       HDMI_AUD_MCLK_768FS,
+       HDMI_AUD_MCLK_1152FS,
+};
+
+enum hdmi_aud_channel_type {
+       HDMI_AUD_CHAN_TYPE_1_0 = 0,
+       HDMI_AUD_CHAN_TYPE_1_1,
+       HDMI_AUD_CHAN_TYPE_2_0,
+       HDMI_AUD_CHAN_TYPE_2_1,
+       HDMI_AUD_CHAN_TYPE_3_0,
+       HDMI_AUD_CHAN_TYPE_3_1,
+       HDMI_AUD_CHAN_TYPE_4_0,
+       HDMI_AUD_CHAN_TYPE_4_1,
+       HDMI_AUD_CHAN_TYPE_5_0,
+       HDMI_AUD_CHAN_TYPE_5_1,
+       HDMI_AUD_CHAN_TYPE_6_0,
+       HDMI_AUD_CHAN_TYPE_6_1,
+       HDMI_AUD_CHAN_TYPE_7_0,
+       HDMI_AUD_CHAN_TYPE_7_1,
+       HDMI_AUD_CHAN_TYPE_3_0_LRS,
+       HDMI_AUD_CHAN_TYPE_3_1_LRS,
+       HDMI_AUD_CHAN_TYPE_4_0_CLRS,
+       HDMI_AUD_CHAN_TYPE_4_1_CLRS,
+       HDMI_AUD_CHAN_TYPE_6_1_CS,
+       HDMI_AUD_CHAN_TYPE_6_1_CH,
+       HDMI_AUD_CHAN_TYPE_6_1_OH,
+       HDMI_AUD_CHAN_TYPE_6_1_CHR,
+       HDMI_AUD_CHAN_TYPE_7_1_LH_RH,
+       HDMI_AUD_CHAN_TYPE_7_1_LSR_RSR,
+       HDMI_AUD_CHAN_TYPE_7_1_LC_RC,
+       HDMI_AUD_CHAN_TYPE_7_1_LW_RW,
+       HDMI_AUD_CHAN_TYPE_7_1_LSD_RSD,
+       HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS,
+       HDMI_AUD_CHAN_TYPE_7_1_LHS_RHS,
+       HDMI_AUD_CHAN_TYPE_7_1_CS_CH,
+       HDMI_AUD_CHAN_TYPE_7_1_CS_OH,
+       HDMI_AUD_CHAN_TYPE_7_1_CS_CHR,
+       HDMI_AUD_CHAN_TYPE_7_1_CH_OH,
+       HDMI_AUD_CHAN_TYPE_7_1_CH_CHR,
+       HDMI_AUD_CHAN_TYPE_7_1_OH_CHR,
+       HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS_LSR_RSR,
+       HDMI_AUD_CHAN_TYPE_6_0_CS,
+       HDMI_AUD_CHAN_TYPE_6_0_CH,
+       HDMI_AUD_CHAN_TYPE_6_0_OH,
+       HDMI_AUD_CHAN_TYPE_6_0_CHR,
+       HDMI_AUD_CHAN_TYPE_7_0_LH_RH,
+       HDMI_AUD_CHAN_TYPE_7_0_LSR_RSR,
+       HDMI_AUD_CHAN_TYPE_7_0_LC_RC,
+       HDMI_AUD_CHAN_TYPE_7_0_LW_RW,
+       HDMI_AUD_CHAN_TYPE_7_0_LSD_RSD,
+       HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS,
+       HDMI_AUD_CHAN_TYPE_7_0_LHS_RHS,
+       HDMI_AUD_CHAN_TYPE_7_0_CS_CH,
+       HDMI_AUD_CHAN_TYPE_7_0_CS_OH,
+       HDMI_AUD_CHAN_TYPE_7_0_CS_CHR,
+       HDMI_AUD_CHAN_TYPE_7_0_CH_OH,
+       HDMI_AUD_CHAN_TYPE_7_0_CH_CHR,
+       HDMI_AUD_CHAN_TYPE_7_0_OH_CHR,
+       HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS_LSR_RSR,
+       HDMI_AUD_CHAN_TYPE_8_0_LH_RH_CS,
+       HDMI_AUD_CHAN_TYPE_UNKNOWN = 0xFF
+};
+
+enum hdmi_aud_channel_swap_type {
+       HDMI_AUD_SWAP_LR,
+       HDMI_AUD_SWAP_LFE_CC,
+       HDMI_AUD_SWAP_LSRS,
+       HDMI_AUD_SWAP_RLS_RRS,
+       HDMI_AUD_SWAP_LR_STATUS,
+};
+
+struct hdmi_audio_param {
+       enum hdmi_audio_coding_type aud_codec;
+       enum hdmi_audio_sample_size aud_sampe_size;
+       enum hdmi_aud_input_type aud_input_type;
+       enum hdmi_aud_i2s_fmt aud_i2s_fmt;
+       enum hdmi_aud_mclk aud_mclk;
+       enum hdmi_aud_channel_type aud_input_chan_type;
+       struct hdmi_codec_params codec_params;
+};
+
+struct mtk_hdmi {
+       struct drm_bridge bridge;
+       struct drm_connector conn;
+       struct device *dev;
+       struct phy *phy;
+       struct device *cec_dev;
+       struct i2c_adapter *ddc_adpt;
+       struct clk *clk[MTK_HDMI_CLK_COUNT];
+       struct drm_display_mode mode;
+       bool dvi_mode;
+       u32 min_clock;
+       u32 max_clock;
+       u32 max_hdisplay;
+       u32 max_vdisplay;
+       u32 ibias;
+       u32 ibias_up;
+       struct regmap *sys_regmap;
+       unsigned int sys_offset;
+       void __iomem *regs;
+       enum hdmi_colorspace csp;
+       struct hdmi_audio_param aud_param;
+       bool audio_enable;
+       bool powered;
+       bool enabled;
+};
+
+static inline struct mtk_hdmi *hdmi_ctx_from_bridge(struct drm_bridge *b)
+{
+       return container_of(b, struct mtk_hdmi, bridge);
+}
+
+static inline struct mtk_hdmi *hdmi_ctx_from_conn(struct drm_connector *c)
+{
+       return container_of(c, struct mtk_hdmi, conn);
+}
+
+static u32 mtk_hdmi_read(struct mtk_hdmi *hdmi, u32 offset)
+{
+       return readl(hdmi->regs + offset);
+}
+
+static void mtk_hdmi_write(struct mtk_hdmi *hdmi, u32 offset, u32 val)
+{
+       writel(val, hdmi->regs + offset);
+}
+
+static void mtk_hdmi_clear_bits(struct mtk_hdmi *hdmi, u32 offset, u32 bits)
+{
+       void __iomem *reg = hdmi->regs + offset;
+       u32 tmp;
+
+       tmp = readl(reg);
+       tmp &= ~bits;
+       writel(tmp, reg);
+}
+
+static void mtk_hdmi_set_bits(struct mtk_hdmi *hdmi, u32 offset, u32 bits)
+{
+       void __iomem *reg = hdmi->regs + offset;
+       u32 tmp;
+
+       tmp = readl(reg);
+       tmp |= bits;
+       writel(tmp, reg);
+}
+
+static void mtk_hdmi_mask(struct mtk_hdmi *hdmi, u32 offset, u32 val, u32 mask)
+{
+       void __iomem *reg = hdmi->regs + offset;
+       u32 tmp;
+
+       tmp = readl(reg);
+       tmp = (tmp & ~mask) | (val & mask);
+       writel(tmp, reg);
+}
+
+static void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi, bool black)
+{
+       mtk_hdmi_mask(hdmi, VIDEO_CFG_4, black ? GEN_RGB : NORMAL_PATH,
+                     VIDEO_SOURCE_SEL);
+}
+
+static void mtk_hdmi_hw_make_reg_writable(struct mtk_hdmi *hdmi, bool enable)
+{
+       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
+                          HDMI_PCLK_FREE_RUN, enable ? HDMI_PCLK_FREE_RUN : 0);
+       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
+                          HDMI_ON | ANLG_ON, enable ? (HDMI_ON | ANLG_ON) : 0);
+}
+
+static void mtk_hdmi_hw_1p4_version_enable(struct mtk_hdmi *hdmi, bool enable)
+{
+       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
+                          HDMI2P0_EN, enable ? 0 : HDMI2P0_EN);
+}
+
+static void mtk_hdmi_hw_aud_mute(struct mtk_hdmi *hdmi)
+{
+       mtk_hdmi_set_bits(hdmi, GRL_AUDIO_CFG, AUDIO_ZERO);
+}
+
+static void mtk_hdmi_hw_aud_unmute(struct mtk_hdmi *hdmi)
+{
+       mtk_hdmi_clear_bits(hdmi, GRL_AUDIO_CFG, AUDIO_ZERO);
+}
+
+static void mtk_hdmi_hw_reset(struct mtk_hdmi *hdmi)
+{
+       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
+                          HDMI_RST, HDMI_RST);
+       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
+                          HDMI_RST, 0);
+       mtk_hdmi_clear_bits(hdmi, GRL_CFG3, CFG3_CONTROL_PACKET_DELAY);
+       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
+                          ANLG_ON, ANLG_ON);
+}
+
+static void mtk_hdmi_hw_enable_notice(struct mtk_hdmi *hdmi, bool enable_notice)
+{
+       mtk_hdmi_mask(hdmi, GRL_CFG2, enable_notice ? CFG2_NOTICE_EN : 0,
+                     CFG2_NOTICE_EN);
+}
+
+static void mtk_hdmi_hw_write_int_mask(struct mtk_hdmi *hdmi, u32 int_mask)
+{
+       mtk_hdmi_write(hdmi, GRL_INT_MASK, int_mask);
+}
+
+static void mtk_hdmi_hw_enable_dvi_mode(struct mtk_hdmi *hdmi, bool enable)
+{
+       mtk_hdmi_mask(hdmi, GRL_CFG1, enable ? CFG1_DVI : 0, CFG1_DVI);
+}
+
+static void mtk_hdmi_hw_send_info_frame(struct mtk_hdmi *hdmi, u8 *buffer,
+                                       u8 len)
+{
+       u32 ctrl_reg = GRL_CTRL;
+       int i;
+       u8 *frame_data;
+       enum hdmi_infoframe_type frame_type;
+       u8 frame_ver;
+       u8 frame_len;
+       u8 checksum;
+       int ctrl_frame_en = 0;
+
+       frame_type = *buffer;
+       buffer += 1;
+       frame_ver = *buffer;
+       buffer += 1;
+       frame_len = *buffer;
+       buffer += 1;
+       checksum = *buffer;
+       buffer += 1;
+       frame_data = buffer;
+
+       dev_dbg(hdmi->dev,
+               "frame_type:0x%x,frame_ver:0x%x,frame_len:0x%x,checksum:0x%x\n",
+               frame_type, frame_ver, frame_len, checksum);
+
+       switch (frame_type) {
+       case HDMI_INFOFRAME_TYPE_AVI:
+               ctrl_frame_en = CTRL_AVI_EN;
+               ctrl_reg = GRL_CTRL;
+               break;
+       case HDMI_INFOFRAME_TYPE_SPD:
+               ctrl_frame_en = CTRL_SPD_EN;
+               ctrl_reg = GRL_CTRL;
+               break;
+       case HDMI_INFOFRAME_TYPE_AUDIO:
+               ctrl_frame_en = CTRL_AUDIO_EN;
+               ctrl_reg = GRL_CTRL;
+               break;
+       case HDMI_INFOFRAME_TYPE_VENDOR:
+               ctrl_frame_en = VS_EN;
+               ctrl_reg = GRL_ACP_ISRC_CTRL;
+               break;
+       }
+       mtk_hdmi_clear_bits(hdmi, ctrl_reg, ctrl_frame_en);
+       mtk_hdmi_write(hdmi, GRL_INFOFRM_TYPE, frame_type);
+       mtk_hdmi_write(hdmi, GRL_INFOFRM_VER, frame_ver);
+       mtk_hdmi_write(hdmi, GRL_INFOFRM_LNG, frame_len);
+
+       mtk_hdmi_write(hdmi, GRL_IFM_PORT, checksum);
+       for (i = 0; i < frame_len; i++)
+               mtk_hdmi_write(hdmi, GRL_IFM_PORT, frame_data[i]);
+
+       mtk_hdmi_set_bits(hdmi, ctrl_reg, ctrl_frame_en);
+}
+
+static void mtk_hdmi_hw_send_aud_packet(struct mtk_hdmi *hdmi, bool enable)
+{
+       mtk_hdmi_mask(hdmi, GRL_SHIFT_R2, enable ? 0 : AUDIO_PACKET_OFF,
+                     AUDIO_PACKET_OFF);
+}
+
+static void mtk_hdmi_hw_config_sys(struct mtk_hdmi *hdmi)
+{
+       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
+                          HDMI_OUT_FIFO_EN | MHL_MODE_ON, 0);
+       usleep_range(2000, 4000);
+       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
+                          HDMI_OUT_FIFO_EN | MHL_MODE_ON, HDMI_OUT_FIFO_EN);
+}
+
+static void mtk_hdmi_hw_set_deep_color_mode(struct mtk_hdmi *hdmi)
+{
+       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
+                          DEEP_COLOR_MODE_MASK | DEEP_COLOR_EN,
+                          COLOR_8BIT_MODE);
+}
+
+static void mtk_hdmi_hw_send_av_mute(struct mtk_hdmi *hdmi)
+{
+       mtk_hdmi_clear_bits(hdmi, GRL_CFG4, CTRL_AVMUTE);
+       usleep_range(2000, 4000);
+       mtk_hdmi_set_bits(hdmi, GRL_CFG4, CTRL_AVMUTE);
+}
+
+static void mtk_hdmi_hw_send_av_unmute(struct mtk_hdmi *hdmi)
+{
+       mtk_hdmi_mask(hdmi, GRL_CFG4, CFG4_AV_UNMUTE_EN,
+                     CFG4_AV_UNMUTE_EN | CFG4_AV_UNMUTE_SET);
+       usleep_range(2000, 4000);
+       mtk_hdmi_mask(hdmi, GRL_CFG4, CFG4_AV_UNMUTE_SET,
+                     CFG4_AV_UNMUTE_EN | CFG4_AV_UNMUTE_SET);
+}
+
+static void mtk_hdmi_hw_ncts_enable(struct mtk_hdmi *hdmi, bool on)
+{
+       mtk_hdmi_mask(hdmi, GRL_CTS_CTRL, on ? 0 : CTS_CTRL_SOFT,
+                     CTS_CTRL_SOFT);
+}
+
+static void mtk_hdmi_hw_ncts_auto_write_enable(struct mtk_hdmi *hdmi,
+                                              bool enable)
+{
+       mtk_hdmi_mask(hdmi, GRL_CTS_CTRL, enable ? NCTS_WRI_ANYTIME : 0,
+                     NCTS_WRI_ANYTIME);
+}
+
+static void mtk_hdmi_hw_msic_setting(struct mtk_hdmi *hdmi,
+                                    struct drm_display_mode *mode)
+{
+       mtk_hdmi_clear_bits(hdmi, GRL_CFG4, CFG4_MHL_MODE);
+
+       if (mode->flags & DRM_MODE_FLAG_INTERLACE &&
+           mode->clock == 74250 &&
+           mode->vdisplay == 1080)
+               mtk_hdmi_clear_bits(hdmi, GRL_CFG2, CFG2_MHL_DE_SEL);
+       else
+               mtk_hdmi_set_bits(hdmi, GRL_CFG2, CFG2_MHL_DE_SEL);
+}
+
+static void mtk_hdmi_hw_aud_set_channel_swap(struct mtk_hdmi *hdmi,
+                                       enum hdmi_aud_channel_swap_type swap)
+{
+       u8 swap_bit;
+
+       switch (swap) {
+       case HDMI_AUD_SWAP_LR:
+               swap_bit = LR_SWAP;
+               break;
+       case HDMI_AUD_SWAP_LFE_CC:
+               swap_bit = LFE_CC_SWAP;
+               break;
+       case HDMI_AUD_SWAP_LSRS:
+               swap_bit = LSRS_SWAP;
+               break;
+       case HDMI_AUD_SWAP_RLS_RRS:
+               swap_bit = RLS_RRS_SWAP;
+               break;
+       case HDMI_AUD_SWAP_LR_STATUS:
+               swap_bit = LR_STATUS_SWAP;
+               break;
+       default:
+               swap_bit = LFE_CC_SWAP;
+               break;
+       }
+       mtk_hdmi_mask(hdmi, GRL_CH_SWAP, swap_bit, 0xff);
+}
+
+static void mtk_hdmi_hw_aud_set_bit_num(struct mtk_hdmi *hdmi,
+                                       enum hdmi_audio_sample_size bit_num)
+{
+       u32 val;
+
+       switch (bit_num) {
+       case HDMI_AUDIO_SAMPLE_SIZE_16:
+               val = AOUT_16BIT;
+               break;
+       case HDMI_AUDIO_SAMPLE_SIZE_20:
+               val = AOUT_20BIT;
+               break;
+       case HDMI_AUDIO_SAMPLE_SIZE_24:
+       case HDMI_AUDIO_SAMPLE_SIZE_STREAM:
+               val = AOUT_24BIT;
+               break;
+       }
+
+       mtk_hdmi_mask(hdmi, GRL_AOUT_CFG, val, AOUT_BNUM_SEL_MASK);
+}
+
+static void mtk_hdmi_hw_aud_set_i2s_fmt(struct mtk_hdmi *hdmi,
+                                       enum hdmi_aud_i2s_fmt i2s_fmt)
+{
+       u32 val;
+
+       val = mtk_hdmi_read(hdmi, GRL_CFG0);
+       val &= ~(CFG0_W_LENGTH_MASK | CFG0_I2S_MODE_MASK);
+
+       switch (i2s_fmt) {
+       case HDMI_I2S_MODE_RJT_24BIT:
+               val |= CFG0_I2S_MODE_RTJ | CFG0_W_LENGTH_24BIT;
+               break;
+       case HDMI_I2S_MODE_RJT_16BIT:
+               val |= CFG0_I2S_MODE_RTJ | CFG0_W_LENGTH_16BIT;
+               break;
+       case HDMI_I2S_MODE_LJT_24BIT:
+       default:
+               val |= CFG0_I2S_MODE_LTJ | CFG0_W_LENGTH_24BIT;
+               break;
+       case HDMI_I2S_MODE_LJT_16BIT:
+               val |= CFG0_I2S_MODE_LTJ | CFG0_W_LENGTH_16BIT;
+               break;
+       case HDMI_I2S_MODE_I2S_24BIT:
+               val |= CFG0_I2S_MODE_I2S | CFG0_W_LENGTH_24BIT;
+               break;
+       case HDMI_I2S_MODE_I2S_16BIT:
+               val |= CFG0_I2S_MODE_I2S | CFG0_W_LENGTH_16BIT;
+               break;
+       }
+       mtk_hdmi_write(hdmi, GRL_CFG0, val);
+}
+
+static void mtk_hdmi_hw_audio_config(struct mtk_hdmi *hdmi, bool dst)
+{
+       const u8 mask = HIGH_BIT_RATE | DST_NORMAL_DOUBLE | SACD_DST | DSD_SEL;
+       u8 val;
+
+       /* Disable high bitrate, set DST packet normal/double */
+       mtk_hdmi_clear_bits(hdmi, GRL_AOUT_CFG, HIGH_BIT_RATE_PACKET_ALIGN);
+
+       if (dst)
+               val = DST_NORMAL_DOUBLE | SACD_DST;
+       else
+               val = 0;
+
+       mtk_hdmi_mask(hdmi, GRL_AUDIO_CFG, val, mask);
+}
+
+static void mtk_hdmi_hw_aud_set_i2s_chan_num(struct mtk_hdmi *hdmi,
+                                       enum hdmi_aud_channel_type channel_type,
+                                       u8 channel_count)
+{
+       unsigned int ch_switch;
+       u8 i2s_uv;
+
+       ch_switch = CH_SWITCH(7, 7) | CH_SWITCH(6, 6) |
+                   CH_SWITCH(5, 5) | CH_SWITCH(4, 4) |
+                   CH_SWITCH(3, 3) | CH_SWITCH(1, 2) |
+                   CH_SWITCH(2, 1) | CH_SWITCH(0, 0);
+
+       if (channel_count == 2) {
+               i2s_uv = I2S_UV_CH_EN(0);
+       } else if (channel_count == 3 || channel_count == 4) {
+               if (channel_count == 4 &&
+                   (channel_type == HDMI_AUD_CHAN_TYPE_3_0_LRS ||
+                   channel_type == HDMI_AUD_CHAN_TYPE_4_0))
+                       i2s_uv = I2S_UV_CH_EN(2) | I2S_UV_CH_EN(0);
+               else
+                       i2s_uv = I2S_UV_CH_EN(3) | I2S_UV_CH_EN(2);
+       } else if (channel_count == 6 || channel_count == 5) {
+               if (channel_count == 6 &&
+                   channel_type != HDMI_AUD_CHAN_TYPE_5_1 &&
+                   channel_type != HDMI_AUD_CHAN_TYPE_4_1_CLRS) {
+                       i2s_uv = I2S_UV_CH_EN(3) | I2S_UV_CH_EN(2) |
+                                I2S_UV_CH_EN(1) | I2S_UV_CH_EN(0);
+               } else {
+                       i2s_uv = I2S_UV_CH_EN(2) | I2S_UV_CH_EN(1) |
+                                I2S_UV_CH_EN(0);
+               }
+       } else if (channel_count == 8 || channel_count == 7) {
+               i2s_uv = I2S_UV_CH_EN(3) | I2S_UV_CH_EN(2) |
+                        I2S_UV_CH_EN(1) | I2S_UV_CH_EN(0);
+       } else {
+               i2s_uv = I2S_UV_CH_EN(0);
+       }
+
+       mtk_hdmi_write(hdmi, GRL_CH_SW0, ch_switch & 0xff);
+       mtk_hdmi_write(hdmi, GRL_CH_SW1, (ch_switch >> 8) & 0xff);
+       mtk_hdmi_write(hdmi, GRL_CH_SW2, (ch_switch >> 16) & 0xff);
+       mtk_hdmi_write(hdmi, GRL_I2S_UV, i2s_uv);
+}
+
+static void mtk_hdmi_hw_aud_set_input_type(struct mtk_hdmi *hdmi,
+                                          enum hdmi_aud_input_type input_type)
+{
+       u32 val;
+
+       val = mtk_hdmi_read(hdmi, GRL_CFG1);
+       if (input_type == HDMI_AUD_INPUT_I2S &&
+           (val & CFG1_SPDIF) == CFG1_SPDIF) {
+               val &= ~CFG1_SPDIF;
+       } else if (input_type == HDMI_AUD_INPUT_SPDIF &&
+               (val & CFG1_SPDIF) == 0) {
+               val |= CFG1_SPDIF;
+       }
+       mtk_hdmi_write(hdmi, GRL_CFG1, val);
+}
+
+static void mtk_hdmi_hw_aud_set_channel_status(struct mtk_hdmi *hdmi,
+                                              u8 *channel_status)
+{
+       int i;
+
+       for (i = 0; i < 5; i++) {
+               mtk_hdmi_write(hdmi, GRL_I2S_C_STA0 + i * 4, channel_status[i]);
+               mtk_hdmi_write(hdmi, GRL_L_STATUS_0 + i * 4, channel_status[i]);
+               mtk_hdmi_write(hdmi, GRL_R_STATUS_0 + i * 4, channel_status[i]);
+       }
+       for (; i < 24; i++) {
+               mtk_hdmi_write(hdmi, GRL_L_STATUS_0 + i * 4, 0);
+               mtk_hdmi_write(hdmi, GRL_R_STATUS_0 + i * 4, 0);
+       }
+}
+
+static void mtk_hdmi_hw_aud_src_reenable(struct mtk_hdmi *hdmi)
+{
+       u32 val;
+
+       val = mtk_hdmi_read(hdmi, GRL_MIX_CTRL);
+       if (val & MIX_CTRL_SRC_EN) {
+               val &= ~MIX_CTRL_SRC_EN;
+               mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
+               usleep_range(255, 512);
+               val |= MIX_CTRL_SRC_EN;
+               mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
+       }
+}
+
+static void mtk_hdmi_hw_aud_src_disable(struct mtk_hdmi *hdmi)
+{
+       u32 val;
+
+       val = mtk_hdmi_read(hdmi, GRL_MIX_CTRL);
+       val &= ~MIX_CTRL_SRC_EN;
+       mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
+       mtk_hdmi_write(hdmi, GRL_SHIFT_L1, 0x00);
+}
+
+static void mtk_hdmi_hw_aud_set_mclk(struct mtk_hdmi *hdmi,
+                                    enum hdmi_aud_mclk mclk)
+{
+       u32 val;
+
+       val = mtk_hdmi_read(hdmi, GRL_CFG5);
+       val &= CFG5_CD_RATIO_MASK;
+
+       switch (mclk) {
+       case HDMI_AUD_MCLK_128FS:
+               val |= CFG5_FS128;
+               break;
+       case HDMI_AUD_MCLK_256FS:
+               val |= CFG5_FS256;
+               break;
+       case HDMI_AUD_MCLK_384FS:
+               val |= CFG5_FS384;
+               break;
+       case HDMI_AUD_MCLK_512FS:
+               val |= CFG5_FS512;
+               break;
+       case HDMI_AUD_MCLK_768FS:
+               val |= CFG5_FS768;
+               break;
+       default:
+               val |= CFG5_FS256;
+               break;
+       }
+       mtk_hdmi_write(hdmi, GRL_CFG5, val);
+}
+
+struct hdmi_acr_n {
+       unsigned int clock;
+       unsigned int n[3];
+};
+
+/* Recommended N values from HDMI specification, tables 7-1 to 7-3 */
+static const struct hdmi_acr_n hdmi_rec_n_table[] = {
+       /* Clock, N: 32kHz 44.1kHz 48kHz */
+       {  25175, {  4576,  7007,  6864 } },
+       {  74176, { 11648, 17836, 11648 } },
+       { 148352, { 11648,  8918,  5824 } },
+       { 296703, {  5824,  4459,  5824 } },
+       { 297000, {  3072,  4704,  5120 } },
+       {      0, {  4096,  6272,  6144 } }, /* all other TMDS clocks */
+};
+
+/**
+ * hdmi_recommended_n() - Return N value recommended by HDMI specification
+ * @freq: audio sample rate in Hz
+ * @clock: rounded TMDS clock in kHz
+ */
+static unsigned int hdmi_recommended_n(unsigned int freq, unsigned int clock)
+{
+       const struct hdmi_acr_n *recommended;
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(hdmi_rec_n_table) - 1; i++) {
+               if (clock == hdmi_rec_n_table[i].clock)
+                       break;
+       }
+       recommended = hdmi_rec_n_table + i;
+
+       switch (freq) {
+       case 32000:
+               return recommended->n[0];
+       case 44100:
+               return recommended->n[1];
+       case 48000:
+               return recommended->n[2];
+       case 88200:
+               return recommended->n[1] * 2;
+       case 96000:
+               return recommended->n[2] * 2;
+       case 176400:
+               return recommended->n[1] * 4;
+       case 192000:
+               return recommended->n[2] * 4;
+       default:
+               return (128 * freq) / 1000;
+       }
+}
+
+static unsigned int hdmi_mode_clock_to_hz(unsigned int clock)
+{
+       switch (clock) {
+       case 25175:
+               return 25174825;        /* 25.2/1.001 MHz */
+       case 74176:
+               return 74175824;        /* 74.25/1.001 MHz */
+       case 148352:
+               return 148351648;       /* 148.5/1.001 MHz */
+       case 296703:
+               return 296703297;       /* 297/1.001 MHz */
+       default:
+               return clock * 1000;
+       }
+}
+
+static unsigned int hdmi_expected_cts(unsigned int audio_sample_rate,
+                                     unsigned int tmds_clock, unsigned int n)
+{
+       return DIV_ROUND_CLOSEST_ULL((u64)hdmi_mode_clock_to_hz(tmds_clock) * n,
+                                    128 * audio_sample_rate);
+}
+
+static void do_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, unsigned int n,
+                                   unsigned int cts)
+{
+       unsigned char val[NCTS_BYTES];
+       int i;
+
+       mtk_hdmi_write(hdmi, GRL_NCTS, 0);
+       mtk_hdmi_write(hdmi, GRL_NCTS, 0);
+       mtk_hdmi_write(hdmi, GRL_NCTS, 0);
+       memset(val, 0, sizeof(val));
+
+       val[0] = (cts >> 24) & 0xff;
+       val[1] = (cts >> 16) & 0xff;
+       val[2] = (cts >> 8) & 0xff;
+       val[3] = cts & 0xff;
+
+       val[4] = (n >> 16) & 0xff;
+       val[5] = (n >> 8) & 0xff;
+       val[6] = n & 0xff;
+
+       for (i = 0; i < NCTS_BYTES; i++)
+               mtk_hdmi_write(hdmi, GRL_NCTS, val[i]);
+}
+
+static void mtk_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi,
+                                    unsigned int sample_rate,
+                                    unsigned int clock)
+{
+       unsigned int n, cts;
+
+       n = hdmi_recommended_n(sample_rate, clock);
+       cts = hdmi_expected_cts(sample_rate, clock, n);
+
+       dev_dbg(hdmi->dev, "%s: sample_rate=%u, clock=%d, cts=%u, n=%u\n",
+               __func__, sample_rate, clock, n, cts);
+
+       mtk_hdmi_mask(hdmi, DUMMY_304, AUDIO_I2S_NCTS_SEL_64,
+                     AUDIO_I2S_NCTS_SEL);
+       do_hdmi_hw_aud_set_ncts(hdmi, n, cts);
+}
+
+static u8 mtk_hdmi_aud_get_chnl_count(enum hdmi_aud_channel_type channel_type)
+{
+       switch (channel_type) {
+       case HDMI_AUD_CHAN_TYPE_1_0:
+       case HDMI_AUD_CHAN_TYPE_1_1:
+       case HDMI_AUD_CHAN_TYPE_2_0:
+               return 2;
+       case HDMI_AUD_CHAN_TYPE_2_1:
+       case HDMI_AUD_CHAN_TYPE_3_0:
+               return 3;
+       case HDMI_AUD_CHAN_TYPE_3_1:
+       case HDMI_AUD_CHAN_TYPE_4_0:
+       case HDMI_AUD_CHAN_TYPE_3_0_LRS:
+               return 4;
+       case HDMI_AUD_CHAN_TYPE_4_1:
+       case HDMI_AUD_CHAN_TYPE_5_0:
+       case HDMI_AUD_CHAN_TYPE_3_1_LRS:
+       case HDMI_AUD_CHAN_TYPE_4_0_CLRS:
+               return 5;
+       case HDMI_AUD_CHAN_TYPE_5_1:
+       case HDMI_AUD_CHAN_TYPE_6_0:
+       case HDMI_AUD_CHAN_TYPE_4_1_CLRS:
+       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 6;
+       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:
+               return 7;
+       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:
+               return 8;
+       default:
+               return 2;
+       }
+}
+
+static int mtk_hdmi_video_change_vpll(struct mtk_hdmi *hdmi, u32 clock)
+{
+       unsigned long rate;
+       int ret;
+
+       /* The DPI driver already should have set TVDPLL to the correct rate */
+       ret = clk_set_rate(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL], clock);
+       if (ret) {
+               dev_err(hdmi->dev, "Failed to set PLL to %u Hz: %d\n", clock,
+                       ret);
+               return ret;
+       }
+
+       rate = clk_get_rate(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]);
+
+       if (DIV_ROUND_CLOSEST(rate, 1000) != DIV_ROUND_CLOSEST(clock, 1000))
+               dev_warn(hdmi->dev, "Want PLL %u Hz, got %lu Hz\n", clock,
+                        rate);
+       else
+               dev_dbg(hdmi->dev, "Want PLL %u Hz, got %lu Hz\n", clock, rate);
+
+       mtk_hdmi_hw_config_sys(hdmi);
+       mtk_hdmi_hw_set_deep_color_mode(hdmi);
+       return 0;
+}
+
+static void mtk_hdmi_video_set_display_mode(struct mtk_hdmi *hdmi,
+                                           struct drm_display_mode *mode)
+{
+       mtk_hdmi_hw_reset(hdmi);
+       mtk_hdmi_hw_enable_notice(hdmi, true);
+       mtk_hdmi_hw_write_int_mask(hdmi, 0xff);
+       mtk_hdmi_hw_enable_dvi_mode(hdmi, hdmi->dvi_mode);
+       mtk_hdmi_hw_ncts_auto_write_enable(hdmi, true);
+
+       mtk_hdmi_hw_msic_setting(hdmi, mode);
+}
+
+static int mtk_hdmi_aud_enable_packet(struct mtk_hdmi *hdmi, bool enable)
+{
+       mtk_hdmi_hw_send_aud_packet(hdmi, enable);
+       return 0;
+}
+
+static int mtk_hdmi_aud_on_off_hw_ncts(struct mtk_hdmi *hdmi, bool on)
+{
+       mtk_hdmi_hw_ncts_enable(hdmi, on);
+       return 0;
+}
+
+static int mtk_hdmi_aud_set_input(struct mtk_hdmi *hdmi)
+{
+       enum hdmi_aud_channel_type chan_type;
+       u8 chan_count;
+       bool dst;
+
+       mtk_hdmi_hw_aud_set_channel_swap(hdmi, HDMI_AUD_SWAP_LFE_CC);
+       mtk_hdmi_set_bits(hdmi, GRL_MIX_CTRL, MIX_CTRL_FLAT);
+
+       if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_SPDIF &&
+           hdmi->aud_param.aud_codec == HDMI_AUDIO_CODING_TYPE_DST) {
+               mtk_hdmi_hw_aud_set_bit_num(hdmi, HDMI_AUDIO_SAMPLE_SIZE_24);
+       } else if (hdmi->aud_param.aud_i2s_fmt == HDMI_I2S_MODE_LJT_24BIT) {
+               hdmi->aud_param.aud_i2s_fmt = HDMI_I2S_MODE_LJT_16BIT;
+       }
+
+       mtk_hdmi_hw_aud_set_i2s_fmt(hdmi, hdmi->aud_param.aud_i2s_fmt);
+       mtk_hdmi_hw_aud_set_bit_num(hdmi, HDMI_AUDIO_SAMPLE_SIZE_24);
+
+       dst = ((hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_SPDIF) &&
+              (hdmi->aud_param.aud_codec == HDMI_AUDIO_CODING_TYPE_DST));
+       mtk_hdmi_hw_audio_config(hdmi, dst);
+
+       if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_SPDIF)
+               chan_type = HDMI_AUD_CHAN_TYPE_2_0;
+       else
+               chan_type = hdmi->aud_param.aud_input_chan_type;
+       chan_count = mtk_hdmi_aud_get_chnl_count(chan_type);
+       mtk_hdmi_hw_aud_set_i2s_chan_num(hdmi, chan_type, chan_count);
+       mtk_hdmi_hw_aud_set_input_type(hdmi, hdmi->aud_param.aud_input_type);
+
+       return 0;
+}
+
+static int mtk_hdmi_aud_set_src(struct mtk_hdmi *hdmi,
+                               struct drm_display_mode *display_mode)
+{
+       unsigned int sample_rate = hdmi->aud_param.codec_params.sample_rate;
+
+       mtk_hdmi_aud_on_off_hw_ncts(hdmi, false);
+       mtk_hdmi_hw_aud_src_disable(hdmi);
+       mtk_hdmi_clear_bits(hdmi, GRL_CFG2, CFG2_ACLK_INV);
+
+       if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_I2S) {
+               switch (sample_rate) {
+               case 32000:
+               case 44100:
+               case 48000:
+               case 88200:
+               case 96000:
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               mtk_hdmi_hw_aud_set_mclk(hdmi, hdmi->aud_param.aud_mclk);
+       } else {
+               switch (sample_rate) {
+               case 32000:
+               case 44100:
+               case 48000:
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               mtk_hdmi_hw_aud_set_mclk(hdmi, HDMI_AUD_MCLK_128FS);
+       }
+
+       mtk_hdmi_hw_aud_set_ncts(hdmi, sample_rate, display_mode->clock);
+
+       mtk_hdmi_hw_aud_src_reenable(hdmi);
+       return 0;
+}
+
+static int mtk_hdmi_aud_output_config(struct mtk_hdmi *hdmi,
+                                     struct drm_display_mode *display_mode)
+{
+       mtk_hdmi_hw_aud_mute(hdmi);
+       mtk_hdmi_aud_enable_packet(hdmi, false);
+
+       mtk_hdmi_aud_set_input(hdmi);
+       mtk_hdmi_aud_set_src(hdmi, display_mode);
+       mtk_hdmi_hw_aud_set_channel_status(hdmi,
+                       hdmi->aud_param.codec_params.iec.status);
+
+       usleep_range(50, 100);
+
+       mtk_hdmi_aud_on_off_hw_ncts(hdmi, true);
+       mtk_hdmi_aud_enable_packet(hdmi, true);
+       mtk_hdmi_hw_aud_unmute(hdmi);
+       return 0;
+}
+
+static int mtk_hdmi_setup_avi_infoframe(struct mtk_hdmi *hdmi,
+                                       struct drm_display_mode *mode)
+{
+       struct hdmi_avi_infoframe frame;
+       u8 buffer[17];
+       ssize_t err;
+
+       err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
+       if (err < 0) {
+               dev_err(hdmi->dev,
+                       "Failed to get AVI infoframe from mode: %zd\n", err);
+               return err;
+       }
+
+       err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
+       if (err < 0) {
+               dev_err(hdmi->dev, "Failed to pack AVI infoframe: %zd\n", err);
+               return err;
+       }
+
+       mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
+       return 0;
+}
+
+static int mtk_hdmi_setup_spd_infoframe(struct mtk_hdmi *hdmi,
+                                       const char *vendor,
+                                       const char *product)
+{
+       struct hdmi_spd_infoframe frame;
+       u8 buffer[29];
+       ssize_t err;
+
+       err = hdmi_spd_infoframe_init(&frame, vendor, product);
+       if (err < 0) {
+               dev_err(hdmi->dev, "Failed to initialize SPD infoframe: %zd\n",
+                       err);
+               return err;
+       }
+
+       err = hdmi_spd_infoframe_pack(&frame, buffer, sizeof(buffer));
+       if (err < 0) {
+               dev_err(hdmi->dev, "Failed to pack SDP infoframe: %zd\n", err);
+               return err;
+       }
+
+       mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
+       return 0;
+}
+
+static int mtk_hdmi_setup_audio_infoframe(struct mtk_hdmi *hdmi)
+{
+       struct hdmi_audio_infoframe frame;
+       u8 buffer[14];
+       ssize_t err;
+
+       err = hdmi_audio_infoframe_init(&frame);
+       if (err < 0) {
+               dev_err(hdmi->dev, "Failed to setup audio infoframe: %zd\n",
+                       err);
+               return err;
+       }
+
+       frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
+       frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
+       frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
+       frame.channels = mtk_hdmi_aud_get_chnl_count(
+                                       hdmi->aud_param.aud_input_chan_type);
+
+       err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
+       if (err < 0) {
+               dev_err(hdmi->dev, "Failed to pack audio infoframe: %zd\n",
+                       err);
+               return err;
+       }
+
+       mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
+       return 0;
+}
+
+static int mtk_hdmi_setup_vendor_specific_infoframe(struct mtk_hdmi *hdmi,
+                                               struct drm_display_mode *mode)
+{
+       struct hdmi_vendor_infoframe frame;
+       u8 buffer[10];
+       ssize_t err;
+
+       err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, mode);
+       if (err) {
+               dev_err(hdmi->dev,
+                       "Failed to get vendor infoframe from mode: %zd\n", err);
+               return err;
+       }
+
+       err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer));
+       if (err) {
+               dev_err(hdmi->dev, "Failed to pack vendor infoframe: %zd\n",
+                       err);
+               return err;
+       }
+
+       mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
+       return 0;
+}
+
+static int mtk_hdmi_output_init(struct mtk_hdmi *hdmi)
+{
+       struct hdmi_audio_param *aud_param = &hdmi->aud_param;
+
+       hdmi->csp = HDMI_COLORSPACE_RGB;
+       aud_param->aud_codec = HDMI_AUDIO_CODING_TYPE_PCM;
+       aud_param->aud_sampe_size = HDMI_AUDIO_SAMPLE_SIZE_16;
+       aud_param->aud_input_type = HDMI_AUD_INPUT_I2S;
+       aud_param->aud_i2s_fmt = HDMI_I2S_MODE_I2S_24BIT;
+       aud_param->aud_mclk = HDMI_AUD_MCLK_128FS;
+       aud_param->aud_input_chan_type = HDMI_AUD_CHAN_TYPE_2_0;
+
+       return 0;
+}
+
+void mtk_hdmi_audio_enable(struct mtk_hdmi *hdmi)
+{
+       mtk_hdmi_aud_enable_packet(hdmi, true);
+       hdmi->audio_enable = true;
+}
+
+void mtk_hdmi_audio_disable(struct mtk_hdmi *hdmi)
+{
+       mtk_hdmi_aud_enable_packet(hdmi, false);
+       hdmi->audio_enable = false;
+}
+
+int mtk_hdmi_audio_set_param(struct mtk_hdmi *hdmi,
+                            struct hdmi_audio_param *param)
+{
+       if (!hdmi->audio_enable) {
+               dev_err(hdmi->dev, "hdmi audio is in disable state!\n");
+               return -EINVAL;
+       }
+       dev_dbg(hdmi->dev, "codec:%d, input:%d, channel:%d, fs:%d\n",
+               param->aud_codec, param->aud_input_type,
+               param->aud_input_chan_type, param->codec_params.sample_rate);
+       memcpy(&hdmi->aud_param, param, sizeof(*param));
+       return mtk_hdmi_aud_output_config(hdmi, &hdmi->mode);
+}
+
+static int mtk_hdmi_output_set_display_mode(struct mtk_hdmi *hdmi,
+                                           struct drm_display_mode *mode)
+{
+       int ret;
+
+       mtk_hdmi_hw_vid_black(hdmi, true);
+       mtk_hdmi_hw_aud_mute(hdmi);
+       mtk_hdmi_hw_send_av_mute(hdmi);
+       phy_power_off(hdmi->phy);
+
+       ret = mtk_hdmi_video_change_vpll(hdmi,
+                                        mode->clock * 1000);
+       if (ret) {
+               dev_err(hdmi->dev, "Failed to set vpll: %d\n", ret);
+               return ret;
+       }
+       mtk_hdmi_video_set_display_mode(hdmi, mode);
+
+       phy_power_on(hdmi->phy);
+       mtk_hdmi_aud_output_config(hdmi, mode);
+
+       mtk_hdmi_setup_audio_infoframe(hdmi);
+       mtk_hdmi_setup_avi_infoframe(hdmi, mode);
+       mtk_hdmi_setup_spd_infoframe(hdmi, "mediatek", "On-chip HDMI");
+       if (mode->flags & DRM_MODE_FLAG_3D_MASK)
+               mtk_hdmi_setup_vendor_specific_infoframe(hdmi, mode);
+
+       mtk_hdmi_hw_vid_black(hdmi, false);
+       mtk_hdmi_hw_aud_unmute(hdmi);
+       mtk_hdmi_hw_send_av_unmute(hdmi);
+
+       return 0;
+}
+
+static const char * const mtk_hdmi_clk_names[MTK_HDMI_CLK_COUNT] = {
+       [MTK_HDMI_CLK_HDMI_PIXEL] = "pixel",
+       [MTK_HDMI_CLK_HDMI_PLL] = "pll",
+       [MTK_HDMI_CLK_AUD_BCLK] = "bclk",
+       [MTK_HDMI_CLK_AUD_SPDIF] = "spdif",
+};
+
+static int mtk_hdmi_get_all_clk(struct mtk_hdmi *hdmi,
+                               struct device_node *np)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(mtk_hdmi_clk_names); i++) {
+               hdmi->clk[i] = of_clk_get_by_name(np,
+                                                 mtk_hdmi_clk_names[i]);
+               if (IS_ERR(hdmi->clk[i]))
+                       return PTR_ERR(hdmi->clk[i]);
+       }
+       return 0;
+}
+
+static int mtk_hdmi_clk_enable_audio(struct mtk_hdmi *hdmi)
+{
+       int ret;
+
+       ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]);
+       if (ret)
+               return ret;
+
+       ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_AUD_SPDIF]);
+       if (ret)
+               goto err;
+
+       return 0;
+err:
+       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]);
+       return ret;
+}
+
+static void mtk_hdmi_clk_disable_audio(struct mtk_hdmi *hdmi)
+{
+       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]);
+       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_SPDIF]);
+}
+
+static enum drm_connector_status hdmi_conn_detect(struct drm_connector *conn,
+                                                 bool force)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
+
+       return mtk_cec_hpd_high(hdmi->cec_dev) ?
+              connector_status_connected : connector_status_disconnected;
+}
+
+static void hdmi_conn_destroy(struct drm_connector *conn)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
+
+       mtk_cec_set_hpd_event(hdmi->cec_dev, NULL, NULL);
+
+       drm_connector_cleanup(conn);
+}
+
+static int mtk_hdmi_conn_get_modes(struct drm_connector *conn)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
+       struct edid *edid;
+       int ret;
+
+       if (!hdmi->ddc_adpt)
+               return -ENODEV;
+
+       edid = drm_get_edid(conn, hdmi->ddc_adpt);
+       if (!edid)
+               return -ENODEV;
+
+       hdmi->dvi_mode = !drm_detect_monitor_audio(edid);
+
+       drm_mode_connector_update_edid_property(conn, edid);
+
+       ret = drm_add_edid_modes(conn, edid);
+       drm_edid_to_eld(conn, edid);
+       kfree(edid);
+       return ret;
+}
+
+static int mtk_hdmi_conn_mode_valid(struct drm_connector *conn,
+                                   struct drm_display_mode *mode)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
+
+       dev_dbg(hdmi->dev, "xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n",
+               mode->hdisplay, mode->vdisplay, mode->vrefresh,
+               !!(mode->flags & DRM_MODE_FLAG_INTERLACE), mode->clock * 1000);
+
+       if (hdmi->bridge.next) {
+               struct drm_display_mode adjusted_mode;
+
+               drm_mode_copy(&adjusted_mode, mode);
+               if (!drm_bridge_mode_fixup(hdmi->bridge.next, mode,
+                                          &adjusted_mode))
+                       return MODE_BAD;
+       }
+
+       if (mode->clock < 27000)
+               return MODE_CLOCK_LOW;
+       if (mode->clock > 297000)
+               return MODE_CLOCK_HIGH;
+
+       return drm_mode_validate_size(mode, 0x1fff, 0x1fff);
+}
+
+static struct drm_encoder *mtk_hdmi_conn_best_enc(struct drm_connector *conn)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
+
+       return hdmi->bridge.encoder;
+}
+
+static const struct drm_connector_funcs mtk_hdmi_connector_funcs = {
+       .dpms = drm_atomic_helper_connector_dpms,
+       .detect = hdmi_conn_detect,
+       .fill_modes = drm_helper_probe_single_connector_modes,
+       .destroy = hdmi_conn_destroy,
+       .reset = drm_atomic_helper_connector_reset,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs
+               mtk_hdmi_connector_helper_funcs = {
+       .get_modes = mtk_hdmi_conn_get_modes,
+       .mode_valid = mtk_hdmi_conn_mode_valid,
+       .best_encoder = mtk_hdmi_conn_best_enc,
+};
+
+static void mtk_hdmi_hpd_event(bool 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);
+}
+
+/*
+ * Bridge callbacks
+ */
+
+static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+       int ret;
+
+       ret = drm_connector_init(bridge->encoder->dev, &hdmi->conn,
+                                &mtk_hdmi_connector_funcs,
+                                DRM_MODE_CONNECTOR_HDMIA);
+       if (ret) {
+               dev_err(hdmi->dev, "Failed to initialize connector: %d\n", ret);
+               return ret;
+       }
+       drm_connector_helper_add(&hdmi->conn, &mtk_hdmi_connector_helper_funcs);
+
+       hdmi->conn.polled = DRM_CONNECTOR_POLL_HPD;
+       hdmi->conn.interlace_allowed = true;
+       hdmi->conn.doublescan_allowed = false;
+
+       ret = drm_mode_connector_attach_encoder(&hdmi->conn,
+                                               bridge->encoder);
+       if (ret) {
+               dev_err(hdmi->dev,
+                       "Failed to attach connector to encoder: %d\n", ret);
+               return ret;
+       }
+
+       if (bridge->next) {
+               bridge->next->encoder = bridge->encoder;
+               ret = drm_bridge_attach(bridge->encoder->dev, bridge->next);
+               if (ret) {
+                       dev_err(hdmi->dev,
+                               "Failed to attach external bridge: %d\n", ret);
+                       return ret;
+               }
+       }
+
+       mtk_cec_set_hpd_event(hdmi->cec_dev, mtk_hdmi_hpd_event, hdmi->dev);
+
+       return 0;
+}
+
+static bool mtk_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
+                                      const struct drm_display_mode *mode,
+                                      struct drm_display_mode *adjusted_mode)
+{
+       return true;
+}
+
+static void mtk_hdmi_bridge_disable(struct drm_bridge *bridge)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+       if (!hdmi->enabled)
+               return;
+
+       phy_power_off(hdmi->phy);
+       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]);
+       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]);
+
+       hdmi->enabled = false;
+}
+
+static void mtk_hdmi_bridge_post_disable(struct drm_bridge *bridge)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+       if (!hdmi->powered)
+               return;
+
+       mtk_hdmi_hw_1p4_version_enable(hdmi, true);
+       mtk_hdmi_hw_make_reg_writable(hdmi, false);
+
+       hdmi->powered = false;
+}
+
+static void mtk_hdmi_bridge_mode_set(struct drm_bridge *bridge,
+                                    struct drm_display_mode *mode,
+                                    struct drm_display_mode *adjusted_mode)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+       dev_dbg(hdmi->dev, "cur info: name:%s, hdisplay:%d\n",
+               adjusted_mode->name, adjusted_mode->hdisplay);
+       dev_dbg(hdmi->dev, "hsync_start:%d,hsync_end:%d, htotal:%d",
+               adjusted_mode->hsync_start, adjusted_mode->hsync_end,
+               adjusted_mode->htotal);
+       dev_dbg(hdmi->dev, "hskew:%d, vdisplay:%d\n",
+               adjusted_mode->hskew, adjusted_mode->vdisplay);
+       dev_dbg(hdmi->dev, "vsync_start:%d, vsync_end:%d, vtotal:%d",
+               adjusted_mode->vsync_start, adjusted_mode->vsync_end,
+               adjusted_mode->vtotal);
+       dev_dbg(hdmi->dev, "vscan:%d, flag:%d\n",
+               adjusted_mode->vscan, adjusted_mode->flags);
+
+       drm_mode_copy(&hdmi->mode, adjusted_mode);
+}
+
+static void mtk_hdmi_bridge_pre_enable(struct drm_bridge *bridge)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+       mtk_hdmi_hw_make_reg_writable(hdmi, true);
+       mtk_hdmi_hw_1p4_version_enable(hdmi, true);
+
+       hdmi->powered = true;
+}
+
+static void mtk_hdmi_bridge_enable(struct drm_bridge *bridge)
+{
+       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+       mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode);
+       clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]);
+       clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]);
+       phy_power_on(hdmi->phy);
+
+       hdmi->enabled = true;
+}
+
+static const struct drm_bridge_funcs mtk_hdmi_bridge_funcs = {
+       .attach = mtk_hdmi_bridge_attach,
+       .mode_fixup = mtk_hdmi_bridge_mode_fixup,
+       .disable = mtk_hdmi_bridge_disable,
+       .post_disable = mtk_hdmi_bridge_post_disable,
+       .mode_set = mtk_hdmi_bridge_mode_set,
+       .pre_enable = mtk_hdmi_bridge_pre_enable,
+       .enable = mtk_hdmi_bridge_enable,
+};
+
+static int mtk_hdmi_dt_parse_pdata(struct mtk_hdmi *hdmi,
+                                  struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
+       struct device_node *cec_np, *port, *ep, *remote, *i2c_np;
+       struct platform_device *cec_pdev;
+       struct regmap *regmap;
+       struct resource *mem;
+       int ret;
+
+       ret = mtk_hdmi_get_all_clk(hdmi, np);
+       if (ret) {
+               dev_err(dev, "Failed to get clocks: %d\n", ret);
+               return ret;
+       }
+
+       /* The CEC module handles HDMI hotplug detection */
+       cec_np = of_find_compatible_node(np->parent, NULL,
+                                        "mediatek,mt8173-cec");
+       if (!cec_np) {
+               dev_err(dev, "Failed to find CEC node\n");
+               return -EINVAL;
+       }
+
+       cec_pdev = of_find_device_by_node(cec_np);
+       if (!cec_pdev) {
+               dev_err(hdmi->dev, "Waiting for CEC device %s\n",
+                       cec_np->full_name);
+               return -EPROBE_DEFER;
+       }
+       hdmi->cec_dev = &cec_pdev->dev;
+
+       /*
+        * The mediatek,syscon-hdmi property contains a phandle link to the
+        * MMSYS_CONFIG device and the register offset of the HDMI_SYS_CFG
+        * registers it contains.
+        */
+       regmap = syscon_regmap_lookup_by_phandle(np, "mediatek,syscon-hdmi");
+       ret = of_property_read_u32_index(np, "mediatek,syscon-hdmi", 1,
+                                        &hdmi->sys_offset);
+       if (IS_ERR(regmap))
+               ret = PTR_ERR(regmap);
+       if (ret) {
+               ret = PTR_ERR(regmap);
+               dev_err(dev,
+                       "Failed to get system configuration registers: %d\n",
+                       ret);
+               return ret;
+       }
+       hdmi->sys_regmap = regmap;
+
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       hdmi->regs = devm_ioremap_resource(dev, mem);
+       if (IS_ERR(hdmi->regs))
+               return PTR_ERR(hdmi->regs);
+
+       port = of_graph_get_port_by_id(np, 1);
+       if (!port) {
+               dev_err(dev, "Missing output port node\n");
+               return -EINVAL;
+       }
+
+       ep = of_get_child_by_name(port, "endpoint");
+       if (!ep) {
+               dev_err(dev, "Missing endpoint node in port %s\n",
+                       port->full_name);
+               of_node_put(port);
+               return -EINVAL;
+       }
+       of_node_put(port);
+
+       remote = of_graph_get_remote_port_parent(ep);
+       if (!remote) {
+               dev_err(dev, "Missing connector/bridge node for endpoint %s\n",
+                       ep->full_name);
+               of_node_put(ep);
+               return -EINVAL;
+       }
+       of_node_put(ep);
+
+       if (!of_device_is_compatible(remote, "hdmi-connector")) {
+               hdmi->bridge.next = of_drm_find_bridge(remote);
+               if (!hdmi->bridge.next) {
+                       dev_err(dev, "Waiting for external bridge\n");
+                       of_node_put(remote);
+                       return -EPROBE_DEFER;
+               }
+       }
+
+       i2c_np = of_parse_phandle(remote, "ddc-i2c-bus", 0);
+       if (!i2c_np) {
+               dev_err(dev, "Failed to find ddc-i2c-bus node in %s\n",
+                       remote->full_name);
+               of_node_put(remote);
+               return -EINVAL;
+       }
+       of_node_put(remote);
+
+       hdmi->ddc_adpt = of_find_i2c_adapter_by_node(i2c_np);
+       if (!hdmi->ddc_adpt) {
+               dev_err(dev, "Failed to get ddc i2c adapter by node\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * HDMI audio codec callbacks
+ */
+
+static int mtk_hdmi_audio_hw_params(struct device *dev,
+                                   struct hdmi_codec_daifmt *daifmt,
+                                   struct hdmi_codec_params *params)
+{
+       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
+       struct hdmi_audio_param hdmi_params;
+       unsigned int chan = params->cea.channels;
+
+       dev_dbg(hdmi->dev, "%s: %u Hz, %d bit, %d channels\n", __func__,
+               params->sample_rate, params->sample_width, chan);
+
+       if (!hdmi->bridge.encoder)
+               return -ENODEV;
+
+       switch (chan) {
+       case 2:
+               hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_2_0;
+               break;
+       case 4:
+               hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_4_0;
+               break;
+       case 6:
+               hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_5_1;
+               break;
+       case 8:
+               hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_7_1;
+               break;
+       default:
+               dev_err(hdmi->dev, "channel[%d] not supported!\n", chan);
+               return -EINVAL;
+       }
+
+       switch (params->sample_rate) {
+       case 32000:
+       case 44100:
+       case 48000:
+       case 88200:
+       case 96000:
+       case 176400:
+       case 192000:
+               break;
+       default:
+               dev_err(hdmi->dev, "rate[%d] not supported!\n",
+                       params->sample_rate);
+               return -EINVAL;
+       }
+
+       switch (daifmt->fmt) {
+       case HDMI_I2S:
+               hdmi_params.aud_codec = HDMI_AUDIO_CODING_TYPE_PCM;
+               hdmi_params.aud_sampe_size = HDMI_AUDIO_SAMPLE_SIZE_16;
+               hdmi_params.aud_input_type = HDMI_AUD_INPUT_I2S;
+               hdmi_params.aud_i2s_fmt = HDMI_I2S_MODE_I2S_24BIT;
+               hdmi_params.aud_mclk = HDMI_AUD_MCLK_128FS;
+               break;
+       default:
+               dev_err(hdmi->dev, "%s: Invalid DAI format %d\n", __func__,
+                       daifmt->fmt);
+               return -EINVAL;
+       }
+
+       memcpy(&hdmi_params.codec_params, params,
+              sizeof(hdmi_params.codec_params));
+
+       mtk_hdmi_audio_set_param(hdmi, &hdmi_params);
+
+       return 0;
+}
+
+static int mtk_hdmi_audio_startup(struct device *dev)
+{
+       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
+
+       dev_dbg(dev, "%s\n", __func__);
+
+       mtk_hdmi_audio_enable(hdmi);
+
+       return 0;
+}
+
+static void mtk_hdmi_audio_shutdown(struct device *dev)
+{
+       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
+
+       dev_dbg(dev, "%s\n", __func__);
+
+       mtk_hdmi_audio_disable(hdmi);
+}
+
+int mtk_hdmi_audio_digital_mute(struct device *dev, bool enable)
+{
+       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
+
+       dev_dbg(dev, "%s(%d)\n", __func__, enable);
+
+       if (enable)
+               mtk_hdmi_hw_aud_mute(hdmi);
+       else
+               mtk_hdmi_hw_aud_unmute(hdmi);
+
+       return 0;
+}
+
+static int mtk_hdmi_audio_get_eld(struct device *dev, uint8_t *buf, size_t len)
+{
+       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
+
+       dev_dbg(dev, "%s\n", __func__);
+
+       memcpy(buf, hdmi->conn.eld, min(sizeof(hdmi->conn.eld), len));
+
+       return 0;
+}
+
+static const struct hdmi_codec_ops mtk_hdmi_audio_codec_ops = {
+       .hw_params = mtk_hdmi_audio_hw_params,
+       .audio_startup = mtk_hdmi_audio_startup,
+       .audio_shutdown = mtk_hdmi_audio_shutdown,
+       .digital_mute = mtk_hdmi_audio_digital_mute,
+       .get_eld = mtk_hdmi_audio_get_eld,
+};
+
+static void mtk_hdmi_register_audio_driver(struct device *dev)
+{
+       struct hdmi_codec_pdata codec_data = {
+               .ops = &mtk_hdmi_audio_codec_ops,
+               .max_i2s_channels = 2,
+               .i2s = 1,
+       };
+       struct platform_device *pdev;
+
+       pdev = platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
+                                            PLATFORM_DEVID_AUTO, &codec_data,
+                                            sizeof(codec_data));
+       if (IS_ERR(pdev))
+               return;
+
+       DRM_INFO("%s driver bound to HDMI\n", HDMI_CODEC_DRV_NAME);
+}
+
+static int mtk_drm_hdmi_probe(struct platform_device *pdev)
+{
+       struct mtk_hdmi *hdmi;
+       struct device *dev = &pdev->dev;
+       int ret;
+
+       hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+       if (!hdmi)
+               return -ENOMEM;
+
+       hdmi->dev = dev;
+
+       ret = mtk_hdmi_dt_parse_pdata(hdmi, pdev);
+       if (ret)
+               return ret;
+
+       hdmi->phy = devm_phy_get(dev, "hdmi");
+       if (IS_ERR(hdmi->phy)) {
+               ret = PTR_ERR(hdmi->phy);
+               dev_err(dev, "Failed to get HDMI PHY: %d\n", ret);
+               return ret;
+       }
+
+       platform_set_drvdata(pdev, hdmi);
+
+       ret = mtk_hdmi_output_init(hdmi);
+       if (ret) {
+               dev_err(dev, "Failed to initialize hdmi output\n");
+               return ret;
+       }
+
+       mtk_hdmi_register_audio_driver(dev);
+
+       hdmi->bridge.funcs = &mtk_hdmi_bridge_funcs;
+       hdmi->bridge.of_node = pdev->dev.of_node;
+       ret = drm_bridge_add(&hdmi->bridge);
+       if (ret) {
+               dev_err(dev, "failed to add bridge, ret = %d\n", ret);
+               return ret;
+       }
+
+       ret = mtk_hdmi_clk_enable_audio(hdmi);
+       if (ret) {
+               dev_err(dev, "Failed to enable audio clocks: %d\n", ret);
+               goto err_bridge_remove;
+       }
+
+       dev_dbg(dev, "mediatek hdmi probe success\n");
+       return 0;
+
+err_bridge_remove:
+       drm_bridge_remove(&hdmi->bridge);
+       return ret;
+}
+
+static int mtk_drm_hdmi_remove(struct platform_device *pdev)
+{
+       struct mtk_hdmi *hdmi = platform_get_drvdata(pdev);
+
+       drm_bridge_remove(&hdmi->bridge);
+       mtk_hdmi_clk_disable_audio(hdmi);
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mtk_hdmi_suspend(struct device *dev)
+{
+       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
+
+       mtk_hdmi_clk_disable_audio(hdmi);
+       dev_dbg(dev, "hdmi suspend success!\n");
+       return 0;
+}
+
+static int mtk_hdmi_resume(struct device *dev)
+{
+       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
+       int ret = 0;
+
+       ret = mtk_hdmi_clk_enable_audio(hdmi);
+       if (ret) {
+               dev_err(dev, "hdmi resume failed!\n");
+               return ret;
+       }
+
+       dev_dbg(dev, "hdmi resume success!\n");
+       return 0;
+}
+#endif
+static SIMPLE_DEV_PM_OPS(mtk_hdmi_pm_ops,
+                        mtk_hdmi_suspend, mtk_hdmi_resume);
+
+static const struct of_device_id mtk_drm_hdmi_of_ids[] = {
+       { .compatible = "mediatek,mt8173-hdmi", },
+       {}
+};
+
+static struct platform_driver mtk_hdmi_driver = {
+       .probe = mtk_drm_hdmi_probe,
+       .remove = mtk_drm_hdmi_remove,
+       .driver = {
+               .name = "mediatek-drm-hdmi",
+               .of_match_table = mtk_drm_hdmi_of_ids,
+               .pm = &mtk_hdmi_pm_ops,
+       },
+};
+
+static struct platform_driver * const mtk_hdmi_drivers[] = {
+       &mtk_hdmi_phy_driver,
+       &mtk_hdmi_ddc_driver,
+       &mtk_cec_driver,
+       &mtk_hdmi_driver,
+};
+
+static int __init mtk_hdmitx_init(void)
+{
+       int ret;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(mtk_hdmi_drivers); i++) {
+               ret = platform_driver_register(mtk_hdmi_drivers[i]);
+               if (ret < 0) {
+                       pr_err("Failed to register %s driver: %d\n",
+                              mtk_hdmi_drivers[i]->driver.name, ret);
+                       goto err;
+               }
+       }
+
+       return 0;
+
+err:
+       while (--i >= 0)
+               platform_driver_unregister(mtk_hdmi_drivers[i]);
+
+       return ret;
+}
+
+static void __exit mtk_hdmitx_exit(void)
+{
+       int i;
+
+       for (i = ARRAY_SIZE(mtk_hdmi_drivers) - 1; i >= 0; i--)
+               platform_driver_unregister(mtk_hdmi_drivers[i]);
+}
+
+module_init(mtk_hdmitx_init);
+module_exit(mtk_hdmitx_exit);
+
+MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek HDMI Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.h b/drivers/gpu/drm/mediatek/mtk_hdmi.h
new file mode 100644 (file)
index 0000000..6371b3d
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _MTK_HDMI_CTRL_H
+#define _MTK_HDMI_CTRL_H
+
+struct platform_driver;
+
+extern struct platform_driver mtk_cec_driver;
+extern struct platform_driver mtk_hdmi_ddc_driver;
+extern struct platform_driver mtk_hdmi_phy_driver;
+
+#endif /* _MTK_HDMI_CTRL_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c
new file mode 100644 (file)
index 0000000..33c9e1b
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+
+#define SIF1_CLOK              (288)
+#define DDC_DDCMCTL0           (0x0)
+#define DDCM_ODRAIN                    BIT(31)
+#define DDCM_CLK_DIV_OFFSET            (16)
+#define DDCM_CLK_DIV_MASK              (0xfff << 16)
+#define DDCM_CS_STATUS                 BIT(4)
+#define DDCM_SCL_STATE                 BIT(3)
+#define DDCM_SDA_STATE                 BIT(2)
+#define DDCM_SM0EN                     BIT(1)
+#define DDCM_SCL_STRECH                        BIT(0)
+#define DDC_DDCMCTL1           (0x4)
+#define DDCM_ACK_OFFSET                        (16)
+#define DDCM_ACK_MASK                  (0xff << 16)
+#define DDCM_PGLEN_OFFSET              (8)
+#define DDCM_PGLEN_MASK                        (0x7 << 8)
+#define DDCM_SIF_MODE_OFFSET           (4)
+#define DDCM_SIF_MODE_MASK             (0x7 << 4)
+#define DDCM_START                     (0x1)
+#define DDCM_WRITE_DATA                        (0x2)
+#define DDCM_STOP                      (0x3)
+#define DDCM_READ_DATA_NO_ACK          (0x4)
+#define DDCM_READ_DATA_ACK             (0x5)
+#define DDCM_TRI                       BIT(0)
+#define DDC_DDCMD0             (0x8)
+#define DDCM_DATA3                     (0xff << 24)
+#define DDCM_DATA2                     (0xff << 16)
+#define DDCM_DATA1                     (0xff << 8)
+#define DDCM_DATA0                     (0xff << 0)
+#define DDC_DDCMD1             (0xc)
+#define DDCM_DATA7                     (0xff << 24)
+#define DDCM_DATA6                     (0xff << 16)
+#define DDCM_DATA5                     (0xff << 8)
+#define DDCM_DATA4                     (0xff << 0)
+
+struct mtk_hdmi_ddc {
+       struct i2c_adapter adap;
+       struct clk *clk;
+       void __iomem *regs;
+};
+
+static inline void sif_set_bit(struct mtk_hdmi_ddc *ddc, unsigned int offset,
+                              unsigned int val)
+{
+       writel(readl(ddc->regs + offset) | val, ddc->regs + offset);
+}
+
+static inline void sif_clr_bit(struct mtk_hdmi_ddc *ddc, unsigned int offset,
+                              unsigned int val)
+{
+       writel(readl(ddc->regs + offset) & ~val, ddc->regs + offset);
+}
+
+static inline bool sif_bit_is_set(struct mtk_hdmi_ddc *ddc, unsigned int offset,
+                                 unsigned int val)
+{
+       return (readl(ddc->regs + offset) & val) == val;
+}
+
+static inline void sif_write_mask(struct mtk_hdmi_ddc *ddc, unsigned int offset,
+                                 unsigned int mask, unsigned int shift,
+                                 unsigned int val)
+{
+       unsigned int tmp;
+
+       tmp = readl(ddc->regs + offset);
+       tmp &= ~mask;
+       tmp |= (val << shift) & mask;
+       writel(tmp, ddc->regs + offset);
+}
+
+static inline unsigned int sif_read_mask(struct mtk_hdmi_ddc *ddc,
+                                        unsigned int offset, unsigned int mask,
+                                        unsigned int shift)
+{
+       return (readl(ddc->regs + offset) & mask) >> shift;
+}
+
+static void ddcm_trigger_mode(struct mtk_hdmi_ddc *ddc, int mode)
+{
+       u32 val;
+
+       sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_SIF_MODE_MASK,
+                      DDCM_SIF_MODE_OFFSET, mode);
+       sif_set_bit(ddc, DDC_DDCMCTL1, DDCM_TRI);
+       readl_poll_timeout(ddc->regs + DDC_DDCMCTL1, val,
+                          (val & DDCM_TRI) != DDCM_TRI, 4, 20000);
+}
+
+static int mtk_hdmi_ddc_read_msg(struct mtk_hdmi_ddc *ddc, struct i2c_msg *msg)
+{
+       struct device *dev = ddc->adap.dev.parent;
+       u32 remain_count, ack_count, ack_final, read_count, temp_count;
+       u32 index = 0;
+       u32 ack;
+       int i;
+
+       ddcm_trigger_mode(ddc, DDCM_START);
+       sif_write_mask(ddc, DDC_DDCMD0, 0xff, 0, (msg->addr << 1) | 0x01);
+       sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET,
+                      0x00);
+       ddcm_trigger_mode(ddc, DDCM_WRITE_DATA);
+       ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET);
+       dev_dbg(dev, "ack = 0x%x\n", ack);
+       if (ack != 0x01) {
+               dev_err(dev, "i2c ack err!\n");
+               return -ENXIO;
+       }
+
+       remain_count = msg->len;
+       ack_count = (msg->len - 1) / 8;
+       ack_final = 0;
+
+       while (remain_count > 0) {
+               if (ack_count > 0) {
+                       read_count = 8;
+                       ack_final = 0;
+                       ack_count--;
+               } else {
+                       read_count = remain_count;
+                       ack_final = 1;
+               }
+
+               sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK,
+                              DDCM_PGLEN_OFFSET, read_count - 1);
+               ddcm_trigger_mode(ddc, (ack_final == 1) ?
+                                 DDCM_READ_DATA_NO_ACK :
+                                 DDCM_READ_DATA_ACK);
+
+               ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK,
+                                   DDCM_ACK_OFFSET);
+               temp_count = 0;
+               while (((ack & (1 << temp_count)) != 0) && (temp_count < 8))
+                       temp_count++;
+               if (((ack_final == 1) && (temp_count != (read_count - 1))) ||
+                   ((ack_final == 0) && (temp_count != read_count))) {
+                       dev_err(dev, "Address NACK! ACK(0x%x)\n", ack);
+                       break;
+               }
+
+               for (i = read_count; i >= 1; i--) {
+                       int shift;
+                       int offset;
+
+                       if (i > 4) {
+                               offset = DDC_DDCMD1;
+                               shift = (i - 5) * 8;
+                       } else {
+                               offset = DDC_DDCMD0;
+                               shift = (i - 1) * 8;
+                       }
+
+                       msg->buf[index + i - 1] = sif_read_mask(ddc, offset,
+                                                               0xff << shift,
+                                                               shift);
+               }
+
+               remain_count -= read_count;
+               index += read_count;
+       }
+
+       return 0;
+}
+
+static int mtk_hdmi_ddc_write_msg(struct mtk_hdmi_ddc *ddc, struct i2c_msg *msg)
+{
+       struct device *dev = ddc->adap.dev.parent;
+       u32 ack;
+
+       ddcm_trigger_mode(ddc, DDCM_START);
+       sif_write_mask(ddc, DDC_DDCMD0, DDCM_DATA0, 0, msg->addr << 1);
+       sif_write_mask(ddc, DDC_DDCMD0, DDCM_DATA1, 8, msg->buf[0]);
+       sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET,
+                      0x1);
+       ddcm_trigger_mode(ddc, DDCM_WRITE_DATA);
+
+       ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET);
+       dev_dbg(dev, "ack = %d\n", ack);
+
+       if (ack != 0x03) {
+               dev_err(dev, "i2c ack err!\n");
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int mtk_hdmi_ddc_xfer(struct i2c_adapter *adapter,
+                            struct i2c_msg *msgs, int num)
+{
+       struct mtk_hdmi_ddc *ddc = adapter->algo_data;
+       struct device *dev = adapter->dev.parent;
+       int ret;
+       int i;
+
+       if (!ddc) {
+               dev_err(dev, "invalid arguments\n");
+               return -EINVAL;
+       }
+
+       sif_set_bit(ddc, DDC_DDCMCTL0, DDCM_SCL_STRECH);
+       sif_set_bit(ddc, DDC_DDCMCTL0, DDCM_SM0EN);
+       sif_clr_bit(ddc, DDC_DDCMCTL0, DDCM_ODRAIN);
+
+       if (sif_bit_is_set(ddc, DDC_DDCMCTL1, DDCM_TRI)) {
+               dev_err(dev, "ddc line is busy!\n");
+               return -EBUSY;
+       }
+
+       sif_write_mask(ddc, DDC_DDCMCTL0, DDCM_CLK_DIV_MASK,
+                      DDCM_CLK_DIV_OFFSET, SIF1_CLOK);
+
+       for (i = 0; i < num; i++) {
+               struct i2c_msg *msg = &msgs[i];
+
+               dev_dbg(dev, "i2c msg, adr:0x%x, flags:%d, len :0x%x\n",
+                       msg->addr, msg->flags, msg->len);
+
+               if (msg->flags & I2C_M_RD)
+                       ret = mtk_hdmi_ddc_read_msg(ddc, msg);
+               else
+                       ret = mtk_hdmi_ddc_write_msg(ddc, msg);
+               if (ret < 0)
+                       goto xfer_end;
+       }
+
+       ddcm_trigger_mode(ddc, DDCM_STOP);
+
+       return i;
+
+xfer_end:
+       ddcm_trigger_mode(ddc, DDCM_STOP);
+       dev_err(dev, "ddc failed!\n");
+       return ret;
+}
+
+static u32 mtk_hdmi_ddc_func(struct i2c_adapter *adapter)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm mtk_hdmi_ddc_algorithm = {
+       .master_xfer = mtk_hdmi_ddc_xfer,
+       .functionality = mtk_hdmi_ddc_func,
+};
+
+static int mtk_hdmi_ddc_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct mtk_hdmi_ddc *ddc;
+       struct resource *mem;
+       int ret;
+
+       ddc = devm_kzalloc(dev, sizeof(struct mtk_hdmi_ddc), GFP_KERNEL);
+       if (!ddc)
+               return -ENOMEM;
+
+       ddc->clk = devm_clk_get(dev, "ddc-i2c");
+       if (IS_ERR(ddc->clk)) {
+               dev_err(dev, "get ddc_clk failed: %p ,\n", ddc->clk);
+               return PTR_ERR(ddc->clk);
+       }
+
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       ddc->regs = devm_ioremap_resource(&pdev->dev, mem);
+       if (IS_ERR(ddc->regs))
+               return PTR_ERR(ddc->regs);
+
+       ret = clk_prepare_enable(ddc->clk);
+       if (ret) {
+               dev_err(dev, "enable ddc clk failed!\n");
+               return ret;
+       }
+
+       strlcpy(ddc->adap.name, "mediatek-hdmi-ddc", sizeof(ddc->adap.name));
+       ddc->adap.owner = THIS_MODULE;
+       ddc->adap.class = I2C_CLASS_DDC;
+       ddc->adap.algo = &mtk_hdmi_ddc_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 = i2c_add_adapter(&ddc->adap);
+       if (ret < 0) {
+               dev_err(dev, "failed to add bus to i2c core\n");
+               goto err_clk_disable;
+       }
+
+       platform_set_drvdata(pdev, ddc);
+
+       dev_dbg(dev, "ddc->adap: %p\n", &ddc->adap);
+       dev_dbg(dev, "ddc->clk: %p\n", ddc->clk);
+       dev_dbg(dev, "physical adr: %pa, end: %pa\n", &mem->start,
+               &mem->end);
+
+       return 0;
+
+err_clk_disable:
+       clk_disable_unprepare(ddc->clk);
+       return ret;
+}
+
+static int mtk_hdmi_ddc_remove(struct platform_device *pdev)
+{
+       struct mtk_hdmi_ddc *ddc = platform_get_drvdata(pdev);
+
+       i2c_del_adapter(&ddc->adap);
+       clk_disable_unprepare(ddc->clk);
+
+       return 0;
+}
+
+static const struct of_device_id mtk_hdmi_ddc_match[] = {
+       { .compatible = "mediatek,mt8173-hdmi-ddc", },
+       {},
+};
+
+struct platform_driver mtk_hdmi_ddc_driver = {
+       .probe = mtk_hdmi_ddc_probe,
+       .remove = mtk_hdmi_ddc_remove,
+       .driver = {
+               .name = "mediatek-hdmi-ddc",
+               .of_match_table = mtk_hdmi_ddc_match,
+       },
+};
+
+MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek HDMI DDC Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
new file mode 100644 (file)
index 0000000..209fbe1
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _MTK_HDMI_REGS_H
+#define _MTK_HDMI_REGS_H
+
+#define GRL_INT_MASK           0x18
+#define GRL_IFM_PORT           0x188
+#define GRL_CH_SWAP            0x198
+#define LR_SWAP                                BIT(0)
+#define LFE_CC_SWAP                    BIT(1)
+#define LSRS_SWAP                      BIT(2)
+#define RLS_RRS_SWAP                   BIT(3)
+#define LR_STATUS_SWAP                 BIT(4)
+#define GRL_I2S_C_STA0         0x140
+#define GRL_I2S_C_STA1         0x144
+#define GRL_I2S_C_STA2         0x148
+#define GRL_I2S_C_STA3         0x14C
+#define GRL_I2S_C_STA4         0x150
+#define GRL_I2S_UV             0x154
+#define I2S_UV_V                       BIT(0)
+#define I2S_UV_U                       BIT(1)
+#define I2S_UV_CH_EN_MASK              0x3c
+#define I2S_UV_CH_EN(x)                        BIT((x) + 2)
+#define I2S_UV_TMDS_DEBUG              BIT(6)
+#define I2S_UV_NORMAL_INFO_INV         BIT(7)
+#define GRL_ACP_ISRC_CTRL      0x158
+#define VS_EN                          BIT(0)
+#define ACP_EN                         BIT(1)
+#define ISRC1_EN                       BIT(2)
+#define ISRC2_EN                       BIT(3)
+#define GAMUT_EN                       BIT(4)
+#define GRL_CTS_CTRL           0x160
+#define CTS_CTRL_SOFT                  BIT(0)
+#define GRL_INT                        0x14
+#define INT_MDI                                BIT(0)
+#define INT_HDCP                       BIT(1)
+#define INT_FIFO_O                     BIT(2)
+#define INT_FIFO_U                     BIT(3)
+#define INT_IFM_ERR                    BIT(4)
+#define INT_INF_DONE                   BIT(5)
+#define INT_NCTS_DONE                  BIT(6)
+#define INT_CTRL_PKT_DONE              BIT(7)
+#define GRL_INT_MASK           0x18
+#define GRL_CTRL               0x1C
+#define CTRL_GEN_EN                    BIT(2)
+#define CTRL_SPD_EN                    BIT(3)
+#define CTRL_MPEG_EN                   BIT(4)
+#define CTRL_AUDIO_EN                  BIT(5)
+#define CTRL_AVI_EN                    BIT(6)
+#define CTRL_AVMUTE                    BIT(7)
+#define        GRL_STATUS              0x20
+#define STATUS_HTPLG                   BIT(0)
+#define STATUS_PORD                    BIT(1)
+#define GRL_DIVN               0x170
+#define NCTS_WRI_ANYTIME               BIT(6)
+#define GRL_AUDIO_CFG          0x17C
+#define AUDIO_ZERO                     BIT(0)
+#define HIGH_BIT_RATE                  BIT(1)
+#define SACD_DST                       BIT(2)
+#define DST_NORMAL_DOUBLE              BIT(3)
+#define DSD_INV                                BIT(4)
+#define LR_INV                         BIT(5)
+#define LR_MIX                         BIT(6)
+#define DSD_SEL                                BIT(7)
+#define GRL_NCTS               0x184
+#define GRL_CH_SW0             0x18C
+#define GRL_CH_SW1             0x190
+#define GRL_CH_SW2             0x194
+#define CH_SWITCH(from, to)            ((from) << ((to) * 3))
+#define GRL_INFOFRM_VER                0x19C
+#define GRL_INFOFRM_TYPE       0x1A0
+#define GRL_INFOFRM_LNG                0x1A4
+#define GRL_MIX_CTRL           0x1B4
+#define MIX_CTRL_SRC_EN                        BIT(0)
+#define BYPASS_VOLUME                  BIT(1)
+#define MIX_CTRL_FLAT                  BIT(7)
+#define GRL_AOUT_CFG           0x1C4
+#define AOUT_BNUM_SEL_MASK             0x03
+#define AOUT_24BIT                     0x00
+#define AOUT_20BIT                     0x02
+#define AOUT_16BIT                     0x03
+#define AOUT_FIFO_ADAP_CTRL            BIT(6)
+#define AOUT_BURST_PREAMBLE_EN         BIT(7)
+#define HIGH_BIT_RATE_PACKET_ALIGN     (AOUT_BURST_PREAMBLE_EN | \
+                                        AOUT_FIFO_ADAP_CTRL)
+#define GRL_SHIFT_L1           0x1C0
+#define GRL_SHIFT_R2           0x1B0
+#define AUDIO_PACKET_OFF               BIT(6)
+#define GRL_CFG0               0x24
+#define CFG0_I2S_MODE_MASK             0x3
+#define CFG0_I2S_MODE_RTJ              0x1
+#define CFG0_I2S_MODE_LTJ              0x0
+#define CFG0_I2S_MODE_I2S              0x2
+#define CFG0_W_LENGTH_MASK             0x30
+#define CFG0_W_LENGTH_24BIT            0x00
+#define CFG0_W_LENGTH_16BIT            0x10
+#define GRL_CFG1               0x28
+#define CFG1_EDG_SEL                   BIT(0)
+#define CFG1_SPDIF                     BIT(1)
+#define CFG1_DVI                       BIT(2)
+#define CFG1_HDCP_DEBUG                        BIT(3)
+#define GRL_CFG2               0x2c
+#define CFG2_MHL_DE_SEL                        BIT(3)
+#define CFG2_MHL_FAKE_DE_SEL           BIT(4)
+#define CFG2_MHL_DATA_REMAP            BIT(5)
+#define CFG2_NOTICE_EN                 BIT(6)
+#define CFG2_ACLK_INV                  BIT(7)
+#define GRL_CFG3               0x30
+#define CFG3_AES_KEY_INDEX_MASK                0x3f
+#define CFG3_CONTROL_PACKET_DELAY      BIT(6)
+#define CFG3_KSV_LOAD_START            BIT(7)
+#define GRL_CFG4               0x34
+#define CFG4_AES_KEY_LOAD              BIT(4)
+#define CFG4_AV_UNMUTE_EN              BIT(5)
+#define CFG4_AV_UNMUTE_SET             BIT(6)
+#define CFG4_MHL_MODE                  BIT(7)
+#define GRL_CFG5               0x38
+#define CFG5_CD_RATIO_MASK     0x8F
+#define CFG5_FS128                     (0x1 << 4)
+#define CFG5_FS256                     (0x2 << 4)
+#define CFG5_FS384                     (0x3 << 4)
+#define CFG5_FS512                     (0x4 << 4)
+#define CFG5_FS768                     (0x6 << 4)
+#define DUMMY_304              0x304
+#define CHMO_SEL                       (0x3 << 2)
+#define CHM1_SEL                       (0x3 << 4)
+#define CHM2_SEL                       (0x3 << 6)
+#define AUDIO_I2S_NCTS_SEL             BIT(1)
+#define AUDIO_I2S_NCTS_SEL_64          (1 << 1)
+#define AUDIO_I2S_NCTS_SEL_128         (0 << 1)
+#define NEW_GCP_CTRL                   BIT(0)
+#define NEW_GCP_CTRL_MERGE             BIT(0)
+#define GRL_L_STATUS_0         0x200
+#define GRL_L_STATUS_1         0x204
+#define GRL_L_STATUS_2         0x208
+#define GRL_L_STATUS_3         0x20c
+#define GRL_L_STATUS_4         0x210
+#define GRL_L_STATUS_5         0x214
+#define GRL_L_STATUS_6         0x218
+#define GRL_L_STATUS_7         0x21c
+#define GRL_L_STATUS_8         0x220
+#define GRL_L_STATUS_9         0x224
+#define GRL_L_STATUS_10                0x228
+#define GRL_L_STATUS_11                0x22c
+#define GRL_L_STATUS_12                0x230
+#define GRL_L_STATUS_13                0x234
+#define GRL_L_STATUS_14                0x238
+#define GRL_L_STATUS_15                0x23c
+#define GRL_L_STATUS_16                0x240
+#define GRL_L_STATUS_17                0x244
+#define GRL_L_STATUS_18                0x248
+#define GRL_L_STATUS_19                0x24c
+#define GRL_L_STATUS_20                0x250
+#define GRL_L_STATUS_21                0x254
+#define GRL_L_STATUS_22                0x258
+#define GRL_L_STATUS_23                0x25c
+#define GRL_R_STATUS_0         0x260
+#define GRL_R_STATUS_1         0x264
+#define GRL_R_STATUS_2         0x268
+#define GRL_R_STATUS_3         0x26c
+#define GRL_R_STATUS_4         0x270
+#define GRL_R_STATUS_5         0x274
+#define GRL_R_STATUS_6         0x278
+#define GRL_R_STATUS_7         0x27c
+#define GRL_R_STATUS_8         0x280
+#define GRL_R_STATUS_9         0x284
+#define GRL_R_STATUS_10                0x288
+#define GRL_R_STATUS_11                0x28c
+#define GRL_R_STATUS_12                0x290
+#define GRL_R_STATUS_13                0x294
+#define GRL_R_STATUS_14                0x298
+#define GRL_R_STATUS_15                0x29c
+#define GRL_R_STATUS_16                0x2a0
+#define GRL_R_STATUS_17                0x2a4
+#define GRL_R_STATUS_18                0x2a8
+#define GRL_R_STATUS_19                0x2ac
+#define GRL_R_STATUS_20                0x2b0
+#define GRL_R_STATUS_21                0x2b4
+#define GRL_R_STATUS_22                0x2b8
+#define GRL_R_STATUS_23                0x2bc
+#define GRL_ABIST_CTRL0                0x2D4
+#define GRL_ABIST_CTRL1                0x2D8
+#define ABIST_EN                       BIT(7)
+#define ABIST_DATA_FMT                 (0x7 << 0)
+#define VIDEO_CFG_0            0x380
+#define VIDEO_CFG_1            0x384
+#define VIDEO_CFG_2            0x388
+#define VIDEO_CFG_3            0x38c
+#define VIDEO_CFG_4            0x390
+#define VIDEO_SOURCE_SEL               BIT(7)
+#define NORMAL_PATH                    (1 << 7)
+#define GEN_RGB                                (0 << 7)
+
+#define HDMI_SYS_CFG1C         0x000
+#define HDMI_ON                                BIT(0)
+#define HDMI_RST                       BIT(1)
+#define ANLG_ON                                BIT(2)
+#define CFG10_DVI                      BIT(3)
+#define HDMI_TST                       BIT(3)
+#define SYS_KEYMASK1                   (0xff << 8)
+#define SYS_KEYMASK2                   (0xff << 16)
+#define AUD_OUTSYNC_EN                 BIT(24)
+#define AUD_OUTSYNC_PRE_EN             BIT(25)
+#define I2CM_ON                                BIT(26)
+#define E2PROM_TYPE_8BIT               BIT(27)
+#define MCM_E2PROM_ON                  BIT(28)
+#define EXT_E2PROM_ON                  BIT(29)
+#define HTPLG_PIN_SEL_OFF              BIT(30)
+#define AES_EFUSE_ENABLE               BIT(31)
+#define HDMI_SYS_CFG20         0x004
+#define DEEP_COLOR_MODE_MASK           (3 << 1)
+#define COLOR_8BIT_MODE                        (0 << 1)
+#define COLOR_10BIT_MODE               (1 << 1)
+#define COLOR_12BIT_MODE               (2 << 1)
+#define COLOR_16BIT_MODE               (3 << 1)
+#define DEEP_COLOR_EN                  BIT(0)
+#define HDMI_AUDIO_TEST_SEL            BIT(8)
+#define HDMI2P0_EN                     BIT(11)
+#define HDMI_OUT_FIFO_EN               BIT(16)
+#define HDMI_OUT_FIFO_CLK_INV          BIT(17)
+#define MHL_MODE_ON                    BIT(28)
+#define MHL_PP_MODE                    BIT(29)
+#define MHL_SYNC_AUTO_EN               BIT(30)
+#define HDMI_PCLK_FREE_RUN             BIT(31)
+
+#endif
diff --git a/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c b/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c
new file mode 100644 (file)
index 0000000..8a24754
--- /dev/null
@@ -0,0 +1,515 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#define HDMI_CON0              0x00
+#define RG_HDMITX_PLL_EN               BIT(31)
+#define RG_HDMITX_PLL_FBKDIV           (0x7f << 24)
+#define PLL_FBKDIV_SHIFT               24
+#define RG_HDMITX_PLL_FBKSEL           (0x3 << 22)
+#define PLL_FBKSEL_SHIFT               22
+#define RG_HDMITX_PLL_PREDIV           (0x3 << 20)
+#define PREDIV_SHIFT                   20
+#define RG_HDMITX_PLL_POSDIV           (0x3 << 18)
+#define POSDIV_SHIFT                   18
+#define RG_HDMITX_PLL_RST_DLY          (0x3 << 16)
+#define RG_HDMITX_PLL_IR               (0xf << 12)
+#define PLL_IR_SHIFT                   12
+#define RG_HDMITX_PLL_IC               (0xf << 8)
+#define PLL_IC_SHIFT                   8
+#define RG_HDMITX_PLL_BP               (0xf << 4)
+#define PLL_BP_SHIFT                   4
+#define RG_HDMITX_PLL_BR               (0x3 << 2)
+#define PLL_BR_SHIFT                   2
+#define RG_HDMITX_PLL_BC               (0x3 << 0)
+#define PLL_BC_SHIFT                   0
+#define HDMI_CON1              0x04
+#define RG_HDMITX_PLL_DIVEN            (0x7 << 29)
+#define PLL_DIVEN_SHIFT                        29
+#define RG_HDMITX_PLL_AUTOK_EN         BIT(28)
+#define RG_HDMITX_PLL_AUTOK_KF         (0x3 << 26)
+#define RG_HDMITX_PLL_AUTOK_KS         (0x3 << 24)
+#define RG_HDMITX_PLL_AUTOK_LOAD       BIT(23)
+#define RG_HDMITX_PLL_BAND             (0x3f << 16)
+#define RG_HDMITX_PLL_REF_SEL          BIT(15)
+#define RG_HDMITX_PLL_BIAS_EN          BIT(14)
+#define RG_HDMITX_PLL_BIAS_LPF_EN      BIT(13)
+#define RG_HDMITX_PLL_TXDIV_EN         BIT(12)
+#define RG_HDMITX_PLL_TXDIV            (0x3 << 10)
+#define PLL_TXDIV_SHIFT                        10
+#define RG_HDMITX_PLL_LVROD_EN         BIT(9)
+#define RG_HDMITX_PLL_MONVC_EN         BIT(8)
+#define RG_HDMITX_PLL_MONCK_EN         BIT(7)
+#define RG_HDMITX_PLL_MONREF_EN                BIT(6)
+#define RG_HDMITX_PLL_TST_EN           BIT(5)
+#define RG_HDMITX_PLL_TST_CK_EN                BIT(4)
+#define RG_HDMITX_PLL_TST_SEL          (0xf << 0)
+#define HDMI_CON2              0x08
+#define RGS_HDMITX_PLL_AUTOK_BAND      (0x7f << 8)
+#define RGS_HDMITX_PLL_AUTOK_FAIL      BIT(1)
+#define RG_HDMITX_EN_TX_CKLDO          BIT(0)
+#define HDMI_CON3              0x0c
+#define RG_HDMITX_SER_EN               (0xf << 28)
+#define RG_HDMITX_PRD_EN               (0xf << 24)
+#define RG_HDMITX_PRD_IMP_EN           (0xf << 20)
+#define RG_HDMITX_DRV_EN               (0xf << 16)
+#define RG_HDMITX_DRV_IMP_EN           (0xf << 12)
+#define DRV_IMP_EN_SHIFT               12
+#define RG_HDMITX_MHLCK_FORCE          BIT(10)
+#define RG_HDMITX_MHLCK_PPIX_EN                BIT(9)
+#define RG_HDMITX_MHLCK_EN             BIT(8)
+#define RG_HDMITX_SER_DIN_SEL          (0xf << 4)
+#define RG_HDMITX_SER_5T1_BIST_EN      BIT(3)
+#define RG_HDMITX_SER_BIST_TOG         BIT(2)
+#define RG_HDMITX_SER_DIN_TOG          BIT(1)
+#define RG_HDMITX_SER_CLKDIG_INV       BIT(0)
+#define HDMI_CON4              0x10
+#define RG_HDMITX_PRD_IBIAS_CLK                (0xf << 24)
+#define RG_HDMITX_PRD_IBIAS_D2         (0xf << 16)
+#define RG_HDMITX_PRD_IBIAS_D1         (0xf << 8)
+#define RG_HDMITX_PRD_IBIAS_D0         (0xf << 0)
+#define PRD_IBIAS_CLK_SHIFT            24
+#define PRD_IBIAS_D2_SHIFT             16
+#define PRD_IBIAS_D1_SHIFT             8
+#define PRD_IBIAS_D0_SHIFT             0
+#define HDMI_CON5              0x14
+#define RG_HDMITX_DRV_IBIAS_CLK                (0x3f << 24)
+#define RG_HDMITX_DRV_IBIAS_D2         (0x3f << 16)
+#define RG_HDMITX_DRV_IBIAS_D1         (0x3f << 8)
+#define RG_HDMITX_DRV_IBIAS_D0         (0x3f << 0)
+#define DRV_IBIAS_CLK_SHIFT            24
+#define DRV_IBIAS_D2_SHIFT             16
+#define DRV_IBIAS_D1_SHIFT             8
+#define DRV_IBIAS_D0_SHIFT             0
+#define HDMI_CON6              0x18
+#define RG_HDMITX_DRV_IMP_CLK          (0x3f << 24)
+#define RG_HDMITX_DRV_IMP_D2           (0x3f << 16)
+#define RG_HDMITX_DRV_IMP_D1           (0x3f << 8)
+#define RG_HDMITX_DRV_IMP_D0           (0x3f << 0)
+#define DRV_IMP_CLK_SHIFT              24
+#define DRV_IMP_D2_SHIFT               16
+#define DRV_IMP_D1_SHIFT               8
+#define DRV_IMP_D0_SHIFT               0
+#define HDMI_CON7              0x1c
+#define RG_HDMITX_MHLCK_DRV_IBIAS      (0x1f << 27)
+#define RG_HDMITX_SER_DIN              (0x3ff << 16)
+#define RG_HDMITX_CHLDC_TST            (0xf << 12)
+#define RG_HDMITX_CHLCK_TST            (0xf << 8)
+#define RG_HDMITX_RESERVE              (0xff << 0)
+#define HDMI_CON8              0x20
+#define RGS_HDMITX_2T1_LEV             (0xf << 16)
+#define RGS_HDMITX_2T1_EDG             (0xf << 12)
+#define RGS_HDMITX_5T1_LEV             (0xf << 8)
+#define RGS_HDMITX_5T1_EDG             (0xf << 4)
+#define RGS_HDMITX_PLUG_TST            BIT(0)
+
+struct mtk_hdmi_phy {
+       void __iomem *regs;
+       struct device *dev;
+       struct clk *pll;
+       struct clk_hw pll_hw;
+       unsigned long pll_rate;
+       u8 drv_imp_clk;
+       u8 drv_imp_d2;
+       u8 drv_imp_d1;
+       u8 drv_imp_d0;
+       u32 ibias;
+       u32 ibias_up;
+};
+
+static const u8 PREDIV[3][4] = {
+       {0x0, 0x0, 0x0, 0x0},   /* 27Mhz */
+       {0x1, 0x1, 0x1, 0x1},   /* 74Mhz */
+       {0x1, 0x1, 0x1, 0x1}    /* 148Mhz */
+};
+
+static const u8 TXDIV[3][4] = {
+       {0x3, 0x3, 0x3, 0x2},   /* 27Mhz */
+       {0x2, 0x1, 0x1, 0x1},   /* 74Mhz */
+       {0x1, 0x0, 0x0, 0x0}    /* 148Mhz */
+};
+
+static const u8 FBKSEL[3][4] = {
+       {0x1, 0x1, 0x1, 0x1},   /* 27Mhz */
+       {0x1, 0x0, 0x1, 0x1},   /* 74Mhz */
+       {0x1, 0x0, 0x1, 0x1}    /* 148Mhz */
+};
+
+static const u8 FBKDIV[3][4] = {
+       {19, 24, 29, 19},       /* 27Mhz */
+       {19, 24, 14, 19},       /* 74Mhz */
+       {19, 24, 14, 19}        /* 148Mhz */
+};
+
+static const u8 DIVEN[3][4] = {
+       {0x2, 0x1, 0x1, 0x2},   /* 27Mhz */
+       {0x2, 0x2, 0x2, 0x2},   /* 74Mhz */
+       {0x2, 0x2, 0x2, 0x2}    /* 148Mhz */
+};
+
+static const u8 HTPLLBP[3][4] = {
+       {0xc, 0xc, 0x8, 0xc},   /* 27Mhz */
+       {0xc, 0xf, 0xf, 0xc},   /* 74Mhz */
+       {0xc, 0xf, 0xf, 0xc}    /* 148Mhz */
+};
+
+static const u8 HTPLLBC[3][4] = {
+       {0x2, 0x3, 0x3, 0x2},   /* 27Mhz */
+       {0x2, 0x3, 0x3, 0x2},   /* 74Mhz */
+       {0x2, 0x3, 0x3, 0x2}    /* 148Mhz */
+};
+
+static const u8 HTPLLBR[3][4] = {
+       {0x1, 0x1, 0x0, 0x1},   /* 27Mhz */
+       {0x1, 0x2, 0x2, 0x1},   /* 74Mhz */
+       {0x1, 0x2, 0x2, 0x1}    /* 148Mhz */
+};
+
+static void mtk_hdmi_phy_clear_bits(struct mtk_hdmi_phy *hdmi_phy, u32 offset,
+                                   u32 bits)
+{
+       void __iomem *reg = hdmi_phy->regs + offset;
+       u32 tmp;
+
+       tmp = readl(reg);
+       tmp &= ~bits;
+       writel(tmp, reg);
+}
+
+static void mtk_hdmi_phy_set_bits(struct mtk_hdmi_phy *hdmi_phy, u32 offset,
+                                 u32 bits)
+{
+       void __iomem *reg = hdmi_phy->regs + offset;
+       u32 tmp;
+
+       tmp = readl(reg);
+       tmp |= bits;
+       writel(tmp, reg);
+}
+
+static void mtk_hdmi_phy_mask(struct mtk_hdmi_phy *hdmi_phy, u32 offset,
+                             u32 val, u32 mask)
+{
+       void __iomem *reg = hdmi_phy->regs + offset;
+       u32 tmp;
+
+       tmp = readl(reg);
+       tmp = (tmp & ~mask) | (val & mask);
+       writel(tmp, reg);
+}
+
+static inline struct mtk_hdmi_phy *to_mtk_hdmi_phy(struct clk_hw *hw)
+{
+       return container_of(hw, struct mtk_hdmi_phy, pll_hw);
+}
+
+static int mtk_hdmi_pll_prepare(struct clk_hw *hw)
+{
+       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+       dev_dbg(hdmi_phy->dev, "%s\n", __func__);
+
+       mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_AUTOK_EN);
+       mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_POSDIV);
+       mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON3, RG_HDMITX_MHLCK_EN);
+       mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_EN);
+       usleep_range(100, 150);
+       mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_EN);
+       usleep_range(100, 150);
+       mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_LPF_EN);
+       mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_TXDIV_EN);
+
+       return 0;
+}
+
+static void mtk_hdmi_pll_unprepare(struct clk_hw *hw)
+{
+       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+       dev_dbg(hdmi_phy->dev, "%s\n", __func__);
+
+       mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_TXDIV_EN);
+       mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_LPF_EN);
+       usleep_range(100, 150);
+       mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_EN);
+       usleep_range(100, 150);
+       mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_EN);
+       mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_POSDIV);
+       mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_AUTOK_EN);
+       usleep_range(100, 150);
+}
+
+static int mtk_hdmi_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+                                unsigned long parent_rate)
+{
+       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+       unsigned int pre_div;
+       unsigned int div;
+
+       dev_dbg(hdmi_phy->dev, "%s: %lu Hz, parent: %lu Hz\n", __func__,
+               rate, parent_rate);
+
+       if (rate <= 27000000) {
+               pre_div = 0;
+               div = 3;
+       } else if (rate <= 74250000) {
+               pre_div = 1;
+               div = 2;
+       } else {
+               pre_div = 1;
+               div = 1;
+       }
+
+       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+                         (pre_div << PREDIV_SHIFT), RG_HDMITX_PLL_PREDIV);
+       mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_POSDIV);
+       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+                         (0x1 << PLL_IC_SHIFT) | (0x1 << PLL_IR_SHIFT),
+                         RG_HDMITX_PLL_IC | RG_HDMITX_PLL_IR);
+       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1,
+                         (div << PLL_TXDIV_SHIFT), RG_HDMITX_PLL_TXDIV);
+       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+                         (0x1 << PLL_FBKSEL_SHIFT) | (19 << PLL_FBKDIV_SHIFT),
+                         RG_HDMITX_PLL_FBKSEL | RG_HDMITX_PLL_FBKDIV);
+       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1,
+                         (0x2 << PLL_DIVEN_SHIFT), RG_HDMITX_PLL_DIVEN);
+       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+                         (0xc << PLL_BP_SHIFT) | (0x2 << PLL_BC_SHIFT) |
+                         (0x1 << PLL_BR_SHIFT),
+                         RG_HDMITX_PLL_BP | RG_HDMITX_PLL_BC |
+                         RG_HDMITX_PLL_BR);
+       mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON3, RG_HDMITX_PRD_IMP_EN);
+       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON4,
+                         (0x3 << PRD_IBIAS_CLK_SHIFT) |
+                         (0x3 << PRD_IBIAS_D2_SHIFT) |
+                         (0x3 << PRD_IBIAS_D1_SHIFT) |
+                         (0x3 << PRD_IBIAS_D0_SHIFT),
+                         RG_HDMITX_PRD_IBIAS_CLK |
+                         RG_HDMITX_PRD_IBIAS_D2 |
+                         RG_HDMITX_PRD_IBIAS_D1 |
+                         RG_HDMITX_PRD_IBIAS_D0);
+       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3,
+                         (0x0 << DRV_IMP_EN_SHIFT), RG_HDMITX_DRV_IMP_EN);
+       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6,
+                         (hdmi_phy->drv_imp_clk << DRV_IMP_CLK_SHIFT) |
+                         (hdmi_phy->drv_imp_d2 << DRV_IMP_D2_SHIFT) |
+                         (hdmi_phy->drv_imp_d1 << DRV_IMP_D1_SHIFT) |
+                         (hdmi_phy->drv_imp_d0 << DRV_IMP_D0_SHIFT),
+                         RG_HDMITX_DRV_IMP_CLK | RG_HDMITX_DRV_IMP_D2 |
+                         RG_HDMITX_DRV_IMP_D1 | RG_HDMITX_DRV_IMP_D0);
+       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON5,
+                         (hdmi_phy->ibias << DRV_IBIAS_CLK_SHIFT) |
+                         (hdmi_phy->ibias << DRV_IBIAS_D2_SHIFT) |
+                         (hdmi_phy->ibias << DRV_IBIAS_D1_SHIFT) |
+                         (hdmi_phy->ibias << DRV_IBIAS_D0_SHIFT),
+                         RG_HDMITX_DRV_IBIAS_CLK | RG_HDMITX_DRV_IBIAS_D2 |
+                         RG_HDMITX_DRV_IBIAS_D1 | RG_HDMITX_DRV_IBIAS_D0);
+       return 0;
+}
+
+static long mtk_hdmi_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+                                   unsigned long *parent_rate)
+{
+       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+       hdmi_phy->pll_rate = rate;
+       if (rate <= 74250000)
+               *parent_rate = rate;
+       else
+               *parent_rate = rate / 2;
+
+       return rate;
+}
+
+static unsigned long mtk_hdmi_pll_recalc_rate(struct clk_hw *hw,
+                                             unsigned long parent_rate)
+{
+       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+       return hdmi_phy->pll_rate;
+}
+
+static const struct clk_ops mtk_hdmi_pll_ops = {
+       .prepare = mtk_hdmi_pll_prepare,
+       .unprepare = mtk_hdmi_pll_unprepare,
+       .set_rate = mtk_hdmi_pll_set_rate,
+       .round_rate = mtk_hdmi_pll_round_rate,
+       .recalc_rate = mtk_hdmi_pll_recalc_rate,
+};
+
+static void mtk_hdmi_phy_enable_tmds(struct mtk_hdmi_phy *hdmi_phy)
+{
+       mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON3,
+                             RG_HDMITX_SER_EN | RG_HDMITX_PRD_EN |
+                             RG_HDMITX_DRV_EN);
+       usleep_range(100, 150);
+}
+
+static void mtk_hdmi_phy_disable_tmds(struct mtk_hdmi_phy *hdmi_phy)
+{
+       mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON3,
+                               RG_HDMITX_DRV_EN | RG_HDMITX_PRD_EN |
+                               RG_HDMITX_SER_EN);
+}
+
+static int mtk_hdmi_phy_power_on(struct phy *phy)
+{
+       struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy);
+       int ret;
+
+       ret = clk_prepare_enable(hdmi_phy->pll);
+       if (ret < 0)
+               return ret;
+
+       mtk_hdmi_phy_enable_tmds(hdmi_phy);
+
+       return 0;
+}
+
+static int mtk_hdmi_phy_power_off(struct phy *phy)
+{
+       struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy);
+
+       mtk_hdmi_phy_disable_tmds(hdmi_phy);
+       clk_disable_unprepare(hdmi_phy->pll);
+
+       return 0;
+}
+
+static const struct phy_ops mtk_hdmi_phy_ops = {
+       .power_on = mtk_hdmi_phy_power_on,
+       .power_off = mtk_hdmi_phy_power_off,
+       .owner = THIS_MODULE,
+};
+
+static int mtk_hdmi_phy_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct mtk_hdmi_phy *hdmi_phy;
+       struct resource *mem;
+       struct clk *ref_clk;
+       const char *ref_clk_name;
+       struct clk_init_data clk_init = {
+               .ops = &mtk_hdmi_pll_ops,
+               .num_parents = 1,
+               .parent_names = (const char * const *)&ref_clk_name,
+               .flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE,
+       };
+       struct phy *phy;
+       struct phy_provider *phy_provider;
+       int ret;
+
+       hdmi_phy = devm_kzalloc(dev, sizeof(*hdmi_phy), GFP_KERNEL);
+       if (!hdmi_phy)
+               return -ENOMEM;
+
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       hdmi_phy->regs = devm_ioremap_resource(dev, mem);
+       if (IS_ERR(hdmi_phy->regs)) {
+               ret = PTR_ERR(hdmi_phy->regs);
+               dev_err(dev, "Failed to get memory resource: %d\n", ret);
+               return ret;
+       }
+
+       ref_clk = devm_clk_get(dev, "pll_ref");
+       if (IS_ERR(ref_clk)) {
+               ret = PTR_ERR(ref_clk);
+               dev_err(&pdev->dev, "Failed to get PLL reference clock: %d\n",
+                       ret);
+               return ret;
+       }
+       ref_clk_name = __clk_get_name(ref_clk);
+
+       ret = of_property_read_string(dev->of_node, "clock-output-names",
+                                     &clk_init.name);
+       if (ret < 0) {
+               dev_err(dev, "Failed to read clock-output-names: %d\n", ret);
+               return ret;
+       }
+
+       hdmi_phy->pll_hw.init = &clk_init;
+       hdmi_phy->pll = devm_clk_register(dev, &hdmi_phy->pll_hw);
+       if (IS_ERR(hdmi_phy->pll)) {
+               ret = PTR_ERR(hdmi_phy->pll);
+               dev_err(dev, "Failed to register PLL: %d\n", ret);
+               return ret;
+       }
+
+       ret = of_property_read_u32(dev->of_node, "mediatek,ibias",
+                                  &hdmi_phy->ibias);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "Failed to get ibias: %d\n", ret);
+               return ret;
+       }
+
+       ret = of_property_read_u32(dev->of_node, "mediatek,ibias_up",
+                                  &hdmi_phy->ibias_up);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "Failed to get ibias up: %d\n", ret);
+               return ret;
+       }
+
+       dev_info(dev, "Using default TX DRV impedance: 4.2k/36\n");
+       hdmi_phy->drv_imp_clk = 0x30;
+       hdmi_phy->drv_imp_d2 = 0x30;
+       hdmi_phy->drv_imp_d1 = 0x30;
+       hdmi_phy->drv_imp_d0 = 0x30;
+
+       phy = devm_phy_create(dev, NULL, &mtk_hdmi_phy_ops);
+       if (IS_ERR(phy)) {
+               dev_err(dev, "Failed to create HDMI PHY\n");
+               return PTR_ERR(phy);
+       }
+       phy_set_drvdata(phy, hdmi_phy);
+
+       phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+       if (IS_ERR(phy_provider))
+               return PTR_ERR(phy_provider);
+
+       hdmi_phy->dev = dev;
+       return of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
+                                  hdmi_phy->pll);
+}
+
+static int mtk_hdmi_phy_remove(struct platform_device *pdev)
+{
+       return 0;
+}
+
+static const struct of_device_id mtk_hdmi_phy_match[] = {
+       { .compatible = "mediatek,mt8173-hdmi-phy", },
+       {},
+};
+
+struct platform_driver mtk_hdmi_phy_driver = {
+       .probe = mtk_hdmi_phy_probe,
+       .remove = mtk_hdmi_phy_remove,
+       .driver = {
+               .name = "mediatek-hdmi-phy",
+               .of_match_table = mtk_hdmi_phy_match,
+       },
+};
+
+MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek MT8173 HDMI PHY Driver");
+MODULE_LICENSE("GPL v2");