diff mbox

[v2,07/13] drm/meson: Add support for HDMI encoder and DW-HDMI bridge + PHY

Message ID 1490109950-21421-8-git-send-email-narmstrong@baylibre.com (mailing list archive)
State New, archived
Headers show

Commit Message

Neil Armstrong March 21, 2017, 3:25 p.m. UTC
The Amlogic Meson GXBB/GXL/GXM SoCs embeds a Synopsys DesignWare HDMI TX
Controller with a custom Bridge + PHY around the Controller.

This driver makes uses of all the custom PHY plat data callbacks and enables
the compatible HDMI modes to be configured as a drm_encoder instance.

Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
---
 drivers/gpu/drm/meson/Kconfig         |   6 +
 drivers/gpu/drm/meson/Makefile        |   1 +
 drivers/gpu/drm/meson/meson_drv.h     |   3 +
 drivers/gpu/drm/meson/meson_dw_hdmi.c | 910 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/meson/meson_dw_hdmi.h | 146 ++++++
 5 files changed, 1066 insertions(+)
 create mode 100644 drivers/gpu/drm/meson/meson_dw_hdmi.c
 create mode 100644 drivers/gpu/drm/meson/meson_dw_hdmi.h

Comments

Daniel Vetter April 4, 2017, 8:57 a.m. UTC | #1
On Tue, Mar 21, 2017 at 04:25:44PM +0100, Neil Armstrong wrote:
> The Amlogic Meson GXBB/GXL/GXM SoCs embeds a Synopsys DesignWare HDMI TX
> Controller with a custom Bridge + PHY around the Controller.
> 
> This driver makes uses of all the custom PHY plat data callbacks and enables
> the compatible HDMI modes to be configured as a drm_encoder instance.
> 
> Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>

[snip]

> +static int meson_venc_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
> +					struct drm_crtc_state *crtc_state,
> +					struct drm_connector_state *conn_state)
> +{
> +	return 0;
> +}

Given the over-the-top complicated mode encoding you seem to have, this
feels like it's a bit too simply.

> +
> +static void meson_venc_hdmi_encoder_disable(struct drm_encoder *encoder)
> +{
> +	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
> +	struct meson_drm *priv = dw_hdmi->priv;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +
> +	writel_bits_relaxed(0x3, 0,
> +			    priv->io_base + _REG(VPU_HDMI_SETTING));
> +
> +	writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN));
> +	writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN));
> +}
> +
> +static void meson_venc_hdmi_encoder_enable(struct drm_encoder *encoder)
> +{
> +	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
> +	struct meson_drm *priv = dw_hdmi->priv;
> +
> +	DRM_DEBUG_DRIVER("%s\n", priv->venc.hdmi_use_enci ? "VENCI" : "VENCP");
> +
> +	if (priv->venc.hdmi_use_enci)
> +		writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN));
> +	else
> +		writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN));
> +}
> +
> +static void meson_venc_hdmi_encoder_mode_set(struct drm_encoder *encoder,
> +				   struct drm_display_mode *mode,
> +				   struct drm_display_mode *adjusted_mode)
> +{
> +	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
> +	struct meson_drm *priv = dw_hdmi->priv;
> +	int vic = drm_match_cea_mode(mode);
> +
> +	DRM_DEBUG_DRIVER("%d:\"%s\" vic %d\n",
> +			 mode->base.id, mode->name, vic);
> +
> +	/* Should have been filtered */
> +	if (!vic)
> +		return;
> +
> +	/* VENC + VENC-DVI Mode setup */
> +	meson_venc_hdmi_mode_set(priv, vic, mode);

So this calls a different module which export_symbol_gpls that thing. I
have no idea why arm-soc people love modularized-to-the-function level
drivers, but it feels over the top. amd/nouveau/i915 all smash everything
into one driver, makes life so much easier.

Note: bridge drivers as separate .ko makes sense, but separate .ko for
every single functional unit in your vendor IP imo totally doesn't.

Not going to stop you either :-)
-Daniel
Neil Armstrong April 4, 2017, 9:16 a.m. UTC | #2
On 04/04/2017 10:57 AM, Daniel Vetter wrote:
> On Tue, Mar 21, 2017 at 04:25:44PM +0100, Neil Armstrong wrote:
>> The Amlogic Meson GXBB/GXL/GXM SoCs embeds a Synopsys DesignWare HDMI TX
>> Controller with a custom Bridge + PHY around the Controller.
>>
>> This driver makes uses of all the custom PHY plat data callbacks and enables
>> the compatible HDMI modes to be configured as a drm_encoder instance.
>>
>> Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
> 
> [snip]
> 
>> +static int meson_venc_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
>> +					struct drm_crtc_state *crtc_state,
>> +					struct drm_connector_state *conn_state)
>> +{
>> +	return 0;
>> +}
> 
> Given the over-the-top complicated mode encoding you seem to have, this
> feels like it's a bit too simply.

Indeed, but the HW is really weird, every supported modes have very specific
timings/parameters so moving the mode validation code from the dw-hdmi mode_valid
to here would only make sense if we need to support a different HDMI controller.

But you are right, but I would have preferred to have a better HW for sure...

> 
>> +
>> +static void meson_venc_hdmi_encoder_disable(struct drm_encoder *encoder)
>> +{
>> +	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
>> +	struct meson_drm *priv = dw_hdmi->priv;
>> +
>> +	DRM_DEBUG_DRIVER("\n");
>> +
>> +	writel_bits_relaxed(0x3, 0,
>> +			    priv->io_base + _REG(VPU_HDMI_SETTING));
>> +
>> +	writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN));
>> +	writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN));
>> +}
>> +
>> +static void meson_venc_hdmi_encoder_enable(struct drm_encoder *encoder)
>> +{
>> +	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
>> +	struct meson_drm *priv = dw_hdmi->priv;
>> +
>> +	DRM_DEBUG_DRIVER("%s\n", priv->venc.hdmi_use_enci ? "VENCI" : "VENCP");
>> +
>> +	if (priv->venc.hdmi_use_enci)
>> +		writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN));
>> +	else
>> +		writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN));
>> +}
>> +
>> +static void meson_venc_hdmi_encoder_mode_set(struct drm_encoder *encoder,
>> +				   struct drm_display_mode *mode,
>> +				   struct drm_display_mode *adjusted_mode)
>> +{
>> +	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
>> +	struct meson_drm *priv = dw_hdmi->priv;
>> +	int vic = drm_match_cea_mode(mode);
>> +
>> +	DRM_DEBUG_DRIVER("%d:\"%s\" vic %d\n",
>> +			 mode->base.id, mode->name, vic);
>> +
>> +	/* Should have been filtered */
>> +	if (!vic)
>> +		return;
>> +
>> +	/* VENC + VENC-DVI Mode setup */
>> +	meson_venc_hdmi_mode_set(priv, vic, mode);
> 
> So this calls a different module which export_symbol_gpls that thing. I
> have no idea why arm-soc people love modularized-to-the-function level
> drivers, but it feels over the top. amd/nouveau/i915 all smash everything
> into one driver, makes life so much easier.

I know, we are doomed on that !
But here, since the wrapping around the dw-hdmi controller is completely custom
if was necessary to add a separate encoder tied to HDMI and have the physical
encoding code in the common driver.
Note that the platform is also able to driver a LCD via LVDS, so this encoder code
should be reusable here.

> 
> Note: bridge drivers as separate .ko makes sense, but separate .ko for
> every single functional unit in your vendor IP imo totally doesn't.

Actually I added a global ko for the "DRM" driver with crtc, planes and CVBS,
and another ko for the HDMI bridge wrapping.

> 
> Not going to stop you either :-)

