[RFC v3 6/7] drm/mediatek: Add HDMI support

Philipp Zabel p.zabel at pengutronix.de
Wed Sep 30 08:30:05 PDT 2015


From: Daniel Kurtz <djkurtz at chromium.org>

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.

Signed-off-by: Jie Qiu <jie.qiu at mediatek.com>
Signed-off-by: Philipp Zabel <p.zabel at pengutronix.de>
---
 drivers/gpu/drm/mediatek/Kconfig            |   6 +
 drivers/gpu/drm/mediatek/Makefile           |   7 +
 drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c | 648 ++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi.c         | 521 ++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi.h         | 120 ++++
 drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c | 362 +++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi_hw.c      | 895 ++++++++++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi_hw.h      |  76 +++
 drivers/gpu/drm/mediatek/mtk_hdmi_phy.c     | 320 ++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi_phy.h     |  20 +
 drivers/gpu/drm/mediatek/mtk_hdmi_regs.h    | 354 +++++++++++
 include/drm/mediatek/mtk_hdmi_audio.h       | 150 +++++
 12 files changed, 3479 insertions(+)
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_hw.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_hw.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_phy.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_phy.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
 create mode 100644 include/drm/mediatek/mtk_hdmi_audio.h

diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig
index 5343cf1..85af51c 100644
--- a/drivers/gpu/drm/mediatek/Kconfig
+++ b/drivers/gpu/drm/mediatek/Kconfig
@@ -14,3 +14,9 @@ config DRM_MEDIATEK
 	  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 GENERIC_PHY
+	help
+	  DRM/KMS HDMI driver for Mediatek SoCs
diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile
index 93380fe..8c82c07 100644
--- a/drivers/gpu/drm/mediatek/Makefile
+++ b/drivers/gpu/drm/mediatek/Makefile
@@ -11,3 +11,10 @@ mediatek-drm-y := mtk_drm_drv.o \
 
 obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
 