I totally agree on the complexity here !

> -Daniel
>
Daniel Vetter April 4, 2017, 1:50 p.m. UTC | #3
On Tue, Apr 04, 2017 at 11:16:23AM +0200, Neil Armstrong wrote:
> On 04/04/2017 10:57 AM, Daniel Vetter wrote:
> > On Tue, Mar 21, 2017 at 04:25:44PM +0100, Neil Armstrong wrote:
> >> The Amlogic Meson GXBB/GXL/GXM SoCs embeds a Synopsys DesignWare HDMI TX
> >> Controller with a custom Bridge + PHY around the Controller.
> >>
> >> This driver makes uses of all the custom PHY plat data callbacks and enables
> >> the compatible HDMI modes to be configured as a drm_encoder instance.
> >>
> >> Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
> > 
> > [snip]
> > 
> >> +static int meson_venc_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
> >> +					struct drm_crtc_state *crtc_state,
> >> +					struct drm_connector_state *conn_state)
> >> +{
> >> +	return 0;
> >> +}
> > 
> > Given the over-the-top complicated mode encoding you seem to have, this
> > feels like it's a bit too simply.
> 
> Indeed, but the HW is really weird, every supported modes have very specific
> timings/parameters so moving the mode validation code from the dw-hdmi mode_valid
> to here would only make sense if we need to support a different HDMI controller.
> 
> But you are right, but I would have preferred to have a better HW for sure...

Oh, if your constraints on the meson encoder match what dw-hdmi needs,
then the mode_valid checks in there are good enough. A comment might be
good in that case.

But it looked to me (at a very cursory glance) that the meson encoder has
some additional fun restrictions on top.

> >> +
> >> +static void meson_venc_hdmi_encoder_disable(struct drm_encoder *encoder)
> >> +{
> >> +	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
> >> +	struct meson_drm *priv = dw_hdmi->priv;
> >> +
> >> +	DRM_DEBUG_DRIVER("\n");
> >> +
> >> +	writel_bits_relaxed(0x3, 0,
> >> +			    priv->io_base + _REG(VPU_HDMI_SETTING));
> >> +
> >> +	writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN));
> >> +	writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN));
> >> +}
> >> +
> >> +static void meson_venc_hdmi_encoder_enable(struct drm_encoder *encoder)
> >> +{
> >> +	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
> >> +	struct meson_drm *priv = dw_hdmi->priv;
> >> +
> >> +	DRM_DEBUG_DRIVER("%s\n", priv->venc.hdmi_use_enci ? "VENCI" : "VENCP");
> >> +
> >> +	if (priv->venc.hdmi_use_enci)
> >> +		writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN));
> >> +	else
> >> +		writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN));
> >> +}
> >> +
> >> +static void meson_venc_hdmi_encoder_mode_set(struct drm_encoder *encoder,
> >> +				   struct drm_display_mode *mode,
> >> +				   struct drm_display_mode *adjusted_mode)
> >> +{
> >> +	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
> >> +	struct meson_drm *priv = dw_hdmi->priv;
> >> +	int vic = drm_match_cea_mode(mode);
> >> +
> >> +	DRM_DEBUG_DRIVER("%d:\"%s\" vic %d\n",
> >> +			 mode->base.id, mode->name, vic);
> >> +
> >> +	/* Should have been filtered */
> >> +	if (!vic)
> >> +		return;
> >> +
> >> +	/* VENC + VENC-DVI Mode setup */
> >> +	meson_venc_hdmi_mode_set(priv, vic, mode);
> > 
> > So this calls a different module which export_symbol_gpls that thing. I
> > have no idea why arm-soc people love modularized-to-the-function level
> > drivers, but it feels over the top. amd/nouveau/i915 all smash everything
> > into one driver, makes life so much easier.
> 
> I know, we are doomed on that !
> But here, since the wrapping around the dw-hdmi controller is completely custom
> if was necessary to add a separate encoder tied to HDMI and have the physical
> encoding code in the common driver.
> Note that the platform is also able to driver a LCD via LVDS, so this encoder code
> should be reusable here.

I'm not talking about the custom encoder or anything like that, or code
reuse. I'm talking about doing piles of separate .ko when you could have
just one (with a bunch of component drivers contained within). At least
this is how the really big drivers all work.

Of course shared ip (like the dw-hdmi bridge driver) need to be in
separate .ko, that part completely makes sense.

> > Note: bridge drivers as separate .ko makes sense, but separate .ko for
> > every single functional unit in your vendor IP imo totally doesn't.
> 
> Actually I added a global ko for the "DRM" driver with crtc, planes and CVBS,
> and another ko for the HDMI bridge wrapping.

Or maybe I just misunderstood things, but meson_venc_hdmi_mode_set looks
like it could be pulled into this module?
-Daniel
diff mbox

Patch

diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig
index 99719af..3ce51d8 100644
--- a/drivers/gpu/drm/meson/Kconfig
+++ b/drivers/gpu/drm/meson/Kconfig
@@ -7,3 +7,9 @@  config DRM_MESON
 	select DRM_GEM_CMA_HELPER
 	select VIDEOMODE_HELPERS
 	select REGMAP_MMIO
+
+config DRM_MESON_DW_HDMI
+	tristate "HDMI Synopsys Controller support for Amlogic Meson Display"
+	depends on DRM_MESON
+	default y if DRM_MESON
+	select DRM_DW_HDMI
diff --git a/drivers/gpu/drm/meson/Makefile b/drivers/gpu/drm/meson/Makefile
index 92cf845..c5c4cc3 100644
--- a/drivers/gpu/drm/meson/Makefile
+++ b/drivers/gpu/drm/meson/Makefile
@@ -2,3 +2,4 @@  meson-drm-y := meson_drv.o meson_plane.o meson_crtc.o meson_venc_cvbs.o
 meson-drm-y += meson_viu.o meson_vpp.o meson_venc.o meson_vclk.o meson_canvas.o
 
 obj-$(CONFIG_DRM_MESON) += meson-drm.o
+obj-$(CONFIG_DRM_MESON_DW_HDMI) += meson_dw_hdmi.o
diff --git a/drivers/gpu/drm/meson/meson_drv.h b/drivers/gpu/drm/meson/meson_drv.h
index 6195327..5e8b392 100644
--- a/drivers/gpu/drm/meson/meson_drv.h
+++ b/drivers/gpu/drm/meson/meson_drv.h
@@ -47,6 +47,9 @@  struct meson_drm {
 
 	struct {
 		unsigned int current_mode;
+		bool hdmi_repeat;
+		bool venc_repeat;
+		bool hdmi_use_enci;
 	} venc;
 };
 
diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c
new file mode 100644
index 0000000..8851dcb
--- /dev/null
+++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c
@@ -0,0 +1,910 @@ 
+/*
+ * Copyright (C) 2016 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/component.h>
+#include <linux/of_graph.h>
+#include <linux/reset.h>
+#include <linux/clk.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/bridge/dw_hdmi.h>
+
+#include <uapi/linux/media-bus-format.h>
+#include <uapi/linux/videodev2.h>
+
+#include "meson_drv.h"
+#include "meson_venc.h"
+#include "meson_vclk.h"
+#include "meson_dw_hdmi.h"
+#include "meson_registers.h"
+
+#define DRIVER_NAME "meson-dw-hdmi"
+#define DRIVER_DESC "Amlogic Meson HDMI-TX DRM driver"
+
+/*
+ * HDMI Output is composed of :
+ * - A Synopsys DesignWare HDMI Controller IP
+ * - A TOP control block controlling the Clocks and PHY
+ * - A custom HDMI PHY in order convert video to TMDS signal
+ *  ___________________________________
+ * |            HDMI TOP               |<= HPD
+ * |___________________________________|
+ * |                  |                |
+ * |  Synopsys HDMI   |   HDMI PHY     |=> TMDS
+ * |    Controller    |________________|
+ * |___________________________________|<=> DDC
+ *
+ * The HDMI TOP block only supports HPD sensing.
+ * The Synopsys HDMI Controller interrupt is routed
+ * through the TOP Block interrupt.
+ * Communication to the TOP Block and the Synopsys
+ * HDMI Controller is done a pair of addr+read/write
+ * registers.
+ * The HDMI PHY is configured by registers in the
+ * HHI register block.
+ *
+ * Pixel data arrives in 4:4:4 format from the VENC
+ * block and the VPU HDMI mux selects either the ENCI
+ * encoder for the 576i or 480i formats or the ENCP
+ * encoder for all the other formats including
+ * interlaced HD formats.
+ * The VENC uses a DVI encoder on top of the ENCI
+ * or ENCP encoders to generate DVI timings for the
+ * HDMI controller.
+ *
+ * GXBB, GXL and GXM embeds the Synopsys DesignWare
+ * HDMI TX IP version 2.01a with HDCP and I2C & S/PDIF
+ * audio source interfaces.
+ *
+ * We handle the following features :
+ * - HPD Rise & Fall interrupt
+ * - HDMI Controller Interrupt
+ * - HDMI PHY Init for 480i to 1080p60
+ * - VENC & HDMI Clock setup for 480i to 1080p60
+ * - VENC Mode setup for 480i to 1080p60
+ *
+ * What is missing :
+ * - PHY, Clock and Mode setup for 2k && 4k modes
+ * - SDDC Scrambling mode for HDMI 2.0a
+ * - HDCP Setup
+ * - CEC Management
+ */
+
+/* TOP Block Communication Channel */
+#define HDMITX_TOP_ADDR_REG	0x0
+#define HDMITX_TOP_DATA_REG	0x4
+#define HDMITX_TOP_CTRL_REG	0x8
+
+/* Controller Communication Channel */
+#define HDMITX_DWC_ADDR_REG	0x10
+#define HDMITX_DWC_DATA_REG	0x14
+#define HDMITX_DWC_CTRL_REG	0x18
+
+/* HHI Registers */
+#define HHI_MEM_PD_REG0		0x100 /* 0x40 */
+#define HHI_HDMI_CLK_CNTL	0x1cc /* 0x73 */
+#define HHI_HDMI_PHY_CNTL0	0x3a0 /* 0xe8 */
+#define HHI_HDMI_PHY_CNTL1	0x3a4 /* 0xe9 */
+#define HHI_HDMI_PHY_CNTL2	0x3a8 /* 0xea */
+#define HHI_HDMI_PHY_CNTL3	0x3ac /* 0xeb */
+
+static DEFINE_SPINLOCK(reg_lock);
+
+enum meson_venc_source {
+	MESON_VENC_SOURCE_NONE = 0,
+	MESON_VENC_SOURCE_ENCI = 1,
+	MESON_VENC_SOURCE_ENCP = 2,
+};
+
+struct meson_dw_hdmi {
+	struct drm_encoder encoder;
+	struct dw_hdmi_plat_data dw_plat_data;
+	struct meson_drm *priv;
+	struct device *dev;
+	void __iomem *hdmitx;
+	struct reset_control *hdmitx_apb;
+	struct reset_control *hdmitx_ctrl;
+	struct reset_control *hdmitx_phy;
+	struct clk *hdmi_pclk;
+	struct clk *venci_clk;
+	u32 irq_stat;
+};
+#define encoder_to_meson_dw_hdmi(x) \
+	container_of(x, struct meson_dw_hdmi, encoder)
+
+static inline int dw_hdmi_is_compatible(struct meson_dw_hdmi *dw_hdmi,
+					const char *compat)
+{
+	return of_device_is_compatible(dw_hdmi->dev->of_node, compat);
+}
+
+/* PHY (via TOP bridge) and Controller dedicated register interface */
+
+static unsigned int dw_hdmi_top_read(struct meson_dw_hdmi *dw_hdmi,
+				     unsigned int addr)
+{
+	unsigned long flags;
+	unsigned int data;
+
+	spin_lock_irqsave(&reg_lock, flags);
+
+	/* ADDR must be written twice */
+	writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG);
+	writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG);
+
+	/* Read needs a second DATA read */
+	data = readl(dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG);
+	data = readl(dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG);
+
+	spin_unlock_irqrestore(&reg_lock, flags);
+
+	return data;
+}
+
+static inline void dw_hdmi_top_write(struct meson_dw_hdmi *dw_hdmi,
+				     unsigned int addr, unsigned int data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&reg_lock, flags);
+
+	/* ADDR must be written twice */
+	writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG);
+	writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG);
+
+	/* Write needs single DATA write */
+	writel(data, dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG);
+
+	spin_unlock_irqrestore(&reg_lock, flags);
+}
+
+/* Helper to change specific bits in PHY registers */
+static inline void dw_hdmi_top_write_bits(struct meson_dw_hdmi *dw_hdmi,
+					  unsigned int addr,
+					  unsigned int mask,
+					  unsigned int val)
+{
+	unsigned int data = dw_hdmi_top_read(dw_hdmi, addr);
+
+	data &= ~mask;
+	data |= val;
+
+	dw_hdmi_top_write(dw_hdmi, addr, data);
+}
+
+static unsigned int dw_hdmi_dwc_read(struct meson_dw_hdmi *dw_hdmi,
+				     unsigned int addr)
+{
+	unsigned long flags;
+	unsigned int data;
+
+	spin_lock_irqsave(&reg_lock, flags);
+
+	/* ADDR must be written twice */
+	writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG);
+	writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG);
+
+	/* Read needs a second DATA read */
+	data = readl(dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG);
+	data = readl(dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG);
+
+	spin_unlock_irqrestore(&reg_lock, flags);
+
+	return data;
+}
+
+static inline void dw_hdmi_dwc_write(struct meson_dw_hdmi *dw_hdmi,
+				     unsigned int addr, unsigned int data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&reg_lock, flags);
+
+	/* ADDR must be written twice */
+	writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG);
+	writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG);
+
+	/* Write needs single DATA write */
+	writel(data, dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG);
+
+	spin_unlock_irqrestore(&reg_lock, flags);
+}
+
+/* Helper to change specific bits in controller registers */
+static inline void dw_hdmi_dwc_write_bits(struct meson_dw_hdmi *dw_hdmi,
+					  unsigned int addr,
+					  unsigned int mask,
+					  unsigned int val)
+{
+	unsigned int data = dw_hdmi_dwc_read(dw_hdmi, addr);
+
+	data &= ~mask;
+	data |= val;
+
+	dw_hdmi_dwc_write(dw_hdmi, addr, data);
+}
+
+/* Bridge */
+
+/* Setup PHY bandwidth modes */
+static void meson_hdmi_phy_setup_mode(struct meson_dw_hdmi *dw_hdmi,
+				      struct drm_display_mode *mode)
+{
+	struct meson_drm *priv = dw_hdmi->priv;
+	unsigned int pixel_clock = mode->clock;
+
+	if (dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxl-dw-hdmi") ||
+	    dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxm-dw-hdmi")) {
+		if (pixel_clock >= 371250) {
+			/* 5.94Gbps, 3.7125Gbps */
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x333d3282);
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2136315b);
+		} else if (pixel_clock >= 297000) {
+			/* 2.97Gbps */
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33303382);
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2036315b);
+		} else if (pixel_clock >= 148500) {
+			/* 1.485Gbps */
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33303362);
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2016315b);
+		} else {
+			/* 742.5Mbps, and below */
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33604142);
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x0016315b);
+		}
+	} else if (dw_hdmi_is_compatible(dw_hdmi,
+					 "amlogic,meson-gxbb-dw-hdmi")) {
+		if (pixel_clock >= 371250) {
+			/* 5.94Gbps, 3.7125Gbps */
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33353245);
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2100115b);
+		} else if (pixel_clock >= 297000) {
+			/* 2.97Gbps */
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33634283);
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0xb000115b);
+		} else {
+			/* 1.485Gbps, and below */
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33632122);
+			regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2000115b);
+		}
+	}
+}
+
+static inline void dw_hdmi_phy_reset(struct meson_dw_hdmi *dw_hdmi)
+{
+	struct meson_drm *priv = dw_hdmi->priv;
+
+	/* Enable and software reset */
+	regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0xf);
+
+	mdelay(2);
+
+	/* Enable and unreset */
+	regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0xe);
+
+	mdelay(2);
+}
+
+static void dw_hdmi_set_vclk(struct meson_dw_hdmi *dw_hdmi,
+			     struct drm_display_mode *mode)
+{
+	struct meson_drm *priv = dw_hdmi->priv;
+	int vic = drm_match_cea_mode(mode);
+	unsigned int vclk_freq;
+	unsigned int venc_freq;
+	unsigned int hdmi_freq;
+
+	vclk_freq = mode->clock;
+
+	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+		vclk_freq *= 2;
+
+	venc_freq = vclk_freq;
+	hdmi_freq = vclk_freq;
+
+	if (meson_venc_hdmi_venc_repeat(vic))
+		venc_freq *= 2;
+
+	vclk_freq = max(venc_freq, hdmi_freq);
+
+	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+		venc_freq /= 2;
+
+	DRM_DEBUG_DRIVER("vclk:%d venc=%d hdmi=%d enci=%d\n",
+		vclk_freq, venc_freq, hdmi_freq,
+		priv->venc.hdmi_use_enci);
+
+	meson_vclk_setup(priv, MESON_VCLK_TARGET_HDMI, vclk_freq,
+			 venc_freq, hdmi_freq, priv->venc.hdmi_use_enci);
+}
+
+static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data,
+			    struct drm_display_mode *mode)
+{
+	struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data;
+	struct meson_drm *priv = dw_hdmi->priv;
+	unsigned int wr_clk =
+		readl_relaxed(priv->io_base + _REG(VPU_HDMI_SETTING));
+
+	DRM_DEBUG_DRIVER("%d:\"%s\"\n", mode->base.id, mode->name);
+
+	/* Enable clocks */
+	regmap_update_bits(priv->hhi, HHI_HDMI_CLK_CNTL, 0xffff, 0x100);
+
+	/* Bring HDMITX MEM output of power down */
+	regmap_update_bits(priv->hhi, HHI_MEM_PD_REG0, 0xff << 8, 0);
+
+	/* Bring out of reset */
+	dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_SW_RESET,  0);
+
+	/* Enable internal pixclk, tmds_clk, spdif_clk, i2s_clk, cecclk */
+	dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_CLK_CNTL,
+			       0x3, 0x3);
+	dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_CLK_CNTL,
+			       0x3 << 4, 0x3 << 4);
+
+	/* Enable normal output to PHY */
+	dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_BIST_CNTL, BIT(12));
+
+	/* TMDS pattern setup (TOFIX pattern for 4k2k scrambling) */
+	dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, 0x001f001f);
+	dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, 0x001f001f);
+
+	/* Load TMDS pattern */
+	dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x1);
+	msleep(20);
+	dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x2);
+
+	/* Setup PHY parameters */
+	meson_hdmi_phy_setup_mode(dw_hdmi, mode);
+
+	/* Setup PHY */
+	regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1,
+			   0xffff << 16, 0x0390 << 16);
+
+	/* BIT_INVERT */
+	if (dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxl-dw-hdmi") ||
+	    dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxm-dw-hdmi"))
+		regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1,
+				   BIT(17), 0);
+	else
+		regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1,
+				   BIT(17), BIT(17));
+
+	/* Disable clock, fifo, fifo_wr */
+	regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0);
+
+	msleep(100);
+
+	/* Reset PHY 3 times in a row */
+	dw_hdmi_phy_reset(dw_hdmi);
+	dw_hdmi_phy_reset(dw_hdmi);
+	dw_hdmi_phy_reset(dw_hdmi);
+
+	/* Temporary Disable VENC video stream */
+	if (priv->venc.hdmi_use_enci)
+		writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN));
+	else
+		writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN));
+
+	/* Temporary Disable HDMI video stream to HDMI-TX */
+	writel_bits_relaxed(0x3, 0,
+			    priv->io_base + _REG(VPU_HDMI_SETTING));
+	writel_bits_relaxed(0xf << 8, 0,
+			    priv->io_base + _REG(VPU_HDMI_SETTING));
+
+	/* Re-Enable VENC video stream */
+	if (priv->venc.hdmi_use_enci)
+		writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN));
+	else
+		writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN));
+
+	/* Push back HDMI clock settings */
+	writel_bits_relaxed(0xf << 8, wr_clk & (0xf << 8),
+			    priv->io_base + _REG(VPU_HDMI_SETTING));
+
+	/* Enable and Select HDMI video source for HDMI-TX */
+	if (priv->venc.hdmi_use_enci)
+		writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCI,
+				    priv->io_base + _REG(VPU_HDMI_SETTING));
+	else
+		writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCP,
+				    priv->io_base + _REG(VPU_HDMI_SETTING));
+
+	return 0;
+}
+
+static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi,
+				void *data)
+{
+	struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data;
+	struct meson_drm *priv = dw_hdmi->priv;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0);
+}
+
+static enum drm_connector_status dw_hdmi_read_hpd(struct dw_hdmi *hdmi,
+			     void *data)
+{
+	struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data;
+
+	return !!dw_hdmi_top_read(dw_hdmi, HDMITX_TOP_STAT0) ?
+		connector_status_connected : connector_status_disconnected;
+}
+
+static void dw_hdmi_setup_hpd(struct dw_hdmi *hdmi,
+			      void *data)
+{
+	struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data;
+
+	/* Setup HPD Filter */
+	dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_HPD_FILTER,
+			  (0xa << 12) | 0xa0);
+
+	/* Clear interrupts */
+	dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR,
+			  HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL);
+
+	/* Unmask interrupts */
+	dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_INTR_MASKN,
+			HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL,
+			HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL);
+}
+
+static const struct dw_hdmi_phy_ops meson_dw_hdmi_phy_ops = {
+	.init = dw_hdmi_phy_init,
+	.disable = dw_hdmi_phy_disable,
+	.read_hpd = dw_hdmi_read_hpd,
+	.setup_hpd = dw_hdmi_setup_hpd,
+};
+
+static irqreturn_t dw_hdmi_top_irq(int irq, void *dev_id)
+{
+	struct meson_dw_hdmi *dw_hdmi = dev_id;
+	u32 stat;
+
+	stat = dw_hdmi_top_read(dw_hdmi, HDMITX_TOP_INTR_STAT);
+	dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, stat);
+
+	/* HPD Events, handle in the threaded interrupt handler */
+	if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) {
+		dw_hdmi->irq_stat = stat;
+		return IRQ_WAKE_THREAD;
+	}
+
+	/* HDMI Controller Interrupt */
+	if (stat & 1)
+		return IRQ_NONE;
+
+	/* TOFIX Handle HDCP Interrupts */
+
+	return IRQ_HANDLED;
+}
+
+/* Threaded interrupt handler to manage HPD events */
+static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id)
+{
+	struct meson_dw_hdmi *dw_hdmi = dev_id;
+	u32 stat = dw_hdmi->irq_stat;
+
+	/* HPD Events */
+	if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) {
+		bool hpd_connected = false;
+
+		if (stat & HDMITX_TOP_INTR_HPD_RISE)
+			hpd_connected = true;
+
+		dw_hdmi_setup_rx_sense(dw_hdmi->dev, hpd_connected,
+				       hpd_connected);
+
+		drm_helper_hpd_irq_event(dw_hdmi->encoder.dev);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/* TOFIX Enable support for non-vic modes */
+static enum drm_mode_status dw_hdmi_mode_valid(struct drm_connector *connector,
+					       struct drm_display_mode *mode)
+{
+	unsigned int vclk_freq;
+	unsigned int venc_freq;
+	unsigned int hdmi_freq;
+	int vic = drm_match_cea_mode(mode);
+
+	DRM_DEBUG_DRIVER("Modeline %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x\n",
+		mode->base.id, mode->name, mode->vrefresh, mode->clock,
+		mode->hdisplay, mode->hsync_start,
+		mode->hsync_end, mode->htotal,
+		mode->vdisplay, mode->vsync_start,
+		mode->vsync_end, mode->vtotal, mode->type, mode->flags);
+
+	/* For now, only accept VIC modes */
+	if (!vic)
+		return MODE_BAD;
+
+	/* For now, filter by supported VIC modes */
+	if (!meson_venc_hdmi_supported_vic(vic))
+		return MODE_BAD;
+
+	vclk_freq = mode->clock;
+
+	/* 480i/576i needs global pixel doubling */
+	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+		vclk_freq *= 2;
+
+	venc_freq = vclk_freq;
+	hdmi_freq = vclk_freq;
+
+	/* VENC double pixels for 1080i and 720p modes */
+	if (meson_venc_hdmi_venc_repeat(vic))
+		venc_freq *= 2;
+
+	vclk_freq = max(venc_freq, hdmi_freq);
+
+	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+		venc_freq /= 2;
+
+	dev_dbg(connector->dev->dev, "%s: vclk:%d venc=%d hdmi=%d\n", __func__,
+		vclk_freq, venc_freq, hdmi_freq);
+
+	/* Finally filter by configurable vclk frequencies */
+	switch (vclk_freq) {
+	case 54000:
+	case 74250:
+	case 148500:
+	case 297000:
+	case 594000:
+		return MODE_OK;
+	}
+
+	return MODE_CLOCK_RANGE;
+}
+
+/* Encoder */
+
+static void meson_venc_hdmi_encoder_destroy(struct drm_encoder *encoder)
+{
+	drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs meson_venc_hdmi_encoder_funcs = {
+	.destroy        = meson_venc_hdmi_encoder_destroy,
+};
+
+static int meson_venc_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state)
+{
+	return 0;
+}
+
+static void meson_venc_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
+	struct meson_drm *priv = dw_hdmi->priv;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	writel_bits_relaxed(0x3, 0,
+			    priv->io_base + _REG(VPU_HDMI_SETTING));
+
+	writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN));
+	writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN));
+}
+
+static void meson_venc_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
+	struct meson_drm *priv = dw_hdmi->priv;
+
+	DRM_DEBUG_DRIVER("%s\n", priv->venc.hdmi_use_enci ? "VENCI" : "VENCP");
+
+	if (priv->venc.hdmi_use_enci)
+		writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN));
+	else
+		writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN));
+}
+
+static void meson_venc_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+				   struct drm_display_mode *mode,
+				   struct drm_display_mode *adjusted_mode)
+{
+	struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder);
+	struct meson_drm *priv = dw_hdmi->priv;
+	int vic = drm_match_cea_mode(mode);
+
+	DRM_DEBUG_DRIVER("%d:\"%s\" vic %d\n",
+			 mode->base.id, mode->name, vic);
+
+	/* Should have been filtered */
+	if (!vic)
+		return;
+
+	/* VENC + VENC-DVI Mode setup */
+	meson_venc_hdmi_mode_set(priv, vic, mode);
+
+	/* VCLK Set clock */
+	dw_hdmi_set_vclk(dw_hdmi, mode);
+
+	/* Setup YUV444 to HDMI-TX, no 10bit diphering */
+	writel_relaxed(0, priv->io_base + _REG(VPU_HDMI_FMT_CTRL));
+}
+
+static const struct drm_encoder_helper_funcs
+				meson_venc_hdmi_encoder_helper_funcs = {
+	.atomic_check	= meson_venc_hdmi_encoder_atomic_check,
+	.disable	= meson_venc_hdmi_encoder_disable,
+	.enable		= meson_venc_hdmi_encoder_enable,
+	.mode_set	= meson_venc_hdmi_encoder_mode_set,
+};
+
+/* DW HDMI Regmap */
+
+static int meson_dw_hdmi_reg_read(void *context, unsigned int reg,
+				  unsigned int *result)
+{
+	*result = dw_hdmi_dwc_read(context, reg);
+
+	return 0;
+
+}
+
+static int meson_dw_hdmi_reg_write(void *context, unsigned int reg,
+				   unsigned int val)
+{
+	dw_hdmi_dwc_write(context, reg, val);
+
+	return 0;
+}
+
+static const struct regmap_config meson_dw_hdmi_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 8,
+	.reg_read = meson_dw_hdmi_reg_read,
+	.reg_write = meson_dw_hdmi_reg_write,
+	.max_register = 0x10000,
+};
+
+static bool meson_hdmi_connector_is_available(struct device *dev)
+{
+	struct device_node *ep, *remote;
+
+	/* HDMI Connector is on the second port, first endpoint */
+	ep = of_graph_get_endpoint_by_regs(dev->of_node, 1, 0);
+	if (!ep)
+		return false;
+
+	/* If the endpoint node exists, consider it enabled */
+	remote = of_graph_get_remote_port(ep);
+	if (remote) {
+		of_node_put(ep);
+		return true;
+	}
+
+	of_node_put(ep);
+	of_node_put(remote);
+
+	return false;
+}
+
+static int meson_dw_hdmi_bind(struct device *dev, struct device *master,
+				void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct meson_dw_hdmi *meson_dw_hdmi;
+	struct drm_device *drm = data;
+	struct meson_drm *priv = drm->dev_private;
+	struct dw_hdmi_plat_data *dw_plat_data;
+	struct drm_encoder *encoder;
+	struct resource *res;
+	int irq;
+	int ret;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	if (!meson_hdmi_connector_is_available(dev)) {
+		dev_info(drm->dev, "HDMI Output connector not available\n");
+		return -ENODEV;
+	}
+
+	meson_dw_hdmi = devm_kzalloc(dev, sizeof(*meson_dw_hdmi),
+				     GFP_KERNEL);
+	if (!meson_dw_hdmi)
+		return -ENOMEM;
+
+	meson_dw_hdmi->priv = priv;
+	meson_dw_hdmi->dev = dev;
+	dw_plat_data = &meson_dw_hdmi->dw_plat_data;
+	encoder = &meson_dw_hdmi->encoder;
+
+	meson_dw_hdmi->hdmitx_apb = devm_reset_control_get_exclusive(dev,
+						"hdmitx_apb");
+	if (IS_ERR(meson_dw_hdmi->hdmitx_apb)) {
+		dev_err(dev, "Failed to get hdmitx_apb reset\n");
+		return PTR_ERR(meson_dw_hdmi->hdmitx_apb);
+	}
+
+	meson_dw_hdmi->hdmitx_ctrl = devm_reset_control_get_exclusive(dev,
+						"hdmitx");
+	if (IS_ERR(meson_dw_hdmi->hdmitx_ctrl)) {
+		dev_err(dev, "Failed to get hdmitx reset\n");
+		return PTR_ERR(meson_dw_hdmi->hdmitx_ctrl);
+	}
+
+	meson_dw_hdmi->hdmitx_phy = devm_reset_control_get_exclusive(dev,
+						"hdmitx_phy");
+	if (IS_ERR(meson_dw_hdmi->hdmitx_phy)) {
+		dev_err(dev, "Failed to get hdmitx_phy reset\n");
+		return PTR_ERR(meson_dw_hdmi->hdmitx_phy);
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	meson_dw_hdmi->hdmitx = devm_ioremap_resource(dev, res);
+	if (IS_ERR(meson_dw_hdmi->hdmitx))
+		return PTR_ERR(meson_dw_hdmi->hdmitx);
+
+	meson_dw_hdmi->hdmi_pclk = devm_clk_get(dev, "isfr");
+	if (IS_ERR(meson_dw_hdmi->hdmi_pclk)) {
+		dev_err(dev, "Unable to get HDMI pclk\n");
+		return PTR_ERR(meson_dw_hdmi->hdmi_pclk);
+	}
+	clk_prepare_enable(meson_dw_hdmi->hdmi_pclk);
+
+	meson_dw_hdmi->venci_clk = devm_clk_get(dev, "venci");
+	if (IS_ERR(meson_dw_hdmi->venci_clk)) {
+		dev_err(dev, "Unable to get venci clk\n");
+		return PTR_ERR(meson_dw_hdmi->venci_clk);
+	}
+	clk_prepare_enable(meson_dw_hdmi->venci_clk);
+
+	dw_plat_data->regm = devm_regmap_init(dev, NULL, meson_dw_hdmi,
+					      &meson_dw_hdmi_regmap_config);
+	if (IS_ERR(dw_plat_data->regm))
+		return PTR_ERR(dw_plat_data->regm);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "Failed to get hdmi top irq\n");
+		return irq;
+	}
+
+	ret = devm_request_threaded_irq(dev, irq, dw_hdmi_top_irq,
+					dw_hdmi_top_thread_irq, IRQF_SHARED,
+					"dw_hdmi_top_irq", meson_dw_hdmi);
+	if (ret) {
+		dev_err(dev, "Failed to request hdmi top irq\n");
+		return ret;
+	}
+
+	/* Encoder */
+
+	drm_encoder_helper_add(encoder, &meson_venc_hdmi_encoder_helper_funcs);
+
+	ret = drm_encoder_init(drm, encoder, &meson_venc_hdmi_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, "meson_hdmi");
+	if (ret) {
+		dev_err(priv->dev, "Failed to init HDMI encoder\n");
+		return ret;
+	}
+
+	encoder->possible_crtcs = BIT(0);
+
+	DRM_DEBUG_DRIVER("encoder initialized\n");
+
+	/* Enable clocks */
+	regmap_update_bits(priv->hhi, HHI_HDMI_CLK_CNTL, 0xffff, 0x100);
+
+	/* Bring HDMITX MEM output of power down */
+	regmap_update_bits(priv->hhi, HHI_MEM_PD_REG0, 0xff << 8, 0);
+
+	/* Reset HDMITX APB & TX & PHY */
+	reset_control_reset(meson_dw_hdmi->hdmitx_apb);
+	reset_control_reset(meson_dw_hdmi->hdmitx_ctrl);
+	reset_control_reset(meson_dw_hdmi->hdmitx_phy);
+
+	/* Enable APB3 fail on error */
+	writel_bits_relaxed(BIT(15), BIT(15),
+			    meson_dw_hdmi->hdmitx + HDMITX_TOP_CTRL_REG);
+	writel_bits_relaxed(BIT(15), BIT(15),
+			    meson_dw_hdmi->hdmitx + HDMITX_DWC_CTRL_REG);
+
+	/* Bring out of reset */
+	dw_hdmi_top_write(meson_dw_hdmi, HDMITX_TOP_SW_RESET,  0);
+
+	msleep(20);
+
+	dw_hdmi_top_write(meson_dw_hdmi, HDMITX_TOP_CLK_CNTL, 0xff);
+
+	/* Enable HDMI-TX Interrupt */
+	dw_hdmi_top_write(meson_dw_hdmi, HDMITX_TOP_INTR_STAT_CLR,
+			  HDMITX_TOP_INTR_CORE);
+
+	dw_hdmi_top_write(meson_dw_hdmi, HDMITX_TOP_INTR_MASKN,
+			  HDMITX_TOP_INTR_CORE);
+
+	/* Bridge / Connector */
+
+	dw_plat_data->mode_valid = dw_hdmi_mode_valid;
+	dw_plat_data->phy_ops = &meson_dw_hdmi_phy_ops;
+	dw_plat_data->phy_name = "meson_dw_hdmi_phy";
+	dw_plat_data->phy_data = meson_dw_hdmi;
+	dw_plat_data->input_bus_format = MEDIA_BUS_FMT_YUV8_1X24;
+	dw_plat_data->input_bus_encoding = V4L2_YCBCR_ENC_709;
+
+	ret = dw_hdmi_bind(pdev, encoder, &meson_dw_hdmi->dw_plat_data);
+	if (ret)
+		return ret;
+
+	DRM_DEBUG_DRIVER("HDMI controller initialized\n");
+
+	return 0;
+}
+
+static void meson_dw_hdmi_unbind(struct device *dev, struct device *master,
+				   void *data)
+{
+	dw_hdmi_unbind(dev);
+}
+
+static const struct component_ops meson_dw_hdmi_ops = {
+	.bind	= meson_dw_hdmi_bind,
+	.unbind	= meson_dw_hdmi_unbind,
+};
+
+static int meson_dw_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &meson_dw_hdmi_ops);
+}
+
+static int meson_dw_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &meson_dw_hdmi_ops);
+
+	return 0;
+}
+
+static const struct of_device_id meson_dw_hdmi_of_table[] = {
+	{ .compatible = "amlogic,meson-gxbb-dw-hdmi" },
+	{ .compatible = "amlogic,meson-gxl-dw-hdmi" },
+	{ .compatible = "amlogic,meson-gxm-dw-hdmi" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, meson_dw_hdmi_of_table);
+
+static struct platform_driver meson_dw_hdmi_platform_driver = {
+	.probe		= meson_dw_hdmi_probe,
+	.remove		= meson_dw_hdmi_remove,
+	.driver		= {
+		.name		= DRIVER_NAME,
+		.of_match_table	= meson_dw_hdmi_of_table,
+	},
+};
+module_platform_driver(meson_dw_hdmi_platform_driver);
+
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.h b/drivers/gpu/drm/meson/meson_dw_hdmi.h
new file mode 100644
index 0000000..0b81183
--- /dev/null
+++ b/drivers/gpu/drm/meson/meson_dw_hdmi.h
@@ -0,0 +1,146 @@ 
+/*
+ * Copyright (C) 2016 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __MESON_DW_HDMI_H
+#define __MESON_DW_HDMI_H
+
+/*
+ * Bit 7 RW Reserved. Default 1.
+ * Bit 6 RW Reserved. Default 1.
+ * Bit 5 RW Reserved. Default 1.
+ * Bit 4 RW sw_reset_phyif: PHY interface. 1=Apply reset; 0=Release from reset.
+ *     Default 1.
+ * Bit 3 RW sw_reset_intr: interrupt module. 1=Apply reset;
+ *     0=Release from reset.
+ *     Default 1.
+ * Bit 2 RW sw_reset_mem: KSV/REVOC mem. 1=Apply reset; 0=Release from reset.
+ *     Default 1.
+ * Bit 1 RW sw_reset_rnd: random number interface to HDCP. 1=Apply reset;
+ *     0=Release from reset. Default 1.
+ * Bit 0 RW sw_reset_core: connects to IP's ~irstz. 1=Apply reset;
+ *     0=Release from reset. Default 1.
+ */
+#define HDMITX_TOP_SW_RESET                     (0x000)
+
+/*
+ * Bit 12 RW i2s_ws_inv:1=Invert i2s_ws; 0=No invert. Default 0.
+ * Bit 11 RW i2s_clk_inv: 1=Invert i2s_clk; 0=No invert. Default 0.
+ * Bit 10 RW spdif_clk_inv: 1=Invert spdif_clk; 0=No invert. Default 0.
+ * Bit 9 RW tmds_clk_inv: 1=Invert tmds_clk; 0=No invert. Default 0.
+ * Bit 8 RW pixel_clk_inv: 1=Invert pixel_clk; 0=No invert. Default 0.
+ * Bit 4 RW cec_clk_en: 1=enable cec_clk; 0=disable. Default 0.
+ * Bit 3 RW i2s_clk_en: 1=enable i2s_clk; 0=disable. Default 0.
+ * Bit 2 RW spdif_clk_en: 1=enable spdif_clk; 0=disable. Default 0.
+ * Bit 1 RW tmds_clk_en: 1=enable tmds_clk;  0=disable. Default 0.
+ * Bit 0 RW pixel_clk_en: 1=enable pixel_clk; 0=disable. Default 0.
+ */
+#define HDMITX_TOP_CLK_CNTL                     (0x001)
+
+/*
+ * Bit 11: 0 RW hpd_valid_width: filter out width <= M*1024.    Default 0.
+ * Bit 15:12 RW hpd_glitch_width: filter out glitch <= N.       Default 0.
+ */
+#define HDMITX_TOP_HPD_FILTER                   (0x002)
+
+/*
+ * intr_maskn: MASK_N, one bit per interrupt source.
+ *     1=Enable interrupt source; 0=Disable interrupt source. Default 0.
+ * [  4] hdcp22_rndnum_err
+ * [  3] nonce_rfrsh_rise
+ * [  2] hpd_fall_intr
+ * [  1] hpd_rise_intr
+ * [  0] core_intr
+ */
+#define HDMITX_TOP_INTR_MASKN                   (0x003)
+
+/*
+ * Bit 30: 0 RW intr_stat: For each bit, write 1 to manually set the interrupt
+ *     bit, read back the interrupt status.
+ * Bit    31 R  IP interrupt status
+ * Bit     2 RW hpd_fall
+ * Bit     1 RW hpd_rise
+ * Bit     0 RW IP interrupt
+ */
+#define HDMITX_TOP_INTR_STAT                    (0x004)
+
+/*
+ * [4]	  hdcp22_rndnum_err
+ * [3]	  nonce_rfrsh_rise
+ * [2]	  hpd_fall
+ * [1]	  hpd_rise
+ * [0]	  core_intr_rise
+ */
+#define HDMITX_TOP_INTR_STAT_CLR                (0x005)
+
+#define HDMITX_TOP_INTR_CORE		BIT(0)
+#define HDMITX_TOP_INTR_HPD_RISE	BIT(1)
+#define HDMITX_TOP_INTR_HPD_FALL	BIT(2)
+
+/* Bit 14:12 RW tmds_sel: 3'b000=Output zero; 3'b001=Output normal TMDS data;
+ *     3'b010=Output PRBS data; 3'b100=Output shift pattern. Default 0.
+ * Bit 11: 9 RW shift_pttn_repeat: 0=New pattern every clk cycle; 1=New pattern
+ *     every 2 clk cycles; ...; 7=New pattern every 8 clk cycles. Default 0.
+ * Bit 8 RW shift_pttn_en: 1= Enable shift pattern generator; 0=Disable.
+ *     Default 0.
+ * Bit 4: 3 RW prbs_pttn_mode: 0=PRBS11; 1=PRBS15; 2=PRBS7; 3=PRBS31. Default 0.
+ * Bit 2: 1 RW prbs_pttn_width: 0=idle; 1=output 8-bit pattern;
+ *     2=Output 1-bit pattern; 3=output 10-bit pattern. Default 0.
+ * Bit 0 RW prbs_pttn_en: 1=Enable PRBS generator; 0=Disable. Default 0.
+ */
+#define HDMITX_TOP_BIST_CNTL                    (0x006)
+
+/* Bit 29:20 RW shift_pttn_data[59:50]. Default 0. */
+/* Bit 19:10 RW shift_pttn_data[69:60]. Default 0. */
+/* Bit  9: 0 RW shift_pttn_data[79:70]. Default 0. */
+#define HDMITX_TOP_SHIFT_PTTN_012               (0x007)
+
+/* Bit 29:20 RW shift_pttn_data[29:20]. Default 0. */
+/* Bit 19:10 RW shift_pttn_data[39:30]. Default 0. */
+/* Bit  9: 0 RW shift_pttn_data[49:40]. Default 0. */
+#define HDMITX_TOP_SHIFT_PTTN_345               (0x008)
+
+/* Bit 19:10 RW shift_pttn_data[ 9: 0]. Default 0. */
+/* Bit  9: 0 RW shift_pttn_data[19:10]. Default 0. */
+#define HDMITX_TOP_SHIFT_PTTN_67                (0x009)
+
+/* Bit 25:16 RW tmds_clk_pttn[19:10]. Default 0. */
+/* Bit  9: 0 RW tmds_clk_pttn[ 9: 0]. Default 0. */
+#define HDMITX_TOP_TMDS_CLK_PTTN_01             (0x00A)
+
+/* Bit 25:16 RW tmds_clk_pttn[39:30]. Default 0. */
+/* Bit  9: 0 RW tmds_clk_pttn[29:20]. Default 0. */
+#define HDMITX_TOP_TMDS_CLK_PTTN_23             (0x00B)
+
+/* Bit 1 RW shift_tmds_clk_pttn:1=Enable shifting clk pattern,
+ * used when TMDS CLK rate = TMDS character rate /4. Default 0.
+ * Bit 0 R  Reserved. Default 0.
+ * [	1] shift_tmds_clk_pttn
+ * [	0] load_tmds_clk_pttn
+ */
+#define HDMITX_TOP_TMDS_CLK_PTTN_CNTL           (0x00C)
+
+/* Bit 0 RW revocmem_wr_fail: Read back 1 to indicate Host write REVOC MEM
+ * failure, write 1 to clear the failure flag.  Default 0.
+ */
+#define HDMITX_TOP_REVOCMEM_STAT                (0x00D)
+
+/* Bit     0 R  filtered HPD status. */
+#define HDMITX_TOP_STAT0                        (0x00E)
+
+#endif /* __MESON_DW_HDMI_H */