+mediatek-drm-hdmi-objs := mtk_drm_hdmi_drv.o \
+			  mtk_hdmi_ddc_drv.o \
+			  mtk_hdmi.o \
+			  mtk_hdmi_hw.o \
+			  mtk_hdmi_phy.o
+
+obj-$(CONFIG_DRM_MEDIATEK_HDMI) += mediatek-drm-hdmi.o
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c
new file mode 100644
index 0000000..19febe5
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu at 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_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/of_platform.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_graph.h>
+#include <linux/of_irq.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include "mtk_hdmi.h"
+#include "mtk_hdmi_hw.h"
+#include "mtk_hdmi_phy.h"
+
+static const char * const mtk_hdmi_clk_names[MTK_HDMI_CLK_COUNT] = {
+	[MTK_HDMI_CLK_CEC] = "cec",
+	[MTK_HDMI_CLK_HDMI_SEL] = "hdmi_sel",
+	[MTK_HDMI_CLK_HDMI_DIV1] = "hdmi_div1",
+	[MTK_HDMI_CLK_HDMI_PIXEL] = "hdmi_pixel",
+	[MTK_HDMI_CLK_HDMI_PLL] = "hdmi_pll",
+	[MTK_HDMI_CLK_AUD_BCLK] = "aud_bclk",
+	[MTK_HDMI_CLK_AUD_SPDIF] = "aud_spdif",
+};
+
+static const enum mtk_hdmi_clk_id mtk_hdmi_enable_clocks[] = {
+	MTK_HDMI_CLK_CEC,
+	MTK_HDMI_CLK_HDMI_PIXEL,
+	MTK_HDMI_CLK_HDMI_PLL,
+	MTK_HDMI_CLK_AUD_BCLK,
+	MTK_HDMI_CLK_AUD_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_all_enable(struct mtk_hdmi *hdmi)
+{
+	int i;
+	int ret;
+
+	for (i = 0; i < ARRAY_SIZE(mtk_hdmi_enable_clocks); i++) {
+		ret = clk_prepare_enable(hdmi->clk[mtk_hdmi_enable_clocks[i]]);
+		if (ret) {
+			dev_err(hdmi->dev, "enable clk %s failed!\n",
+				mtk_hdmi_clk_names[mtk_hdmi_enable_clocks[i]]);
+			goto err;
+		}
+	}
+
+	return 0;
+
+err:
+	while (--i >= 0)
+		clk_disable_unprepare(hdmi->clk[mtk_hdmi_enable_clocks[i]]);
+
+	return ret;
+}
+
+static void mtk_hdmi_clk_all_disable(struct mtk_hdmi *hdmi)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(mtk_hdmi_enable_clocks); i++)
+		clk_disable_unprepare(hdmi->clk[mtk_hdmi_enable_clocks[i]]);
+}
+
+static enum drm_connector_status hdmi_conn_detect(struct drm_connector *conn,
+						  bool force)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
+
+	return hdmi->hpd ?
+	       connector_status_connected : connector_status_disconnected;
+}
+
+static void hdmi_conn_destroy(struct drm_connector *conn)
+{
+	drm_connector_unregister(conn);
+	drm_connector_cleanup(conn);
+}
+
+static int hdmi_conn_set_property(struct drm_connector *conn,
+				  struct drm_property *property, uint64_t val)
+{
+	return 0;
+}
+
+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_hdmi_monitor(edid);
+
+	drm_mode_connector_update_edid_property(conn, edid);
+
+	ret = drm_add_edid_modes(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_info(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 >= hdmi->min_clock &&
+	    mode->clock <= hdmi->max_clock &&
+	    mode->hdisplay <= 0x1fff &&
+	    mode->vdisplay <= 0x1fff)
+		return MODE_OK;
+
+	return MODE_BAD;
+}
+
+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,
+	.set_property = hdmi_conn_set_property,
+	.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,
+};
+
+/*
+ * Bridge callbacks
+ */
+
+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_connector_register(&hdmi->conn);
+	if (ret) {
+		dev_err(hdmi->dev, "Failed to register connector: %d\n", ret);
+		return ret;
+	}
+
+	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;
+		}
+	}
+
+	return 0;
+}
+
+bool mtk_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
+				const struct drm_display_mode *mode,
+				struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+
+void mtk_hdmi_bridge_disable(struct drm_bridge *bridge)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+	phy_power_off(hdmi->phy);
+}
+
+void mtk_hdmi_bridge_post_disable(struct drm_bridge *bridge)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+	mtk_hdmi_power_off(hdmi);
+}
+
+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_info(hdmi->dev, "cur info: name:%s, hdisplay:%d\n",
+		 adjusted_mode->name, adjusted_mode->hdisplay);
+	dev_info(hdmi->dev, "hsync_start:%d,hsync_end:%d, htotal:%d",
+		 adjusted_mode->hsync_start, adjusted_mode->hsync_end,
+		 adjusted_mode->htotal);
+	dev_info(hdmi->dev, "hskew:%d, vdisplay:%d\n",
+		 adjusted_mode->hskew, adjusted_mode->vdisplay);
+	dev_info(hdmi->dev, "vsync_start:%d, vsync_end:%d, vtotal:%d",
+		 adjusted_mode->vsync_start, adjusted_mode->vsync_end,
+		 adjusted_mode->vtotal);
+	dev_info(hdmi->dev, "vscan:%d, flag:%d\n",
+		 adjusted_mode->vscan, adjusted_mode->flags);
+
+	drm_mode_copy(&hdmi->mode, adjusted_mode);
+	hdmi->mode_changed = true;
+}
+
+void mtk_hdmi_bridge_pre_enable(struct drm_bridge *bridge)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+	mtk_hdmi_power_on(hdmi);
+}
+
+void mtk_hdmi_bridge_enable(struct drm_bridge *bridge)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+	if (hdmi->mode_changed) {
+		mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode);
+		hdmi->mode_changed = false;
+	}
+	phy_power_on(hdmi->phy);
+}
+
+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 void __iomem *mtk_hdmi_resource_ioremap(struct platform_device *pdev,
+					       const char *name)
+{
+	struct resource *mem;
+
+	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+	if (mem)
+		dev_info(&pdev->dev,
+			 "name: %s, physical adr: 0x%llx, end: 0x%llx\n",
+			 name, mem->start, mem->end);
+
+	return devm_ioremap_resource(&pdev->dev, mem);
+}
+
+static int mtk_hdmi_dt_parse_pdata(struct mtk_hdmi *hdmi,
+				   struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *i2c_np = NULL;
+	int ret;
+
+	hdmi->flt_n_5v_gpio = of_get_named_gpio(np, "flt_n_5v-gpios", 0);
+	if (hdmi->flt_n_5v_gpio < 0) {
+		dev_err(&pdev->dev, "hdmi->flt_n_5v_gpio = %d\n",
+			hdmi->flt_n_5v_gpio);
+		goto err;
+	}
+
+	ret = mtk_hdmi_get_all_clk(hdmi, np);
+	if (ret) {
+		dev_err(&pdev->dev, "get clk from dt failed!\n");
+		goto err;
+	}
+
+	hdmi->sys_regs = mtk_hdmi_resource_ioremap(pdev, "sys");
+	if (IS_ERR(hdmi->sys_regs)) {
+		dev_err(&pdev->dev, "Failed to ioremap mmsys config: %ld\n",
+			PTR_ERR(hdmi->sys_regs));
+		goto err;
+	}
+
+	hdmi->grl_regs = mtk_hdmi_resource_ioremap(pdev, "grl");
+	if (IS_ERR(hdmi->grl_regs)) {
+		dev_err(&pdev->dev, "Failed to ioremap hdmi_shell: %ld\n",
+			PTR_ERR(hdmi->grl_regs));
+		goto err;
+	}
+
+	hdmi->cec_regs = mtk_hdmi_resource_ioremap(pdev, "cec");
+	if (IS_ERR(hdmi->cec_regs)) {
+		dev_err(&pdev->dev, "Failed to ioremap cec: %ld\n",
+			PTR_ERR(hdmi->grl_regs));
+		goto err;
+	}
+
+	hdmi->irq = irq_of_parse_and_map(np, 0);
+	if (hdmi->irq < 0) {
+		dev_err(&pdev->dev, "get irq failed, irq = %d !\n", hdmi->irq);
+		goto err;
+	}
+
+	i2c_np = of_parse_phandle(np, "ddc-i2c-bus", 0);
+	if (!i2c_np) {
+		dev_err(&pdev->dev, "find i2c node failed!\n");
+		goto err;
+	}
+
+	hdmi->ddc_adpt = of_find_i2c_adapter_by_node(i2c_np);
+	if (!hdmi->ddc_adpt) {
+		dev_err(&pdev->dev, "Failed to get ddc i2c adapter by node\n");
+		goto err;
+	}
+
+	ret = of_property_read_u32(np, "min_clock", &hdmi->min_clock);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "fail to get min clock\n");
+		goto err;
+	}
+
+	ret = of_property_read_u32(np, "max_clock", &hdmi->max_clock);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "fail to get max clock\n");
+		goto err;
+	}
+
+	return 0;
+
+err:
+	return -ENXIO;
+}
+
+static irqreturn_t hdmi_flt_n_5v_irq_thread(int irq, void *arg)
+{
+	struct mtk_hdmi *hdmi = arg;
+
+	dev_err(hdmi->dev, "detected 5v pin error status\n");
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t hdmi_htplg_isr_thread(int irq, void *arg)
+{
+	struct mtk_hdmi *hdmi = arg;
+	bool hpd;
+
+	mtk_hdmi_htplg_irq_clr(hdmi);
+	hpd = mtk_hdmi_hpd_high(hdmi);
+
+	if (hdmi->hpd != hpd) {
+		dev_err(hdmi->dev, "hotplug event!,cur hpd = %d, hpd = %d\n",
+			hdmi->hpd, hpd);
+		hdmi->hpd = hpd;
+		if (hdmi->bridge.encoder && hdmi->bridge.encoder->dev)
+			drm_helper_hpd_irq_event(hdmi->bridge.encoder->dev);
+	}
+	return IRQ_HANDLED;
+}
+
+static int mtk_drm_hdmi_probe(struct platform_device *pdev)
+{
+	struct mtk_hdmi *hdmi;
+	struct device *dev = &pdev->dev;
+	int ret;
+	struct device_node *ep, *bridge_node;
+	struct mtk_hdmi_audio_data audio_data;
+	struct platform_device_info audio_pdev_info;
+
+	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) {
+		dev_err(dev, "mtk_hdmi_dt_parse_pdata failed!!\n");
+		return ret;
+	}
+
+	ep = of_graph_get_next_endpoint(dev->of_node, NULL);
+	if (ep) {
+		ep = of_graph_get_next_endpoint(dev->of_node, ep);
+		bridge_node = of_graph_get_remote_port_parent(ep);
+		of_node_put(ep);
+		if (bridge_node) {
+			dev_info(dev, "Found external bridge node %s\n",
+				 bridge_node->full_name);
+			hdmi->bridge.next = of_drm_find_bridge(bridge_node);
+			if (!hdmi->bridge.next) {
+				dev_err(dev, "Waiting for external bridge\n");
+				return -EPROBE_DEFER;
+			}
+		}
+	}
+
+	hdmi->flt_n_5v_irq = gpio_to_irq(hdmi->flt_n_5v_gpio);
+	if (hdmi->flt_n_5v_irq < 0) {
+		dev_err(dev, "hdmi->flt_n_5v_irq = %d\n",
+			hdmi->flt_n_5v_irq);
+		return hdmi->flt_n_5v_irq;
+	}
+
+	ret = devm_request_threaded_irq(dev, hdmi->flt_n_5v_irq,
+					NULL, hdmi_flt_n_5v_irq_thread,
+					IRQF_TRIGGER_RISING |
+					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+					"hdmi flt_n_5v", hdmi);
+	if (ret) {
+		dev_err(dev, "Failed to register hdmi flt_n_5v interrupt\n");
+		return ret;
+	}
+
+	ret = devm_request_threaded_irq(dev, hdmi->irq,
+					NULL, hdmi_htplg_isr_thread,
+					IRQF_SHARED | IRQF_TRIGGER_LOW |
+					IRQF_ONESHOT,
+					"hdmi hpd", hdmi);
+	if (ret) {
+		dev_err(dev, "Failed to register hdmi hpd interrupt\n");
+		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;
+	}
+
+	audio_data.irq = hdmi->irq;
+	audio_data.mtk_hdmi = hdmi;
+	audio_data.enable = mtk_hdmi_audio_enable;
+	audio_data.disable = mtk_hdmi_audio_disable;
+	audio_data.set_audio_param = mtk_hdmi_audio_set_param;
+	audio_data.hpd_detect = mtk_hdmi_hpd_high;
+	audio_data.detect_dvi_monitor = mtk_hdmi_detect_dvi_monitor;
+
+	audio_pdev_info.parent = dev;
+	audio_pdev_info.id = PLATFORM_DEVID_NONE;
+	audio_pdev_info.name = "mtk-hdmi-codec";
+	audio_pdev_info.dma_mask = DMA_BIT_MASK(32);
+	audio_pdev_info.data = &audio_data;
+	audio_pdev_info.size_data = sizeof(audio_data);
+	hdmi->audio_pdev = platform_device_register_full(&audio_pdev_info);
+
+	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_all_enable(hdmi);
+	if (ret) {
+		dev_err(dev, "enable all clk failed!\n");
+		goto err_bridge_remove;
+	}
+
+	dev_info(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);
+	platform_device_unregister(hdmi->audio_pdev);
+	platform_set_drvdata(pdev, NULL);
+	mtk_hdmi_clk_all_disable(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_power_off(hdmi);
+	mtk_hdmi_clk_all_disable(hdmi);
+	dev_info(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_all_enable(hdmi);
+	if (ret) {
+		dev_err(dev, "hdmi resume failed!\n");
+		return ret;
+	}
+
+	mtk_hdmi_power_on(hdmi);
+	mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode);
+	hdmi->mode_changed = false;
+	dev_info(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", },
+	{}
+};
+
+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 int __init mtk_hdmitx_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&mtk_hdmi_phy_driver);
+	if (ret < 0) {
+		pr_err("register hdmiddc platform driver failed!");
+		goto err;
+	}
+
+	ret = platform_driver_register(&mtk_hdmi_ddc_driver);
+	if (ret < 0) {
+		pr_err("register hdmiddc platform driver failed!");
+		goto phy_err;
+	}
+
+	ret = platform_driver_register(&mtk_hdmi_driver);
+	if (ret < 0) {
+		pr_err("register hdmitx platform driver failed!");
+		goto ddc_err;
+	}
+
+	return 0;
+
+ddc_err:
+	platform_driver_unregister(&mtk_hdmi_ddc_driver);
+phy_err:
+	platform_driver_unregister(&mtk_hdmi_phy_driver);
+err:
+	return ret;
+}
+
+static void __exit mtk_hdmitx_exit(void)
+{
+	platform_driver_unregister(&mtk_hdmi_driver);
+	platform_driver_unregister(&mtk_hdmi_ddc_driver);
+	platform_driver_unregister(&mtk_hdmi_phy_driver);
+}
+
+module_init(mtk_hdmitx_init);
+module_exit(mtk_hdmitx_exit);
+
+MODULE_AUTHOR("Jie Qiu <jie.qiu at mediatek.com>");
+MODULE_DESCRIPTION("MediaTek HDMI Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c
new file mode 100644
index 0000000..99c2401
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu at 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/drm_edid.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/phy/phy.h>
+#include "mtk_hdmi.h"
+#include "mtk_hdmi_hw.h"
+#include "mtk_hdmi_phy.h"
+
+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,
+				      enum hdmi_display_color_depth depth)
+{
+	unsigned long rate;
+	int ret;
+
+	ret = clk_set_parent(hdmi->clk[MTK_HDMI_CLK_HDMI_SEL],
+			     hdmi->clk[MTK_HDMI_CLK_HDMI_DIV1]);
+	if (ret) {
+		dev_err(hdmi->dev, "Failed to set HDMI PLL divider: %d\n", ret);
+		return ret;
+	}
+
+	/* The DPI driver already should have set TVDPLL to the correct rate */
+	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, "Expected PLL rate: %u kHz, got: %lu kHz",
+			 DIV_ROUND_CLOSEST(clock, 1000),
+			 DIV_ROUND_CLOSEST(rate, 1000));
+	}
+
+	mtk_hdmi_phy_set_pll(hdmi->phy, clock, depth);
+	mtk_hdmi_hw_config_sys(hdmi);
+	mtk_hdmi_hw_set_deep_color_mode(hdmi, depth);
+	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)
+{
+	u8 chan_count;
+
+	mtk_hdmi_hw_aud_set_channel_swap(hdmi, HDMI_AUD_SWAP_LFE_CC);
+	mtk_hdmi_hw_aud_raw_data_enable(hdmi, true);
+
+	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);
+
+	mtk_hdmi_hw_aud_set_high_bitrate(hdmi, false);
+	mtk_hdmi_phy_aud_dst_normal_double_enable(hdmi, false);
+	mtk_hdmi_hw_aud_dst_enable(hdmi, false);
+
+	if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_SPDIF) {
+		mtk_hdmi_hw_aud_dsd_enable(hdmi, false);
+		if (hdmi->aud_param.aud_codec ==
+			HDMI_AUDIO_CODING_TYPE_DST) {
+			mtk_hdmi_phy_aud_dst_normal_double_enable(hdmi,
+								  true);
+			mtk_hdmi_hw_aud_dst_enable(hdmi, true);
+		}
+
+		chan_count = mtk_hdmi_aud_get_chnl_count
+						 (HDMI_AUD_CHAN_TYPE_2_0);
+		mtk_hdmi_hw_aud_set_i2s_chan_num(hdmi,
+						 HDMI_AUD_CHAN_TYPE_2_0,
+						 chan_count);
+		mtk_hdmi_hw_aud_set_input_type(hdmi,
+					       HDMI_AUD_INPUT_SPDIF);
+	} else {
+		mtk_hdmi_hw_aud_dsd_enable(hdmi, false);
+		chan_count =
+			mtk_hdmi_aud_get_chnl_count(
+			hdmi->aud_param.aud_input_chan_type);
+		mtk_hdmi_hw_aud_set_i2s_chan_num(
+			hdmi,
+			hdmi->aud_param.aud_input_chan_type,
+			chan_count);
+		mtk_hdmi_hw_aud_set_input_type(hdmi,
+					       HDMI_AUD_INPUT_I2S);
+	}
+	return 0;
+}
+
+static int mtk_hdmi_aud_set_src(struct mtk_hdmi *hdmi,
+				struct drm_display_mode *display_mode)
+{
+	mtk_hdmi_aud_on_off_hw_ncts(hdmi, false);
+
+	if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_I2S) {
+		switch (hdmi->aud_param.aud_hdmi_fs) {
+		case HDMI_AUDIO_SAMPLE_FREQUENCY_32000:
+		case HDMI_AUDIO_SAMPLE_FREQUENCY_44100:
+		case HDMI_AUDIO_SAMPLE_FREQUENCY_48000:
+		case HDMI_AUDIO_SAMPLE_FREQUENCY_88200:
+		case HDMI_AUDIO_SAMPLE_FREQUENCY_96000:
+			mtk_hdmi_hw_aud_src_off(hdmi);
+			/* mtk_hdmi_hw_aud_src_enable(hdmi, false); */
+			mtk_hdmi_hw_aud_set_mclk(
+			hdmi,
+			hdmi->aud_param.aud_mclk);
+			mtk_hdmi_hw_aud_aclk_inv_enable(hdmi, false);
+			break;
+		default:
+			break;
+		}
+	} else {
+		switch (hdmi->aud_param.iec_frame_fs) {
+		case HDMI_IEC_32K:
+			hdmi->aud_param.aud_hdmi_fs =
+			    HDMI_AUDIO_SAMPLE_FREQUENCY_32000;
+			mtk_hdmi_hw_aud_src_off(hdmi);
+			mtk_hdmi_hw_aud_set_mclk(hdmi,
+						 HDMI_AUD_MCLK_128FS);
+			mtk_hdmi_hw_aud_aclk_inv_enable(hdmi, false);
+			break;
+		case HDMI_IEC_48K:
+			hdmi->aud_param.aud_hdmi_fs =
+			    HDMI_AUDIO_SAMPLE_FREQUENCY_48000;
+			mtk_hdmi_hw_aud_src_off(hdmi);
+			mtk_hdmi_hw_aud_set_mclk(hdmi,
+						 HDMI_AUD_MCLK_128FS);
+			mtk_hdmi_hw_aud_aclk_inv_enable(hdmi, false);
+			break;
+		case HDMI_IEC_44K:
+			hdmi->aud_param.aud_hdmi_fs =
+			    HDMI_AUDIO_SAMPLE_FREQUENCY_44100;
+			mtk_hdmi_hw_aud_src_off(hdmi);
+			mtk_hdmi_hw_aud_set_mclk(hdmi,
+						 HDMI_AUD_MCLK_128FS);
+			mtk_hdmi_hw_aud_aclk_inv_enable(hdmi, false);
+			break;
+		default:
+			break;
+		}
+	}
+	mtk_hdmi_hw_aud_set_ncts(hdmi, hdmi->depth, hdmi->aud_param.aud_hdmi_fs,
+				 display_mode->clock);
+
+	mtk_hdmi_hw_aud_src_reenable(hdmi);
+	return 0;
+}
+
+static int mtk_hdmi_aud_set_chnl_status(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_hw_aud_set_channel_status(
+		hdmi,
+	   hdmi->aud_param.hdmi_l_channel_state,
+	   hdmi->aud_param.hdmi_r_channel_state,
+	   hdmi->aud_param.aud_hdmi_fs);
+	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, true);
+	mtk_hdmi_aud_enable_packet(hdmi, false);
+
+	mtk_hdmi_aud_set_input(hdmi);
+	mtk_hdmi_aud_set_src(hdmi, display_mode);
+	mtk_hdmi_aud_set_chnl_status(hdmi);
+
+	usleep_range(50, 100);
+
+	mtk_hdmi_aud_on_off_hw_ncts(hdmi, true);
+	mtk_hdmi_aud_enable_packet(hdmi, true);
+	mtk_hdmi_hw_aud_mute(hdmi, false);
+	return 0;
+}
+
+static int mtk_hdmi_setup_av_mute_packet(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_hw_send_av_mute(hdmi);
+	return 0;
+}
+
+static int mtk_hdmi_setup_av_unmute_packet(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_hw_send_av_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: %ld\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: %ld\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 %ld\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: %ld\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, "Faied to setup audio infoframe: %ld\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: %ld\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: %ld\n", err);
+		return err;
+	}
+
+	err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer));
+	if (err) {
+		dev_err(hdmi->dev, "Failed to pack vendor infoframe: %ld\n",
+			err);
+		return err;
+	}
+
+	mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
+	return 0;
+}
+
+int mtk_hdmi_hpd_high(struct mtk_hdmi *hdmi)
+{
+	return mtk_hdmi_hw_is_hpd_high(hdmi);
+}
+
+void mtk_hdmi_htplg_irq_clr(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_hw_clear_htplg_irq(hdmi);
+}
+
+int mtk_hdmi_output_init(struct mtk_hdmi *hdmi)
+{
+	struct hdmi_audio_param *aud_param = &hdmi->aud_param;
+
+	if (hdmi->init)
+		return -EINVAL;
+
+	hdmi->csp = HDMI_COLORSPACE_RGB;
+	hdmi->depth = HDMI_DEEP_COLOR_24BITS;
+	hdmi->output = true;
+	aud_param->aud_codec = HDMI_AUDIO_CODING_TYPE_PCM;
+	aud_param->aud_hdmi_fs = HDMI_AUDIO_SAMPLE_FREQUENCY_48000;
+	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->iec_frame_fs = HDMI_IEC_48K;
+	aud_param->aud_input_chan_type = HDMI_AUD_CHAN_TYPE_2_0;
+	aud_param->hdmi_l_channel_state[2] = 2;
+	aud_param->hdmi_r_channel_state[2] = 2;
+	hdmi->init = true;
+
+	return 0;
+}
+
+void mtk_hdmi_power_on(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_hw_make_reg_writable(hdmi, true);
+	mtk_hdmi_hw_1p4_version_enable(hdmi, true);
+	mtk_hdmi_hw_htplg_irq_enable(hdmi);
+}
+
+void mtk_hdmi_power_off(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_hw_make_reg_writable(hdmi, false);
+	mtk_hdmi_hw_1p4_version_enable(hdmi, true);
+	mtk_hdmi_hw_htplg_irq_disable(hdmi);
+}
+
+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_info(hdmi->dev, "codec:%d, input:%d, channel:%d, fs:%d\n",
+		 param->aud_codec, param->aud_input_type,
+		 param->aud_input_chan_type, param->aud_hdmi_fs);
+	memcpy(&hdmi->aud_param, param, sizeof(*param));
+	return mtk_hdmi_aud_output_config(hdmi, &hdmi->mode);
+}
+
+int mtk_hdmi_detect_dvi_monitor(struct mtk_hdmi *hdmi)
+{
+	return hdmi->dvi_mode;
+}
+
+int mtk_hdmi_output_set_display_mode(struct mtk_hdmi *hdmi,
+				     struct drm_display_mode *mode)
+{
+	int ret;
+
+	if (!hdmi->init) {
+		dev_err(hdmi->dev, "doesn't init hdmi control context!\n");
+		return -EINVAL;
+	}
+
+	mtk_hdmi_hw_vid_black(hdmi, true);
+	mtk_hdmi_hw_aud_mute(hdmi, true);
+	mtk_hdmi_setup_av_mute_packet(hdmi);
+	phy_power_off(hdmi->phy);
+
+	ret = mtk_hdmi_video_change_vpll(hdmi,
+					 mode->clock * 1000,
+					 hdmi->depth);
+	if (ret) {
+		dev_err(hdmi->dev, "set vpll failed!\n");
+		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", "chromebook");
+	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_mute(hdmi, false);
+	mtk_hdmi_setup_av_unmute_packet(hdmi);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.h b/drivers/gpu/drm/mediatek/mtk_hdmi.h
new file mode 100644
index 0000000..798d0c1
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu at 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
+
+#include <drm/drm_crtc.h>
+#include <drm/mediatek/mtk_hdmi_audio.h>
+#include <linux/clk.h>
+#include <linux/hdmi.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+enum mtk_hdmi_clk_id {
+	MTK_HDMI_CLK_CEC,
+	MTK_HDMI_CLK_HDMI_SEL,
+	MTK_HDMI_CLK_HDMI_DIV1,
+	MTK_HDMI_CLK_HDMI_PIXEL,
+	MTK_HDMI_CLK_HDMI_PLL,
+	MTK_HDMI_CLK_DPI_PIXEL,
+	MTK_HDMI_CLK_DPI_ENGINE,
+	MTK_HDMI_CLK_AUD_BCLK,
+	MTK_HDMI_CLK_AUD_SPDIF,
+	MTK_HDMI_CLK_COUNT
+};
+
+enum hdmi_display_color_depth {
+	HDMI_DEEP_COLOR_24BITS,
+	HDMI_DEEP_COLOR_30BITS,
+	HDMI_DEEP_COLOR_36BITS,
+	HDMI_DEEP_COLOR_48BITS,
+};
+
+struct mtk_hdmi {
+	struct drm_bridge bridge;
+	struct drm_connector conn;
+	struct device *dev;
+	struct phy *phy;
+	struct i2c_adapter *ddc_adpt;
+	struct clk *clk[MTK_HDMI_CLK_COUNT];
+#if defined(CONFIG_DEBUG_FS)
+	struct dentry *debugfs;
+#endif
+	struct platform_device *audio_pdev;
+	struct drm_display_mode mode;
+	bool mode_changed;
+	bool dvi_mode;
+	int flt_n_5v_gpio;
+	int flt_n_5v_irq;
+	bool hpd;
+	int irq;
+	u32 min_clock;
+	u32 max_clock;
+	u32 max_hdisplay;
+	u32 max_vdisplay;
+	u32 ibias;
+	u32 ibias_up;
+	void __iomem *sys_regs;
+	void __iomem *grl_regs;
+	void __iomem *pll_regs;
+	void __iomem *cec_regs;
+	bool init;
+	enum hdmi_display_color_depth depth;
+	enum hdmi_colorspace csp;
+	bool audio_enable;
+	bool output;
+	struct hdmi_audio_param aud_param;
+};
+
+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);
+}
+
+int mtk_hdmi_output_init(struct mtk_hdmi *hdmi);
+int mtk_hdmi_hpd_high(struct mtk_hdmi *hdmi);
+void mtk_hdmi_htplg_irq_clr(struct mtk_hdmi *hdmi);
+int mtk_hdmi_output_set_display_mode(struct mtk_hdmi *hdmi,
+				     struct drm_display_mode *mode);
+void mtk_hdmi_power_on(struct mtk_hdmi *hdmi);
+void mtk_hdmi_power_off(struct mtk_hdmi *hdmi);
+void mtk_hdmi_audio_enable(struct mtk_hdmi *hctx);
+void mtk_hdmi_audio_disable(struct mtk_hdmi *hctx);
+int mtk_hdmi_audio_set_param(struct mtk_hdmi *hctx,
+			     struct hdmi_audio_param *param);
+int mtk_hdmi_detect_dvi_monitor(struct mtk_hdmi *hctx);
+#if defined(CONFIG_DEBUG_FS)
+int mtk_drm_hdmi_debugfs_init(struct mtk_hdmi *hdmi);
+void mtk_drm_hdmi_debugfs_exit(struct mtk_hdmi *hdmi);
+#else
+int mtk_drm_hdmi_debugfs_init(struct mtk_hdmi *hdmi)
+{
+	return 0;
+}
+
+void mtk_drm_hdmi_debugfs_exit(struct mtk_hdmi *hdmi)
+{
+}
+#endif /* CONFIG_DEBUG_FS */
+
+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_drv.c b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c
new file mode 100644
index 0000000..22e5487
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu at 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/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_i2c {
+	struct i2c_adapter adap;
+	struct clk *clk;
+	void __iomem *regs;
+};
+
+static inline void sif_set_bit(struct mtk_hdmi_i2c *i2c, unsigned int offset,
+			       unsigned int val)
+{
+	writel(readl(i2c->regs + offset) | val, i2c->regs + offset);
+}
+
+static inline void sif_clr_bit(struct mtk_hdmi_i2c *i2c, unsigned int offset,
+			       unsigned int val)
+{
+	writel(readl(i2c->regs + offset) & ~val, i2c->regs + offset);
+}
+
+static inline bool sif_bit_is_set(struct mtk_hdmi_i2c *i2c, unsigned int offset,
+				  unsigned int val)
+{
+	return (readl(i2c->regs + offset) & val) == val;
+}
+
+static inline void sif_write_mask(struct mtk_hdmi_i2c *i2c, unsigned int offset,
+				  unsigned int mask, unsigned int shift,
+				  unsigned int val)
+{
+	unsigned int tmp;
+
+	tmp = readl(i2c->regs + offset);
+	tmp &= ~mask;
+	tmp |= (val << shift) & mask;
+	writel(tmp, i2c->regs + offset);
+}
+
+static inline unsigned int sif_read_mask(struct mtk_hdmi_i2c *i2c,
+					 unsigned int offset, unsigned int mask,
+					 unsigned int shift)
+{
+	return (readl(i2c->regs + offset) & mask) >> shift;
+}
+
+static void ddcm_trigger_mode(struct mtk_hdmi_i2c *i2c, int mode)
+{
+	int timeout = 20 * 1000;
+
+	sif_write_mask(i2c, DDC_DDCMCTL1, DDCM_SIF_MODE_MASK,
+		       DDCM_SIF_MODE_OFFSET, mode);
+	sif_set_bit(i2c, DDC_DDCMCTL1, DDCM_TRI);
+	while (sif_bit_is_set(i2c, DDC_DDCMCTL1, DDCM_TRI)) {
+		timeout -= 2;
+		udelay(2);
+		if (timeout <= 0)
+			break;
+	}
+}
+
+static int mtk_hdmi_i2c_read_msg(struct mtk_hdmi_i2c *i2c, struct i2c_msg *msg)
+{
+	struct device *dev = i2c->adap.dev.parent;
+	u32 remain_count, ack_count, ack_final, read_count, temp_count;
+	u32 index = 0;
+	u32 ack;
+	int i;
+
+	ddcm_trigger_mode(i2c, DDCM_START);
+	sif_write_mask(i2c, DDC_DDCMD0, 0xff, 0, (msg->addr << 1) | 0x01);
+	sif_write_mask(i2c, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET,
+		       0x00);
+	ddcm_trigger_mode(i2c, DDCM_WRITE_DATA);
+	ack = sif_read_mask(i2c, 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(i2c, DDC_DDCMCTL1, DDCM_PGLEN_MASK,
+			       DDCM_PGLEN_OFFSET, read_count - 1);
+		ddcm_trigger_mode(i2c, (ack_final == 1) ?
+				  DDCM_READ_DATA_NO_ACK :
+				  DDCM_READ_DATA_ACK);
+
+		ack = sif_read_mask(i2c, 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(i2c, offset,
+								0xff << shift,
+								shift);
+		}
+
+		remain_count -= read_count;
+		index += read_count;
+	}
+
+	return 0;
+}
+
+static int mtk_hdmi_i2c_write_msg(struct mtk_hdmi_i2c *i2c, struct i2c_msg *msg)
+{
+	struct device *dev = i2c->adap.dev.parent;
+	u32 ack;
+
+	ddcm_trigger_mode(i2c, DDCM_START);
+	sif_write_mask(i2c, DDC_DDCMD0, DDCM_DATA0, 0, msg->addr << 1);
+	sif_write_mask(i2c, DDC_DDCMD0, DDCM_DATA1, 8, msg->buf[0]);
+	sif_write_mask(i2c, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET,
+		       0x1);
+	ddcm_trigger_mode(i2c, DDCM_WRITE_DATA);
+
+	ack = sif_read_mask(i2c, 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_i2c_xfer(struct i2c_adapter *adapter,
+			     struct i2c_msg *msgs, int num)
+{
+	struct mtk_hdmi_i2c *i2c = adapter->algo_data;
+	struct device *dev = adapter->dev.parent;
+	int ret;
+	int i;
+
+	if (!i2c) {
+		dev_err(dev, "invalid arguments\n");
+		return -EINVAL;
+	}
+
+	sif_set_bit(i2c, DDC_DDCMCTL0, DDCM_SCL_STRECH);
+	sif_set_bit(i2c, DDC_DDCMCTL0, DDCM_SM0EN);
+	sif_clr_bit(i2c, DDC_DDCMCTL0, DDCM_ODRAIN);
+
+	if (sif_bit_is_set(i2c, DDC_DDCMCTL1, DDCM_TRI)) {
+		dev_err(dev, "ddc line is busy!\n");
+		return -EBUSY;
+	}
+
+	sif_write_mask(i2c, 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_i2c_read_msg(i2c, msg);
+		else
+			ret = mtk_hdmi_i2c_write_msg(i2c, msg);
+		if (ret < 0)
+			goto xfer_end;
+	}
+
+	ddcm_trigger_mode(i2c, DDCM_STOP);
+
+	return i;
+
+xfer_end:
+	ddcm_trigger_mode(i2c, DDCM_STOP);
+	dev_err(dev, "ddc failed!\n");
+	return ret;
+}
+
+static u32 mtk_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm mtk_hdmi_i2c_algorithm = {
+	.master_xfer = mtk_hdmi_i2c_xfer,
+	.functionality = mtk_hdmi_i2c_func,
+};
+
+static int mtk_hdmi_i2c_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_hdmi_i2c *i2c;
+	struct resource *mem;
+	int ret;
+
+	i2c = devm_kzalloc(dev, sizeof(struct mtk_hdmi_i2c), GFP_KERNEL);
+	if (!i2c)
+		return -ENOMEM;
+
+	i2c->clk = devm_clk_get(dev, "ddc-i2c");
+	if (IS_ERR(i2c->clk)) {
+		dev_err(dev, "get ddc_clk failed : %p ,\n", i2c->clk);
+		return PTR_ERR(i2c->clk);
+	}
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(i2c->regs)) {
+		dev_err(dev, "get memory source fail!\n");
+		return PTR_ERR(i2c->regs);
+	}
+
+	ret = clk_prepare_enable(i2c->clk);
+	if (ret) {
+		dev_err(dev, "enable ddc clk failed!\n");
+		return ret;
+	}
+
+	strlcpy(i2c->adap.name, "mediatek-hdmi-i2c", sizeof(i2c->adap.name));
+	i2c->adap.owner = THIS_MODULE;
+	i2c->adap.algo = &mtk_hdmi_i2c_algorithm;
+	i2c->adap.retries = 3;
+	i2c->adap.dev.of_node = dev->of_node;
+	i2c->adap.algo_data = i2c;
+	i2c->adap.dev.parent = &pdev->dev;
+
+	ret = i2c_add_adapter(&i2c->adap);
+	if (ret < 0) {
+		dev_err(dev, "failed to add bus to i2c core\n");
+		goto err_clk_disable;
+	}
+
+	platform_set_drvdata(pdev, i2c);
+
+	dev_dbg(dev, "i2c->adap: %p\n", &i2c->adap);
+	dev_dbg(dev, "i2c->clk: %p\n", i2c->clk);
+	dev_dbg(dev, "physical adr: 0x%llx, end: 0x%llx\n", mem->start,
+		mem->end);
+
+	return 0;
+
+err_clk_disable:
+	clk_disable_unprepare(i2c->clk);
+	return ret;
+}
+
+static int mtk_hdmi_i2c_remove(struct platform_device *pdev)
+{
+	struct mtk_hdmi_i2c *i2c = platform_get_drvdata(pdev);
+
+	clk_disable_unprepare(i2c->clk);
+	i2c_del_adapter(&i2c->adap);
+
+	return 0;
+}
+
+static const struct of_device_id mtk_hdmi_i2c_match[] = {
+	{ .compatible = "mediatek,mt8173-hdmi-ddc", },
+	{},
+};
+
+struct platform_driver mtk_hdmi_ddc_driver = {
+	.probe = mtk_hdmi_i2c_probe,
+	.remove = mtk_hdmi_i2c_remove,
+	.driver = {
+		.name = "mediatek-hdmi-ddc",
+		.of_match_table = mtk_hdmi_i2c_match,
+	},
+};
+
+MODULE_AUTHOR("Jie Qiu <jie.qiu at mediatek.com>");
+MODULE_DESCRIPTION("MediaTek HDMI i2c Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c
new file mode 100644
index 0000000..2d08b78
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c
@@ -0,0 +1,895 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu at 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 "mtk_hdmi_hw.h"
+#include "mtk_hdmi_regs.h"
+#include "mtk_hdmi.h"
+
+#include <linux/delay.h>
+#include <linux/hdmi.h>
+#include <linux/io.h>
+#include <linux/compiler.h>
+#include <asm/cpu_ops.h>
+#include <asm/smp_plat.h>
+#include <asm/system_misc.h>
+
+#define MTK_HDMI_READ_BANK(bank) \
+static u32 mtk_hdmi_read_##bank(struct mtk_hdmi *hdmi, \
+				u32 offset) \
+{ \
+	return readl(hdmi->bank##_regs + offset); \
+}
+
+#define MTK_HDMI_WRITE_BANK(bank) \
+static void mtk_hdmi_write_##bank(struct mtk_hdmi *hdmi, \
+				  u32 offset, u32 val) \
+{ \
+	writel(val, hdmi->bank##_regs + offset); \
+}
+
+#define MTK_HDMI_MASK_BANK(bank) \
+static void mtk_hdmi_mask_##bank(struct mtk_hdmi *hdmi, \
+				 u32 offset,  u32 val, u32 mask) \
+{ \
+	u32 tmp = mtk_hdmi_read_##bank(hdmi, offset) & ~mask; \
+	tmp |= (val & mask); \
+	mtk_hdmi_write_##bank(hdmi, offset, tmp); \
+}
+
+#define MTK_HDMI_ACCESS_BANK(bank) \
+		MTK_HDMI_READ_BANK(bank) \
+		MTK_HDMI_WRITE_BANK(bank) \
+		MTK_HDMI_MASK_BANK(bank)
+
+MTK_HDMI_ACCESS_BANK(grl)
+MTK_HDMI_ACCESS_BANK(sys)
+MTK_HDMI_ACCESS_BANK(cec)
+
+static u32 internal_read_reg(phys_addr_t addr)
+{
+	void __iomem *ptr = NULL;
+	u32 val;
+
+	ptr = ioremap(addr, 0x4);
+	val = readl(ptr);
+	iounmap(ptr);
+	return val;
+}
+
+static void internal_write_reg(phys_addr_t addr, size_t val)
+{
+	void __iomem *ptr = NULL;
+
+	ptr = ioremap(addr, 0x4);
+	writel(val, ptr);
+	iounmap(ptr);
+}
+
+#define internal_read(addr) internal_read_reg(addr)
+#define internal_write(addr, val) internal_write_reg(addr, val)
+#define internal_write_msk(addr, val, msk) \
+	internal_write((addr), \
+	(internal_read(addr) & (~(msk))) | ((val) & (msk)))
+
+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 */
+};
+
+#define NCTS_BYTES          0x07
+static const u8 HDMI_NCTS[7][9][NCTS_BYTES] = {
+	{{0x00, 0x00, 0x69, 0x78, 0x00, 0x10, 0x00},
+	 {0x00, 0x00, 0xd2, 0xf0, 0x00, 0x10, 0x00},
+	 {0x00, 0x03, 0x37, 0xf9, 0x00, 0x2d, 0x80},
+	 {0x00, 0x01, 0x22, 0x0a, 0x00, 0x10, 0x00},
+	 {0x00, 0x06, 0x6f, 0xf3, 0x00, 0x2d, 0x80},
+	 {0x00, 0x02, 0x44, 0x14, 0x00, 0x10, 0x00},
+	 {0x00, 0x01, 0xA5, 0xe0, 0x00, 0x10, 0x00},
+	 {0x00, 0x06, 0x6F, 0xF3, 0x00, 0x16, 0xC0},
+	 {0x00, 0x03, 0x66, 0x1E, 0x00, 0x0C, 0x00}
+	 },
+	{{0x00, 0x00, 0x75, 0x30, 0x00, 0x18, 0x80},
+	 {0x00, 0x00, 0xea, 0x60, 0x00, 0x18, 0x80},
+	 {0x00, 0x03, 0x93, 0x87, 0x00, 0x45, 0xac},
+	 {0x00, 0x01, 0x42, 0x44, 0x00, 0x18, 0x80},
+	 {0x00, 0x03, 0x93, 0x87, 0x00, 0x22, 0xd6},
+	 {0x00, 0x02, 0x84, 0x88, 0x00, 0x18, 0x80},
+	 {0x00, 0x01, 0xd4, 0xc0, 0x00, 0x18, 0x80},
+	 {0x00, 0x03, 0x93, 0x87, 0x00, 0x11, 0x6B},
+	 {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x12, 0x60}
+	 },
+	{{0x00, 0x00, 0x69, 0x78, 0x00, 0x18, 0x00},
+	 {0x00, 0x00, 0xd2, 0xf0, 0x00, 0x18, 0x00},
+	 {0x00, 0x02, 0x25, 0x51, 0x00, 0x2d, 0x80},
+	 {0x00, 0x01, 0x22, 0x0a, 0x00, 0x18, 0x00},
+	 {0x00, 0x02, 0x25, 0x51, 0x00, 0x16, 0xc0},
+	 {0x00, 0x02, 0x44, 0x14, 0x00, 0x18, 0x00},
+	 {0x00, 0x01, 0xA5, 0xe0, 0x00, 0x18, 0x00},
+	 {0x00, 0x04, 0x4A, 0xA2, 0x00, 0x16, 0xC0},
+	 {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x14, 0x00}
+	 },
+	{{0x00, 0x00, 0x75, 0x30, 0x00, 0x31, 0x00},
+	 {0x00, 0x00, 0xea, 0x60, 0x00, 0x31, 0x00},
+	 {0x00, 0x03, 0x93, 0x87, 0x00, 0x8b, 0x58},
+	 {0x00, 0x01, 0x42, 0x44, 0x00, 0x31, 0x00},
+	 {0x00, 0x03, 0x93, 0x87, 0x00, 0x45, 0xac},
+	 {0x00, 0x02, 0x84, 0x88, 0x00, 0x31, 0x00},
+	 {0x00, 0x01, 0xd4, 0xc0, 0x00, 0x31, 0x00},
+	 {0x00, 0x03, 0x93, 0x87, 0x00, 0x22, 0xD6},
+	 {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x24, 0xC0}
+	 },
+	{{0x00, 0x00, 0x69, 0x78, 0x00, 0x30, 0x00},
+	 {0x00, 0x00, 0xd2, 0xf0, 0x00, 0x30, 0x00},
+	 {0x00, 0x02, 0x25, 0x51, 0x00, 0x5b, 0x00},
+	 {0x00, 0x01, 0x22, 0x0a, 0x00, 0x30, 0x00},
+	 {0x00, 0x02, 0x25, 0x51, 0x00, 0x2d, 0x80},
+	 {0x00, 0x02, 0x44, 0x14, 0x00, 0x30, 0x00},
+	 {0x00, 0x01, 0xA5, 0xe0, 0x00, 0x30, 0x00},
+	 {0x00, 0x04, 0x4A, 0xA2, 0x00, 0x2D, 0x80},
+	 {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x28, 0x80}
+	 },
+	{{0x00, 0x00, 0x75, 0x30, 0x00, 0x62, 0x00},
+	 {0x00, 0x00, 0xea, 0x60, 0x00, 0x62, 0x00},
+	 {0x00, 0x03, 0x93, 0x87, 0x01, 0x16, 0xb0},
+	 {0x00, 0x01, 0x42, 0x44, 0x00, 0x62, 0x00},
+	 {0x00, 0x03, 0x93, 0x87, 0x00, 0x8b, 0x58},
+	 {0x00, 0x02, 0x84, 0x88, 0x00, 0x62, 0x00},
+	 {0x00, 0x01, 0xd4, 0xc0, 0x00, 0x62, 0x00},
+	 {0x00, 0x03, 0x93, 0x87, 0x00, 0x45, 0xAC},
+	 {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x49, 0x80}
+	 },
+	{{0x00, 0x00, 0x69, 0x78, 0x00, 0x60, 0x00},
+	 {0x00, 0x00, 0xd2, 0xf0, 0x00, 0x60, 0x00},
+	 {0x00, 0x02, 0x25, 0x51, 0x00, 0xb6, 0x00},
+	 {0x00, 0x01, 0x22, 0x0a, 0x00, 0x60, 0x00},
+	 {0x00, 0x02, 0x25, 0x51, 0x00, 0x5b, 0x00},
+	 {0x00, 0x02, 0x44, 0x14, 0x00, 0x60, 0x00},
+	 {0x00, 0x01, 0xA5, 0xe0, 0x00, 0x60, 0x00},
+	 {0x00, 0x04, 0x4A, 0xA2, 0x00, 0x5B, 0x00},
+	 {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x50, 0x00}
+	 }
+};
+
+void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi,
+			   bool black)
+{
+	mtk_hdmi_mask_grl(hdmi, VIDEO_CFG_4,
+			  black ? GEN_RGB : NORMAL_PATH,
+			  VIDEO_SOURCE_SEL);
+}
+
+void mtk_hdmi_hw_make_reg_writable(struct mtk_hdmi *hdmi,
+				   bool enable)
+{
+	if (enable) {
+		mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG20, HDMI_PCLK_FREE_RUN,
+				  HDMI_PCLK_FREE_RUN);
+		mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG1C, HDMI_ON | ANLG_ON,
+				  HDMI_ON | ANLG_ON);
+	} else {
+		mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG20, 0, HDMI_PCLK_FREE_RUN);
+		mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG1C, 0, HDMI_ON | ANLG_ON);
+	}
+}
+
+void mtk_hdmi_hw_1p4_version_enable(struct mtk_hdmi *hdmi,
+				    bool enable)
+{
+	mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG20,
+			  enable ? 0 : HDMI2P0_EN, HDMI2P0_EN);
+}
+
+bool mtk_hdmi_hw_is_hpd_high(struct mtk_hdmi *hdmi)
+{
+	unsigned int status;
+
+	status = mtk_hdmi_read_cec(hdmi, RX_EVENT);
+	return (HDMI_PORD & status) == HDMI_PORD &&
+			(HDMI_HTPLG & status) == HDMI_HTPLG;
+}
+
+void mtk_hdmi_hw_aud_mute(struct mtk_hdmi *hdmi, bool mute)
+{
+	if (mute)
+		mtk_hdmi_mask_grl(hdmi, GRL_AUDIO_CFG, AUDIO_ZERO, AUDIO_ZERO);
+	else
+		mtk_hdmi_mask_grl(hdmi, GRL_AUDIO_CFG, 0, AUDIO_ZERO);
+}
+
+void mtk_hdmi_hw_reset(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG1C, HDMI_RST, HDMI_RST);
+	mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG1C, 0, HDMI_RST);
+	mtk_hdmi_mask_grl(hdmi, GRL_CFG3, 0, CFG3_CONTROL_PACKET_DELAY);
+	mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG1C, ANLG_ON, ANLG_ON);
+}
+
+void mtk_hdmi_hw_enable_notice(struct mtk_hdmi *hdmi,
+			       bool enable_notice)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_CFG2,
+			  enable_notice ? CFG2_NOTICE_EN : 0, CFG2_NOTICE_EN);
+}
+
+void mtk_hdmi_hw_write_int_mask(struct mtk_hdmi *hdmi,
+				u32 int_mask)
+{
+	mtk_hdmi_write_grl(hdmi, GRL_INT_MASK, int_mask);
+}
+
+void mtk_hdmi_hw_enable_dvi_mode(struct mtk_hdmi *hdmi,
+				 bool enable)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_CFG1, enable ? CFG1_DVI : 0, CFG1_DVI);
+}
+
+u32 mtk_hdmi_hw_get_int_type(struct mtk_hdmi *hdmi)
+{
+	return mtk_hdmi_read_grl(hdmi, GRL_INT);
+}
+
+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;
+	u8 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_info(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;
+	default:
+		break;
+	}
+	mtk_hdmi_mask_grl(hdmi, ctrl_reg, 0, ctrl_frame_en);
+	mtk_hdmi_write_grl(hdmi, GRL_INFOFRM_TYPE, frame_type);
+	mtk_hdmi_write_grl(hdmi, GRL_INFOFRM_VER, frame_ver);
+	mtk_hdmi_write_grl(hdmi, GRL_INFOFRM_LNG, frame_len);
+
+	mtk_hdmi_write_grl(hdmi, GRL_IFM_PORT, checksum);
+	for (i = 0; i < frame_len; i++)
+		mtk_hdmi_write_grl(hdmi, GRL_IFM_PORT, frame_data[i]);
+
+	mtk_hdmi_mask_grl(hdmi, ctrl_reg, ctrl_frame_en, ctrl_frame_en);
+}
+
+void mtk_hdmi_hw_send_aud_packet(struct mtk_hdmi *hdmi, bool enable)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_SHIFT_R2,
+			  enable ? 0 : AUDIO_PACKET_OFF,
+			  AUDIO_PACKET_OFF);
+}
+
+void mtk_hdmi_hw_config_sys(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG20,
+			  0, HDMI_OUT_FIFO_EN | MHL_MODE_ON);
+	mdelay(2);
+	mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG20, HDMI_OUT_FIFO_EN,
+			  HDMI_OUT_FIFO_EN | MHL_MODE_ON);
+
+	/* Undocumented, PLL_TEST_CON0[31:16] are marked as PLLGP_RESERVE */
+	internal_write_msk(0x10209040, 0x3 << 16, 0x7 << 16);
+}
+
+void mtk_hdmi_hw_set_deep_color_mode(struct mtk_hdmi *hdmi,
+				     enum hdmi_display_color_depth depth)
+{
+	u32 val = 0;
+
+	switch (depth) {
+	case HDMI_DEEP_COLOR_24BITS:
+		val = COLOR_8BIT_MODE;
+		break;
+	case HDMI_DEEP_COLOR_30BITS:
+		val = COLOR_10BIT_MODE;
+		break;
+	case HDMI_DEEP_COLOR_36BITS:
+		val = COLOR_12BIT_MODE;
+		break;
+	case HDMI_DEEP_COLOR_48BITS:
+		val = COLOR_16BIT_MODE;
+		break;
+	default:
+		val = COLOR_8BIT_MODE;
+		break;
+	}
+
+	if (val == COLOR_8BIT_MODE) {
+		mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG20, val,
+				  DEEP_COLOR_MODE_MASK | DEEP_COLOR_EN);
+	} else {
+		mtk_hdmi_mask_sys(hdmi, HDMI_SYS_CFG20, val | DEEP_COLOR_EN,
+				  DEEP_COLOR_MODE_MASK | DEEP_COLOR_EN);
+	}
+}
+
+void mtk_hdmi_hw_send_av_mute(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_CFG4, 0, CTRL_AVMUTE);
+	mdelay(2);
+	mtk_hdmi_mask_grl(hdmi, GRL_CFG4, CTRL_AVMUTE, CTRL_AVMUTE);
+}
+
+void mtk_hdmi_hw_send_av_unmute(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_CFG4, CFG4_AV_UNMUTE_EN,
+			  CFG4_AV_UNMUTE_EN | CFG4_AV_UNMUTE_SET);
+	mdelay(2);
+	mtk_hdmi_mask_grl(hdmi, GRL_CFG4, CFG4_AV_UNMUTE_SET,
+			  CFG4_AV_UNMUTE_EN | CFG4_AV_UNMUTE_SET);
+}
+
+void mtk_hdmi_hw_ncts_enable(struct mtk_hdmi *hdmi,
+			     bool on)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_CTS_CTRL,
+			  on ? 0 : CTS_CTRL_SOFT, CTS_CTRL_SOFT);
+}
+
+void mtk_hdmi_hw_ncts_auto_write_enable(struct mtk_hdmi
+					*hdmi, bool enable)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_CTS_CTRL,
+			  enable ? NCTS_WRI_ANYTIME : 0, NCTS_WRI_ANYTIME);
+}
+
+void mtk_hdmi_hw_msic_setting(struct mtk_hdmi *hdmi,
+			      struct drm_display_mode *mode)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_CFG4, 0, CFG_MHL_MODE);
+
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE &&
+	    mode->clock == 74250 &&
+	    mode->vdisplay == 1080)
+		mtk_hdmi_mask_grl(hdmi, GRL_CFG2, 0, MHL_DE_SEL);
+	else
+		mtk_hdmi_mask_grl(hdmi, GRL_CFG2, MHL_DE_SEL, MHL_DE_SEL);
+}
+
+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_grl(hdmi, GRL_CH_SWAP, swap_bit, 0xff);
+}
+
+void mtk_hdmi_hw_aud_raw_data_enable(struct mtk_hdmi *hdmi,
+				     bool enable)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_MIX_CTRL,
+			  enable ? MIX_CTRL_FLAT : 0, MIX_CTRL_FLAT);
+}
+
+void mtk_hdmi_hw_aud_set_bit_num(struct mtk_hdmi *hdmi,
+				 enum hdmi_audio_sample_size bit_num)
+{
+	u32 val = 0;
+
+	if (bit_num == HDMI_AUDIO_SAMPLE_SIZE_16)
+		val = AOUT_16BIT;
+	else if (bit_num == HDMI_AUDIO_SAMPLE_SIZE_20)
+		val = AOUT_20BIT;
+	else if (bit_num == HDMI_AUDIO_SAMPLE_SIZE_24)
+		val = AOUT_24BIT;
+
+	mtk_hdmi_mask_grl(hdmi, GRL_AOUT_BNUM_SEL, val, 0x03);
+}
+
+void mtk_hdmi_hw_aud_set_i2s_fmt(struct mtk_hdmi *hdmi,
+				 enum hdmi_aud_i2s_fmt i2s_fmt)
+{
+	u32 val = 0;
+
+	val = mtk_hdmi_read_grl(hdmi, GRL_CFG0);
+	val &= ~0x33;
+
+	switch (i2s_fmt) {
+	case HDMI_I2S_MODE_RJT_24BIT:
+		val |= (CFG0_I2S_MODE_RTJ | CFG0_I2S_MODE_24BIT);
+		break;
+	case HDMI_I2S_MODE_RJT_16BIT:
+		val |= (CFG0_I2S_MODE_RTJ | CFG0_I2S_MODE_16BIT);
+		break;
+	case HDMI_I2S_MODE_LJT_24BIT:
+		val |= (CFG0_I2S_MODE_LTJ | CFG0_I2S_MODE_24BIT);
+		break;
+	case HDMI_I2S_MODE_LJT_16BIT:
+		val |= (CFG0_I2S_MODE_LTJ | CFG0_I2S_MODE_16BIT);
+		break;
+	case HDMI_I2S_MODE_I2S_24BIT:
+		val |= (CFG0_I2S_MODE_I2S | CFG0_I2S_MODE_24BIT);
+		break;
+	case HDMI_I2S_MODE_I2S_16BIT:
+		val |= (CFG0_I2S_MODE_I2S | CFG0_I2S_MODE_16BIT);
+		break;
+	default:
+		break;
+	}
+	mtk_hdmi_write_grl(hdmi, GRL_CFG0, val);
+}
+
+void mtk_hdmi_hw_aud_set_high_bitrate(struct mtk_hdmi
+				      *hdmi, bool enable)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_AOUT_BNUM_SEL,
+			  enable ? HIGH_BIT_RATE_PACKET_ALIGN : 0,
+			  HIGH_BIT_RATE_PACKET_ALIGN);
+	mtk_hdmi_mask_grl(hdmi, GRL_AUDIO_CFG,
+			  enable ? HIGH_BIT_RATE : 0, HIGH_BIT_RATE);
+}
+
+void mtk_hdmi_phy_aud_dst_normal_double_enable(struct mtk_hdmi
+					       *hdmi, bool enable)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_AUDIO_CFG,
+			  enable ? DST_NORMAL_DOUBLE : 0, DST_NORMAL_DOUBLE);
+}
+
+void mtk_hdmi_hw_aud_dst_enable(struct mtk_hdmi *hdmi,
+				bool enable)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_AUDIO_CFG, enable ? SACD_DST : 0, SACD_DST);
+}
+
+void mtk_hdmi_hw_aud_dsd_enable(struct mtk_hdmi *hdmi,
+				bool enable)
+{
+	mtk_hdmi_mask_grl(hdmi, GRL_AUDIO_CFG, enable ? SACD_SEL : 0, SACD_SEL);
+}
+
+void mtk_hdmi_hw_aud_set_i2s_chan_num(struct mtk_hdmi
+				      *hdmi,
+				      enum hdmi_aud_channel_type channel_type,
+				      u8 channel_count)
+{
+	u8 val_1, val_2, val_3, val_4;
+
+	if (channel_count == 2) {
+		val_1 = 0x04;
+		val_2 = 0x50;
+	} 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)) {
+			val_1 = 0x14;
+		} else {
+			val_1 = 0x0c;
+		}
+		val_2 = 0x50;
+	} 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) {
+			val_1 = 0x3c;
+			val_2 = 0x50;
+		} else {
+			val_1 = 0x1c;
+			val_2 = 0x50;
+		}
+	} else if (channel_count == 8 || channel_count == 7) {
+		val_1 = 0x3c;
+		val_2 = 0x50;
+	} else {
+		val_1 = 0x04;
+		val_2 = 0x50;
+	}
+
+	val_3 = 0xc6;
+	val_4 = 0xfa;
+
+	mtk_hdmi_write_grl(hdmi, GRL_CH_SW0, val_2);
+	mtk_hdmi_write_grl(hdmi, GRL_CH_SW1, val_3);
+	mtk_hdmi_write_grl(hdmi, GRL_CH_SW2, val_4);
+	mtk_hdmi_write_grl(hdmi, GRL_I2S_UV, val_1);
+}
+
+void mtk_hdmi_hw_aud_set_input_type(struct mtk_hdmi *hdmi,
+				    enum hdmi_aud_input_type input_type)
+{
+	u32 val = 0;
+
+	val = mtk_hdmi_read_grl(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_grl(hdmi, GRL_CFG1, val);
+}
+
+void mtk_hdmi_hw_aud_set_channel_status(struct mtk_hdmi
+					*hdmi, u8 *l_chan_status,
+					u8 *r_chan_staus,
+					enum hdmi_audio_sample_frequency
+					aud_hdmi_fs)
+{
+	u8 l_status[5];
+	u8 r_status[5];
+	u8 val = 0;
+
+	l_status[0] = l_chan_status[0];
+	l_status[1] = l_chan_status[1];
+	l_status[2] = l_chan_status[2];
+	r_status[0] = r_chan_staus[0];
+	r_status[1] = r_chan_staus[1];
+	r_status[2] = r_chan_staus[2];
+
+	l_status[0] &= ~0x02;
+	r_status[0] &= ~0x02;
+
+	val = l_chan_status[3] & 0xf0;
+	switch (aud_hdmi_fs) {
+	case HDMI_AUDIO_SAMPLE_FREQUENCY_32000:
+		val |= 0x03;
+		break;
+	case HDMI_AUDIO_SAMPLE_FREQUENCY_44100:
+		break;
+	case HDMI_AUDIO_SAMPLE_FREQUENCY_88200:
+		val |= 0x08;
+		break;
+	case HDMI_AUDIO_SAMPLE_FREQUENCY_96000:
+		val |= 0x0a;
+		break;
+	case HDMI_AUDIO_SAMPLE_FREQUENCY_48000:
+		val |= 0x02;
+		break;
+	default:
+		val |= 0x02;
+		break;
+	}
+	l_status[3] = val;
+	r_status[3] = val;
+
+	val = l_chan_status[4];
+	val |= ((~(l_status[3] & 0x0f)) << 4);
+	l_status[4] = val;
+	r_status[4] = val;
+
+	val = l_status[0];
+	mtk_hdmi_write_grl(hdmi, GRL_I2S_C_STA0, val);
+	mtk_hdmi_write_grl(hdmi, GRL_L_STATUS_0, val);
+
+	val = r_status[0];
+	mtk_hdmi_write_grl(hdmi, GRL_R_STATUS_0, val);
+
+	val = l_status[1];
+	mtk_hdmi_write_grl(hdmi, GRL_I2S_C_STA1, val);
+	mtk_hdmi_write_grl(hdmi, GRL_L_STATUS_1, val);
+
+	val = r_status[1];
+	mtk_hdmi_write_grl(hdmi, GRL_R_STATUS_1, val);
+
+	val = l_status[2];
+	mtk_hdmi_write_grl(hdmi, GRL_I2S_C_STA2, val);
+	mtk_hdmi_write_grl(hdmi, GRL_L_STATUS_2, val);
+
+	val = r_status[2];
+	mtk_hdmi_write_grl(hdmi, GRL_R_STATUS_2, val);
+
+	val = l_status[3];
+	mtk_hdmi_write_grl(hdmi, GRL_I2S_C_STA3, val);
+	mtk_hdmi_write_grl(hdmi, GRL_L_STATUS_3, val);
+
+	val = r_status[3];
+	mtk_hdmi_write_grl(hdmi, GRL_R_STATUS_3, val);
+
+	val = l_status[4];
+	mtk_hdmi_write_grl(hdmi, GRL_I2S_C_STA4, val);
+	mtk_hdmi_write_grl(hdmi, GRL_L_STATUS_4, val);
+
+	val = r_status[4];
+	mtk_hdmi_write_grl(hdmi, GRL_R_STATUS_4, val);
+
+	for (val = 0; val < 19; val++) {
+		mtk_hdmi_write_grl(hdmi, GRL_L_STATUS_5 + val * 4, 0);
+		mtk_hdmi_write_grl(hdmi, GRL_R_STATUS_5 + val * 4, 0);
+	}
+}
+
+void mtk_hdmi_hw_aud_src_reenable(struct mtk_hdmi *hdmi)
+{
+	u32 val;
+
+	val = mtk_hdmi_read_grl(hdmi, GRL_MIX_CTRL);
+	if (val & MIX_CTRL_SRC_EN) {
+		val &= ~MIX_CTRL_SRC_EN;
+		mtk_hdmi_write_grl(hdmi, GRL_MIX_CTRL, val);
+		usleep_range(255, 512);
+		val |= MIX_CTRL_SRC_EN;
+		mtk_hdmi_write_grl(hdmi, GRL_MIX_CTRL, val);
+	}
+}
+
+void mtk_hdmi_hw_aud_src_off(struct mtk_hdmi *hdmi)
+{
+	u32 val;
+
+	val = mtk_hdmi_read_grl(hdmi, GRL_MIX_CTRL);
+	val &= ~MIX_CTRL_SRC_EN;
+	mtk_hdmi_write_grl(hdmi, GRL_MIX_CTRL, val);
+	mtk_hdmi_write_grl(hdmi, GRL_SHIFT_L1, 0x00);
+}
+
+void mtk_hdmi_hw_aud_set_mclk(struct mtk_hdmi *hdmi,
+			      enum hdmi_aud_mclk mclk)
+{
+	u32 val;
+
+	val = mtk_hdmi_read_grl(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_grl(hdmi, GRL_CFG5, val);
+}
+
+void mtk_hdmi_hw_aud_aclk_inv_enable(struct mtk_hdmi *hdmi,
+				     bool enable)
+{
+	u32 val;
+
+	val = mtk_hdmi_read_grl(hdmi, GRL_CFG2);
+	if (enable)
+		val |= 0x80;
+	else
+		val &= ~0x80;
+	mtk_hdmi_write_grl(hdmi, GRL_CFG2, val);
+}
+
+static void do_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi,
+				    enum hdmi_display_color_depth depth,
+				    enum hdmi_audio_sample_frequency freq,
+				    int pix)
+{
+	unsigned char val[NCTS_BYTES];
+	unsigned int temp;
+	int i = 0;
+
+	mtk_hdmi_write_grl(hdmi, GRL_NCTS, 0);
+	mtk_hdmi_write_grl(hdmi, GRL_NCTS, 0);
+	mtk_hdmi_write_grl(hdmi, GRL_NCTS, 0);
+	memset(val, 0, sizeof(val));
+
+	if (depth == HDMI_DEEP_COLOR_24BITS) {
+		for (i = 0; i < NCTS_BYTES; i++) {
+			if ((freq < 8) && (pix < 9))
+				val[i] = HDMI_NCTS[freq - 1][pix][i];
+		}
+		temp = (val[0] << 24) | (val[1] << 16) |
+			(val[2] << 8) | (val[3]);	/* CTS */
+	} else {
+		for (i = 0; i < NCTS_BYTES; i++) {
+			if ((freq < 7) && (pix < 9))
+				val[i] = HDMI_NCTS[freq - 1][pix][i];
+		}
+
+		temp =
+		    (val[0] << 24) | (val[1] << 16) | (val[2] << 8) | (val[3]);
+
+		if (depth == HDMI_DEEP_COLOR_30BITS)
+			temp = (temp >> 2) * 5;
+		else if (depth == HDMI_DEEP_COLOR_36BITS)
+			temp = (temp >> 1) * 3;
+		else if (depth == HDMI_DEEP_COLOR_48BITS)
+			temp = (temp << 1);
+
+		val[0] = (temp >> 24) & 0xff;
+		val[1] = (temp >> 16) & 0xff;
+		val[2] = (temp >> 8) & 0xff;
+		val[3] = (temp) & 0xff;
+	}
+
+	for (i = 0; i < NCTS_BYTES; i++)
+		mtk_hdmi_write_grl(hdmi, GRL_NCTS, val[i]);
+}
+
+void mtk_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi,
+			      enum hdmi_display_color_depth depth,
+			      enum hdmi_audio_sample_frequency freq, int clock)
+{
+	int pix = 0;
+
+	switch (clock) {
+	case 27000:
+		pix = 0;
+		break;
+	case 74175:
+		pix = 2;
+		break;
+	case 74250:
+		pix = 3;
+		break;
+	case 148350:
+		pix = 4;
+		break;
+	case 148500:
+		pix = 5;
+		break;
+	default:
+		pix = 0;
+		break;
+	}
+
+	mtk_hdmi_mask_grl(hdmi, DUMMY_304, AUDIO_I2S_NCTS_SEL_64,
+			  AUDIO_I2S_NCTS_SEL);
+	do_hdmi_hw_aud_set_ncts(hdmi, depth, freq, pix);
+}
+
+void mtk_hdmi_hw_htplg_irq_enable(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_mask_cec(hdmi, CEC_CKGEN, 0, PDN);
+
+	mtk_hdmi_mask_cec(hdmi, CEC_CKGEN, CEC_32K_PDN, CEC_32K_PDN);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, HDMI_PORD_INT_32K_CLR,
+			  HDMI_PORD_INT_32K_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, RX_INT_32K_CLR, RX_INT_32K_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, HDMI_HTPLG_INT_32K_CLR,
+			  HDMI_HTPLG_INT_32K_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, 0, RX_INT_32K_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, 0, HDMI_PORD_INT_32K_EN);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, 0, RX_INT_32K_EN);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_EN);
+
+	mtk_hdmi_mask_cec(hdmi, RX_EVENT, HDMI_PORD_INT_EN, HDMI_PORD_INT_EN);
+	mtk_hdmi_mask_cec(hdmi, RX_EVENT, HDMI_HTPLG_INT_EN, HDMI_HTPLG_INT_EN);
+}
+
+void mtk_hdmi_hw_htplg_irq_disable(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_mask_cec(hdmi, RX_EVENT, 0, HDMI_PORD_INT_EN);
+	mtk_hdmi_mask_cec(hdmi, RX_EVENT, 0, HDMI_HTPLG_INT_EN);
+}
+
+void mtk_hdmi_hw_clear_htplg_irq(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_mask_cec(hdmi, TR_CONFIG, CLEAR_CEC_IRQ, CLEAR_CEC_IRQ);
+	mtk_hdmi_mask_cec(hdmi, NORMAL_INT_CTRL,
+			  HDMI_HTPLG_INT_CLR, HDMI_HTPLG_INT_CLR);
+	mtk_hdmi_mask_cec(hdmi, NORMAL_INT_CTRL,
+			  HDMI_PORD_INT_CLR, HDMI_PORD_INT_CLR);
+	mtk_hdmi_mask_cec(hdmi, NORMAL_INT_CTRL,
+			  HDMI_FULL_INT_CLR, HDMI_FULL_INT_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, HDMI_PORD_INT_32K_CLR,
+			  HDMI_PORD_INT_32K_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, RX_INT_32K_CLR, RX_INT_32K_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, HDMI_HTPLG_INT_32K_CLR,
+			  HDMI_HTPLG_INT_32K_CLR);
+	udelay(5);
+	mtk_hdmi_mask_cec(hdmi, NORMAL_INT_CTRL, 0, HDMI_HTPLG_INT_CLR);
+	mtk_hdmi_mask_cec(hdmi, NORMAL_INT_CTRL, 0, HDMI_PORD_INT_CLR);
+	mtk_hdmi_mask_cec(hdmi, TR_CONFIG, 0, CLEAR_CEC_IRQ);
+	mtk_hdmi_mask_cec(hdmi, NORMAL_INT_CTRL, 0, HDMI_FULL_INT_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, 0, RX_INT_32K_CLR);
+	mtk_hdmi_mask_cec(hdmi, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_CLR);
+}
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_hw.h b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.h
new file mode 100644
index 0000000..0ced80a
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu at 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_HW_H
+#define _MTK_HDMI_HW_H
+
+#include <linux/types.h>
+#include <linux/hdmi.h>
+#include "mtk_hdmi.h"
+
+void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi, bool black);
+void mtk_hdmi_hw_aud_mute(struct mtk_hdmi *hdmi, bool mute);
+void mtk_hdmi_hw_send_info_frame(struct mtk_hdmi *hdmi, u8 *buffer, u8 len);
+void mtk_hdmi_hw_send_aud_packet(struct mtk_hdmi *hdmi, bool enable);
+int mtk_hdmi_hw_set_clock(struct mtk_hdmi *hctx, u32 clock);
+void mtk_hdmi_hw_config_sys(struct mtk_hdmi *hdmi);
+void mtk_hdmi_hw_set_deep_color_mode(struct mtk_hdmi *hdmi,
+				     enum hdmi_display_color_depth depth);
+void mtk_hdmi_hw_send_av_mute(struct mtk_hdmi *hdmi);
+void mtk_hdmi_hw_send_av_unmute(struct mtk_hdmi *hdmi);
+void mtk_hdmi_hw_ncts_enable(struct mtk_hdmi *hdmi, bool on);
+void mtk_hdmi_hw_aud_set_channel_swap(struct mtk_hdmi *hdmi,
+				      enum hdmi_aud_channel_swap_type swap);
+void mtk_hdmi_hw_aud_raw_data_enable(struct mtk_hdmi *hdmi, bool enable);
+void mtk_hdmi_hw_aud_set_bit_num(struct mtk_hdmi *hdmi,
+				 enum hdmi_audio_sample_size bit_num);
+void mtk_hdmi_hw_aud_set_high_bitrate(struct mtk_hdmi *hdmi, bool enable);
+void mtk_hdmi_phy_aud_dst_normal_double_enable(struct mtk_hdmi *hdmi,
+					       bool enable);
+void mtk_hdmi_hw_aud_dst_enable(struct mtk_hdmi *hdmi, bool enable);
+void mtk_hdmi_hw_aud_dsd_enable(struct mtk_hdmi *hdmi, bool enable);
+void mtk_hdmi_hw_aud_set_i2s_fmt(struct mtk_hdmi *hdmi,
+				 enum hdmi_aud_i2s_fmt i2s_fmt);
+void mtk_hdmi_hw_aud_set_i2s_chan_num(struct mtk_hdmi *hdmi,
+				      enum hdmi_aud_channel_type channel_type,
+				      u8 channel_count);
+void mtk_hdmi_hw_aud_set_input_type(struct mtk_hdmi *hdmi,
+				    enum hdmi_aud_input_type input_type);
+void mtk_hdmi_hw_aud_set_channel_status(struct mtk_hdmi *hdmi,
+					u8 *l_chan_status, u8 *r_chan_staus,
+					enum hdmi_audio_sample_frequency
+					aud_hdmi_fs);
+void mtk_hdmi_hw_aud_src_enable(struct mtk_hdmi *hdmi, bool enable);
+void mtk_hdmi_hw_aud_set_mclk(struct mtk_hdmi *hdmi, enum hdmi_aud_mclk mclk);
+void mtk_hdmi_hw_aud_src_off(struct mtk_hdmi *hdmi);
+void mtk_hdmi_hw_aud_src_reenable(struct mtk_hdmi *hdmi);
+void mtk_hdmi_hw_aud_aclk_inv_enable(struct mtk_hdmi *hdmi, bool enable);
+void mtk_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi,
+			      enum hdmi_display_color_depth depth,
+			      enum hdmi_audio_sample_frequency freq,
+			      int clock);
+bool mtk_hdmi_hw_is_hpd_high(struct mtk_hdmi *hdmi);
+void mtk_hdmi_hw_make_reg_writable(struct mtk_hdmi *hdmi, bool enable);
+void mtk_hdmi_hw_reset(struct mtk_hdmi *hdmi);
+void mtk_hdmi_hw_enable_notice(struct mtk_hdmi *hdmi, bool enable_notice);
+void mtk_hdmi_hw_write_int_mask(struct mtk_hdmi *hdmi, u32 int_mask);
+void mtk_hdmi_hw_enable_dvi_mode(struct mtk_hdmi *hdmi, bool enable);
+void mtk_hdmi_hw_ncts_auto_write_enable(struct mtk_hdmi *hdmi, bool enable);
+void mtk_hdmi_hw_msic_setting(struct mtk_hdmi *hdmi,
+			      struct drm_display_mode *mode);
+void mtk_hdmi_hw_1p4_version_enable(struct mtk_hdmi *hdmi, bool enable);
+void mtk_hdmi_hw_htplg_irq_enable(struct mtk_hdmi *hdmi);
+void mtk_hdmi_hw_htplg_irq_disable(struct mtk_hdmi *hdmi);
+void mtk_hdmi_hw_clear_htplg_irq(struct mtk_hdmi *hdmi);
+
+#endif /* _MTK_HDMI_HW_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_phy.c b/drivers/gpu/drm/mediatek/mtk_hdmi_phy.c
new file mode 100644
index 0000000..59fbfaf
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_phy.c
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu at 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/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>
+#include "mtk_hdmi.h"
+#include "mtk_hdmi_regs.h"
+
+struct mtk_hdmi_phy {
+	void __iomem *regs;
+	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_mask(struct mtk_hdmi_phy *hdmi_phy, u32 offset,
+			      u32 val, u32 mask)
+{
+	u32 tmp = readl(hdmi_phy->regs  + offset) & ~mask;
+
+	tmp |= (val & mask);
+	writel(tmp, hdmi_phy->regs + offset);
+}
+
+static void mtk_hdmi_phy_enable_tmds(struct mtk_hdmi_phy *hdmi_phy)
+{
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_AUTOK_EN,
+			  RG_HDMITX_PLL_AUTOK_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_POSDIV,
+			  RG_HDMITX_PLL_POSDIV);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, 0, RG_HDMITX_MHLCK_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_EN,
+			  RG_HDMITX_PLL_BIAS_EN);
+	usleep_range(100, 150);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_EN,
+			  RG_HDMITX_PLL_EN);
+	usleep_range(100, 150);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_LPF_EN,
+			  RG_HDMITX_PLL_BIAS_LPF_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_TXDIV_EN,
+			  RG_HDMITX_PLL_TXDIV_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, RG_HDMITX_SER_EN,
+			  RG_HDMITX_SER_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, RG_HDMITX_PRD_EN,
+			  RG_HDMITX_PRD_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, RG_HDMITX_DRV_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_mask(hdmi_phy, HDMI_CON3, 0, RG_HDMITX_DRV_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, 0, RG_HDMITX_PRD_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, 0, RG_HDMITX_SER_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, 0, RG_HDMITX_PLL_TXDIV_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, 0, RG_HDMITX_PLL_BIAS_LPF_EN);
+	usleep_range(100, 150);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, 0, RG_HDMITX_PLL_EN);
+	usleep_range(100, 150);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, 0, RG_HDMITX_PLL_BIAS_EN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, 0, RG_HDMITX_PLL_POSDIV);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, 0, RG_HDMITX_PLL_AUTOK_EN);
+	usleep_range(100, 150);
+}
+
+void mtk_hdmi_phy_set_pll(struct phy *phy, u32 clock,
+			  enum hdmi_display_color_depth depth)
+{
+	struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy);
+	unsigned int ibias;
+	unsigned int pix;
+
+	/* FIXME - get rate from PLL clock */
+	if (clock <= 27000000)
+		pix = 0;
+	else if (clock <= 74000000)
+		pix = 1;
+	else
+		pix = 2;
+
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+			  ((PREDIV[pix][depth]) << PREDIV_SHIFT),
+			  RG_HDMITX_PLL_PREDIV);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_POSDIV,
+			  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,
+			  ((TXDIV[pix][depth]) << PLL_TXDIV_SHIFT),
+			  RG_HDMITX_PLL_TXDIV);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+			  ((FBKSEL[pix][depth]) << PLL_FBKSEL_SHIFT) |
+			  ((FBKDIV[pix][depth]) << PLL_FBKDIV_SHIFT),
+			  RG_HDMITX_PLL_FBKSEL | RG_HDMITX_PLL_FBKDIV);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1,
+			  ((DIVEN[pix][depth]) << PLL_DIVEN_SHIFT),
+			  RG_HDMITX_PLL_DIVEN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+			  ((HTPLLBP[pix][depth]) << PLL_BP_SHIFT) |
+			  ((HTPLLBC[pix][depth]) << PLL_BC_SHIFT) |
+			  ((HTPLLBR[pix][depth]) << PLL_BR_SHIFT),
+			  RG_HDMITX_PLL_BP | RG_HDMITX_PLL_BC |
+			  RG_HDMITX_PLL_BR);
+
+	if ((pix == 2) && (depth != HDMI_DEEP_COLOR_24BITS)) {
+		mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, RG_HDMITX_PRD_IMP_EN,
+				  RG_HDMITX_PRD_IMP_EN);
+		mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON4,
+				  (0x6 << PRD_IBIAS_CLK_SHIFT) |
+				  (0x6 << PRD_IBIAS_D2_SHIFT) |
+				  (0x6 << PRD_IBIAS_D1_SHIFT) |
+				  (0x6 << 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,
+				  0xf << DRV_IMP_EN_SHIFT,
+				  RG_HDMITX_DRV_IMP_EN);
+		ibias = hdmi_phy->ibias_up;
+	} else {
+		mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, 0, 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);
+		ibias = hdmi_phy->ibias;
+	}
+
+	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,
+			  (ibias << DRV_IBIAS_CLK_SHIFT) |
+			  (ibias << DRV_IBIAS_D2_SHIFT) |
+			  (ibias << DRV_IBIAS_D1_SHIFT) |
+			  (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);
+}
+
+static int mtk_hdmi_phy_power_on(struct phy *phy)
+{
+	struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy);
+
+	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);
+
+	return 0;
+}
+
+static 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 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;
+	}
+
+	ret = of_property_read_u32(dev->of_node, "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, "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);
+
+	return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+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 at mediatek.com>");
+MODULE_DESCRIPTION("MediaTek HDMI PHY Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_phy.h b/drivers/gpu/drm/mediatek/mtk_hdmi_phy.h
new file mode 100644
index 0000000..ff3e95a
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_phy.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu at 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_PHY_H
+#define _MTK_HDMI_PHY_H
+
+void mtk_hdmi_phy_set_pll(struct phy *phy, u32 clock,
+			  enum hdmi_display_color_depth depth);
+
+#endif /* _MTK_HDMI_PHY_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
new file mode 100644
index 0000000..baf629b
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu at 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 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 SACD_SEL	BIT(7)
+#define GRL_NCTS            (0x184)
+#define GRL_CH_SW0          0x18C
+#define GRL_CH_SW1          0x190
+#define GRL_CH_SW2          0x194
+#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_BNUM_SEL    0x1C4
+#define AOUT_24BIT         0x00
+#define AOUT_20BIT         0x02
+#define AOUT_16BIT         0x03
+#define HIGH_BIT_RATE_PACKET_ALIGN (0x3 << 6)
+#define GRL_SHIFT_L1	(0x1C0)
+#define GRL_SHIFT_R2	(0x1B0)
+#define AUDIO_PACKET_OFF    BIT(6)
+#define GRL_CFG0            0x24
+#define CFG0_I2S_MODE_RTJ	0x1
+#define CFG0_I2S_MODE_LTJ	0x0
+#define CFG0_I2S_MODE_I2S	0x2
+#define CFG0_I2S_MODE_24BIT	0x00
+#define CFG0_I2S_MODE_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_NOTICE_EN	BIT(6)
+#define MHL_DE_SEL	BIT(3)
+#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 CFG_MHL_MODE BIT(7)
+#define GRL_CFG5	0x38
+#define CFG5_CD_RATIO_MASK	0x8F
+#define CFG5_FS128	BIT(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   BIT(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 BIT(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	BIT(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)
+#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	0x11c
+#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)
+
+#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)
+
+#endif
diff --git a/include/drm/mediatek/mtk_hdmi_audio.h b/include/drm/mediatek/mtk_hdmi_audio.h
new file mode 100644
index 0000000..2ae64d8
--- /dev/null
+++ b/include/drm/mediatek/mtk_hdmi_audio.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu at 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_AUDIO_H
+#define _MTK_HDMI_AUDIO_H
+
+#include <linux/hdmi.h>
+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_iec_frame_rate {
+	HDMI_IEC_32K = 0,
+	HDMI_IEC_96K,
+	HDMI_IEC_192K,
+	HDMI_IEC_768K,
+	HDMI_IEC_44K,
+	HDMI_IEC_88K,
+	HDMI_IEC_176K,
+	HDMI_IEC_705K,
+	HDMI_IEC_16K,
+	HDMI_IEC_22K,
+	HDMI_IEC_24K,
+	HDMI_IEC_48K,
+};
+
+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_frequency aud_hdmi_fs;
+	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_iec_frame_rate iec_frame_fs;
+	enum hdmi_aud_channel_type aud_input_chan_type;
+	u8 hdmi_l_channel_state[6];
+	u8 hdmi_r_channel_state[6];
+};
+
+struct mtk_hdmi;
+
+struct mtk_hdmi_audio_data {
+	int irq;
+	struct mtk_hdmi *mtk_hdmi;
+	int (*detect_dvi_monitor)(struct mtk_hdmi *hctx);
+	int (*hpd_detect)(struct mtk_hdmi *hctx);
+	void (*enable)(struct mtk_hdmi *hctx);
+	void (*disable)(struct mtk_hdmi *hctx);
+	int (*set_audio_param)(struct mtk_hdmi *hctx,
+				struct hdmi_audio_param *param);
+};
+
+#endif /* _MTK_HDMI_AUDIO_H */
-- 
2.5.3



More information about the dri-devel mailing list