devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v13 00/14] MT8173 DRM support
@ 2016-03-08 13:27 Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 01/14] dt-bindings: drm/mediatek: Add Mediatek display subsystem dts binding Philipp Zabel
                   ` (13 more replies)
  0 siblings, 14 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

Once more, the MT8173 DRM driver. I have dropped the iommus property
from the mmsys device tree node and changed the GEM code to use an
OVL device with the iommus property for allocations instead of the
mmsys device.

Changes since v12:
 - use OVL device instead of mmsys device for DMA allocations
 - Remove iommus property from mmsys DT node

regards
Philipp

CK Hu (5):
  dt-bindings: drm/mediatek: Add Mediatek display subsystem dts binding
  drm/mediatek: Add DRM Driver for Mediatek SoC MT8173.
  drm/mediatek: Add DSI sub driver
  arm64: dts: mt8173: Add display subsystem related nodes
  arm64: dts: mt8173: Add HDMI related nodes

Jie Qiu (3):
  drm/mediatek: Add DPI sub driver
  drm/mediatek: Add HDMI support
  drm/mediatek: enable hdmi output control bit

Philipp Zabel (6):
  dt-bindings: drm/mediatek: Add Mediatek HDMI dts binding
  clk: mediatek: make dpi0_sel propagate rate changes
  clk: mediatek: Add hdmi_ref HDMI PHY PLL reference clock output
  dt-bindings: hdmi-connector: add DDC I2C bus phandle documentation
  clk: mediatek: remove hdmitx_dig_cts from TOP clocks
  arm64: dts: mt8173-evb: enable HDMI output

 .../bindings/display/connector/hdmi-connector.txt  |   1 +
 .../bindings/display/mediatek/mediatek,disp.txt    | 203 +++++
 .../bindings/display/mediatek/mediatek,dpi.txt     |  35 +
 .../bindings/display/mediatek/mediatek,dsi.txt     |  60 ++
 .../bindings/display/mediatek/mediatek,hdmi.txt    | 148 ++++
 arch/arm64/boot/dts/mediatek/mt8173-evb.dts        |  38 +
 arch/arm64/boot/dts/mediatek/mt8173.dtsi           | 300 +++++++
 drivers/clk/mediatek/clk-mt8173.c                  |  12 +-
 drivers/clk/mediatek/clk-mtk.h                     |  15 +-
 drivers/gpu/drm/Kconfig                            |   2 +
 drivers/gpu/drm/Makefile                           |   1 +
 drivers/gpu/drm/mediatek/Kconfig                   |  21 +
 drivers/gpu/drm/mediatek/Makefile                  |  23 +
 drivers/gpu/drm/mediatek/mtk_cec.c                 | 245 ++++++
 drivers/gpu/drm/mediatek/mtk_cec.h                 |  25 +
 drivers/gpu/drm/mediatek/mtk_disp_ovl.c            | 302 +++++++
 drivers/gpu/drm/mediatek/mtk_disp_rdma.c           | 240 ++++++
 drivers/gpu/drm/mediatek/mtk_dpi.c                 | 757 +++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_dpi.h                 |  85 ++
 drivers/gpu/drm/mediatek/mtk_dpi_regs.h            | 228 +++++
 drivers/gpu/drm/mediatek/mtk_drm_crtc.c            | 582 +++++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_crtc.h            |  32 +
 drivers/gpu/drm/mediatek/mtk_drm_ddp.c             | 355 ++++++++
 drivers/gpu/drm/mediatek/mtk_drm_ddp.h             |  41 +
 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c        | 225 +++++
 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h        | 150 ++++
 drivers/gpu/drm/mediatek/mtk_drm_drv.c             | 596 +++++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_drv.h             |  57 ++
 drivers/gpu/drm/mediatek/mtk_drm_fb.c              | 165 ++++
 drivers/gpu/drm/mediatek/mtk_drm_fb.h              |  23 +
 drivers/gpu/drm/mediatek/mtk_drm_gem.c             | 269 ++++++
 drivers/gpu/drm/mediatek/mtk_drm_gem.h             |  59 ++
 drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c        | 579 +++++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_plane.c           | 240 ++++++
 drivers/gpu/drm/mediatek/mtk_drm_plane.h           |  59 ++
 drivers/gpu/drm/mediatek/mtk_dsi.c                 | 942 +++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi.c                | 479 +++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi.h                | 221 +++++
 drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c        | 362 ++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi_hw.c             | 664 +++++++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi_hw.h             |  73 ++
 drivers/gpu/drm/mediatek/mtk_hdmi_regs.h           | 222 +++++
 drivers/gpu/drm/mediatek/mtk_mipi_tx.c             | 487 +++++++++++
 drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c     | 505 +++++++++++
 include/dt-bindings/clock/mt8173-clk.h             |   3 +-
 45 files changed, 10126 insertions(+), 5 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt
 create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt
 create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt
 create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt
 create mode 100644 drivers/gpu/drm/mediatek/Kconfig
 create mode 100644 drivers/gpu/drm/mediatek/Makefile
 create mode 100644 drivers/gpu/drm/mediatek/mtk_cec.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_cec.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_disp_ovl.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_disp_rdma.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi_regs.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_crtc.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_crtc.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_drv.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_drv.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_fb.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_fb.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_gem.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_gem.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_plane.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_plane.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_dsi.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_regs.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_mipi_tx.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c

-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v13 01/14] dt-bindings: drm/mediatek: Add Mediatek display subsystem dts binding
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 02/14] drm/mediatek: Add DRM Driver for Mediatek SoC MT8173 Philipp Zabel
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

From: CK Hu <ck.hu@mediatek.com>

Add device tree binding documentation for the display subsystem in
Mediatek MT8173 SoCs.

Signed-off-by: CK Hu <ck.hu@mediatek.com>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
Acked-by: Rob Herring <robh@kernel.org>
---
 .../bindings/display/mediatek/mediatek,disp.txt    | 203 +++++++++++++++++++++
 .../bindings/display/mediatek/mediatek,dpi.txt     |  35 ++++
 .../bindings/display/mediatek/mediatek,dsi.txt     |  60 ++++++
 3 files changed, 298 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt
 create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt
 create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt

diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt
new file mode 100644
index 0000000..db6e77e
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt
@@ -0,0 +1,203 @@
+Mediatek display subsystem
+==========================
+
+The Mediatek display subsystem consists of various DISP function blocks in the
+MMSYS register space. The connections between them can be configured by output
+and input selectors in the MMSYS_CONFIG register space. Pixel clock and start
+of frame signal are distributed to the other function blocks by a DISP_MUTEX
+function block.
+
+All DISP device tree nodes must be siblings to the central MMSYS_CONFIG node.
+For a description of the MMSYS_CONFIG binding, see
+Documentation/devicetree/bindings/arm/mediatek/mediatek,mmsys.txt.
+
+DISP function blocks
+====================
+
+A display stream starts at a source function block that reads pixel data from
+memory and ends with a sink function block that drives pixels on a display
+interface, or writes pixels back to memory. All DISP function blocks have
+their own register space, interrupt, and clock gate. The blocks that can
+access memory additionally have to list the IOMMU and local arbiter they are
+connected to.
+
+For a description of the display interface sink function blocks, see
+Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt and
+Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt.
+
+Required properties (all function blocks):
+- compatible: "mediatek,<chip>-disp-<function>", one of
+	"mediatek,<chip>-disp-ovl"   - overlay (4 layers, blending, csc)
+	"mediatek,<chip>-disp-rdma"  - read DMA / line buffer
+	"mediatek,<chip>-disp-wdma"  - write DMA
+	"mediatek,<chip>-disp-color" - color processor
+	"mediatek,<chip>-disp-aal"   - adaptive ambient light controller
+	"mediatek,<chip>-disp-gamma" - gamma correction
+	"mediatek,<chip>-disp-merge" - merge streams from two RDMA sources
+	"mediatek,<chip>-disp-split" - split stream to two encoders
+	"mediatek,<chip>-disp-ufoe"  - data compression engine
+	"mediatek,<chip>-dsi"        - DSI controller, see mediatek,dsi.txt
+	"mediatek,<chip>-dpi"        - DPI controller, see mediatek,dpi.txt
+	"mediatek,<chip>-disp-mutex" - display mutex
+	"mediatek,<chip>-disp-od"    - overdrive
+- reg: Physical base address and length of the function block register space
+- interrupts: The interrupt signal from the function block (required, except for
+  merge and split function blocks).
+- clocks: device clocks
+  See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+  For most function blocks this is just a single clock input. Only the DSI and
+  DPI controller nodes have multiple clock inputs. These are documented in
+  mediatek,dsi.txt and mediatek,dpi.txt, respectively.
+
+Required properties (DMA function blocks):
+- compatible: Should be one of
+	"mediatek,<chip>-disp-ovl"
+	"mediatek,<chip>-disp-rdma"
+	"mediatek,<chip>-disp-wdma"
+- larb: Should contain a phandle pointing to the local arbiter device as defined
+  in Documentation/devicetree/bindings/soc/mediatek/mediatek,smi-larb.txt
+- iommus: Should point to the respective IOMMU block with master port as
+  argument, see Documentation/devicetree/bindings/iommu/mediatek,iommu.txt
+  for details.
+
+Examples:
+
+mmsys: clock-controller@14000000 {
+	compatible = "mediatek,mt8173-mmsys", "syscon";
+	reg = <0 0x14000000 0 0x1000>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	#clock-cells = <1>;
+};
+
+ovl0: ovl@1400c000 {
+	compatible = "mediatek,mt8173-disp-ovl";
+	reg = <0 0x1400c000 0 0x1000>;
+	interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_OVL0>;
+	iommus = <&iommu M4U_PORT_DISP_OVL0>;
+	mediatek,larb = <&larb0>;
+};
+
+ovl1: ovl@1400d000 {
+	compatible = "mediatek,mt8173-disp-ovl";
+	reg = <0 0x1400d000 0 0x1000>;
+	interrupts = <GIC_SPI 181 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_OVL1>;
+	iommus = <&iommu M4U_PORT_DISP_OVL1>;
+	mediatek,larb = <&larb4>;
+};
+
+rdma0: rdma@1400e000 {
+	compatible = "mediatek,mt8173-disp-rdma";
+	reg = <0 0x1400e000 0 0x1000>;
+	interrupts = <GIC_SPI 182 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_RDMA0>;
+	iommus = <&iommu M4U_PORT_DISP_RDMA0>;
+	mediatek,larb = <&larb0>;
+};
+
+rdma1: rdma@1400f000 {
+	compatible = "mediatek,mt8173-disp-rdma";
+	reg = <0 0x1400f000 0 0x1000>;
+	interrupts = <GIC_SPI 183 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_RDMA1>;
+	iommus = <&iommu M4U_PORT_DISP_RDMA1>;
+	mediatek,larb = <&larb4>;
+};
+
+rdma2: rdma@14010000 {
+	compatible = "mediatek,mt8173-disp-rdma";
+	reg = <0 0x14010000 0 0x1000>;
+	interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_RDMA2>;
+	iommus = <&iommu M4U_PORT_DISP_RDMA2>;
+	mediatek,larb = <&larb4>;
+};
+
+wdma0: wdma@14011000 {
+	compatible = "mediatek,mt8173-disp-wdma";
+	reg = <0 0x14011000 0 0x1000>;
+	interrupts = <GIC_SPI 185 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_WDMA0>;
+	iommus = <&iommu M4U_PORT_DISP_WDMA0>;
+	mediatek,larb = <&larb0>;
+};
+
+wdma1: wdma@14012000 {
+	compatible = "mediatek,mt8173-disp-wdma";
+	reg = <0 0x14012000 0 0x1000>;
+	interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_WDMA1>;
+	iommus = <&iommu M4U_PORT_DISP_WDMA1>;
+	mediatek,larb = <&larb4>;
+};
+
+color0: color@14013000 {
+	compatible = "mediatek,mt8173-disp-color";
+	reg = <0 0x14013000 0 0x1000>;
+	interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_COLOR0>;
+};
+
+color1: color@14014000 {
+	compatible = "mediatek,mt8173-disp-color";
+	reg = <0 0x14014000 0 0x1000>;
+	interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_COLOR1>;
+};
+
+aal@14015000 {
+	compatible = "mediatek,mt8173-disp-aal";
+	reg = <0 0x14015000 0 0x1000>;
+	interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_AAL>;
+};
+
+gamma@14016000 {
+	compatible = "mediatek,mt8173-disp-gamma";
+	reg = <0 0x14016000 0 0x1000>;
+	interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_GAMMA>;
+};
+
+ufoe@1401a000 {
+	compatible = "mediatek,mt8173-disp-ufoe";
+	reg = <0 0x1401a000 0 0x1000>;
+	interrupts = <GIC_SPI 191 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_UFOE>;
+};
+
+dsi0: dsi@1401b000 {
+	/* See mediatek,dsi.txt for details */
+};
+
+dpi0: dpi@1401d000 {
+	/* See mediatek,dpi.txt for details */
+};
+
+mutex: mutex@14020000 {
+	compatible = "mediatek,mt8173-disp-mutex";
+	reg = <0 0x14020000 0 0x1000>;
+	interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_LOW>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_MUTEX_32K>;
+};
+
+od@14023000 {
+	compatible = "mediatek,mt8173-disp-od";
+	reg = <0 0x14023000 0 0x1000>;
+	power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+	clocks = <&mmsys CLK_MM_DISP_OD>;
+};
diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt
new file mode 100644
index 0000000..b6a7e73
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt
@@ -0,0 +1,35 @@
+Mediatek DPI Device
+===================
+
+The Mediatek DPI function block is a sink of the display subsystem and
+provides 8-bit RGB/YUV444 or 8/10/10-bit YUV422 pixel data on a parallel
+output bus.
+
+Required properties:
+- compatible: "mediatek,<chip>-dpi"
+- reg: Physical base address and length of the controller's registers
+- interrupts: The interrupt signal from the function block.
+- clocks: device clocks
+  See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+- clock-names: must contain "pixel", "engine", and "pll"
+- port: Output port node with endpoint definitions as described in
+  Documentation/devicetree/bindings/graph.txt. This port should be connected
+  to the input port of an attached HDMI or LVDS encoder chip.
+
+Example:
+
+dpi0: dpi@1401d000 {
+	compatible = "mediatek,mt8173-dpi";
+	reg = <0 0x1401d000 0 0x1000>;
+	interrupts = <GIC_SPI 194 IRQ_TYPE_LEVEL_LOW>;
+	clocks = <&mmsys CLK_MM_DPI_PIXEL>,
+		 <&mmsys CLK_MM_DPI_ENGINE>,
+		 <&apmixedsys CLK_APMIXED_TVDPLL>;
+	clock-names = "pixel", "engine", "pll";
+
+	port {
+		dpi0_out: endpoint {
+			remote-endpoint = <&hdmi0_in>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt
new file mode 100644
index 0000000..2b1585a
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt
@@ -0,0 +1,60 @@
+Mediatek DSI Device
+===================
+
+The Mediatek DSI function block is a sink of the display subsystem and can
+drive up to 4-lane MIPI DSI output. Two DSIs can be synchronized for dual-
+channel output.
+
+Required properties:
+- compatible: "mediatek,<chip>-dsi"
+- reg: Physical base address and length of the controller's registers
+- interrupts: The interrupt signal from the function block.
+- clocks: device clocks
+  See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+- clock-names: must contain "engine", "digital", and "hs"
+- phys: phandle link to the MIPI D-PHY controller.
+- phy-names: must contain "dphy"
+- port: Output port node with endpoint definitions as described in
+  Documentation/devicetree/bindings/graph.txt. This port should be connected
+  to the input port of an attached DSI panel or DSI-to-eDP encoder chip.
+
+MIPI TX Configuration Module
+============================
+
+The MIPI TX configuration module controls the MIPI D-PHY.
+
+Required properties:
+- compatible: "mediatek,<chip>-mipi-tx"
+- reg: Physical base address and length of the controller's registers
+- clocks: PLL reference clock
+- clock-output-names: name of the output clock line to the DSI encoder
+- #clock-cells: must be <0>;
+- #phy-cells: must be <0>.
+
+Example:
+
+mipi_tx0: mipi-dphy@10215000 {
+	compatible = "mediatek,mt8173-mipi-tx";
+	reg = <0 0x10215000 0 0x1000>;
+	clocks = <&clk26m>;
+	clock-output-names = "mipi_tx0_pll";
+	#clock-cells = <0>;
+	#phy-cells = <0>;
+};
+
+dsi0: dsi@1401b000 {
+	compatible = "mediatek,mt8173-dsi";
+	reg = <0 0x1401b000 0 0x1000>;
+	interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_LOW>;
+	clocks = <&mmsys MM_DSI0_ENGINE>, <&mmsys MM_DSI0_DIGITAL>,
+		 <&mipi_tx0>;
+	clock-names = "engine", "digital", "hs";
+	phys = <&mipi_tx0>;
+	phy-names = "dphy";
+
+	port {
+		dsi0_out: endpoint {
+			remote-endpoint = <&panel_in>;
+		};
+	};
+};
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 02/14] drm/mediatek: Add DRM Driver for Mediatek SoC MT8173.
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 01/14] dt-bindings: drm/mediatek: Add Mediatek display subsystem dts binding Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 03/14] drm/mediatek: Add DSI sub driver Philipp Zabel
                   ` (11 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, Mao Huang, YT Shen, Yingjoe Chen, devicetree,
	Jitao Shi, kernel, Pawel Moll, Ian Campbell, Rob Herring,
	linux-mediatek, Matthias Brugger, Paul Bolle, Emil Velikov,
	Tomasz Figa, Kumar Gala

From: CK Hu <ck.hu@mediatek.com>

This patch adds an initial DRM driver for the Mediatek MT8173 DISP
subsystem. It currently supports two fixed output streams from the
OVL0/OVL1 sources to the DSI0/DPI0 sinks, respectively.

Signed-off-by: CK Hu <ck.hu@mediatek.com>
Signed-off-by: YT Shen <yt.shen@mediatek.com>
Signed-off-by: Daniel Kurtz <djkurtz@chromium.org>
Signed-off-by: Bibby Hsieh <bibby.hsieh@mediatek.com>
Signed-off-by: Mao Huang <littlecvr@chromium.org>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
---
Changes since v12:
 - use OVL device instead of mmsys device for DMA allocations
---
 drivers/gpu/drm/Kconfig                     |   2 +
 drivers/gpu/drm/Makefile                    |   1 +
 drivers/gpu/drm/mediatek/Kconfig            |  12 +
 drivers/gpu/drm/mediatek/Makefile           |  11 +
 drivers/gpu/drm/mediatek/mtk_disp_ovl.c     | 302 ++++++++++++++
 drivers/gpu/drm/mediatek/mtk_disp_rdma.c    | 240 +++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_crtc.c     | 582 +++++++++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_crtc.h     |  32 ++
 drivers/gpu/drm/mediatek/mtk_drm_ddp.c      | 355 +++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_ddp.h      |  41 ++
 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c | 225 +++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h | 150 +++++++
 drivers/gpu/drm/mediatek/mtk_drm_drv.c      | 592 ++++++++++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_drv.h      |  54 +++
 drivers/gpu/drm/mediatek/mtk_drm_fb.c       | 165 ++++++++
 drivers/gpu/drm/mediatek/mtk_drm_fb.h       |  23 ++
 drivers/gpu/drm/mediatek/mtk_drm_gem.c      | 269 +++++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_gem.h      |  59 +++
 drivers/gpu/drm/mediatek/mtk_drm_plane.c    | 240 +++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_plane.h    |  59 +++
 20 files changed, 3414 insertions(+)
 create mode 100644 drivers/gpu/drm/mediatek/Kconfig
 create mode 100644 drivers/gpu/drm/mediatek/Makefile
 create mode 100644 drivers/gpu/drm/mediatek/mtk_disp_ovl.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_disp_rdma.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_crtc.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_crtc.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_drv.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_drv.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_fb.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_fb.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_gem.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_gem.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_plane.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_plane.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 8ae7ab6..f7b0d79 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -269,3 +269,5 @@ source "drivers/gpu/drm/imx/Kconfig"
 source "drivers/gpu/drm/vc4/Kconfig"
 
 source "drivers/gpu/drm/etnaviv/Kconfig"
+
+source "drivers/gpu/drm/mediatek/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 61766de..7b0d1ab 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_DRM_MSM) += msm/
 obj-$(CONFIG_DRM_TEGRA) += tegra/
 obj-$(CONFIG_DRM_STI) += sti/
 obj-$(CONFIG_DRM_IMX) += imx/
+obj-$(CONFIG_DRM_MEDIATEK) += mediatek/
 obj-y			+= i2c/
 obj-y			+= panel/
 obj-y			+= bridge/
diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig
new file mode 100644
index 0000000..8dad892
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/Kconfig
@@ -0,0 +1,12 @@
+config DRM_MEDIATEK
+	tristate "DRM Support for Mediatek SoCs"
+	depends on DRM
+	depends on ARCH_MEDIATEK || (ARM && COMPILE_TEST)
+	select DRM_KMS_HELPER
+	select IOMMU_DMA
+	select MTK_SMI
+	help
+	  Choose this option if you have a Mediatek SoCs.
+	  The module will be called mediatek-drm
+	  This driver provides kernel mode setting and
+	  buffer management to userspace.
diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile
new file mode 100644
index 0000000..d4bde7c
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/Makefile
@@ -0,0 +1,11 @@
+mediatek-drm-y := mtk_disp_ovl.o \
+		  mtk_disp_rdma.o \
+		  mtk_drm_crtc.o \
+		  mtk_drm_ddp.o \
+		  mtk_drm_ddp_comp.o \
+		  mtk_drm_drv.o \
+		  mtk_drm_fb.o \
+		  mtk_drm_gem.o \
+		  mtk_drm_plane.o
+
+obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
diff --git a/drivers/gpu/drm/mediatek/mtk_disp_ovl.c b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c
new file mode 100644
index 0000000..8f62671f
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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 <linux/clk.h>
+#include <linux/component.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+
+#include "mtk_drm_crtc.h"
+#include "mtk_drm_ddp_comp.h"
+
+#define DISP_REG_OVL_INTEN			0x0004
+#define OVL_FME_CPL_INT					BIT(1)
+#define DISP_REG_OVL_INTSTA			0x0008
+#define DISP_REG_OVL_EN				0x000c
+#define DISP_REG_OVL_RST			0x0014
+#define DISP_REG_OVL_ROI_SIZE			0x0020
+#define DISP_REG_OVL_ROI_BGCLR			0x0028
+#define DISP_REG_OVL_SRC_CON			0x002c
+#define DISP_REG_OVL_CON(n)			(0x0030 + 0x20 * (n))
+#define DISP_REG_OVL_SRC_SIZE(n)		(0x0038 + 0x20 * (n))
+#define DISP_REG_OVL_OFFSET(n)			(0x003c + 0x20 * (n))
+#define DISP_REG_OVL_PITCH(n)			(0x0044 + 0x20 * (n))
+#define DISP_REG_OVL_RDMA_CTRL(n)		(0x00c0 + 0x20 * (n))
+#define DISP_REG_OVL_RDMA_GMC(n)		(0x00c8 + 0x20 * (n))
+#define DISP_REG_OVL_ADDR(n)			(0x0f40 + 0x20 * (n))
+
+#define	OVL_RDMA_MEM_GMC	0x40402020
+
+#define OVL_CON_BYTE_SWAP	BIT(24)
+#define OVL_CON_CLRFMT_RGB565	(0 << 12)
+#define OVL_CON_CLRFMT_RGB888	(1 << 12)
+#define OVL_CON_CLRFMT_RGBA8888	(2 << 12)
+#define OVL_CON_CLRFMT_ARGB8888	(3 << 12)
+#define	OVL_CON_AEN		BIT(8)
+#define	OVL_CON_ALPHA		0xff
+
+/**
+ * struct mtk_disp_ovl - DISP_OVL driver structure
+ * @ddp_comp - structure containing type enum and hardware resources
+ * @crtc - associated crtc to report vblank events to
+ */
+struct mtk_disp_ovl {
+	struct mtk_ddp_comp		ddp_comp;
+	struct drm_crtc			*crtc;
+};
+
+static irqreturn_t mtk_disp_ovl_irq_handler(int irq, void *dev_id)
+{
+	struct mtk_disp_ovl *priv = dev_id;
+	struct mtk_ddp_comp *ovl = &priv->ddp_comp;
+
+	/* Clear frame completion interrupt */
+	writel(0x0, ovl->regs + DISP_REG_OVL_INTSTA);
+
+	if (!priv->crtc)
+		return IRQ_NONE;
+
+	mtk_crtc_ddp_irq(priv->crtc, ovl);
+
+	return IRQ_HANDLED;
+}
+
+static void mtk_ovl_enable_vblank(struct mtk_ddp_comp *comp,
+				  struct drm_crtc *crtc)
+{
+	struct mtk_disp_ovl *priv = container_of(comp, struct mtk_disp_ovl,
+						 ddp_comp);
+
+	priv->crtc = crtc;
+	writel_relaxed(OVL_FME_CPL_INT, comp->regs + DISP_REG_OVL_INTEN);
+}
+
+static void mtk_ovl_disable_vblank(struct mtk_ddp_comp *comp)
+{
+	struct mtk_disp_ovl *priv = container_of(comp, struct mtk_disp_ovl,
+						 ddp_comp);
+
+	priv->crtc = NULL;
+	writel_relaxed(0x0, comp->regs + DISP_REG_OVL_INTEN);
+}
+
+static void mtk_ovl_start(struct mtk_ddp_comp *comp)
+{
+	writel_relaxed(0x1, comp->regs + DISP_REG_OVL_EN);
+}
+
+static void mtk_ovl_stop(struct mtk_ddp_comp *comp)
+{
+	writel_relaxed(0x0, comp->regs + DISP_REG_OVL_EN);
+}
+
+static void mtk_ovl_config(struct mtk_ddp_comp *comp, unsigned int w,
+			   unsigned int h, unsigned int vrefresh)
+{
+	if (w != 0 && h != 0)
+		writel_relaxed(h << 16 | w, comp->regs + DISP_REG_OVL_ROI_SIZE);
+	writel_relaxed(0x0, comp->regs + DISP_REG_OVL_ROI_BGCLR);
+
+	writel(0x1, comp->regs + DISP_REG_OVL_RST);
+	writel(0x0, comp->regs + DISP_REG_OVL_RST);
+}
+
+static void mtk_ovl_layer_on(struct mtk_ddp_comp *comp, unsigned int idx)
+{
+	unsigned int reg;
+
+	writel(0x1, comp->regs + DISP_REG_OVL_RDMA_CTRL(idx));
+	writel(OVL_RDMA_MEM_GMC, comp->regs + DISP_REG_OVL_RDMA_GMC(idx));
+
+	reg = readl(comp->regs + DISP_REG_OVL_SRC_CON);
+	reg = reg | BIT(idx);
+	writel(reg, comp->regs + DISP_REG_OVL_SRC_CON);
+}
+
+static void mtk_ovl_layer_off(struct mtk_ddp_comp *comp, unsigned int idx)
+{
+	unsigned int reg;
+
+	reg = readl(comp->regs + DISP_REG_OVL_SRC_CON);
+	reg = reg & ~BIT(idx);
+	writel(reg, comp->regs + DISP_REG_OVL_SRC_CON);
+
+	writel(0x0, comp->regs + DISP_REG_OVL_RDMA_CTRL(idx));
+}
+
+static unsigned int ovl_fmt_convert(unsigned int fmt)
+{
+	switch (fmt) {
+	default:
+	case DRM_FORMAT_RGB565:
+		return OVL_CON_CLRFMT_RGB565;
+	case DRM_FORMAT_BGR565:
+		return OVL_CON_CLRFMT_RGB565 | OVL_CON_BYTE_SWAP;
+	case DRM_FORMAT_RGB888:
+		return OVL_CON_CLRFMT_RGB888;
+	case DRM_FORMAT_BGR888:
+		return OVL_CON_CLRFMT_RGB888 | OVL_CON_BYTE_SWAP;
+	case DRM_FORMAT_RGBX8888:
+	case DRM_FORMAT_RGBA8888:
+		return OVL_CON_CLRFMT_ARGB8888;
+	case DRM_FORMAT_BGRX8888:
+	case DRM_FORMAT_BGRA8888:
+		return OVL_CON_CLRFMT_ARGB8888 | OVL_CON_BYTE_SWAP;
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+		return OVL_CON_CLRFMT_RGBA8888;
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+		return OVL_CON_CLRFMT_RGBA8888 | OVL_CON_BYTE_SWAP;
+	}
+}
+
+static void mtk_ovl_layer_config(struct mtk_ddp_comp *comp, unsigned int idx,
+				 struct mtk_plane_state *state)
+{
+	struct mtk_plane_pending_state *pending = &state->pending;
+	unsigned int addr = pending->addr;
+	unsigned int pitch = pending->pitch & 0xffff;
+	unsigned int fmt = pending->format;
+	unsigned int offset = (pending->y << 16) | pending->x;
+	unsigned int src_size = (pending->height << 16) | pending->width;
+	unsigned int con;
+
+	if (!pending->enable)
+		mtk_ovl_layer_off(comp, idx);
+
+	con = ovl_fmt_convert(fmt);
+	if (idx != 0)
+		con |= OVL_CON_AEN | OVL_CON_ALPHA;
+
+	writel_relaxed(con, comp->regs + DISP_REG_OVL_CON(idx));
+	writel_relaxed(pitch, comp->regs + DISP_REG_OVL_PITCH(idx));
+	writel_relaxed(src_size, comp->regs + DISP_REG_OVL_SRC_SIZE(idx));
+	writel_relaxed(offset, comp->regs + DISP_REG_OVL_OFFSET(idx));
+	writel_relaxed(addr, comp->regs + DISP_REG_OVL_ADDR(idx));
+
+	if (pending->enable)
+		mtk_ovl_layer_on(comp, idx);
+}
+
+static const struct mtk_ddp_comp_funcs mtk_disp_ovl_funcs = {
+	.config = mtk_ovl_config,
+	.start = mtk_ovl_start,
+	.stop = mtk_ovl_stop,
+	.enable_vblank = mtk_ovl_enable_vblank,
+	.disable_vblank = mtk_ovl_disable_vblank,
+	.layer_on = mtk_ovl_layer_on,
+	.layer_off = mtk_ovl_layer_off,
+	.layer_config = mtk_ovl_layer_config,
+};
+
+static int mtk_disp_ovl_bind(struct device *dev, struct device *master,
+			     void *data)
+{
+	struct mtk_disp_ovl *priv = dev_get_drvdata(dev);
+	struct drm_device *drm_dev = data;
+	int ret;
+
+	ret = mtk_ddp_comp_register(drm_dev, &priv->ddp_comp);
+	if (ret < 0) {
+		dev_err(dev, "Failed to register component %s: %d\n",
+			dev->of_node->full_name, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void mtk_disp_ovl_unbind(struct device *dev, struct device *master,
+				void *data)
+{
+	struct mtk_disp_ovl *priv = dev_get_drvdata(dev);
+	struct drm_device *drm_dev = data;
+
+	mtk_ddp_comp_unregister(drm_dev, &priv->ddp_comp);
+}
+
+static const struct component_ops mtk_disp_ovl_component_ops = {
+	.bind	= mtk_disp_ovl_bind,
+	.unbind = mtk_disp_ovl_unbind,
+};
+
+static int mtk_disp_ovl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_disp_ovl *priv;
+	int comp_id;
+	int irq;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	ret = devm_request_irq(dev, irq, mtk_disp_ovl_irq_handler,
+			       IRQF_TRIGGER_NONE, dev_name(dev), priv);
+	if (ret < 0) {
+		dev_err(dev, "Failed to request irq %d: %d\n", irq, ret);
+		return ret;
+	}
+
+	comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DISP_OVL);
+	if (comp_id < 0) {
+		dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
+		return comp_id;
+	}
+
+	ret = mtk_ddp_comp_init(dev, dev->of_node, &priv->ddp_comp, comp_id,
+				&mtk_disp_ovl_funcs);
+	if (ret) {
+		dev_err(dev, "Failed to initialize component: %d\n", ret);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, priv);
+
+	ret = component_add(dev, &mtk_disp_ovl_component_ops);
+	if (ret)
+		dev_err(dev, "Failed to add component: %d\n", ret);
+
+	return ret;
+}
+
+static int mtk_disp_ovl_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &mtk_disp_ovl_component_ops);
+
+	return 0;
+}
+
+static const struct of_device_id mtk_disp_ovl_driver_dt_match[] = {
+	{ .compatible = "mediatek,mt8173-disp-ovl", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mtk_disp_ovl_driver_dt_match);
+
+struct platform_driver mtk_disp_ovl_driver = {
+	.probe		= mtk_disp_ovl_probe,
+	.remove		= mtk_disp_ovl_remove,
+	.driver		= {
+		.name	= "mediatek-disp-ovl",
+		.owner	= THIS_MODULE,
+		.of_match_table = mtk_disp_ovl_driver_dt_match,
+	},
+};
diff --git a/drivers/gpu/drm/mediatek/mtk_disp_rdma.c b/drivers/gpu/drm/mediatek/mtk_disp_rdma.c
new file mode 100644
index 0000000..368ae87
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_disp_rdma.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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 <linux/clk.h>
+#include <linux/component.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+
+#include "mtk_drm_crtc.h"
+#include "mtk_drm_ddp_comp.h"
+
+#define DISP_REG_RDMA_INT_ENABLE		0x0000
+#define DISP_REG_RDMA_INT_STATUS		0x0004
+#define RDMA_TARGET_LINE_INT				BIT(5)
+#define RDMA_FIFO_UNDERFLOW_INT				BIT(4)
+#define RDMA_EOF_ABNORMAL_INT				BIT(3)
+#define RDMA_FRAME_END_INT				BIT(2)
+#define RDMA_FRAME_START_INT				BIT(1)
+#define RDMA_REG_UPDATE_INT				BIT(0)
+#define DISP_REG_RDMA_GLOBAL_CON		0x0010
+#define RDMA_ENGINE_EN					BIT(0)
+#define DISP_REG_RDMA_SIZE_CON_0		0x0014
+#define DISP_REG_RDMA_SIZE_CON_1		0x0018
+#define DISP_REG_RDMA_TARGET_LINE		0x001c
+#define DISP_REG_RDMA_FIFO_CON			0x0040
+#define RDMA_FIFO_UNDERFLOW_EN				BIT(31)
+#define RDMA_FIFO_PSEUDO_SIZE(bytes)			(((bytes) / 16) << 16)
+#define RDMA_OUTPUT_VALID_FIFO_THRESHOLD(bytes)		((bytes) / 16)
+
+/**
+ * struct mtk_disp_rdma - DISP_RDMA driver structure
+ * @ddp_comp - structure containing type enum and hardware resources
+ * @crtc - associated crtc to report irq events to
+ */
+struct mtk_disp_rdma {
+	struct mtk_ddp_comp		ddp_comp;
+	struct drm_crtc			*crtc;
+};
+
+static irqreturn_t mtk_disp_rdma_irq_handler(int irq, void *dev_id)
+{
+	struct mtk_disp_rdma *priv = dev_id;
+	struct mtk_ddp_comp *rdma = &priv->ddp_comp;
+
+	/* Clear frame completion interrupt */
+	writel(0x0, rdma->regs + DISP_REG_RDMA_INT_STATUS);
+
+	if (!priv->crtc)
+		return IRQ_NONE;
+
+	mtk_crtc_ddp_irq(priv->crtc, rdma);
+
+	return IRQ_HANDLED;
+}
+
+static void rdma_update_bits(struct mtk_ddp_comp *comp, unsigned int reg,
+			     unsigned int mask, unsigned int val)
+{
+	unsigned int tmp = readl(comp->regs + reg);
+
+	tmp = (tmp & ~mask) | (val & mask);
+	writel(tmp, comp->regs + reg);
+}
+
+static void mtk_rdma_enable_vblank(struct mtk_ddp_comp *comp,
+				   struct drm_crtc *crtc)
+{
+	struct mtk_disp_rdma *priv = container_of(comp, struct mtk_disp_rdma,
+						  ddp_comp);
+
+	priv->crtc = crtc;
+	rdma_update_bits(comp, DISP_REG_RDMA_INT_ENABLE, RDMA_FRAME_END_INT,
+			 RDMA_FRAME_END_INT);
+}
+
+static void mtk_rdma_disable_vblank(struct mtk_ddp_comp *comp)
+{
+	struct mtk_disp_rdma *priv = container_of(comp, struct mtk_disp_rdma,
+						  ddp_comp);
+
+	priv->crtc = NULL;
+	rdma_update_bits(comp, DISP_REG_RDMA_INT_ENABLE, RDMA_FRAME_END_INT, 0);
+}
+
+static void mtk_rdma_start(struct mtk_ddp_comp *comp)
+{
+	rdma_update_bits(comp, DISP_REG_RDMA_GLOBAL_CON, RDMA_ENGINE_EN,
+			 RDMA_ENGINE_EN);
+}
+
+static void mtk_rdma_stop(struct mtk_ddp_comp *comp)
+{
+	rdma_update_bits(comp, DISP_REG_RDMA_GLOBAL_CON, RDMA_ENGINE_EN, 0);
+}
+
+static void mtk_rdma_config(struct mtk_ddp_comp *comp, unsigned width,
+			    unsigned height, unsigned int vrefresh)
+{
+	unsigned int threshold;
+	unsigned int reg;
+
+	rdma_update_bits(comp, DISP_REG_RDMA_SIZE_CON_0, 0xfff, width);
+	rdma_update_bits(comp, DISP_REG_RDMA_SIZE_CON_1, 0xfffff, height);
+
+	/*
+	 * Enable FIFO underflow since DSI and DPI can't be blocked.
+	 * Keep the FIFO pseudo size reset default of 8 KiB. Set the
+	 * output threshold to 6 microseconds with 7/6 overhead to
+	 * account for blanking, and with a pixel depth of 4 bytes:
+	 */
+	threshold = width * height * vrefresh * 4 * 7 / 1000000;
+	reg = RDMA_FIFO_UNDERFLOW_EN |
+	      RDMA_FIFO_PSEUDO_SIZE(SZ_8K) |
+	      RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold);
+	writel(reg, comp->regs + DISP_REG_RDMA_FIFO_CON);
+}
+
+static const struct mtk_ddp_comp_funcs mtk_disp_rdma_funcs = {
+	.config = mtk_rdma_config,
+	.start = mtk_rdma_start,
+	.stop = mtk_rdma_stop,
+	.enable_vblank = mtk_rdma_enable_vblank,
+	.disable_vblank = mtk_rdma_disable_vblank,
+};
+
+static int mtk_disp_rdma_bind(struct device *dev, struct device *master,
+			      void *data)
+{
+	struct mtk_disp_rdma *priv = dev_get_drvdata(dev);
+	struct drm_device *drm_dev = data;
+	int ret;
+
+	ret = mtk_ddp_comp_register(drm_dev, &priv->ddp_comp);
+	if (ret < 0) {
+		dev_err(dev, "Failed to register component %s: %d\n",
+			dev->of_node->full_name, ret);
+		return ret;
+	}
+
+	return 0;
+
+}
+
+static void mtk_disp_rdma_unbind(struct device *dev, struct device *master,
+				 void *data)
+{
+	struct mtk_disp_rdma *priv = dev_get_drvdata(dev);
+	struct drm_device *drm_dev = data;
+
+	mtk_ddp_comp_unregister(drm_dev, &priv->ddp_comp);
+}
+
+static const struct component_ops mtk_disp_rdma_component_ops = {
+	.bind	= mtk_disp_rdma_bind,
+	.unbind = mtk_disp_rdma_unbind,
+};
+
+static int mtk_disp_rdma_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_disp_rdma *priv;
+	int comp_id;
+	int irq;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DISP_RDMA);
+	if (comp_id < 0) {
+		dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
+		return comp_id;
+	}
+
+	ret = mtk_ddp_comp_init(dev, dev->of_node, &priv->ddp_comp, comp_id,
+				&mtk_disp_rdma_funcs);
+	if (ret) {
+		dev_err(dev, "Failed to initialize component: %d\n", ret);
+		return ret;
+	}
+
+	/* Disable and clear pending interrupts */
+	writel(0x0, priv->ddp_comp.regs + DISP_REG_RDMA_INT_ENABLE);
+	writel(0x0, priv->ddp_comp.regs + DISP_REG_RDMA_INT_STATUS);
+
+	ret = devm_request_irq(dev, irq, mtk_disp_rdma_irq_handler,
+			       IRQF_TRIGGER_NONE, dev_name(dev), priv);
+	if (ret < 0) {
+		dev_err(dev, "Failed to request irq %d: %d\n", irq, ret);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, priv);
+
+	ret = component_add(dev, &mtk_disp_rdma_component_ops);
+	if (ret)
+		dev_err(dev, "Failed to add component: %d\n", ret);
+
+	return ret;
+}
+
+static int mtk_disp_rdma_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &mtk_disp_rdma_component_ops);
+
+	return 0;
+}
+
+static const struct of_device_id mtk_disp_rdma_driver_dt_match[] = {
+	{ .compatible = "mediatek,mt8173-disp-rdma", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mtk_disp_rdma_driver_dt_match);
+
+struct platform_driver mtk_disp_rdma_driver = {
+	.probe		= mtk_disp_rdma_probe,
+	.remove		= mtk_disp_rdma_remove,
+	.driver		= {
+		.name	= "mediatek-disp-rdma",
+		.owner	= THIS_MODULE,
+		.of_match_table = mtk_disp_rdma_driver_dt_match,
+	},
+};
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_crtc.c b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c
new file mode 100644
index 0000000..ce3efa0
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c
@@ -0,0 +1,582 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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 <asm/barrier.h>
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <linux/clk.h>
+#include <linux/pm_runtime.h>
+#include <soc/mediatek/smi.h>
+
+#include "mtk_drm_drv.h"
+#include "mtk_drm_crtc.h"
+#include "mtk_drm_ddp.h"
+#include "mtk_drm_ddp_comp.h"
+#include "mtk_drm_gem.h"
+#include "mtk_drm_plane.h"
+
+/**
+ * struct mtk_drm_crtc - MediaTek specific crtc structure.
+ * @base: crtc object.
+ * @enabled: records whether crtc_enable succeeded
+ * @planes: array of 4 mtk_drm_plane structures, one for each overlay plane
+ * @pending_planes: whether any plane has pending changes to be applied
+ * @config_regs: memory mapped mmsys configuration register space
+ * @mutex: handle to one of the ten disp_mutex streams
+ * @ddp_comp_nr: number of components in ddp_comp
+ * @ddp_comp: array of pointers the mtk_ddp_comp structures used by this crtc
+ */
+struct mtk_drm_crtc {
+	struct drm_crtc			base;
+	bool				enabled;
+
+	bool				pending_needs_vblank;
+	struct drm_pending_vblank_event	*event;
+
+	struct mtk_drm_plane		planes[OVL_LAYER_NR];
+	bool				pending_planes;
+
+	void __iomem			*config_regs;
+	struct mtk_disp_mutex		*mutex;
+	unsigned int			ddp_comp_nr;
+	struct mtk_ddp_comp		**ddp_comp;
+};
+
+struct mtk_crtc_state {
+	struct drm_crtc_state		base;
+
+	bool				pending_config;
+	unsigned int			pending_width;
+	unsigned int			pending_height;
+	unsigned int			pending_vrefresh;
+};
+
+static inline struct mtk_drm_crtc *to_mtk_crtc(struct drm_crtc *c)
+{
+	return container_of(c, struct mtk_drm_crtc, base);
+}
+
+static inline struct mtk_crtc_state *to_mtk_crtc_state(struct drm_crtc_state *s)
+{
+	return container_of(s, struct mtk_crtc_state, base);
+}
+
+static void mtk_drm_crtc_finish_page_flip(struct mtk_drm_crtc *mtk_crtc)
+{
+	struct drm_crtc *crtc = &mtk_crtc->base;
+	unsigned long flags;
+
+	spin_lock_irqsave(&crtc->dev->event_lock, flags);
+	drm_send_vblank_event(crtc->dev, mtk_crtc->event->pipe, mtk_crtc->event);
+	drm_crtc_vblank_put(crtc);
+	mtk_crtc->event = NULL;
+	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+}
+
+static void mtk_drm_finish_page_flip(struct mtk_drm_crtc *mtk_crtc)
+{
+	drm_crtc_handle_vblank(&mtk_crtc->base);
+	if (mtk_crtc->pending_needs_vblank) {
+		mtk_drm_crtc_finish_page_flip(mtk_crtc);
+		mtk_crtc->pending_needs_vblank = false;
+	}
+}
+
+static void mtk_drm_crtc_destroy(struct drm_crtc *crtc)
+{
+	struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
+	int i;
+
+	for (i = 0; i < mtk_crtc->ddp_comp_nr; i++)
+		clk_unprepare(mtk_crtc->ddp_comp[i]->clk);
+
+	mtk_disp_mutex_put(mtk_crtc->mutex);
+
+	drm_crtc_cleanup(crtc);
+}
+
+static void mtk_drm_crtc_reset(struct drm_crtc *crtc)
+{
+	struct mtk_crtc_state *state;
+
+	if (crtc->state) {
+		if (crtc->state->mode_blob)
+			drm_property_unreference_blob(crtc->state->mode_blob);
+
+		state = to_mtk_crtc_state(crtc->state);
+		memset(state, 0, sizeof(*state));
+	} else {
+		state = kzalloc(sizeof(*state), GFP_KERNEL);
+		if (!state)
+			return;
+		crtc->state = &state->base;
+	}
+
+	state->base.crtc = crtc;
+}
+
+static struct drm_crtc_state *mtk_drm_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+	struct mtk_crtc_state *state;
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	__drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+
+	WARN_ON(state->base.crtc != crtc);
+	state->base.crtc = crtc;
+
+	return &state->base;
+}
+
+static void mtk_drm_crtc_destroy_state(struct drm_crtc *crtc,
+				       struct drm_crtc_state *state)
+{
+	__drm_atomic_helper_crtc_destroy_state(crtc, state);
+	kfree(to_mtk_crtc_state(state));
+}
+
+static bool mtk_drm_crtc_mode_fixup(struct drm_crtc *crtc,
+				    const struct drm_display_mode *mode,
+				    struct drm_display_mode *adjusted_mode)
+{
+	/* Nothing to do here, but this callback is mandatory. */
+	return true;
+}
+
+static void mtk_drm_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
+
+	state->pending_width = crtc->mode.hdisplay;
+	state->pending_height = crtc->mode.vdisplay;
+	state->pending_vrefresh = crtc->mode.vrefresh;
+	wmb();	/* Make sure the above parameters are set before update */
+	state->pending_config = true;
+}
+
+int mtk_drm_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe)
+{
+	struct mtk_drm_private *priv = drm->dev_private;
+	struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(priv->crtc[pipe]);
+	struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0];
+
+	mtk_ddp_comp_enable_vblank(ovl, &mtk_crtc->base);
+
+	return 0;
+}
+
+void mtk_drm_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe)
+{
+	struct mtk_drm_private *priv = drm->dev_private;
+	struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(priv->crtc[pipe]);
+	struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0];
+
+	mtk_ddp_comp_disable_vblank(ovl);
+}
+
+static int mtk_crtc_ddp_clk_enable(struct mtk_drm_crtc *mtk_crtc)
+{
+	int ret;
+	int i;
+
+	DRM_DEBUG_DRIVER("%s\n", __func__);
+	for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) {
+		ret = clk_enable(mtk_crtc->ddp_comp[i]->clk);
+		if (ret) {
+			DRM_ERROR("Failed to enable clock %d: %d\n", i, ret);
+			goto err;
+		}
+	}
+
+	return 0;
+err:
+	while (--i >= 0)
+		clk_disable(mtk_crtc->ddp_comp[i]->clk);
+	return ret;
+}
+
+static void mtk_crtc_ddp_clk_disable(struct mtk_drm_crtc *mtk_crtc)
+{
+	int i;
+
+	DRM_DEBUG_DRIVER("%s\n", __func__);
+	for (i = 0; i < mtk_crtc->ddp_comp_nr; i++)
+		clk_disable(mtk_crtc->ddp_comp[i]->clk);
+}
+
+static int mtk_crtc_ddp_hw_init(struct mtk_drm_crtc *mtk_crtc)
+{
+	struct drm_crtc *crtc = &mtk_crtc->base;
+	unsigned int width, height, vrefresh;
+	int ret;
+	int i;
+
+	DRM_DEBUG_DRIVER("%s\n", __func__);
+	if (WARN_ON(!crtc->state))
+		return -EINVAL;
+
+	width = crtc->state->adjusted_mode.hdisplay;
+	height = crtc->state->adjusted_mode.vdisplay;
+	vrefresh = crtc->state->adjusted_mode.vrefresh;
+
+	ret = pm_runtime_get_sync(crtc->dev->dev);
+	if (ret < 0) {
+		DRM_ERROR("Failed to enable power domain: %d\n", ret);
+		return ret;
+	}
+
+	ret = mtk_disp_mutex_prepare(mtk_crtc->mutex);
+	if (ret < 0) {
+		DRM_ERROR("Failed to enable mutex clock: %d\n", ret);
+		goto err_pm_runtime_put;
+	}
+
+	ret = mtk_crtc_ddp_clk_enable(mtk_crtc);
+	if (ret < 0) {
+		DRM_ERROR("Failed to enable component clocks: %d\n", ret);
+		goto err_mutex_unprepare;
+	}
+
+	DRM_DEBUG_DRIVER("mediatek_ddp_ddp_path_setup\n");
+	for (i = 0; i < mtk_crtc->ddp_comp_nr - 1; i++) {
+		mtk_ddp_add_comp_to_path(mtk_crtc->config_regs,
+					 mtk_crtc->ddp_comp[i]->id,
+					 mtk_crtc->ddp_comp[i + 1]->id);
+		mtk_disp_mutex_add_comp(mtk_crtc->mutex,
+					mtk_crtc->ddp_comp[i]->id);
+	}
+	mtk_disp_mutex_add_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id);
+	mtk_disp_mutex_enable(mtk_crtc->mutex);
+
+	for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) {
+		struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[i];
+
+		mtk_ddp_comp_config(comp, width, height, vrefresh);
+		mtk_ddp_comp_start(comp);
+	}
+
+	/* Initially configure all planes */
+	for (i = 0; i < OVL_LAYER_NR; i++) {
+		struct drm_plane *plane = &mtk_crtc->planes[i].base;
+		struct mtk_plane_state *plane_state;
+
+		plane_state = to_mtk_plane_state(plane->state);
+		mtk_ddp_comp_layer_config(mtk_crtc->ddp_comp[0], i,
+					  plane_state);
+	}
+
+	return 0;
+
+err_mutex_unprepare:
+	mtk_disp_mutex_unprepare(mtk_crtc->mutex);
+err_pm_runtime_put:
+	pm_runtime_put(crtc->dev->dev);
+	return ret;
+}
+
+static void mtk_crtc_ddp_hw_fini(struct mtk_drm_crtc *mtk_crtc)
+{
+	struct drm_device *drm = mtk_crtc->base.dev;
+	int i;
+
+	DRM_DEBUG_DRIVER("%s\n", __func__);
+	for (i = 0; i < mtk_crtc->ddp_comp_nr; i++)
+		mtk_ddp_comp_stop(mtk_crtc->ddp_comp[i]);
+	for (i = 0; i < mtk_crtc->ddp_comp_nr; i++)
+		mtk_disp_mutex_remove_comp(mtk_crtc->mutex,
+					   mtk_crtc->ddp_comp[i]->id);
+	mtk_disp_mutex_disable(mtk_crtc->mutex);
+	for (i = 0; i < mtk_crtc->ddp_comp_nr - 1; i++) {
+		mtk_ddp_remove_comp_from_path(mtk_crtc->config_regs,
+					      mtk_crtc->ddp_comp[i]->id,
+					      mtk_crtc->ddp_comp[i + 1]->id);
+		mtk_disp_mutex_remove_comp(mtk_crtc->mutex,
+					   mtk_crtc->ddp_comp[i]->id);
+	}
+	mtk_disp_mutex_remove_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id);
+	mtk_crtc_ddp_clk_disable(mtk_crtc);
+	mtk_disp_mutex_unprepare(mtk_crtc->mutex);
+
+	pm_runtime_put(drm->dev);
+}
+
+static void mtk_drm_crtc_enable(struct drm_crtc *crtc)
+{
+	struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
+	struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0];
+	int ret;
+
+	DRM_DEBUG_DRIVER("%s %d\n", __func__, crtc->base.id);
+
+	ret = mtk_smi_larb_get(ovl->larb_dev);
+	if (ret) {
+		DRM_ERROR("Failed to get larb: %d\n", ret);
+		return;
+	}
+
+	ret = mtk_crtc_ddp_hw_init(mtk_crtc);
+	if (ret) {
+		mtk_smi_larb_put(ovl->larb_dev);
+		return;
+	}
+
+	drm_crtc_vblank_on(crtc);
+	mtk_crtc->enabled = true;
+}
+
+static void mtk_drm_crtc_disable(struct drm_crtc *crtc)
+{
+	struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
+	struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0];
+	int i;
+
+	DRM_DEBUG_DRIVER("%s %d\n", __func__, crtc->base.id);
+	if (WARN_ON(!mtk_crtc->enabled))
+		return;
+
+	/* Set all pending plane state to disabled */
+	for (i = 0; i < OVL_LAYER_NR; i++) {
+		struct drm_plane *plane = &mtk_crtc->planes[i].base;
+		struct mtk_plane_state *plane_state;
+
+		plane_state = to_mtk_plane_state(plane->state);
+		plane_state->pending.enable = false;
+		plane_state->pending.config = true;
+	}
+	mtk_crtc->pending_planes = true;
+
+	/* Wait for planes to be disabled */
+	drm_crtc_wait_one_vblank(crtc);
+
+	drm_crtc_vblank_off(crtc);
+	mtk_crtc_ddp_hw_fini(mtk_crtc);
+	mtk_smi_larb_put(ovl->larb_dev);
+
+	mtk_crtc->enabled = false;
+}
+
+static void mtk_drm_crtc_atomic_begin(struct drm_crtc *crtc,
+				      struct drm_crtc_state *old_crtc_state)
+{
+	struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
+	struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
+
+	if (mtk_crtc->event && state->base.event)
+		DRM_ERROR("new event while there is still a pending event\n");
+
+	if (state->base.event) {
+		state->base.event->pipe = drm_crtc_index(crtc);
+		WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+		mtk_crtc->event = state->base.event;
+		state->base.event = NULL;
+	}
+}
+
+static void mtk_drm_crtc_atomic_flush(struct drm_crtc *crtc,
+				      struct drm_crtc_state *old_crtc_state)
+{
+	struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
+	unsigned int pending_planes = 0;
+	int i;
+
+	if (mtk_crtc->event)
+		mtk_crtc->pending_needs_vblank = true;
+	for (i = 0; i < OVL_LAYER_NR; i++) {
+		struct drm_plane *plane = &mtk_crtc->planes[i].base;
+		struct mtk_plane_state *plane_state;
+
+		plane_state = to_mtk_plane_state(plane->state);
+		if (plane_state->pending.dirty) {
+			plane_state->pending.config = true;
+			plane_state->pending.dirty = false;
+			pending_planes |= BIT(i);
+		}
+	}
+	if (pending_planes)
+		mtk_crtc->pending_planes = true;
+}
+
+static const struct drm_crtc_funcs mtk_crtc_funcs = {
+	.set_config		= drm_atomic_helper_set_config,
+	.page_flip		= drm_atomic_helper_page_flip,
+	.destroy		= mtk_drm_crtc_destroy,
+	.reset			= mtk_drm_crtc_reset,
+	.atomic_duplicate_state	= mtk_drm_crtc_duplicate_state,
+	.atomic_destroy_state	= mtk_drm_crtc_destroy_state,
+};
+
+static const struct drm_crtc_helper_funcs mtk_crtc_helper_funcs = {
+	.mode_fixup	= mtk_drm_crtc_mode_fixup,
+	.mode_set_nofb	= mtk_drm_crtc_mode_set_nofb,
+	.enable		= mtk_drm_crtc_enable,
+	.disable	= mtk_drm_crtc_disable,
+	.atomic_begin	= mtk_drm_crtc_atomic_begin,
+	.atomic_flush	= mtk_drm_crtc_atomic_flush,
+};
+
+static int mtk_drm_crtc_init(struct drm_device *drm,
+			     struct mtk_drm_crtc *mtk_crtc,
+			     struct drm_plane *primary,
+			     struct drm_plane *cursor, unsigned int pipe)
+{
+	int ret;
+
+	ret = drm_crtc_init_with_planes(drm, &mtk_crtc->base, primary, cursor,
+					&mtk_crtc_funcs, NULL);
+	if (ret)
+		goto err_cleanup_crtc;
+
+	drm_crtc_helper_add(&mtk_crtc->base, &mtk_crtc_helper_funcs);
+
+	return 0;
+
+err_cleanup_crtc:
+	drm_crtc_cleanup(&mtk_crtc->base);
+	return ret;
+}
+
+void mtk_crtc_ddp_irq(struct drm_crtc *crtc, struct mtk_ddp_comp *ovl)
+{
+	struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
+	struct mtk_crtc_state *state = to_mtk_crtc_state(mtk_crtc->base.state);
+	unsigned int i;
+
+	/*
+	 * TODO: instead of updating the registers here, we should prepare
+	 * working registers in atomic_commit and let the hardware command
+	 * queue update module registers on vblank.
+	 */
+	if (state->pending_config) {
+		mtk_ddp_comp_config(ovl, state->pending_width,
+				    state->pending_height,
+				    state->pending_vrefresh);
+
+		state->pending_config = false;
+	}
+
+	if (mtk_crtc->pending_planes) {
+		for (i = 0; i < OVL_LAYER_NR; i++) {
+			struct drm_plane *plane = &mtk_crtc->planes[i].base;
+			struct mtk_plane_state *plane_state;
+
+			plane_state = to_mtk_plane_state(plane->state);
+
+			if (plane_state->pending.config) {
+				mtk_ddp_comp_layer_config(ovl, i, plane_state);
+				plane_state->pending.config = false;
+			}
+		}
+		mtk_crtc->pending_planes = false;
+	}
+
+	mtk_drm_finish_page_flip(mtk_crtc);
+}
+
+int mtk_drm_crtc_create(struct drm_device *drm_dev,
+			const enum mtk_ddp_comp_id *path, unsigned int path_len)
+{
+	struct mtk_drm_private *priv = drm_dev->dev_private;
+	struct device *dev = drm_dev->dev;
+	struct mtk_drm_crtc *mtk_crtc;
+	enum drm_plane_type type;
+	unsigned int zpos;
+	int pipe = priv->num_pipes;
+	int ret;
+	int i;
+
+	for (i = 0; i < path_len; i++) {
+		enum mtk_ddp_comp_id comp_id = path[i];
+		struct device_node *node;
+
+		node = priv->comp_node[comp_id];
+		if (!node) {
+			dev_info(dev,
+				 "Not creating crtc %d because component %d is disabled or missing\n",
+				 pipe, comp_id);
+			return 0;
+		}
+	}
+
+	mtk_crtc = devm_kzalloc(dev, sizeof(*mtk_crtc), GFP_KERNEL);
+	if (!mtk_crtc)
+		return -ENOMEM;
+
+	mtk_crtc->config_regs = priv->config_regs;
+	mtk_crtc->ddp_comp_nr = path_len;
+	mtk_crtc->ddp_comp = devm_kmalloc_array(dev, mtk_crtc->ddp_comp_nr,
+						sizeof(*mtk_crtc->ddp_comp),
+						GFP_KERNEL);
+
+	mtk_crtc->mutex = mtk_disp_mutex_get(priv->mutex_dev, pipe);
+	if (IS_ERR(mtk_crtc->mutex)) {
+		ret = PTR_ERR(mtk_crtc->mutex);
+		dev_err(dev, "Failed to get mutex: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) {
+		enum mtk_ddp_comp_id comp_id = path[i];
+		struct mtk_ddp_comp *comp;
+		struct device_node *node;
+
+		node = priv->comp_node[comp_id];
+		comp = priv->ddp_comp[comp_id];
+		if (!comp) {
+			dev_err(dev, "Component %s not initialized\n",
+				node->full_name);
+			ret = -ENODEV;
+			goto unprepare;
+		}
+
+		ret = clk_prepare(comp->clk);
+		if (ret) {
+			dev_err(dev,
+				"Failed to prepare clock for component %s: %d\n",
+				node->full_name, ret);
+			goto unprepare;
+		}
+
+		mtk_crtc->ddp_comp[i] = comp;
+	}
+
+	for (zpos = 0; zpos < OVL_LAYER_NR; zpos++) {
+		type = (zpos == 0) ? DRM_PLANE_TYPE_PRIMARY :
+				(zpos == 1) ? DRM_PLANE_TYPE_CURSOR :
+						DRM_PLANE_TYPE_OVERLAY;
+		ret = mtk_plane_init(drm_dev, &mtk_crtc->planes[zpos],
+				     BIT(pipe), type, zpos);
+		if (ret)
+			goto unprepare;
+	}
+
+	ret = mtk_drm_crtc_init(drm_dev, mtk_crtc, &mtk_crtc->planes[0].base,
+				&mtk_crtc->planes[1].base, pipe);
+	if (ret < 0)
+		goto unprepare;
+
+	priv->crtc[pipe] = &mtk_crtc->base;
+	priv->num_pipes++;
+
+	return 0;
+
+unprepare:
+	while (--i >= 0)
+		clk_unprepare(mtk_crtc->ddp_comp[i]->clk);
+
+	return ret;
+}
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_crtc.h b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h
new file mode 100644
index 0000000..81e5566
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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_DRM_CRTC_H
+#define MTK_DRM_CRTC_H
+
+#include <drm/drm_crtc.h>
+#include "mtk_drm_ddp_comp.h"
+#include "mtk_drm_plane.h"
+
+#define OVL_LAYER_NR	4
+
+int mtk_drm_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe);
+void mtk_drm_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe);
+void mtk_drm_crtc_check_flush(struct drm_crtc *crtc);
+void mtk_drm_crtc_commit(struct drm_crtc *crtc);
+void mtk_crtc_ddp_irq(struct drm_crtc *crtc, struct mtk_ddp_comp *ovl);
+int mtk_drm_crtc_create(struct drm_device *drm_dev,
+			const enum mtk_ddp_comp_id *path,
+			unsigned int path_len);
+
+#endif /* MTK_DRM_CRTC_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp.c b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c
new file mode 100644
index 0000000..16584e4
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "mtk_drm_ddp.h"
+#include "mtk_drm_ddp_comp.h"
+
+#define DISP_REG_CONFIG_DISP_OVL0_MOUT_EN	0x040
+#define DISP_REG_CONFIG_DISP_OVL1_MOUT_EN	0x044
+#define DISP_REG_CONFIG_DISP_OD_MOUT_EN		0x048
+#define DISP_REG_CONFIG_DISP_GAMMA_MOUT_EN	0x04c
+#define DISP_REG_CONFIG_DISP_UFOE_MOUT_EN	0x050
+#define DISP_REG_CONFIG_DISP_COLOR0_SEL_IN	0x084
+#define DISP_REG_CONFIG_DISP_COLOR1_SEL_IN	0x088
+#define DISP_REG_CONFIG_DPI_SEL_IN		0x0ac
+#define DISP_REG_CONFIG_DISP_RDMA1_MOUT_EN	0x0c8
+#define DISP_REG_CONFIG_MMSYS_CG_CON0		0x100
+
+#define DISP_REG_MUTEX_EN(n)	(0x20 + 0x20 * (n))
+#define DISP_REG_MUTEX_RST(n)	(0x28 + 0x20 * (n))
+#define DISP_REG_MUTEX_MOD(n)	(0x2c + 0x20 * (n))
+#define DISP_REG_MUTEX_SOF(n)	(0x30 + 0x20 * (n))
+
+#define MUTEX_MOD_DISP_OVL0		BIT(11)
+#define MUTEX_MOD_DISP_OVL1		BIT(12)
+#define MUTEX_MOD_DISP_RDMA0		BIT(13)
+#define MUTEX_MOD_DISP_RDMA1		BIT(14)
+#define MUTEX_MOD_DISP_RDMA2		BIT(15)
+#define MUTEX_MOD_DISP_WDMA0		BIT(16)
+#define MUTEX_MOD_DISP_WDMA1		BIT(17)
+#define MUTEX_MOD_DISP_COLOR0		BIT(18)
+#define MUTEX_MOD_DISP_COLOR1		BIT(19)
+#define MUTEX_MOD_DISP_AAL		BIT(20)
+#define MUTEX_MOD_DISP_GAMMA		BIT(21)
+#define MUTEX_MOD_DISP_UFOE		BIT(22)
+#define MUTEX_MOD_DISP_PWM0		BIT(23)
+#define MUTEX_MOD_DISP_PWM1		BIT(24)
+#define MUTEX_MOD_DISP_OD		BIT(25)
+
+#define MUTEX_SOF_SINGLE_MODE		0
+#define MUTEX_SOF_DSI0			1
+#define MUTEX_SOF_DSI1			2
+#define MUTEX_SOF_DPI0			3
+
+#define OVL0_MOUT_EN_COLOR0		0x1
+#define OD_MOUT_EN_RDMA0		0x1
+#define UFOE_MOUT_EN_DSI0		0x1
+#define COLOR0_SEL_IN_OVL0		0x1
+#define OVL1_MOUT_EN_COLOR1		0x1
+#define GAMMA_MOUT_EN_RDMA1		0x1
+#define RDMA1_MOUT_DPI0			0x2
+#define DPI0_SEL_IN_RDMA1		0x1
+#define COLOR1_SEL_IN_OVL1		0x1
+
+struct mtk_disp_mutex {
+	int id;
+	bool claimed;
+};
+
+struct mtk_ddp {
+	struct device			*dev;
+	struct clk			*clk;
+	void __iomem			*regs;
+	struct mtk_disp_mutex		mutex[10];
+};
+
+static const unsigned int mutex_mod[DDP_COMPONENT_ID_MAX] = {
+	[DDP_COMPONENT_AAL] = MUTEX_MOD_DISP_AAL,
+	[DDP_COMPONENT_COLOR0] = MUTEX_MOD_DISP_COLOR0,
+	[DDP_COMPONENT_COLOR1] = MUTEX_MOD_DISP_COLOR1,
+	[DDP_COMPONENT_GAMMA] = MUTEX_MOD_DISP_GAMMA,
+	[DDP_COMPONENT_OD] = MUTEX_MOD_DISP_OD,
+	[DDP_COMPONENT_OVL0] = MUTEX_MOD_DISP_OVL0,
+	[DDP_COMPONENT_OVL1] = MUTEX_MOD_DISP_OVL1,
+	[DDP_COMPONENT_PWM0] = MUTEX_MOD_DISP_PWM0,
+	[DDP_COMPONENT_PWM1] = MUTEX_MOD_DISP_PWM1,
+	[DDP_COMPONENT_RDMA0] = MUTEX_MOD_DISP_RDMA0,
+	[DDP_COMPONENT_RDMA1] = MUTEX_MOD_DISP_RDMA1,
+	[DDP_COMPONENT_RDMA2] = MUTEX_MOD_DISP_RDMA2,
+	[DDP_COMPONENT_UFOE] = MUTEX_MOD_DISP_UFOE,
+	[DDP_COMPONENT_WDMA0] = MUTEX_MOD_DISP_WDMA0,
+	[DDP_COMPONENT_WDMA1] = MUTEX_MOD_DISP_WDMA1,
+};
+
+static unsigned int mtk_ddp_mout_en(enum mtk_ddp_comp_id cur,
+				    enum mtk_ddp_comp_id next,
+				    unsigned int *addr)
+{
+	unsigned int value;
+
+	if (cur == DDP_COMPONENT_OVL0 && next == DDP_COMPONENT_COLOR0) {
+		*addr = DISP_REG_CONFIG_DISP_OVL0_MOUT_EN;
+		value = OVL0_MOUT_EN_COLOR0;
+	} else if (cur == DDP_COMPONENT_OD && next == DDP_COMPONENT_RDMA0) {
+		*addr = DISP_REG_CONFIG_DISP_OD_MOUT_EN;
+		value = OD_MOUT_EN_RDMA0;
+	} else if (cur == DDP_COMPONENT_UFOE && next == DDP_COMPONENT_DSI0) {
+		*addr = DISP_REG_CONFIG_DISP_UFOE_MOUT_EN;
+		value = UFOE_MOUT_EN_DSI0;
+	} else if (cur == DDP_COMPONENT_OVL1 && next == DDP_COMPONENT_COLOR1) {
+		*addr = DISP_REG_CONFIG_DISP_OVL1_MOUT_EN;
+		value = OVL1_MOUT_EN_COLOR1;
+	} else if (cur == DDP_COMPONENT_GAMMA && next == DDP_COMPONENT_RDMA1) {
+		*addr = DISP_REG_CONFIG_DISP_GAMMA_MOUT_EN;
+		value = GAMMA_MOUT_EN_RDMA1;
+	} else if (cur == DDP_COMPONENT_RDMA1 && next == DDP_COMPONENT_DPI0) {
+		*addr = DISP_REG_CONFIG_DISP_RDMA1_MOUT_EN;
+		value = RDMA1_MOUT_DPI0;
+	} else {
+		value = 0;
+	}
+
+	return value;
+}
+
+static unsigned int mtk_ddp_sel_in(enum mtk_ddp_comp_id cur,
+				   enum mtk_ddp_comp_id next,
+				   unsigned int *addr)
+{
+	unsigned int value;
+
+	if (cur == DDP_COMPONENT_OVL0 && next == DDP_COMPONENT_COLOR0) {
+		*addr = DISP_REG_CONFIG_DISP_COLOR0_SEL_IN;
+		value = COLOR0_SEL_IN_OVL0;
+	} else if (cur == DDP_COMPONENT_RDMA1 && next == DDP_COMPONENT_DPI0) {
+		*addr = DISP_REG_CONFIG_DPI_SEL_IN;
+		value = DPI0_SEL_IN_RDMA1;
+	} else if (cur == DDP_COMPONENT_OVL1 && next == DDP_COMPONENT_COLOR1) {
+		*addr = DISP_REG_CONFIG_DISP_COLOR1_SEL_IN;
+		value = COLOR1_SEL_IN_OVL1;
+	} else {
+		value = 0;
+	}
+
+	return value;
+}
+
+void mtk_ddp_add_comp_to_path(void __iomem *config_regs,
+			      enum mtk_ddp_comp_id cur,
+			      enum mtk_ddp_comp_id next)
+{
+	unsigned int addr, value, reg;
+
+	value = mtk_ddp_mout_en(cur, next, &addr);
+	if (value) {
+		reg = readl_relaxed(config_regs + addr) | value;
+		writel_relaxed(reg, config_regs + addr);
+	}
+
+	value = mtk_ddp_sel_in(cur, next, &addr);
+	if (value) {
+		reg = readl_relaxed(config_regs + addr) | value;
+		writel_relaxed(reg, config_regs + addr);
+	}
+}
+
+void mtk_ddp_remove_comp_from_path(void __iomem *config_regs,
+				   enum mtk_ddp_comp_id cur,
+				   enum mtk_ddp_comp_id next)
+{
+	unsigned int addr, value, reg;
+
+	value = mtk_ddp_mout_en(cur, next, &addr);
+	if (value) {
+		reg = readl_relaxed(config_regs + addr) & ~value;
+		writel_relaxed(reg, config_regs + addr);
+	}
+
+	value = mtk_ddp_sel_in(cur, next, &addr);
+	if (value) {
+		reg = readl_relaxed(config_regs + addr) & ~value;
+		writel_relaxed(reg, config_regs + addr);
+	}
+}
+
+struct mtk_disp_mutex *mtk_disp_mutex_get(struct device *dev, unsigned int id)
+{
+	struct mtk_ddp *ddp = dev_get_drvdata(dev);
+
+	if (id >= 10)
+		return ERR_PTR(-EINVAL);
+	if (ddp->mutex[id].claimed)
+		return ERR_PTR(-EBUSY);
+
+	ddp->mutex[id].claimed = true;
+
+	return &ddp->mutex[id];
+}
+
+void mtk_disp_mutex_put(struct mtk_disp_mutex *mutex)
+{
+	struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
+					   mutex[mutex->id]);
+
+	WARN_ON(&ddp->mutex[mutex->id] != mutex);
+
+	mutex->claimed = false;
+}
+
+int mtk_disp_mutex_prepare(struct mtk_disp_mutex *mutex)
+{
+	struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
+					   mutex[mutex->id]);
+	return clk_prepare_enable(ddp->clk);
+}
+
+void mtk_disp_mutex_unprepare(struct mtk_disp_mutex *mutex)
+{
+	struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
+					   mutex[mutex->id]);
+	clk_disable_unprepare(ddp->clk);
+}
+
+void mtk_disp_mutex_add_comp(struct mtk_disp_mutex *mutex,
+			     enum mtk_ddp_comp_id id)
+{
+	struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
+					   mutex[mutex->id]);
+	unsigned int reg;
+
+	WARN_ON(&ddp->mutex[mutex->id] != mutex);
+
+	switch (id) {
+	case DDP_COMPONENT_DSI0:
+		reg = MUTEX_SOF_DSI0;
+		break;
+	case DDP_COMPONENT_DSI1:
+		reg = MUTEX_SOF_DSI0;
+		break;
+	case DDP_COMPONENT_DPI0:
+		reg = MUTEX_SOF_DPI0;
+		break;
+	default:
+		reg = readl_relaxed(ddp->regs + DISP_REG_MUTEX_MOD(mutex->id));
+		reg |= mutex_mod[id];
+		writel_relaxed(reg, ddp->regs + DISP_REG_MUTEX_MOD(mutex->id));
+		return;
+	}
+
+	writel_relaxed(reg, ddp->regs + DISP_REG_MUTEX_SOF(mutex->id));
+}
+
+void mtk_disp_mutex_remove_comp(struct mtk_disp_mutex *mutex,
+				enum mtk_ddp_comp_id id)
+{
+	struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
+					   mutex[mutex->id]);
+	unsigned int reg;
+
+	WARN_ON(&ddp->mutex[mutex->id] != mutex);
+
+	switch (id) {
+	case DDP_COMPONENT_DSI0:
+	case DDP_COMPONENT_DSI1:
+	case DDP_COMPONENT_DPI0:
+		writel_relaxed(MUTEX_SOF_SINGLE_MODE,
+			       ddp->regs + DISP_REG_MUTEX_SOF(mutex->id));
+		break;
+	default:
+		reg = readl_relaxed(ddp->regs + DISP_REG_MUTEX_MOD(mutex->id));
+		reg &= ~mutex_mod[id];
+		writel_relaxed(reg, ddp->regs + DISP_REG_MUTEX_MOD(mutex->id));
+		break;
+	}
+}
+
+void mtk_disp_mutex_enable(struct mtk_disp_mutex *mutex)
+{
+	struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
+					   mutex[mutex->id]);
+
+	WARN_ON(&ddp->mutex[mutex->id] != mutex);
+
+	writel(1, ddp->regs + DISP_REG_MUTEX_EN(mutex->id));
+}
+
+void mtk_disp_mutex_disable(struct mtk_disp_mutex *mutex)
+{
+	struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
+					   mutex[mutex->id]);
+
+	WARN_ON(&ddp->mutex[mutex->id] != mutex);
+
+	writel(0, ddp->regs + DISP_REG_MUTEX_EN(mutex->id));
+}
+
+static int mtk_ddp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_ddp *ddp;
+	struct resource *regs;
+	int i;
+
+	ddp = devm_kzalloc(dev, sizeof(*ddp), GFP_KERNEL);
+	if (!ddp)
+		return -ENOMEM;
+
+	for (i = 0; i < 10; i++)
+		ddp->mutex[i].id = i;
+
+	ddp->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(ddp->clk)) {
+		dev_err(dev, "Failed to get clock\n");
+		return PTR_ERR(ddp->clk);
+	}
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	ddp->regs = devm_ioremap_resource(dev, regs);
+	if (IS_ERR(ddp->regs)) {
+		dev_err(dev, "Failed to map mutex registers\n");
+		return PTR_ERR(ddp->regs);
+	}
+
+	platform_set_drvdata(pdev, ddp);
+
+	return 0;
+}
+
+static int mtk_ddp_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static const struct of_device_id ddp_driver_dt_match[] = {
+	{ .compatible = "mediatek,mt8173-disp-mutex" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ddp_driver_dt_match);
+
+static struct platform_driver mtk_ddp_driver = {
+	.probe		= mtk_ddp_probe,
+	.remove		= mtk_ddp_remove,
+	.driver		= {
+		.name	= "mediatek-ddp",
+		.owner	= THIS_MODULE,
+		.of_match_table = ddp_driver_dt_match,
+	},
+};
+
+module_platform_driver(mtk_ddp_driver);
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp.h b/drivers/gpu/drm/mediatek/mtk_drm_ddp.h
new file mode 100644
index 0000000..92c1175
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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_DRM_DDP_H
+#define MTK_DRM_DDP_H
+
+#include "mtk_drm_ddp_comp.h"
+
+struct regmap;
+struct device;
+struct mtk_disp_mutex;
+
+void mtk_ddp_add_comp_to_path(void __iomem *config_regs,
+			      enum mtk_ddp_comp_id cur,
+			      enum mtk_ddp_comp_id next);
+void mtk_ddp_remove_comp_from_path(void __iomem *config_regs,
+				   enum mtk_ddp_comp_id cur,
+				   enum mtk_ddp_comp_id next);
+
+struct mtk_disp_mutex *mtk_disp_mutex_get(struct device *dev, unsigned int id);
+int mtk_disp_mutex_prepare(struct mtk_disp_mutex *mutex);
+void mtk_disp_mutex_add_comp(struct mtk_disp_mutex *mutex,
+			     enum mtk_ddp_comp_id id);
+void mtk_disp_mutex_enable(struct mtk_disp_mutex *mutex);
+void mtk_disp_mutex_disable(struct mtk_disp_mutex *mutex);
+void mtk_disp_mutex_remove_comp(struct mtk_disp_mutex *mutex,
+				enum mtk_ddp_comp_id id);
+void mtk_disp_mutex_unprepare(struct mtk_disp_mutex *mutex);
+void mtk_disp_mutex_put(struct mtk_disp_mutex *mutex);
+
+#endif /* MTK_DRM_DDP_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c
new file mode 100644
index 0000000..3970fcf
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ * Authors:
+ *	YT Shen <yt.shen@mediatek.com>
+ *	CK Hu <ck.hu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <drm/drmP.h>
+#include "mtk_drm_drv.h"
+#include "mtk_drm_plane.h"
+#include "mtk_drm_ddp_comp.h"
+
+#define DISP_OD_EN				0x0000
+#define DISP_OD_INTEN				0x0008
+#define DISP_OD_INTSTA				0x000c
+#define DISP_OD_CFG				0x0020
+#define DISP_OD_SIZE				0x0030
+
+#define DISP_REG_UFO_START			0x0000
+
+#define DISP_COLOR_CFG_MAIN			0x0400
+#define DISP_COLOR_START			0x0c00
+#define DISP_COLOR_WIDTH			0x0c50
+#define DISP_COLOR_HEIGHT			0x0c54
+
+#define	OD_RELAY_MODE		BIT(0)
+
+#define	UFO_BYPASS		BIT(2)
+
+#define	COLOR_BYPASS_ALL	BIT(7)
+#define	COLOR_SEQ_SEL		BIT(13)
+
+static void mtk_color_config(struct mtk_ddp_comp *comp, unsigned int w,
+			     unsigned int h, unsigned int vrefresh)
+{
+	writel(w, comp->regs + DISP_COLOR_WIDTH);
+	writel(h, comp->regs + DISP_COLOR_HEIGHT);
+}
+
+static void mtk_color_start(struct mtk_ddp_comp *comp)
+{
+	writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL,
+	       comp->regs + DISP_COLOR_CFG_MAIN);
+	writel(0x1, comp->regs + DISP_COLOR_START);
+}
+
+static void mtk_od_config(struct mtk_ddp_comp *comp, unsigned int w,
+			  unsigned int h, unsigned int vrefresh)
+{
+	writel(w << 16 | h, comp->regs + DISP_OD_SIZE);
+}
+
+static void mtk_od_start(struct mtk_ddp_comp *comp)
+{
+	writel(OD_RELAY_MODE, comp->regs + DISP_OD_CFG);
+	writel(1, comp->regs + DISP_OD_EN);
+}
+
+static void mtk_ufoe_start(struct mtk_ddp_comp *comp)
+{
+	writel(UFO_BYPASS, comp->regs + DISP_REG_UFO_START);
+}
+
+static const struct mtk_ddp_comp_funcs ddp_color = {
+	.config = mtk_color_config,
+	.start = mtk_color_start,
+};
+
+static const struct mtk_ddp_comp_funcs ddp_od = {
+	.config = mtk_od_config,
+	.start = mtk_od_start,
+};
+
+static const struct mtk_ddp_comp_funcs ddp_ufoe = {
+	.start = mtk_ufoe_start,
+};
+
+static const char * const mtk_ddp_comp_stem[MTK_DDP_COMP_TYPE_MAX] = {
+	[MTK_DISP_OVL] = "ovl",
+	[MTK_DISP_RDMA] = "rdma",
+	[MTK_DISP_WDMA] = "wdma",
+	[MTK_DISP_COLOR] = "color",
+	[MTK_DISP_AAL] = "aal",
+	[MTK_DISP_GAMMA] = "gamma",
+	[MTK_DISP_UFOE] = "ufoe",
+	[MTK_DSI] = "dsi",
+	[MTK_DPI] = "dpi",
+	[MTK_DISP_PWM] = "pwm",
+	[MTK_DISP_MUTEX] = "mutex",
+	[MTK_DISP_OD] = "od",
+};
+
+struct mtk_ddp_comp_match {
+	enum mtk_ddp_comp_type type;
+	int alias_id;
+	const struct mtk_ddp_comp_funcs *funcs;
+};
+
+static const struct mtk_ddp_comp_match mtk_ddp_matches[DDP_COMPONENT_ID_MAX] = {
+	[DDP_COMPONENT_AAL]	= { MTK_DISP_AAL,	0, NULL },
+	[DDP_COMPONENT_COLOR0]	= { MTK_DISP_COLOR,	0, &ddp_color },
+	[DDP_COMPONENT_COLOR1]	= { MTK_DISP_COLOR,	1, &ddp_color },
+	[DDP_COMPONENT_DPI0]	= { MTK_DPI,		0, NULL },
+	[DDP_COMPONENT_DSI0]	= { MTK_DSI,		0, NULL },
+	[DDP_COMPONENT_DSI1]	= { MTK_DSI,		1, NULL },
+	[DDP_COMPONENT_GAMMA]	= { MTK_DISP_GAMMA,	0, NULL },
+	[DDP_COMPONENT_OD]	= { MTK_DISP_OD,	0, &ddp_od },
+	[DDP_COMPONENT_OVL0]	= { MTK_DISP_OVL,	0, NULL },
+	[DDP_COMPONENT_OVL1]	= { MTK_DISP_OVL,	1, NULL },
+	[DDP_COMPONENT_PWM0]	= { MTK_DISP_PWM,	0, NULL },
+	[DDP_COMPONENT_RDMA0]	= { MTK_DISP_RDMA,	0, NULL },
+	[DDP_COMPONENT_RDMA1]	= { MTK_DISP_RDMA,	1, NULL },
+	[DDP_COMPONENT_RDMA2]	= { MTK_DISP_RDMA,	2, NULL },
+	[DDP_COMPONENT_UFOE]	= { MTK_DISP_UFOE,	0, &ddp_ufoe },
+	[DDP_COMPONENT_WDMA0]	= { MTK_DISP_WDMA,	0, NULL },
+	[DDP_COMPONENT_WDMA1]	= { MTK_DISP_WDMA,	1, NULL },
+};
+
+int mtk_ddp_comp_get_id(struct device_node *node,
+			enum mtk_ddp_comp_type comp_type)
+{
+	int id = of_alias_get_id(node, mtk_ddp_comp_stem[comp_type]);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(mtk_ddp_matches); i++) {
+		if (comp_type == mtk_ddp_matches[i].type &&
+		    (id < 0 || id == mtk_ddp_matches[i].alias_id))
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+int mtk_ddp_comp_init(struct device *dev, struct device_node *node,
+		      struct mtk_ddp_comp *comp, enum mtk_ddp_comp_id comp_id,
+		      const struct mtk_ddp_comp_funcs *funcs)
+{
+	enum mtk_ddp_comp_type type;
+	struct device_node *larb_node;
+	struct platform_device *larb_pdev;
+
+	if (comp_id < 0 || comp_id >= DDP_COMPONENT_ID_MAX)
+		return -EINVAL;
+
+	comp->id = comp_id;
+	comp->funcs = funcs ?: mtk_ddp_matches[comp_id].funcs;
+
+	if (comp_id == DDP_COMPONENT_DPI0 ||
+	    comp_id == DDP_COMPONENT_DSI0 ||
+	    comp_id == DDP_COMPONENT_PWM0) {
+		comp->regs = NULL;
+		comp->clk = NULL;
+		comp->irq = 0;
+		return 0;
+	}
+
+	comp->regs = of_iomap(node, 0);
+	comp->irq = of_irq_get(node, 0);
+	comp->clk = of_clk_get(node, 0);
+	if (IS_ERR(comp->clk))
+		comp->clk = NULL;
+
+	type = mtk_ddp_matches[comp_id].type;
+
+	/* Only DMA capable components need the LARB property */
+	comp->larb_dev = NULL;
+	if (type != MTK_DISP_OVL &&
+	    type != MTK_DISP_RDMA &&
+	    type != MTK_DISP_WDMA)
+		return 0;
+
+	larb_node = of_parse_phandle(node, "mediatek,larb", 0);
+	if (!larb_node) {
+		dev_err(dev,
+			"Missing mediadek,larb phandle in %s node\n",
+			node->full_name);
+		return -EINVAL;
+	}
+
+	larb_pdev = of_find_device_by_node(larb_node);
+	if (!larb_pdev) {
+		dev_warn(dev, "Waiting for larb device %s\n",
+			 larb_node->full_name);
+		of_node_put(larb_node);
+		return -EPROBE_DEFER;
+	}
+	of_node_put(larb_node);
+
+	comp->larb_dev = &larb_pdev->dev;
+
+	return 0;
+}
+
+int mtk_ddp_comp_register(struct drm_device *drm, struct mtk_ddp_comp *comp)
+{
+	struct mtk_drm_private *private = drm->dev_private;
+
+	if (private->ddp_comp[comp->id])
+		return -EBUSY;
+
+	private->ddp_comp[comp->id] = comp;
+	return 0;
+}
+
+void mtk_ddp_comp_unregister(struct drm_device *drm, struct mtk_ddp_comp *comp)
+{
+	struct mtk_drm_private *private = drm->dev_private;
+
+	private->ddp_comp[comp->id] = NULL;
+}
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h
new file mode 100644
index 0000000..6b13ba9
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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_DRM_DDP_COMP_H
+#define MTK_DRM_DDP_COMP_H
+
+#include <linux/io.h>
+
+struct device;
+struct device_node;
+struct drm_crtc;
+struct drm_device;
+struct mtk_plane_state;
+
+enum mtk_ddp_comp_type {
+	MTK_DISP_OVL,
+	MTK_DISP_RDMA,
+	MTK_DISP_WDMA,
+	MTK_DISP_COLOR,
+	MTK_DISP_AAL,
+	MTK_DISP_GAMMA,
+	MTK_DISP_UFOE,
+	MTK_DSI,
+	MTK_DPI,
+	MTK_DISP_PWM,
+	MTK_DISP_MUTEX,
+	MTK_DISP_OD,
+	MTK_DDP_COMP_TYPE_MAX,
+};
+
+enum mtk_ddp_comp_id {
+	DDP_COMPONENT_AAL,
+	DDP_COMPONENT_COLOR0,
+	DDP_COMPONENT_COLOR1,
+	DDP_COMPONENT_DPI0,
+	DDP_COMPONENT_DSI0,
+	DDP_COMPONENT_DSI1,
+	DDP_COMPONENT_GAMMA,
+	DDP_COMPONENT_OD,
+	DDP_COMPONENT_OVL0,
+	DDP_COMPONENT_OVL1,
+	DDP_COMPONENT_PWM0,
+	DDP_COMPONENT_PWM1,
+	DDP_COMPONENT_RDMA0,
+	DDP_COMPONENT_RDMA1,
+	DDP_COMPONENT_RDMA2,
+	DDP_COMPONENT_UFOE,
+	DDP_COMPONENT_WDMA0,
+	DDP_COMPONENT_WDMA1,
+	DDP_COMPONENT_ID_MAX,
+};
+
+struct mtk_ddp_comp;
+
+struct mtk_ddp_comp_funcs {
+	void (*config)(struct mtk_ddp_comp *comp, unsigned int w,
+		       unsigned int h, unsigned int vrefresh);
+	void (*start)(struct mtk_ddp_comp *comp);
+	void (*stop)(struct mtk_ddp_comp *comp);
+	void (*enable_vblank)(struct mtk_ddp_comp *comp, struct drm_crtc *crtc);
+	void (*disable_vblank)(struct mtk_ddp_comp *comp);
+	void (*layer_on)(struct mtk_ddp_comp *comp, unsigned int idx);
+	void (*layer_off)(struct mtk_ddp_comp *comp, unsigned int idx);
+	void (*layer_config)(struct mtk_ddp_comp *comp, unsigned int idx,
+			     struct mtk_plane_state *state);
+};
+
+struct mtk_ddp_comp {
+	struct clk *clk;
+	void __iomem *regs;
+	int irq;
+	struct device *larb_dev;
+	enum mtk_ddp_comp_id id;
+	const struct mtk_ddp_comp_funcs *funcs;
+};
+
+static inline void mtk_ddp_comp_config(struct mtk_ddp_comp *comp,
+				       unsigned int w, unsigned int h,
+				       unsigned int vrefresh)
+{
+	if (comp->funcs && comp->funcs->config)
+		comp->funcs->config(comp, w, h, vrefresh);
+}
+
+static inline void mtk_ddp_comp_start(struct mtk_ddp_comp *comp)
+{
+	if (comp->funcs && comp->funcs->start)
+		comp->funcs->start(comp);
+}
+
+static inline void mtk_ddp_comp_stop(struct mtk_ddp_comp *comp)
+{
+	if (comp->funcs && comp->funcs->stop)
+		comp->funcs->stop(comp);
+}
+
+static inline void mtk_ddp_comp_enable_vblank(struct mtk_ddp_comp *comp,
+					      struct drm_crtc *crtc)
+{
+	if (comp->funcs && comp->funcs->enable_vblank)
+		comp->funcs->enable_vblank(comp, crtc);
+}
+
+static inline void mtk_ddp_comp_disable_vblank(struct mtk_ddp_comp *comp)
+{
+	if (comp->funcs && comp->funcs->disable_vblank)
+		comp->funcs->disable_vblank(comp);
+}
+
+static inline void mtk_ddp_comp_layer_on(struct mtk_ddp_comp *comp,
+					 unsigned int idx)
+{
+	if (comp->funcs && comp->funcs->layer_on)
+		comp->funcs->layer_on(comp, idx);
+}
+
+static inline void mtk_ddp_comp_layer_off(struct mtk_ddp_comp *comp,
+					  unsigned int idx)
+{
+	if (comp->funcs && comp->funcs->layer_off)
+		comp->funcs->layer_off(comp, idx);
+}
+
+static inline void mtk_ddp_comp_layer_config(struct mtk_ddp_comp *comp,
+					     unsigned int idx,
+					     struct mtk_plane_state *state)
+{
+	if (comp->funcs && comp->funcs->layer_config)
+		comp->funcs->layer_config(comp, idx, state);
+}
+
+int mtk_ddp_comp_get_id(struct device_node *node,
+			enum mtk_ddp_comp_type comp_type);
+int mtk_ddp_comp_init(struct device *dev, struct device_node *comp_node,
+		      struct mtk_ddp_comp *comp, enum mtk_ddp_comp_id comp_id,
+		      const struct mtk_ddp_comp_funcs *funcs);
+int mtk_ddp_comp_register(struct drm_device *drm, struct mtk_ddp_comp *comp);
+void mtk_ddp_comp_unregister(struct drm_device *drm, struct mtk_ddp_comp *comp);
+
+#endif /* MTK_DRM_DDP_COMP_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
new file mode 100644
index 0000000..8a21ca7
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
@@ -0,0 +1,592 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ * Author: YT SHEN <yt.shen@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem.h>
+#include <linux/component.h>
+#include <linux/iommu.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+
+#include "mtk_drm_crtc.h"
+#include "mtk_drm_ddp.h"
+#include "mtk_drm_ddp_comp.h"
+#include "mtk_drm_drv.h"
+#include "mtk_drm_fb.h"
+#include "mtk_drm_gem.h"
+
+#define DRIVER_NAME "mediatek"
+#define DRIVER_DESC "Mediatek SoC DRM"
+#define DRIVER_DATE "20150513"
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+
+static void mtk_atomic_schedule(struct mtk_drm_private *private,
+				struct drm_atomic_state *state)
+{
+	private->commit.state = state;
+	schedule_work(&private->commit.work);
+}
+
+static void mtk_atomic_wait_for_fences(struct drm_atomic_state *state)
+{
+	struct drm_plane *plane;
+	struct drm_plane_state *plane_state;
+	int i;
+
+	for_each_plane_in_state(state, plane, plane_state, i)
+		mtk_fb_wait(plane->state->fb);
+}
+
+static void mtk_atomic_complete(struct mtk_drm_private *private,
+				struct drm_atomic_state *state)
+{
+	struct drm_device *drm = private->drm;
+
+	mtk_atomic_wait_for_fences(state);
+
+	drm_atomic_helper_commit_modeset_disables(drm, state);
+	drm_atomic_helper_commit_planes(drm, state, false);
+	drm_atomic_helper_commit_modeset_enables(drm, state);
+	drm_atomic_helper_wait_for_vblanks(drm, state);
+	drm_atomic_helper_cleanup_planes(drm, state);
+	drm_atomic_state_free(state);
+}
+
+static void mtk_atomic_work(struct work_struct *work)
+{
+	struct mtk_drm_private *private = container_of(work,
+			struct mtk_drm_private, commit.work);
+
+	mtk_atomic_complete(private, private->commit.state);
+}
+
+static int mtk_atomic_commit(struct drm_device *drm,
+			     struct drm_atomic_state *state,
+			     bool async)
+{
+	struct mtk_drm_private *private = drm->dev_private;
+	int ret;
+
+	ret = drm_atomic_helper_prepare_planes(drm, state);
+	if (ret)
+		return ret;
+
+	mutex_lock(&private->commit.lock);
+	flush_work(&private->commit.work);
+
+	drm_atomic_helper_swap_state(drm, state);
+
+	if (async)
+		mtk_atomic_schedule(private, state);
+	else
+		mtk_atomic_complete(private, state);
+
+	mutex_unlock(&private->commit.lock);
+
+	return 0;
+}
+
+static const struct drm_mode_config_funcs mtk_drm_mode_config_funcs = {
+	.fb_create = mtk_drm_mode_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = mtk_atomic_commit,
+};
+
+static const enum mtk_ddp_comp_id mtk_ddp_main[] = {
+	DDP_COMPONENT_OVL0,
+	DDP_COMPONENT_COLOR0,
+	DDP_COMPONENT_AAL,
+	DDP_COMPONENT_OD,
+	DDP_COMPONENT_RDMA0,
+	DDP_COMPONENT_UFOE,
+	DDP_COMPONENT_DSI0,
+	DDP_COMPONENT_PWM0,
+};
+
+static const enum mtk_ddp_comp_id mtk_ddp_ext[] = {
+	DDP_COMPONENT_OVL1,
+	DDP_COMPONENT_COLOR1,
+	DDP_COMPONENT_GAMMA,
+	DDP_COMPONENT_RDMA1,
+	DDP_COMPONENT_DPI0,
+};
+
+static int mtk_drm_kms_init(struct drm_device *drm)
+{
+	struct mtk_drm_private *private = drm->dev_private;
+	struct platform_device *pdev;
+	struct device_node *np;
+	int ret;
+
+	if (!iommu_present(&platform_bus_type))
+		return -EPROBE_DEFER;
+
+	pdev = of_find_device_by_node(private->mutex_node);
+	if (!pdev) {
+		dev_err(drm->dev, "Waiting for disp-mutex device %s\n",
+			private->mutex_node->full_name);
+		of_node_put(private->mutex_node);
+		return -EPROBE_DEFER;
+	}
+	private->mutex_dev = &pdev->dev;
+
+	drm_mode_config_init(drm);
+
+	drm->mode_config.min_width = 64;
+	drm->mode_config.min_height = 64;
+
+	/*
+	 * set max width and height as default value(4096x4096).
+	 * this value would be used to check framebuffer size limitation
+	 * at drm_mode_addfb().
+	 */
+	drm->mode_config.max_width = 4096;
+	drm->mode_config.max_height = 4096;
+	drm->mode_config.funcs = &mtk_drm_mode_config_funcs;
+
+	ret = component_bind_all(drm->dev, drm);
+	if (ret)
+		goto err_config_cleanup;
+
+	/*
+	 * We currently support two fixed data streams, each optional,
+	 * and each statically assigned to a crtc:
+	 * OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0 ...
+	 */
+	ret = mtk_drm_crtc_create(drm, mtk_ddp_main, ARRAY_SIZE(mtk_ddp_main));
+	if (ret < 0)
+		goto err_component_unbind;
+	/* ... and OVL1 -> COLOR1 -> GAMMA -> RDMA1 -> DPI0. */
+	ret = mtk_drm_crtc_create(drm, mtk_ddp_ext, ARRAY_SIZE(mtk_ddp_ext));
+	if (ret < 0)
+		goto err_component_unbind;
+
+	/* Use OVL device for all DMA memory allocations */
+	np = private->comp_node[mtk_ddp_main[0]] ?:
+	     private->comp_node[mtk_ddp_ext[0]];
+	pdev = of_find_device_by_node(np);
+	if (!pdev) {
+		ret = -ENODEV;
+		dev_err(drm->dev, "Need at least one OVL device\n");
+		goto err_component_unbind;
+	}
+
+	private->dma_dev = &pdev->dev;
+
+	/*
+	 * We don't use the drm_irq_install() helpers provided by the DRM
+	 * core, so we need to set this manually in order to allow the
+	 * DRM_IOCTL_WAIT_VBLANK to operate correctly.
+	 */
+	drm->irq_enabled = true;
+	ret = drm_vblank_init(drm, MAX_CRTC);
+	if (ret < 0)
+		goto err_component_unbind;
+
+	drm_kms_helper_poll_init(drm);
+	drm_mode_config_reset(drm);
+
+	return 0;
+
+err_component_unbind:
+	component_unbind_all(drm->dev, drm);
+err_config_cleanup:
+	drm_mode_config_cleanup(drm);
+
+	return ret;
+}
+
+static void mtk_drm_kms_deinit(struct drm_device *drm)
+{
+	drm_kms_helper_poll_fini(drm);
+
+	drm_vblank_cleanup(drm);
+	component_unbind_all(drm->dev, drm);
+	drm_mode_config_cleanup(drm);
+}
+
+static int mtk_drm_unload(struct drm_device *drm)
+{
+	mtk_drm_kms_deinit(drm);
+	drm->dev_private = NULL;
+
+	return 0;
+}
+
+static const struct vm_operations_struct mtk_drm_gem_vm_ops = {
+	.open = drm_gem_vm_open,
+	.close = drm_gem_vm_close,
+};
+
+static const struct file_operations mtk_drm_fops = {
+	.owner = THIS_MODULE,
+	.open = drm_open,
+	.release = drm_release,
+	.unlocked_ioctl = drm_ioctl,
+	.mmap = mtk_drm_gem_mmap,
+	.poll = drm_poll,
+	.read = drm_read,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = drm_compat_ioctl,
+#endif
+};
+
+static struct drm_driver mtk_drm_driver = {
+	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME |
+			   DRIVER_ATOMIC,
+	.unload = mtk_drm_unload,
+
+	.get_vblank_counter = drm_vblank_count,
+	.enable_vblank = mtk_drm_crtc_enable_vblank,
+	.disable_vblank = mtk_drm_crtc_disable_vblank,
+
+	.gem_free_object = mtk_drm_gem_free_object,
+	.gem_vm_ops = &mtk_drm_gem_vm_ops,
+	.dumb_create = mtk_drm_gem_dumb_create,
+	.dumb_map_offset = mtk_drm_gem_dumb_map_offset,
+	.dumb_destroy = drm_gem_dumb_destroy,
+
+	.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+	.gem_prime_export = drm_gem_prime_export,
+	.gem_prime_import = drm_gem_prime_import,
+	.gem_prime_get_sg_table = mtk_gem_prime_get_sg_table,
+	.gem_prime_import_sg_table = mtk_gem_prime_import_sg_table,
+	.gem_prime_mmap = mtk_drm_gem_mmap_buf,
+	.fops = &mtk_drm_fops,
+
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.date = DRIVER_DATE,
+	.major = DRIVER_MAJOR,
+	.minor = DRIVER_MINOR,
+};
+
+static int compare_of(struct device *dev, void *data)
+{
+	return dev->of_node == data;
+}
+
+static int mtk_drm_bind(struct device *dev)
+{
+	struct mtk_drm_private *private = dev_get_drvdata(dev);
+	struct drm_device *drm;
+	int ret;
+
+	drm = drm_dev_alloc(&mtk_drm_driver, dev);
+	if (!drm)
+		return -ENOMEM;
+
+	drm_dev_set_unique(drm, dev_name(dev));
+
+	ret = drm_dev_register(drm, 0);
+	if (ret < 0)
+		goto err_free;
+
+	drm->dev_private = private;
+	private->drm = drm;
+
+	ret = mtk_drm_kms_init(drm);
+	if (ret < 0)
+		goto err_unregister;
+
+	return 0;
+
+err_unregister:
+	drm_dev_unregister(drm);
+err_free:
+	drm_dev_unref(drm);
+	return ret;
+}
+
+static void mtk_drm_unbind(struct device *dev)
+{
+	struct mtk_drm_private *private = dev_get_drvdata(dev);
+
+	drm_put_dev(private->drm);
+	private->drm = NULL;
+}
+
+static const struct component_master_ops mtk_drm_ops = {
+	.bind		= mtk_drm_bind,
+	.unbind		= mtk_drm_unbind,
+};
+
+static const struct of_device_id mtk_ddp_comp_dt_ids[] = {
+	{ .compatible = "mediatek,mt8173-disp-ovl",   .data = (void *)MTK_DISP_OVL },
+	{ .compatible = "mediatek,mt8173-disp-rdma",  .data = (void *)MTK_DISP_RDMA },
+	{ .compatible = "mediatek,mt8173-disp-wdma",  .data = (void *)MTK_DISP_WDMA },
+	{ .compatible = "mediatek,mt8173-disp-color", .data = (void *)MTK_DISP_COLOR },
+	{ .compatible = "mediatek,mt8173-disp-aal",   .data = (void *)MTK_DISP_AAL},
+	{ .compatible = "mediatek,mt8173-disp-gamma", .data = (void *)MTK_DISP_GAMMA, },
+	{ .compatible = "mediatek,mt8173-disp-ufoe",  .data = (void *)MTK_DISP_UFOE },
+	{ .compatible = "mediatek,mt8173-dsi",        .data = (void *)MTK_DSI },
+	{ .compatible = "mediatek,mt8173-dpi",        .data = (void *)MTK_DPI },
+	{ .compatible = "mediatek,mt8173-disp-mutex", .data = (void *)MTK_DISP_MUTEX },
+	{ .compatible = "mediatek,mt8173-disp-pwm",   .data = (void *)MTK_DISP_PWM },
+	{ .compatible = "mediatek,mt8173-disp-od",    .data = (void *)MTK_DISP_OD },
+	{ }
+};
+
+static int mtk_drm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_drm_private *private;
+	struct resource *mem;
+	struct device_node *node;
+	struct component_match *match = NULL;
+	int ret;
+	int i;
+
+	private = devm_kzalloc(dev, sizeof(*private), GFP_KERNEL);
+	if (!private)
+		return -ENOMEM;
+
+	mutex_init(&private->commit.lock);
+	INIT_WORK(&private->commit.work, mtk_atomic_work);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	private->config_regs = devm_ioremap_resource(dev, mem);
+	if (IS_ERR(private->config_regs)) {
+		ret = PTR_ERR(private->config_regs);
+		dev_err(dev, "Failed to ioremap mmsys-config resource: %d\n",
+			ret);
+		return ret;
+	}
+
+	/* Iterate over sibling DISP function blocks */
+	for_each_child_of_node(dev->of_node->parent, node) {
+		const struct of_device_id *of_id;
+		enum mtk_ddp_comp_type comp_type;
+		int comp_id;
+
+		of_id = of_match_node(mtk_ddp_comp_dt_ids, node);
+		if (!of_id)
+			continue;
+
+		if (!of_device_is_available(node)) {
+			dev_dbg(dev, "Skipping disabled component %s\n",
+				node->full_name);
+			continue;
+		}
+
+		comp_type = (enum mtk_ddp_comp_type)of_id->data;
+
+		if (comp_type == MTK_DISP_MUTEX) {
+			private->mutex_node = of_node_get(node);
+			continue;
+		}
+
+		comp_id = mtk_ddp_comp_get_id(node, comp_type);
+		if (comp_id < 0) {
+			dev_warn(dev, "Skipping unknown component %s\n",
+				 node->full_name);
+			continue;
+		}
+
+		private->comp_node[comp_id] = of_node_get(node);
+
+		/*
+		 * Currently only the OVL, RDMA, DSI, and DPI blocks have
+		 * separate component platform drivers and initialize their own
+		 * DDP component structure. The others are initialized here.
+		 */
+		if (comp_type == MTK_DISP_OVL ||
+		    comp_type == MTK_DISP_RDMA ||
+		    comp_type == MTK_DSI ||
+		    comp_type == MTK_DPI) {
+			dev_info(dev, "Adding component match for %s\n",
+				 node->full_name);
+			component_match_add(dev, &match, compare_of, node);
+		} else {
+			struct mtk_ddp_comp *comp;
+
+			comp = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL);
+			if (!comp) {
+				ret = -ENOMEM;
+				goto err_node;
+			}
+
+			ret = mtk_ddp_comp_init(dev, node, comp, comp_id, NULL);
+			if (ret)
+				goto err_node;
+
+			private->ddp_comp[comp_id] = comp;
+		}
+	}
+
+	if (!private->mutex_node) {
+		dev_err(dev, "Failed to find disp-mutex node\n");
+		ret = -ENODEV;
+		goto err_node;
+	}
+
+	pm_runtime_enable(dev);
+
+	platform_set_drvdata(pdev, private);
+
+	ret = component_master_add_with_match(dev, &mtk_drm_ops, match);
+	if (ret)
+		goto err_pm;
+
+	return 0;
+
+err_pm:
+	pm_runtime_disable(dev);
+err_node:
+	of_node_put(private->mutex_node);
+	for (i = 0; i < DDP_COMPONENT_ID_MAX; i++)
+		of_node_put(private->comp_node[i]);
+	return ret;
+}
+
+static int mtk_drm_remove(struct platform_device *pdev)
+{
+	struct mtk_drm_private *private = platform_get_drvdata(pdev);
+	int i;
+
+	component_master_del(&pdev->dev, &mtk_drm_ops);
+	pm_runtime_disable(&pdev->dev);
+	of_node_put(private->mutex_node);
+	for (i = 0; i < DDP_COMPONENT_ID_MAX; i++)
+		of_node_put(private->comp_node[i]);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mtk_drm_sys_suspend(struct device *dev)
+{
+	struct mtk_drm_private *private = dev_get_drvdata(dev);
+	struct drm_device *drm = private->drm;
+	struct drm_connector *conn;
+
+	drm_kms_helper_poll_disable(drm);
+
+	drm_modeset_lock_all(drm);
+	list_for_each_entry(conn, &drm->mode_config.connector_list, head) {
+		int old_dpms = conn->dpms;
+
+		if (conn->funcs->dpms)
+			conn->funcs->dpms(conn, DRM_MODE_DPMS_OFF);
+
+		/* Set the old mode back to the connector for resume */
+		conn->dpms = old_dpms;
+	}
+	drm_modeset_unlock_all(drm);
+
+	DRM_DEBUG_DRIVER("mtk_drm_sys_suspend\n");
+	return 0;
+}
+
+static int mtk_drm_sys_resume(struct device *dev)
+{
+	struct mtk_drm_private *private = dev_get_drvdata(dev);
+	struct drm_device *drm = private->drm;
+	struct drm_connector *conn;
+
+	drm_modeset_lock_all(drm);
+	list_for_each_entry(conn, &drm->mode_config.connector_list, head) {
+		int desired_mode = conn->dpms;
+
+		/*
+		 * at suspend time, we save dpms to connector->dpms,
+		 * restore the old_dpms, and at current time, the connector
+		 * dpms status must be DRM_MODE_DPMS_OFF.
+		 */
+		conn->dpms = DRM_MODE_DPMS_OFF;
+
+		/*
+		 * If the connector has been disconnected during suspend,
+		 * disconnect it from the encoder and leave it off. We'll notify
+		 * userspace at the end.
+		 */
+		if (conn->funcs->dpms)
+			conn->funcs->dpms(conn, desired_mode);
+	}
+	drm_modeset_unlock_all(drm);
+
+	drm_kms_helper_poll_enable(drm);
+
+	DRM_DEBUG_DRIVER("mtk_drm_sys_resume\n");
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(mtk_drm_pm_ops, mtk_drm_sys_suspend,
+			 mtk_drm_sys_resume);
+
+static const struct of_device_id mtk_drm_of_ids[] = {
+	{ .compatible = "mediatek,mt8173-mmsys", },
+	{ }
+};
+
+static struct platform_driver mtk_drm_platform_driver = {
+	.probe	= mtk_drm_probe,
+	.remove	= mtk_drm_remove,
+	.driver	= {
+		.name	= "mediatek-drm",
+		.of_match_table = mtk_drm_of_ids,
+		.pm     = &mtk_drm_pm_ops,
+	},
+};
+
+static struct platform_driver * const mtk_drm_drivers[] = {
+	&mtk_drm_platform_driver,
+	&mtk_disp_ovl_driver,
+	&mtk_disp_rdma_driver,
+};
+
+static int __init mtk_drm_init(void)
+{
+	int ret;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(mtk_drm_drivers); i++) {
+		ret = platform_driver_register(mtk_drm_drivers[i]);
+		if (ret < 0) {
+			pr_err("Failed to register %s driver: %d\n",
+			       mtk_drm_drivers[i]->driver.name, ret);
+			goto err;
+		}
+	}
+
+	return 0;
+
+err:
+	while (--i >= 0)
+		platform_driver_unregister(mtk_drm_drivers[i]);
+
+	return ret;
+}
+
+static void __exit mtk_drm_exit(void)
+{
+	int i;
+
+	for (i = ARRAY_SIZE(mtk_drm_drivers) - 1; i >= 0; i--)
+		platform_driver_unregister(mtk_drm_drivers[i]);
+}
+
+module_init(mtk_drm_init);
+module_exit(mtk_drm_exit);
+
+MODULE_AUTHOR("YT SHEN <yt.shen@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek SoC DRM driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
new file mode 100644
index 0000000..efb744c
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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_DRM_DRV_H
+#define MTK_DRM_DRV_H
+
+#include <linux/io.h>
+#include "mtk_drm_ddp_comp.h"
+
+#define MAX_CRTC	2
+#define MAX_CONNECTOR	2
+
+struct device;
+struct device_node;
+struct drm_crtc;
+struct drm_device;
+struct drm_fb_helper;
+struct drm_property;
+struct regmap;
+
+struct mtk_drm_private {
+	struct drm_device *drm;
+	struct device *dma_dev;
+
+	struct drm_crtc *crtc[MAX_CRTC];
+	unsigned int num_pipes;
+
+	struct device_node *mutex_node;
+	struct device *mutex_dev;
+	void __iomem *config_regs;
+	struct device_node *comp_node[DDP_COMPONENT_ID_MAX];
+	struct mtk_ddp_comp *ddp_comp[DDP_COMPONENT_ID_MAX];
+
+	struct {
+		struct drm_atomic_state *state;
+		struct work_struct work;
+		struct mutex lock;
+	} commit;
+};
+
+extern struct platform_driver mtk_disp_ovl_driver;
+extern struct platform_driver mtk_disp_rdma_driver;
+
+#endif /* MTK_DRM_DRV_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.c b/drivers/gpu/drm/mediatek/mtk_drm_fb.c
new file mode 100644
index 0000000..33d30c1
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem.h>
+#include <linux/dma-buf.h>
+#include <linux/reservation.h>
+
+#include "mtk_drm_drv.h"
+#include "mtk_drm_fb.h"
+#include "mtk_drm_gem.h"
+
+/*
+ * mtk specific framebuffer structure.
+ *
+ * @fb: drm framebuffer object.
+ * @gem_obj: array of gem objects.
+ */
+struct mtk_drm_fb {
+	struct drm_framebuffer	base;
+	/* For now we only support a single plane */
+	struct drm_gem_object	*gem_obj;
+};
+
+#define to_mtk_fb(x) container_of(x, struct mtk_drm_fb, base)
+
+struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb)
+{
+	struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb);
+
+	return mtk_fb->gem_obj;
+}
+
+static int mtk_drm_fb_create_handle(struct drm_framebuffer *fb,
+				    struct drm_file *file_priv,
+				    unsigned int *handle)
+{
+	struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb);
+
+	return drm_gem_handle_create(file_priv, mtk_fb->gem_obj, handle);
+}
+
+static void mtk_drm_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb);
+
+	drm_framebuffer_cleanup(fb);
+
+	drm_gem_object_unreference_unlocked(mtk_fb->gem_obj);
+
+	kfree(mtk_fb);
+}
+
+static const struct drm_framebuffer_funcs mtk_drm_fb_funcs = {
+	.create_handle = mtk_drm_fb_create_handle,
+	.destroy = mtk_drm_fb_destroy,
+};
+
+static struct mtk_drm_fb *mtk_drm_framebuffer_init(struct drm_device *dev,
+					const struct drm_mode_fb_cmd2 *mode,
+					struct drm_gem_object *obj)
+{
+	struct mtk_drm_fb *mtk_fb;
+	int ret;
+
+	if (drm_format_num_planes(mode->pixel_format) != 1)
+		return ERR_PTR(-EINVAL);
+
+	mtk_fb = kzalloc(sizeof(*mtk_fb), GFP_KERNEL);
+	if (!mtk_fb)
+		return ERR_PTR(-ENOMEM);
+
+	drm_helper_mode_fill_fb_struct(&mtk_fb->base, mode);
+
+	mtk_fb->gem_obj = obj;
+
+	ret = drm_framebuffer_init(dev, &mtk_fb->base, &mtk_drm_fb_funcs);
+	if (ret) {
+		DRM_ERROR("failed to initialize framebuffer\n");
+		kfree(mtk_fb);
+		return ERR_PTR(ret);
+	}
+
+	return mtk_fb;
+}
+
+/*
+ * Wait for any exclusive fence in fb's gem object's reservation object.
+ *
+ * Returns -ERESTARTSYS if interrupted, else 0.
+ */
+int mtk_fb_wait(struct drm_framebuffer *fb)
+{
+	struct drm_gem_object *gem;
+	struct reservation_object *resv;
+	long ret;
+
+	if (!fb)
+		return 0;
+
+	gem = mtk_fb_get_gem_obj(fb);
+	if (!gem || !gem->dma_buf || !gem->dma_buf->resv)
+		return 0;
+
+	resv = gem->dma_buf->resv;
+	ret = reservation_object_wait_timeout_rcu(resv, false, true,
+						  MAX_SCHEDULE_TIMEOUT);
+	/* MAX_SCHEDULE_TIMEOUT on success, -ERESTARTSYS if interrupted */
+	if (WARN_ON(ret < 0))
+		return ret;
+
+	return 0;
+}
+
+struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev,
+					       struct drm_file *file,
+					       const struct drm_mode_fb_cmd2 *cmd)
+{
+	struct mtk_drm_fb *mtk_fb;
+	struct drm_gem_object *gem;
+	unsigned int width = cmd->width;
+	unsigned int height = cmd->height;
+	unsigned int size, bpp;
+	int ret;
+
+	if (drm_format_num_planes(cmd->pixel_format) != 1)
+		return ERR_PTR(-EINVAL);
+
+	gem = drm_gem_object_lookup(dev, file, cmd->handles[0]);
+	if (!gem)
+		return ERR_PTR(-ENOENT);
+
+	bpp = drm_format_plane_cpp(cmd->pixel_format, 0);
+	size = (height - 1) * cmd->pitches[0] + width * bpp;
+	size += cmd->offsets[0];
+
+	if (gem->size < size) {
+		ret = -EINVAL;
+		goto unreference;
+	}
+
+	mtk_fb = mtk_drm_framebuffer_init(dev, cmd, gem);
+	if (IS_ERR(mtk_fb)) {
+		ret = PTR_ERR(mtk_fb);
+		goto unreference;
+	}
+
+	return &mtk_fb->base;
+
+unreference:
+	drm_gem_object_unreference_unlocked(gem);
+	return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.h b/drivers/gpu/drm/mediatek/mtk_drm_fb.h
new file mode 100644
index 0000000..9b2ae34
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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_DRM_FB_H
+#define MTK_DRM_FB_H
+
+struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb);
+int mtk_fb_wait(struct drm_framebuffer *fb);
+struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev,
+					       struct drm_file *file,
+					       const struct drm_mode_fb_cmd2 *cmd);
+
+#endif /* MTK_DRM_FB_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_gem.c b/drivers/gpu/drm/mediatek/mtk_drm_gem.c
new file mode 100644
index 0000000..a773bfa
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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_gem.h>
+#include <linux/dma-buf.h>
+
+#include "mtk_drm_drv.h"
+#include "mtk_drm_gem.h"
+
+static struct mtk_drm_gem_obj *mtk_drm_gem_init(struct drm_device *dev,
+						unsigned long size)
+{
+	struct mtk_drm_gem_obj *mtk_gem_obj;
+	int ret;
+
+	size = round_up(size, PAGE_SIZE);
+
+	mtk_gem_obj = kzalloc(sizeof(*mtk_gem_obj), GFP_KERNEL);
+	if (!mtk_gem_obj)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drm_gem_object_init(dev, &mtk_gem_obj->base, size);
+	if (ret < 0) {
+		DRM_ERROR("failed to initialize gem object\n");
+		kfree(mtk_gem_obj);
+		return ERR_PTR(ret);
+	}
+
+	return mtk_gem_obj;
+}
+
+struct mtk_drm_gem_obj *mtk_drm_gem_create(struct drm_device *dev,
+					   size_t size, bool alloc_kmap)
+{
+	struct mtk_drm_private *priv = dev->dev_private;
+	struct mtk_drm_gem_obj *mtk_gem;
+	struct drm_gem_object *obj;
+	int ret;
+
+	mtk_gem = mtk_drm_gem_init(dev, size);
+	if (IS_ERR(mtk_gem))
+		return ERR_CAST(mtk_gem);
+
+	obj = &mtk_gem->base;
+
+	init_dma_attrs(&mtk_gem->dma_attrs);
+	dma_set_attr(DMA_ATTR_WRITE_COMBINE, &mtk_gem->dma_attrs);
+
+	if (!alloc_kmap)
+		dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &mtk_gem->dma_attrs);
+
+	mtk_gem->cookie = dma_alloc_attrs(priv->dma_dev, obj->size,
+					  &mtk_gem->dma_addr, GFP_KERNEL,
+					  &mtk_gem->dma_attrs);
+	if (!mtk_gem->cookie) {
+		DRM_ERROR("failed to allocate %zx byte dma buffer", obj->size);
+		ret = -ENOMEM;
+		goto err_gem_free;
+	}
+
+	if (alloc_kmap)
+		mtk_gem->kvaddr = mtk_gem->cookie;
+
+	DRM_DEBUG_DRIVER("cookie = %p dma_addr = %pad size = %zu\n",
+			 mtk_gem->cookie, &mtk_gem->dma_addr,
+			 size);
+
+	return mtk_gem;
+
+err_gem_free:
+	drm_gem_object_release(obj);
+	kfree(mtk_gem);
+	return ERR_PTR(ret);
+}
+
+void mtk_drm_gem_free_object(struct drm_gem_object *obj)
+{
+	struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj);
+	struct mtk_drm_private *priv = obj->dev->dev_private;
+
+	if (mtk_gem->sg)
+		drm_prime_gem_destroy(obj, mtk_gem->sg);
+	else
+		dma_free_attrs(priv->dma_dev, obj->size, mtk_gem->cookie,
+			       mtk_gem->dma_addr, &mtk_gem->dma_attrs);
+
+	/* release file pointer to gem object. */
+	drm_gem_object_release(obj);
+
+	kfree(mtk_gem);
+}
+
+int mtk_drm_gem_dumb_create(struct drm_file *file_priv, struct drm_device *dev,
+			    struct drm_mode_create_dumb *args)
+{
+	struct mtk_drm_gem_obj *mtk_gem;
+	int ret;
+
+	args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+	args->size = args->pitch * args->height;
+
+	mtk_gem = mtk_drm_gem_create(dev, args->size, false);
+	if (IS_ERR(mtk_gem))
+		return PTR_ERR(mtk_gem);
+
+	/*
+	 * allocate a id of idr table where the obj is registered
+	 * and handle has the id what user can see.
+	 */
+	ret = drm_gem_handle_create(file_priv, &mtk_gem->base, &args->handle);
+	if (ret)
+		goto err_handle_create;
+
+	/* drop reference from allocate - handle holds it now. */
+	drm_gem_object_unreference_unlocked(&mtk_gem->base);
+
+	return 0;
+
+err_handle_create:
+	mtk_drm_gem_free_object(&mtk_gem->base);
+	return ret;
+}
+
+int mtk_drm_gem_dumb_map_offset(struct drm_file *file_priv,
+				struct drm_device *dev, uint32_t handle,
+				uint64_t *offset)
+{
+	struct drm_gem_object *obj;
+	int ret;
+
+	obj = drm_gem_object_lookup(dev, file_priv, handle);
+	if (!obj) {
+		DRM_ERROR("failed to lookup gem object.\n");
+		return -EINVAL;
+	}
+
+	ret = drm_gem_create_mmap_offset(obj);
+	if (ret)
+		goto out;
+
+	*offset = drm_vma_node_offset_addr(&obj->vma_node);
+	DRM_DEBUG_KMS("offset = 0x%llx\n", *offset);
+
+out:
+	drm_gem_object_unreference_unlocked(obj);
+	return ret;
+}
+
+static int mtk_drm_gem_object_mmap(struct drm_gem_object *obj,
+				   struct vm_area_struct *vma)
+
+{
+	int ret;
+	struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj);
+	struct mtk_drm_private *priv = obj->dev->dev_private;
+
+	/*
+	 * dma_alloc_attrs() allocated a struct page table for mtk_gem, so clear
+	 * VM_PFNMAP flag that was set by drm_gem_mmap_obj()/drm_gem_mmap().
+	 */
+	vma->vm_flags &= ~VM_PFNMAP;
+	vma->vm_pgoff = 0;
+
+	ret = dma_mmap_attrs(priv->dma_dev, vma, mtk_gem->cookie,
+			     mtk_gem->dma_addr, obj->size, &mtk_gem->dma_attrs);
+	if (ret)
+		drm_gem_vm_close(vma);
+
+	return ret;
+}
+
+int mtk_drm_gem_mmap_buf(struct drm_gem_object *obj, struct vm_area_struct *vma)
+{
+	int ret;
+
+	ret = drm_gem_mmap_obj(obj, obj->size, vma);
+	if (ret)
+		return ret;
+
+	return mtk_drm_gem_object_mmap(obj, vma);
+}
+
+int mtk_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	struct drm_gem_object *obj;
+	int ret;
+
+	ret = drm_gem_mmap(filp, vma);
+	if (ret)
+		return ret;
+
+	obj = vma->vm_private_data;
+
+	return mtk_drm_gem_object_mmap(obj, vma);
+}
+
+/*
+ * Allocate a sg_table for this GEM object.
+ * Note: Both the table's contents, and the sg_table itself must be freed by
+ *       the caller.
+ * Returns a pointer to the newly allocated sg_table, or an ERR_PTR() error.
+ */
+struct sg_table *mtk_gem_prime_get_sg_table(struct drm_gem_object *obj)
+{
+	struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj);
+	struct mtk_drm_private *priv = obj->dev->dev_private;
+	struct sg_table *sgt;
+	int ret;
+
+	sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
+	if (!sgt)
+		return ERR_PTR(-ENOMEM);
+
+	ret = dma_get_sgtable_attrs(priv->dma_dev, sgt, mtk_gem->cookie,
+				    mtk_gem->dma_addr, obj->size,
+				    &mtk_gem->dma_attrs);
+	if (ret) {
+		DRM_ERROR("failed to allocate sgt, %d\n", ret);
+		kfree(sgt);
+		return ERR_PTR(ret);
+	}
+
+	return sgt;
+}
+
+struct drm_gem_object *mtk_gem_prime_import_sg_table(struct drm_device *dev,
+			struct dma_buf_attachment *attach, struct sg_table *sg)
+{
+	struct mtk_drm_gem_obj *mtk_gem;
+	int ret;
+	struct scatterlist *s;
+	unsigned int i;
+	dma_addr_t expected;
+
+	mtk_gem = mtk_drm_gem_init(dev, attach->dmabuf->size);
+
+	if (IS_ERR(mtk_gem))
+		return ERR_PTR(PTR_ERR(mtk_gem));
+
+	expected = sg_dma_address(sg->sgl);
+	for_each_sg(sg->sgl, s, sg->nents, i) {
+		if (sg_dma_address(s) != expected) {
+			DRM_ERROR("sg_table is not contiguous");
+			ret = -EINVAL;
+			goto err_gem_free;
+		}
+		expected = sg_dma_address(s) + sg_dma_len(s);
+	}
+
+	mtk_gem->dma_addr = sg_dma_address(sg->sgl);
+	mtk_gem->sg = sg;
+
+	return &mtk_gem->base;
+
+err_gem_free:
+	kfree(mtk_gem);
+	return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_gem.h b/drivers/gpu/drm/mediatek/mtk_drm_gem.h
new file mode 100644
index 0000000..3a2a562
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * 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_DRM_GEM_H_
+#define _MTK_DRM_GEM_H_
+
+#include <drm/drm_gem.h>
+
+/*
+ * mtk drm buffer structure.
+ *
+ * @base: a gem object.
+ *	- a new handle to this gem object would be created
+ *	by drm_gem_handle_create().
+ * @cookie: the return value of dma_alloc_attrs(), keep it for dma_free_attrs()
+ * @kvaddr: kernel virtual address of gem buffer.
+ * @dma_addr: dma address of gem buffer.
+ * @dma_attrs: dma attributes of gem buffer.
+ *
+ * P.S. this object would be transferred to user as kms_bo.handle so
+ *	user can access the buffer through kms_bo.handle.
+ */
+struct mtk_drm_gem_obj {
+	struct drm_gem_object	base;
+	void			*cookie;
+	void			*kvaddr;
+	dma_addr_t		dma_addr;
+	struct dma_attrs	dma_attrs;
+	struct sg_table		*sg;
+};
+
+#define to_mtk_gem_obj(x)	container_of(x, struct mtk_drm_gem_obj, base)
+
+void mtk_drm_gem_free_object(struct drm_gem_object *gem);
+struct mtk_drm_gem_obj *mtk_drm_gem_create(struct drm_device *dev, size_t size,
+					   bool alloc_kmap);
+int mtk_drm_gem_dumb_create(struct drm_file *file_priv, struct drm_device *dev,
+			    struct drm_mode_create_dumb *args);
+int mtk_drm_gem_dumb_map_offset(struct drm_file *file_priv,
+				struct drm_device *dev, uint32_t handle,
+				uint64_t *offset);
+int mtk_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
+int mtk_drm_gem_mmap_buf(struct drm_gem_object *obj,
+			 struct vm_area_struct *vma);
+struct sg_table *mtk_gem_prime_get_sg_table(struct drm_gem_object *obj);
+struct drm_gem_object *mtk_gem_prime_import_sg_table(struct drm_device *dev,
+			struct dma_buf_attachment *attach, struct sg_table *sg);
+
+#endif
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.c b/drivers/gpu/drm/mediatek/mtk_drm_plane.c
new file mode 100644
index 0000000..c898788
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ * Author: CK Hu <ck.hu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "mtk_drm_crtc.h"
+#include "mtk_drm_ddp_comp.h"
+#include "mtk_drm_drv.h"
+#include "mtk_drm_fb.h"
+#include "mtk_drm_gem.h"
+#include "mtk_drm_plane.h"
+
+static const u32 formats[] = {
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_RGB565,
+};
+
+static void mtk_plane_enable(struct mtk_drm_plane *mtk_plane, bool enable,
+			     dma_addr_t addr, struct drm_rect *dest)
+{
+	struct drm_plane *plane = &mtk_plane->base;
+	struct mtk_plane_state *state = to_mtk_plane_state(plane->state);
+	unsigned int pitch, format;
+	int x, y;
+
+	if (WARN_ON(!plane->state || (enable && !plane->state->fb)))
+		return;
+
+	if (plane->state->fb) {
+		pitch = plane->state->fb->pitches[0];
+		format = plane->state->fb->pixel_format;
+	} else {
+		pitch = 0;
+		format = DRM_FORMAT_RGBA8888;
+	}
+
+	x = plane->state->crtc_x;
+	y = plane->state->crtc_y;
+
+	if (x < 0) {
+		addr -= x * 4;
+		x = 0;
+	}
+
+	if (y < 0) {
+		addr -= y * pitch;
+		y = 0;
+	}
+
+	state->pending.enable = enable;
+	state->pending.pitch = pitch;
+	state->pending.format = format;
+	state->pending.addr = addr;
+	state->pending.x = x;
+	state->pending.y = y;
+	state->pending.width = dest->x2 - dest->x1;
+	state->pending.height = dest->y2 - dest->y1;
+	wmb(); /* Make sure the above parameters are set before update */
+	state->pending.dirty = true;
+}
+
+static void mtk_plane_reset(struct drm_plane *plane)
+{
+	struct mtk_plane_state *state;
+
+	if (plane->state) {
+		if (plane->state->fb)
+			drm_framebuffer_unreference(plane->state->fb);
+
+		state = to_mtk_plane_state(plane->state);
+		memset(state, 0, sizeof(*state));
+	} else {
+		state = kzalloc(sizeof(*state), GFP_KERNEL);
+		if (!state)
+			return;
+		plane->state = &state->base;
+	}
+
+	state->base.plane = plane;
+	state->pending.format = DRM_FORMAT_RGB565;
+}
+
+static struct drm_plane_state *mtk_plane_duplicate_state(struct drm_plane *plane)
+{
+	struct mtk_plane_state *old_state = to_mtk_plane_state(plane->state);
+	struct mtk_plane_state *state;
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	__drm_atomic_helper_plane_duplicate_state(plane, &state->base);
+
+	WARN_ON(state->base.plane != plane);
+
+	state->pending = old_state->pending;
+
+	return &state->base;
+}
+
+static void mtk_drm_plane_destroy_state(struct drm_plane *plane,
+					struct drm_plane_state *state)
+{
+	__drm_atomic_helper_plane_destroy_state(plane, state);
+	kfree(to_mtk_plane_state(state));
+}
+
+static const struct drm_plane_funcs mtk_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.destroy = drm_plane_cleanup,
+	.reset = mtk_plane_reset,
+	.atomic_duplicate_state = mtk_plane_duplicate_state,
+	.atomic_destroy_state = mtk_drm_plane_destroy_state,
+};
+
+static int mtk_plane_atomic_check(struct drm_plane *plane,
+				  struct drm_plane_state *state)
+{
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_crtc_state *crtc_state;
+	bool visible;
+	struct drm_rect dest = {
+		.x1 = state->crtc_x,
+		.y1 = state->crtc_y,
+		.x2 = state->crtc_x + state->crtc_w,
+		.y2 = state->crtc_y + state->crtc_h,
+	};
+	struct drm_rect src = {
+		/* 16.16 fixed point */
+		.x1 = state->src_x,
+		.y1 = state->src_y,
+		.x2 = state->src_x + state->src_w,
+		.y2 = state->src_y + state->src_h,
+	};
+	struct drm_rect clip = { 0, };
+
+	if (!fb)
+		return 0;
+
+	if (!mtk_fb_get_gem_obj(fb)) {
+		DRM_DEBUG_KMS("buffer is null\n");
+		return -EFAULT;
+	}
+
+	if (!state->crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
+	if (IS_ERR(crtc_state))
+		return PTR_ERR(crtc_state);
+
+	clip.x2 = crtc_state->mode.hdisplay;
+	clip.y2 = crtc_state->mode.vdisplay;
+
+	return drm_plane_helper_check_update(plane, state->crtc, fb,
+					     &src, &dest, &clip,
+					     DRM_PLANE_HELPER_NO_SCALING,
+					     DRM_PLANE_HELPER_NO_SCALING,
+					     true, true, &visible);
+}
+
+static void mtk_plane_atomic_update(struct drm_plane *plane,
+				    struct drm_plane_state *old_state)
+{
+	struct mtk_plane_state *state = to_mtk_plane_state(plane->state);
+	struct drm_crtc *crtc = state->base.crtc;
+	struct drm_gem_object *gem;
+	struct mtk_drm_gem_obj *mtk_gem;
+	struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane);
+	struct drm_rect dest = {
+		.x1 = state->base.crtc_x,
+		.y1 = state->base.crtc_y,
+		.x2 = state->base.crtc_x + state->base.crtc_w,
+		.y2 = state->base.crtc_y + state->base.crtc_h,
+	};
+	struct drm_rect clip = { 0, };
+
+	if (!crtc)
+		return;
+
+	clip.x2 = state->base.crtc->state->mode.hdisplay;
+	clip.y2 = state->base.crtc->state->mode.vdisplay;
+	drm_rect_intersect(&dest, &clip);
+
+	gem = mtk_fb_get_gem_obj(state->base.fb);
+	mtk_gem = to_mtk_gem_obj(gem);
+	mtk_plane_enable(mtk_plane, true, mtk_gem->dma_addr, &dest);
+}
+
+static void mtk_plane_atomic_disable(struct drm_plane *plane,
+				     struct drm_plane_state *old_state)
+{
+	struct mtk_plane_state *state = to_mtk_plane_state(plane->state);
+
+	state->pending.enable = false;
+	wmb(); /* Make sure the above parameter is set before update */
+	state->pending.dirty = true;
+}
+
+static const struct drm_plane_helper_funcs mtk_plane_helper_funcs = {
+	.atomic_check = mtk_plane_atomic_check,
+	.atomic_update = mtk_plane_atomic_update,
+	.atomic_disable = mtk_plane_atomic_disable,
+};
+
+int mtk_plane_init(struct drm_device *dev, struct mtk_drm_plane *mtk_plane,
+		   unsigned long possible_crtcs, enum drm_plane_type type,
+		   unsigned int zpos)
+{
+	int err;
+
+	err = drm_universal_plane_init(dev, &mtk_plane->base, possible_crtcs,
+				       &mtk_plane_funcs, formats,
+				       ARRAY_SIZE(formats), type, NULL);
+	if (err) {
+		DRM_ERROR("failed to initialize plane\n");
+		return err;
+	}
+
+	drm_plane_helper_add(&mtk_plane->base, &mtk_plane_helper_funcs);
+	mtk_plane->idx = zpos;
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.h b/drivers/gpu/drm/mediatek/mtk_drm_plane.h
new file mode 100644
index 0000000..72a7b3e
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ * Author: CK Hu <ck.hu@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_DRM_PLANE_H_
+#define _MTK_DRM_PLANE_H_
+
+#include <drm/drm_crtc.h>
+#include <linux/types.h>
+
+struct mtk_drm_plane {
+	struct drm_plane		base;
+	unsigned int			idx;
+};
+
+struct mtk_plane_pending_state {
+	bool				config;
+	bool				enable;
+	dma_addr_t			addr;
+	unsigned int			pitch;
+	unsigned int			format;
+	unsigned int			x;
+	unsigned int			y;
+	unsigned int			width;
+	unsigned int			height;
+	bool				dirty;
+};
+
+struct mtk_plane_state {
+	struct drm_plane_state		base;
+	struct mtk_plane_pending_state	pending;
+};
+
+static inline struct mtk_drm_plane *to_mtk_plane(struct drm_plane *plane)
+{
+	return container_of(plane, struct mtk_drm_plane, base);
+}
+
+static inline struct mtk_plane_state *
+to_mtk_plane_state(struct drm_plane_state *state)
+{
+	return container_of(state, struct mtk_plane_state, base);
+}
+
+int mtk_plane_init(struct drm_device *dev, struct mtk_drm_plane *mtk_plane,
+		   unsigned long possible_crtcs, enum drm_plane_type type,
+		   unsigned int zpos);
+
+#endif
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 03/14] drm/mediatek: Add DSI sub driver
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 01/14] dt-bindings: drm/mediatek: Add Mediatek display subsystem dts binding Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 02/14] drm/mediatek: Add DRM Driver for Mediatek SoC MT8173 Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
       [not found]   ` <1457443649-12133-4-git-send-email-p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
  2016-03-08 13:27 ` [PATCH v13 04/14] drm/mediatek: Add DPI " Philipp Zabel
                   ` (10 subsequent siblings)
  13 siblings, 1 reply; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

From: CK Hu <ck.hu@mediatek.com>

This patch add a drm encoder/connector driver for the MIPI DSI function
block of the Mediatek display subsystem and a phy driver for the MIPI TX
D-PHY control module.

Signed-off-by: Jitao Shi <jitao.shi@mediatek.com>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
--
 drivers/gpu/drm/mediatek/Kconfig       |   2 +
 drivers/gpu/drm/mediatek/Makefile      |   4 +-
 drivers/gpu/drm/mediatek/mtk_drm_drv.c |   2 +
 drivers/gpu/drm/mediatek/mtk_drm_drv.h |   2 +
 drivers/gpu/drm/mediatek/mtk_dsi.c     | 942 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_mipi_tx.c | 487 +++++++++++++++++
 6 files changed, 1438 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/mediatek/mtk_dsi.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_mipi_tx.c

diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig
index 8dad892..0c49a94 100644
--- a/drivers/gpu/drm/mediatek/Kconfig
+++ b/drivers/gpu/drm/mediatek/Kconfig
@@ -3,6 +3,8 @@ config DRM_MEDIATEK
 	depends on DRM
 	depends on ARCH_MEDIATEK || (ARM && COMPILE_TEST)
 	select DRM_KMS_HELPER
+	select DRM_MIPI_DSI
+	select DRM_PANEL
 	select IOMMU_DMA
 	select MTK_SMI
 	help
diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile
index d4bde7c..e781db5a 100644
--- a/drivers/gpu/drm/mediatek/Makefile
+++ b/drivers/gpu/drm/mediatek/Makefile
@@ -6,6 +6,8 @@ mediatek-drm-y := mtk_disp_ovl.o \
 		  mtk_drm_drv.o \
 		  mtk_drm_fb.o \
 		  mtk_drm_gem.o \
-		  mtk_drm_plane.o
+		  mtk_drm_plane.o \
+		  mtk_dsi.o \
+		  mtk_mipi_tx.o
 
 obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
index 8a21ca7..4fcc0e0 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
@@ -551,6 +551,8 @@ static struct platform_driver * const mtk_drm_drivers[] = {
 	&mtk_drm_platform_driver,
 	&mtk_disp_ovl_driver,
 	&mtk_disp_rdma_driver,
+	&mtk_dsi_driver,
+	&mtk_mipi_tx_driver,
 };
 
 static int __init mtk_drm_init(void)
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
index efb744c..161a362 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_drv.h
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
@@ -50,5 +50,7 @@ struct mtk_drm_private {
 
 extern struct platform_driver mtk_disp_ovl_driver;
 extern struct platform_driver mtk_disp_rdma_driver;
+extern struct platform_driver mtk_dsi_driver;
+extern struct platform_driver mtk_mipi_tx_driver;
 
 #endif /* MTK_DRM_DRV_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
new file mode 100644
index 0000000..463d389
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -0,0 +1,942 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_graph.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <video/videomode.h>
+
+#include "mtk_drm_ddp_comp.h"
+
+#define DSI_VIDEO_FIFO_DEPTH	(1920 / 4)
+#define DSI_HOST_FIFO_DEPTH	64
+
+#define DSI_START		0x00
+
+#define DSI_CON_CTRL		0x10
+#define DSI_RESET			BIT(0)
+#define DSI_EN				BIT(1)
+
+#define DSI_MODE_CTRL		0x14
+#define MODE				(3)
+#define CMD_MODE			0
+#define SYNC_PULSE_MODE			1
+#define SYNC_EVENT_MODE			2
+#define BURST_MODE			3
+#define FRM_MODE			BIT(16)
+#define MIX_MODE			BIT(17)
+
+#define DSI_TXRX_CTRL		0x18
+#define VC_NUM				(2 << 0)
+#define LANE_NUM			(0xf << 2)
+#define DIS_EOT				BIT(6)
+#define NULL_EN				BIT(7)
+#define TE_FREERUN			BIT(8)
+#define EXT_TE_EN			BIT(9)
+#define EXT_TE_EDGE			BIT(10)
+#define MAX_RTN_SIZE			(0xf << 12)
+#define HSTX_CKLP_EN			BIT(16)
+
+#define DSI_PSCTRL		0x1c
+#define DSI_PS_WC			0x3fff
+#define DSI_PS_SEL			(3 << 16)
+#define PACKED_PS_16BIT_RGB565		(0 << 16)
+#define LOOSELY_PS_18BIT_RGB666		(1 << 16)
+#define PACKED_PS_18BIT_RGB666		(2 << 16)
+#define PACKED_PS_24BIT_RGB888		(3 << 16)
+
+#define DSI_VSA_NL		0x20
+#define DSI_VBP_NL		0x24
+#define DSI_VFP_NL		0x28
+#define DSI_VACT_NL		0x2C
+#define DSI_HSA_WC		0x50
+#define DSI_HBP_WC		0x54
+#define DSI_HFP_WC		0x58
+
+#define DSI_HSTX_CKL_WC		0x64
+
+#define DSI_PHY_LCCON		0x104
+#define LC_HS_TX_EN			BIT(0)
+#define LC_ULPM_EN			BIT(1)
+#define LC_WAKEUP_EN			BIT(2)
+
+#define DSI_PHY_LD0CON		0x108
+#define LD0_HS_TX_EN			BIT(0)
+#define LD0_ULPM_EN			BIT(1)
+#define LD0_WAKEUP_EN			BIT(2)
+
+#define DSI_PHY_TIMECON0	0x110
+#define LPX				(0xff << 0)
+#define HS_PRPR				(0xff << 8)
+#define HS_ZERO				(0xff << 16)
+#define HS_TRAIL			(0xff << 24)
+
+#define DSI_PHY_TIMECON1	0x114
+#define TA_GO				(0xff << 0)
+#define TA_SURE				(0xff << 8)
+#define TA_GET				(0xff << 16)
+#define DA_HS_EXIT			(0xff << 24)
+
+#define DSI_PHY_TIMECON2	0x118
+#define CONT_DET			(0xff << 0)
+#define CLK_ZERO			(0xff << 16)
+#define CLK_TRAIL			(0xff << 24)
+
+#define DSI_PHY_TIMECON3	0x11c
+#define CLK_HS_PRPR			(0xff << 0)
+#define CLK_HS_POST			(0xff << 8)
+#define CLK_HS_EXIT			(0xff << 16)
+
+#define NS_TO_CYCLE(n, c)    ((n) / (c) + (((n) % (c)) ? 1 : 0))
+
+struct phy;
+
+struct mtk_dsi {
+	struct mtk_ddp_comp ddp_comp;
+	struct device *dev;
+	struct mipi_dsi_host host;
+	struct drm_encoder encoder;
+	struct drm_connector conn;
+	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+	struct phy *phy;
+
+	void __iomem *regs;
+
+	struct clk *engine_clk;
+	struct clk *digital_clk;
+	struct clk *hs_clk;
+
+	u32 data_rate;
+
+	unsigned long mode_flags;
+	enum mipi_dsi_pixel_format format;
+	unsigned int lanes;
+	struct videomode vm;
+	int refcount;
+	bool enabled;
+};
+
+static inline struct mtk_dsi *encoder_to_dsi(struct drm_encoder *e)
+{
+	return container_of(e, struct mtk_dsi, encoder);
+}
+
+static inline struct mtk_dsi *connector_to_dsi(struct drm_connector *c)
+{
+	return container_of(c, struct mtk_dsi, conn);
+}
+
+static inline struct mtk_dsi *host_to_dsi(struct mipi_dsi_host *h)
+{
+	return container_of(h, struct mtk_dsi, host);
+}
+
+static void mtk_dsi_mask(struct mtk_dsi *dsi, u32 offset, u32 mask, u32 data)
+{
+	u32 temp = readl(dsi->regs + offset);
+
+	writel((temp & ~mask) | (data & mask), dsi->regs + offset);
+}
+
+static void dsi_phy_timconfig(struct mtk_dsi *dsi)
+{
+	u32 timcon0, timcon1, timcon2, timcon3;
+	unsigned int ui, cycle_time;
+	unsigned int lpx;
+
+	ui = 1000 / dsi->data_rate + 0x01;
+	cycle_time = 8000 / dsi->data_rate + 0x01;
+	lpx = 5;
+
+	timcon0 = (8 << 24) | (0xa << 16) | (0x6 << 8) | lpx;
+	timcon1 = (7 << 24) | (5 * lpx << 16) | ((3 * lpx) / 2) << 8 |
+		  (4 * lpx);
+	timcon2 = ((NS_TO_CYCLE(0x64, cycle_time) + 0xa) << 24) |
+		  (NS_TO_CYCLE(0x150, cycle_time) << 16);
+	timcon3 = (2 * lpx) << 16 | NS_TO_CYCLE(80 + 52 * ui, cycle_time) << 8 |
+		   NS_TO_CYCLE(0x40, cycle_time);
+
+	writel(timcon0, dsi->regs + DSI_PHY_TIMECON0);
+	writel(timcon1, dsi->regs + DSI_PHY_TIMECON1);
+	writel(timcon2, dsi->regs + DSI_PHY_TIMECON2);
+	writel(timcon3, dsi->regs + DSI_PHY_TIMECON3);
+}
+
+static void mtk_dsi_enable(struct mtk_dsi *dsi)
+{
+	mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, DSI_EN);
+}
+
+static void mtk_dsi_disable(struct mtk_dsi *dsi)
+{
+	mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, 0);
+}
+
+static void mtk_dsi_reset(struct mtk_dsi *dsi)
+{
+	mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_RESET, DSI_RESET);
+	mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_RESET, 0);
+}
+
+static int mtk_dsi_poweron(struct mtk_dsi *dsi)
+{
+	struct device *dev = dsi->dev;
+	int ret;
+
+	if (++dsi->refcount != 1)
+		return 0;
+
+	/**
+	 * data_rate = (pixel_clock / 1000) * pixel_dipth * mipi_ratio;
+	 * pixel_clock unit is Khz, data_rata unit is MHz, so need divide 1000.
+	 * mipi_ratio is mipi clk coefficient for balance the pixel clk in mipi.
+	 * we set mipi_ratio is 1.05.
+	 */
+	dsi->data_rate = dsi->vm.pixelclock * 3 * 21 / (1 * 1000 * 10);
+
+	ret = clk_set_rate(dsi->hs_clk, dsi->data_rate * 1000000);
+	if (ret < 0) {
+		dev_err(dev, "Failed to set data rate: %d\n", ret);
+		goto err_refcount;
+	}
+
+	phy_power_on(dsi->phy);
+
+	ret = clk_prepare_enable(dsi->engine_clk);
+	if (ret < 0) {
+		dev_err(dev, "Failed to enable engine clock: %d\n", ret);
+		goto err_phy_power_off;
+	}
+
+	ret = clk_prepare_enable(dsi->digital_clk);
+	if (ret < 0) {
+		dev_err(dev, "Failed to enable digital clock: %d\n", ret);
+		goto err_disable_engine_clk;
+	}
+
+	mtk_dsi_enable(dsi);
+	mtk_dsi_reset(dsi);
+	dsi_phy_timconfig(dsi);
+
+	return 0;
+
+err_disable_engine_clk:
+	clk_disable_unprepare(dsi->engine_clk);
+err_phy_power_off:
+	phy_power_off(dsi->phy);
+err_refcount:
+	dsi->refcount--;
+	return ret;
+}
+
+static void dsi_clk_ulp_mode_enter(struct mtk_dsi *dsi)
+{
+	mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0);
+	mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_ULPM_EN, 0);
+}
+
+static void dsi_clk_ulp_mode_leave(struct mtk_dsi *dsi)
+{
+	mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_ULPM_EN, 0);
+	mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_WAKEUP_EN, LC_WAKEUP_EN);
+	mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_WAKEUP_EN, 0);
+}
+
+static void dsi_lane0_ulp_mode_enter(struct mtk_dsi *dsi)
+{
+	mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_HS_TX_EN, 0);
+	mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_ULPM_EN, 0);
+}
+
+static void dsi_lane0_ulp_mode_leave(struct mtk_dsi *dsi)
+{
+	mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_ULPM_EN, 0);
+	mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_WAKEUP_EN, LD0_WAKEUP_EN);
+	mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_WAKEUP_EN, 0);
+}
+
+static bool dsi_clk_hs_state(struct mtk_dsi *dsi)
+{
+	u32 tmp_reg1;
+
+	tmp_reg1 = readl(dsi->regs + DSI_PHY_LCCON);
+	return ((tmp_reg1 & LC_HS_TX_EN) == 1) ? true : false;
+}
+
+static void dsi_clk_hs_mode(struct mtk_dsi *dsi, bool enter)
+{
+	if (enter && !dsi_clk_hs_state(dsi))
+		mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, LC_HS_TX_EN);
+	else if (!enter && dsi_clk_hs_state(dsi))
+		mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0);
+}
+
+static void dsi_set_mode(struct mtk_dsi *dsi)
+{
+	u32 vid_mode = CMD_MODE;
+
+	if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
+		vid_mode = SYNC_PULSE_MODE;
+
+		if ((dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) &&
+		    !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))
+			vid_mode = BURST_MODE;
+	}
+
+	writel(vid_mode, dsi->regs + DSI_MODE_CTRL);
+}
+
+static void dsi_ps_control_vact(struct mtk_dsi *dsi)
+{
+	struct videomode *vm = &dsi->vm;
+	u32 dsi_buf_bpp, ps_wc;
+	u32 ps_bpp_mode;
+
+	if (dsi->format == MIPI_DSI_FMT_RGB565)
+		dsi_buf_bpp = 2;
+	else
+		dsi_buf_bpp = 3;
+
+	ps_wc = vm->hactive * dsi_buf_bpp;
+	ps_bpp_mode = ps_wc;
+
+	switch (dsi->format) {
+	case MIPI_DSI_FMT_RGB888:
+		ps_bpp_mode |= PACKED_PS_24BIT_RGB888;
+		break;
+	case MIPI_DSI_FMT_RGB666:
+		ps_bpp_mode |= PACKED_PS_18BIT_RGB666;
+		break;
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		ps_bpp_mode |= LOOSELY_PS_18BIT_RGB666;
+		break;
+	case MIPI_DSI_FMT_RGB565:
+		ps_bpp_mode |= PACKED_PS_16BIT_RGB565;
+		break;
+	}
+
+	writel(vm->vactive, dsi->regs + DSI_VACT_NL);
+	writel(ps_bpp_mode, dsi->regs + DSI_PSCTRL);
+	writel(ps_wc, dsi->regs + DSI_HSTX_CKL_WC);
+}
+
+static void dsi_rxtx_control(struct mtk_dsi *dsi)
+{
+	u32 tmp_reg;
+
+	switch (dsi->lanes) {
+	case 1:
+		tmp_reg = 1 << 2;
+		break;
+	case 2:
+		tmp_reg = 3 << 2;
+		break;
+	case 3:
+		tmp_reg = 7 << 2;
+		break;
+	case 4:
+		tmp_reg = 0xf << 2;
+		break;
+	default:
+		tmp_reg = 0xf << 2;
+		break;
+	}
+
+	writel(tmp_reg, dsi->regs + DSI_TXRX_CTRL);
+}
+
+static void dsi_ps_control(struct mtk_dsi *dsi)
+{
+	unsigned int dsi_tmp_buf_bpp;
+	u32 tmp_reg;
+
+	switch (dsi->format) {
+	case MIPI_DSI_FMT_RGB888:
+		tmp_reg = PACKED_PS_24BIT_RGB888;
+		dsi_tmp_buf_bpp = 3;
+		break;
+	case MIPI_DSI_FMT_RGB666:
+		tmp_reg = LOOSELY_PS_18BIT_RGB666;
+		dsi_tmp_buf_bpp = 3;
+		break;
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		tmp_reg = PACKED_PS_18BIT_RGB666;
+		dsi_tmp_buf_bpp = 3;
+		break;
+	case MIPI_DSI_FMT_RGB565:
+		tmp_reg = PACKED_PS_16BIT_RGB565;
+		dsi_tmp_buf_bpp = 2;
+		break;
+	default:
+		tmp_reg = PACKED_PS_24BIT_RGB888;
+		dsi_tmp_buf_bpp = 3;
+		break;
+	}
+
+	tmp_reg += dsi->vm.hactive * dsi_tmp_buf_bpp & DSI_PS_WC;
+	writel(tmp_reg, dsi->regs + DSI_PSCTRL);
+}
+
+static void dsi_config_vdo_timing(struct mtk_dsi *dsi)
+{
+	unsigned int horizontal_sync_active_byte;
+	unsigned int horizontal_backporch_byte;
+	unsigned int horizontal_frontporch_byte;
+	unsigned int dsi_tmp_buf_bpp;
+
+	struct videomode *vm = &dsi->vm;
+
+	if (dsi->format == MIPI_DSI_FMT_RGB565)
+		dsi_tmp_buf_bpp = 2;
+	else
+		dsi_tmp_buf_bpp = 3;
+
+	writel(vm->vsync_len, dsi->regs + DSI_VSA_NL);
+	writel(vm->vback_porch, dsi->regs + DSI_VBP_NL);
+	writel(vm->vfront_porch, dsi->regs + DSI_VFP_NL);
+	writel(vm->vactive, dsi->regs + DSI_VACT_NL);
+
+	horizontal_sync_active_byte = (vm->hsync_len * dsi_tmp_buf_bpp - 10);
+
+	if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+		horizontal_backporch_byte =
+			(vm->hback_porch * dsi_tmp_buf_bpp - 10);
+	else
+		horizontal_backporch_byte = ((vm->hback_porch + vm->hsync_len) *
+			dsi_tmp_buf_bpp - 10);
+
+	horizontal_frontporch_byte = (vm->hfront_porch * dsi_tmp_buf_bpp - 12);
+
+	writel(horizontal_sync_active_byte, dsi->regs + DSI_HSA_WC);
+	writel(horizontal_backporch_byte, dsi->regs + DSI_HBP_WC);
+	writel(horizontal_frontporch_byte, dsi->regs + DSI_HFP_WC);
+
+	dsi_ps_control(dsi);
+}
+
+static void mtk_dsi_start(struct mtk_dsi *dsi)
+{
+	writel(0, dsi->regs + DSI_START);
+	writel(1, dsi->regs + DSI_START);
+}
+
+static void mtk_dsi_poweroff(struct mtk_dsi *dsi)
+{
+	if (WARN_ON(dsi->refcount == 0))
+		return;
+
+	if (--dsi->refcount != 0)
+		return;
+
+	dsi_lane0_ulp_mode_enter(dsi);
+	dsi_clk_ulp_mode_enter(dsi);
+
+	mtk_dsi_disable(dsi);
+
+	clk_disable_unprepare(dsi->engine_clk);
+	clk_disable_unprepare(dsi->digital_clk);
+
+	phy_power_off(dsi->phy);
+}
+
+static void mtk_output_dsi_enable(struct mtk_dsi *dsi)
+{
+	int ret;
+
+	if (dsi->enabled)
+		return;
+
+	if (dsi->panel) {
+		if (drm_panel_prepare(dsi->panel)) {
+			DRM_ERROR("failed to setup the panel\n");
+			return;
+		}
+	}
+
+	ret = mtk_dsi_poweron(dsi);
+	if (ret < 0) {
+		DRM_ERROR("failed to power on dsi\n");
+		return;
+	}
+
+	dsi_rxtx_control(dsi);
+
+	dsi_clk_ulp_mode_leave(dsi);
+	dsi_lane0_ulp_mode_leave(dsi);
+	dsi_clk_hs_mode(dsi, 0);
+	dsi_set_mode(dsi);
+
+	dsi_ps_control_vact(dsi);
+	dsi_config_vdo_timing(dsi);
+
+	dsi_set_mode(dsi);
+	dsi_clk_hs_mode(dsi, 1);
+
+	mtk_dsi_start(dsi);
+
+	dsi->enabled = true;
+}
+
+static void mtk_output_dsi_disable(struct mtk_dsi *dsi)
+{
+	if (!dsi->enabled)
+		return;
+
+	if (dsi->panel) {
+		if (drm_panel_disable(dsi->panel)) {
+			DRM_ERROR("failed to disable the panel\n");
+			return;
+		}
+	}
+
+	mtk_dsi_poweroff(dsi);
+
+	dsi->enabled = false;
+}
+
+static void mtk_dsi_encoder_destroy(struct drm_encoder *encoder)
+{
+	drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs mtk_dsi_encoder_funcs = {
+	.destroy = mtk_dsi_encoder_destroy,
+};
+
+static bool mtk_dsi_encoder_mode_fixup(struct drm_encoder *encoder,
+				       const struct drm_display_mode *mode,
+				       struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static void mtk_dsi_encoder_mode_set(struct drm_encoder *encoder,
+				     struct drm_display_mode *mode,
+				     struct drm_display_mode *adjusted)
+{
+	struct mtk_dsi *dsi = encoder_to_dsi(encoder);
+
+	dsi->vm.pixelclock = adjusted->clock;
+	dsi->vm.hactive = adjusted->hdisplay;
+	dsi->vm.hback_porch = adjusted->htotal - adjusted->hsync_end;
+	dsi->vm.hfront_porch = adjusted->hsync_start - adjusted->hdisplay;
+	dsi->vm.hsync_len = adjusted->hsync_end - adjusted->hsync_start;
+
+	dsi->vm.vactive = adjusted->vdisplay;
+	dsi->vm.vback_porch = adjusted->vtotal - adjusted->vsync_end;
+	dsi->vm.vfront_porch = adjusted->vsync_start - adjusted->vdisplay;
+	dsi->vm.vsync_len = adjusted->vsync_end - adjusted->vsync_start;
+}
+
+static void mtk_dsi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct mtk_dsi *dsi = encoder_to_dsi(encoder);
+
+	mtk_output_dsi_disable(dsi);
+}
+
+static void mtk_dsi_encoder_enable(struct drm_encoder *encoder)
+{
+	struct mtk_dsi *dsi = encoder_to_dsi(encoder);
+
+	mtk_output_dsi_enable(dsi);
+}
+
+static enum drm_connector_status mtk_dsi_connector_detect(
+	struct drm_connector *connector, bool force)
+{
+	return connector_status_connected;
+}
+
+static void mtk_dsi_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static int mtk_dsi_connector_get_modes(struct drm_connector *connector)
+{
+	struct mtk_dsi *dsi = connector_to_dsi(connector);
+
+	return drm_panel_get_modes(dsi->panel);
+}
+
+static struct drm_encoder *mtk_dsi_connector_best_encoder(
+		struct drm_connector *connector)
+{
+	struct mtk_dsi *dsi = connector_to_dsi(connector);
+
+	return &dsi->encoder;
+}
+
+static const struct drm_encoder_helper_funcs mtk_dsi_encoder_helper_funcs = {
+	.mode_fixup = mtk_dsi_encoder_mode_fixup,
+	.mode_set = mtk_dsi_encoder_mode_set,
+	.disable = mtk_dsi_encoder_disable,
+	.enable = mtk_dsi_encoder_enable,
+};
+
+static const struct drm_connector_funcs mtk_dsi_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.detect = mtk_dsi_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = mtk_dsi_connector_destroy,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs
+	mtk_dsi_connector_helper_funcs = {
+	.get_modes = mtk_dsi_connector_get_modes,
+	.best_encoder = mtk_dsi_connector_best_encoder,
+};
+
+static int mtk_drm_attach_bridge(struct drm_bridge *bridge,
+				 struct drm_encoder *encoder)
+{
+	int ret;
+
+	if (!bridge)
+		return -ENOENT;
+
+	encoder->bridge = bridge;
+	bridge->encoder = encoder;
+	ret = drm_bridge_attach(encoder->dev, bridge);
+	if (ret) {
+		DRM_ERROR("Failed to attach bridge to drm\n");
+		encoder->bridge = NULL;
+		bridge->encoder = NULL;
+	}
+
+	return ret;
+}
+
+static int mtk_dsi_create_conn_enc(struct drm_device *drm, struct mtk_dsi *dsi)
+{
+	int ret;
+
+	ret = drm_encoder_init(drm, &dsi->encoder, &mtk_dsi_encoder_funcs,
+			       DRM_MODE_ENCODER_DSI, NULL);
+	if (ret) {
+		DRM_ERROR("Failed to encoder init to drm\n");
+		return ret;
+	}
+	drm_encoder_helper_add(&dsi->encoder, &mtk_dsi_encoder_helper_funcs);
+
+	/*
+	 * Currently display data paths are statically assigned to a crtc each.
+	 * crtc 0 is OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0
+	 */
+	dsi->encoder.possible_crtcs = 1;
+
+	/* Pre-empt DP connector creation if there's a bridge */
+	ret = mtk_drm_attach_bridge(dsi->bridge, &dsi->encoder);
+	if (!ret)
+		return 0;
+
+	ret = drm_connector_init(drm, &dsi->conn, &mtk_dsi_connector_funcs,
+				 DRM_MODE_CONNECTOR_DSI);
+	if (ret) {
+		DRM_ERROR("Failed to connector init to drm\n");
+		goto err_encoder_cleanup;
+	}
+
+	drm_connector_helper_add(&dsi->conn, &mtk_dsi_connector_helper_funcs);
+
+	ret = drm_connector_register(&dsi->conn);
+	if (ret) {
+		DRM_ERROR("Failed to connector register to drm\n");
+		goto err_connector_cleanup;
+	}
+
+	dsi->conn.dpms = DRM_MODE_DPMS_OFF;
+	drm_mode_connector_attach_encoder(&dsi->conn, &dsi->encoder);
+
+	if (dsi->panel) {
+		ret = drm_panel_attach(dsi->panel, &dsi->conn);
+		if (ret) {
+			DRM_ERROR("Failed to attach panel to drm\n");
+			goto err_connector_unregister;
+		}
+	}
+	return 0;
+
+err_connector_unregister:
+	drm_connector_unregister(&dsi->conn);
+err_connector_cleanup:
+	drm_connector_cleanup(&dsi->conn);
+err_encoder_cleanup:
+	drm_encoder_cleanup(&dsi->encoder);
+
+	return ret;
+}
+
+static void mtk_dsi_destroy_conn_enc(struct mtk_dsi *dsi)
+{
+	drm_encoder_cleanup(&dsi->encoder);
+	/* Skip connector cleanup if creation was delegated to the bridge */
+	if (dsi->conn.dev) {
+		drm_connector_unregister(&dsi->conn);
+		drm_connector_cleanup(&dsi->conn);
+	}
+}
+
+static void mtk_dsi_ddp_start(struct mtk_ddp_comp *comp)
+{
+	struct mtk_dsi *dsi = container_of(comp, struct mtk_dsi, ddp_comp);
+
+	mtk_dsi_poweron(dsi);
+}
+
+static void mtk_dsi_ddp_stop(struct mtk_ddp_comp *comp)
+{
+	struct mtk_dsi *dsi = container_of(comp, struct mtk_dsi, ddp_comp);
+
+	mtk_dsi_poweroff(dsi);
+}
+
+static const struct mtk_ddp_comp_funcs mtk_dsi_funcs = {
+	.start = mtk_dsi_ddp_start,
+	.stop = mtk_dsi_ddp_stop,
+};
+
+static int mtk_dsi_host_attach(struct mipi_dsi_host *host,
+			       struct mipi_dsi_device *device)
+{
+	struct mtk_dsi *dsi = host_to_dsi(host);
+
+	dsi->lanes = device->lanes;
+	dsi->format = device->format;
+	dsi->mode_flags = device->mode_flags;
+
+	if (dsi->conn.dev)
+		drm_helper_hpd_irq_event(dsi->conn.dev);
+
+	return 0;
+}
+
+static int mtk_dsi_host_detach(struct mipi_dsi_host *host,
+			       struct mipi_dsi_device *device)
+{
+	struct mtk_dsi *dsi = host_to_dsi(host);
+
+	if (dsi->conn.dev)
+		drm_helper_hpd_irq_event(dsi->conn.dev);
+
+	return 0;
+}
+
+static const struct mipi_dsi_host_ops mtk_dsi_ops = {
+	.attach = mtk_dsi_host_attach,
+	.detach = mtk_dsi_host_detach,
+};
+
+static int mtk_dsi_bind(struct device *dev, struct device *master, void *data)
+{
+	int ret;
+	struct drm_device *drm = data;
+	struct mtk_dsi *dsi = dev_get_drvdata(dev);
+
+	ret = mtk_ddp_comp_register(drm, &dsi->ddp_comp);
+	if (ret < 0) {
+		dev_err(dev, "Failed to register component %s: %d\n",
+			dev->of_node->full_name, ret);
+		return ret;
+	}
+
+	ret = mipi_dsi_host_register(&dsi->host);
+	if (ret < 0) {
+		dev_err(dev, "failed to register DSI host: %d\n", ret);
+		goto err_ddp_comp_unregister;
+	}
+
+	ret = mtk_dsi_create_conn_enc(drm, dsi);
+	if (ret) {
+		DRM_ERROR("Encoder create failed with %d\n", ret);
+		goto err_unregister;
+	}
+
+	return 0;
+
+err_unregister:
+	mipi_dsi_host_unregister(&dsi->host);
+err_ddp_comp_unregister:
+	mtk_ddp_comp_unregister(drm, &dsi->ddp_comp);
+	return ret;
+}
+
+static void mtk_dsi_unbind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct drm_device *drm = data;
+	struct mtk_dsi *dsi = dev_get_drvdata(dev);
+
+	mtk_dsi_destroy_conn_enc(dsi);
+	mipi_dsi_host_unregister(&dsi->host);
+	mtk_ddp_comp_unregister(drm, &dsi->ddp_comp);
+}
+
+static const struct component_ops mtk_dsi_component_ops = {
+	.bind = mtk_dsi_bind,
+	.unbind = mtk_dsi_unbind,
+};
+
+static int mtk_dsi_probe(struct platform_device *pdev)
+{
+	struct mtk_dsi *dsi;
+	struct device *dev = &pdev->dev;
+	struct device_node *remote_node, *endpoint;
+	struct resource *regs;
+	int comp_id;
+	int ret;
+
+	dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+	if (!dsi)
+		return -ENOMEM;
+
+	dsi->host.ops = &mtk_dsi_ops;
+	dsi->host.dev = dev;
+
+	endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
+	if (endpoint) {
+		remote_node = of_graph_get_remote_port_parent(endpoint);
+		if (!remote_node) {
+			dev_err(dev, "No panel connected\n");
+			return -ENODEV;
+		}
+
+		dsi->bridge = of_drm_find_bridge(remote_node);
+		dsi->panel = of_drm_find_panel(remote_node);
+		of_node_put(remote_node);
+		if (!dsi->bridge && !dsi->panel) {
+			dev_info(dev, "Waiting for bridge or panel driver\n");
+			return -EPROBE_DEFER;
+		}
+	}
+
+	dsi->engine_clk = devm_clk_get(dev, "engine");
+	if (IS_ERR(dsi->engine_clk)) {
+		ret = PTR_ERR(dsi->engine_clk);
+		dev_err(dev, "Failed to get engine clock: %d\n", ret);
+		return ret;
+	}
+
+	dsi->digital_clk = devm_clk_get(dev, "digital");
+	if (IS_ERR(dsi->digital_clk)) {
+		ret = PTR_ERR(dsi->digital_clk);
+		dev_err(dev, "Failed to get digital clock: %d\n", ret);
+		return ret;
+	}
+
+	dsi->hs_clk = devm_clk_get(dev, "hs");
+	if (IS_ERR(dsi->hs_clk)) {
+		ret = PTR_ERR(dsi->hs_clk);
+		dev_err(dev, "Failed to get hs clock: %d\n", ret);
+		return ret;
+	}
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dsi->regs = devm_ioremap_resource(dev, regs);
+	if (IS_ERR(dsi->regs)) {
+		ret = PTR_ERR(dsi->regs);
+		dev_err(dev, "Failed to ioremap memory: %d\n", ret);
+		return ret;
+	}
+
+	dsi->phy = devm_phy_get(dev, "dphy");
+	if (IS_ERR(dsi->phy)) {
+		ret = PTR_ERR(dsi->phy);
+		dev_err(dev, "Failed to get MIPI-DPHY: %d\n", ret);
+		return ret;
+	}
+
+	comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DSI);
+	if (comp_id < 0) {
+		dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
+		return comp_id;
+	}
+
+	ret = mtk_ddp_comp_init(dev, dev->of_node, &dsi->ddp_comp, comp_id,
+				&mtk_dsi_funcs);
+	if (ret) {
+		dev_err(dev, "Failed to initialize component: %d\n", ret);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, dsi);
+
+	return component_add(&pdev->dev, &mtk_dsi_component_ops);
+}
+
+static int mtk_dsi_remove(struct platform_device *pdev)
+{
+	struct mtk_dsi *dsi = platform_get_drvdata(pdev);
+
+	mtk_output_dsi_disable(dsi);
+	component_del(&pdev->dev, &mtk_dsi_component_ops);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int mtk_dsi_suspend(struct device *dev)
+{
+	struct mtk_dsi *dsi;
+
+	dsi = dev_get_drvdata(dev);
+
+	mtk_output_dsi_disable(dsi);
+	DRM_DEBUG_DRIVER("dsi suspend success!\n");
+
+	return 0;
+}
+
+static int mtk_dsi_resume(struct device *dev)
+{
+	struct mtk_dsi *dsi;
+
+	dsi = dev_get_drvdata(dev);
+
+	mtk_output_dsi_enable(dsi);
+	DRM_DEBUG_DRIVER("dsi resume success!\n");
+
+	return 0;
+}
+#endif
+static SIMPLE_DEV_PM_OPS(mtk_dsi_pm_ops, mtk_dsi_suspend, mtk_dsi_resume);
+
+static const struct of_device_id mtk_dsi_of_match[] = {
+	{ .compatible = "mediatek,mt8173-dsi" },
+	{ },
+};
+
+struct platform_driver mtk_dsi_driver = {
+	.probe = mtk_dsi_probe,
+	.remove = mtk_dsi_remove,
+	.driver = {
+		.name = "mtk-dsi",
+		.of_match_table = mtk_dsi_of_match,
+		.pm = &mtk_dsi_pm_ops,
+	},
+};
diff --git a/drivers/gpu/drm/mediatek/mtk_mipi_tx.c b/drivers/gpu/drm/mediatek/mtk_mipi_tx.c
new file mode 100644
index 0000000..b1e8e34
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_mipi_tx.c
@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+
+#define MIPITX_DSI_CON		0x00
+#define RG_DSI_LDOCORE_EN		BIT(0)
+#define RG_DSI_CKG_LDOOUT_EN		BIT(1)
+#define RG_DSI_BCLK_SEL			(3 << 2)
+#define RG_DSI_LD_IDX_SEL		(7 << 4)
+#define RG_DSI_PHYCLK_SEL		(2 << 8)
+#define RG_DSI_DSICLK_FREQ_SEL		BIT(10)
+#define RG_DSI_LPTX_CLMP_EN		BIT(11)
+
+#define MIPITX_DSI_CLOCK_LANE	0x04
+#define RG_DSI_LNTC_LDOOUT_EN		BIT(0)
+#define RG_DSI_LNTC_CKLANE_EN		BIT(1)
+#define RG_DSI_LNTC_LPTX_IPLUS1		BIT(2)
+#define RG_DSI_LNTC_LPTX_IPLUS2		BIT(3)
+#define RG_DSI_LNTC_LPTX_IMINUS		BIT(4)
+#define RG_DSI_LNTC_LPCD_IPLUS		BIT(5)
+#define RG_DSI_LNTC_LPCD_IMLUS		BIT(6)
+#define RG_DSI_LNTC_RT_CODE		(0xf << 8)
+
+#define MIPITX_DSI_DATA_LANE0	0x08
+#define RG_DSI_LNT0_LDOOUT_EN		BIT(0)
+#define RG_DSI_LNT0_CKLANE_EN		BIT(1)
+#define RG_DSI_LNT0_LPTX_IPLUS1		BIT(2)
+#define RG_DSI_LNT0_LPTX_IPLUS2		BIT(3)
+#define RG_DSI_LNT0_LPTX_IMINUS		BIT(4)
+#define RG_DSI_LNT0_LPCD_IPLUS		BIT(5)
+#define RG_DSI_LNT0_LPCD_IMINUS		BIT(6)
+#define RG_DSI_LNT0_RT_CODE		(0xf << 8)
+
+#define MIPITX_DSI_DATA_LANE1	0x0c
+#define RG_DSI_LNT1_LDOOUT_EN		BIT(0)
+#define RG_DSI_LNT1_CKLANE_EN		BIT(1)
+#define RG_DSI_LNT1_LPTX_IPLUS1		BIT(2)
+#define RG_DSI_LNT1_LPTX_IPLUS2		BIT(3)
+#define RG_DSI_LNT1_LPTX_IMINUS		BIT(4)
+#define RG_DSI_LNT1_LPCD_IPLUS		BIT(5)
+#define RG_DSI_LNT1_LPCD_IMINUS		BIT(6)
+#define RG_DSI_LNT1_RT_CODE		(0xf << 8)
+
+#define MIPITX_DSI_DATA_LANE2	0x10
+#define RG_DSI_LNT2_LDOOUT_EN		BIT(0)
+#define RG_DSI_LNT2_CKLANE_EN		BIT(1)
+#define RG_DSI_LNT2_LPTX_IPLUS1		BIT(2)
+#define RG_DSI_LNT2_LPTX_IPLUS2		BIT(3)
+#define RG_DSI_LNT2_LPTX_IMINUS		BIT(4)
+#define RG_DSI_LNT2_LPCD_IPLUS		BIT(5)
+#define RG_DSI_LNT2_LPCD_IMINUS		BIT(6)
+#define RG_DSI_LNT2_RT_CODE		(0xf << 8)
+
+#define MIPITX_DSI_DATA_LANE3	0x14
+#define RG_DSI_LNT3_LDOOUT_EN		BIT(0)
+#define RG_DSI_LNT3_CKLANE_EN		BIT(1)
+#define RG_DSI_LNT3_LPTX_IPLUS1		BIT(2)
+#define RG_DSI_LNT3_LPTX_IPLUS2		BIT(3)
+#define RG_DSI_LNT3_LPTX_IMINUS		BIT(4)
+#define RG_DSI_LNT3_LPCD_IPLUS		BIT(5)
+#define RG_DSI_LNT3_LPCD_IMINUS		BIT(6)
+#define RG_DSI_LNT3_RT_CODE		(0xf << 8)
+
+#define MIPITX_DSI_TOP_CON	0x40
+#define RG_DSI_LNT_INTR_EN		BIT(0)
+#define RG_DSI_LNT_HS_BIAS_EN		BIT(1)
+#define RG_DSI_LNT_IMP_CAL_EN		BIT(2)
+#define RG_DSI_LNT_TESTMODE_EN		BIT(3)
+#define RG_DSI_LNT_IMP_CAL_CODE		(0xf << 4)
+#define RG_DSI_LNT_AIO_SEL		(7 << 8)
+#define RG_DSI_PAD_TIE_LOW_EN		BIT(11)
+#define RG_DSI_DEBUG_INPUT_EN		BIT(12)
+#define RG_DSI_PRESERVE			(7 << 13)
+
+#define MIPITX_DSI_BG_CON	0x44
+#define RG_DSI_BG_CORE_EN		BIT(0)
+#define RG_DSI_BG_CKEN			BIT(1)
+#define RG_DSI_BG_DIV			(0x3 << 2)
+#define RG_DSI_BG_FAST_CHARGE		BIT(4)
+#define RG_DSI_VOUT_MSK			(0x3ffff << 5)
+#define RG_DSI_V12_SEL			(7 << 5)
+#define RG_DSI_V10_SEL			(7 << 8)
+#define RG_DSI_V072_SEL			(7 << 11)
+#define RG_DSI_V04_SEL			(7 << 14)
+#define RG_DSI_V032_SEL			(7 << 17)
+#define RG_DSI_V02_SEL			(7 << 20)
+#define RG_DSI_BG_R1_TRIM		(0xf << 24)
+#define RG_DSI_BG_R2_TRIM		(0xf << 28)
+
+#define MIPITX_DSI_PLL_CON0	0x50
+#define RG_DSI_MPPLL_PLL_EN		BIT(0)
+#define RG_DSI_MPPLL_DIV_MSK		(0x1ff << 1)
+#define RG_DSI_MPPLL_PREDIV		(3 << 1)
+#define RG_DSI_MPPLL_TXDIV0		(3 << 3)
+#define RG_DSI_MPPLL_TXDIV1		(3 << 5)
+#define RG_DSI_MPPLL_POSDIV		(7 << 7)
+#define RG_DSI_MPPLL_MONVC_EN		BIT(10)
+#define RG_DSI_MPPLL_MONREF_EN		BIT(11)
+#define RG_DSI_MPPLL_VOD_EN		BIT(12)
+
+#define MIPITX_DSI_PLL_CON1	0x54
+#define RG_DSI_MPPLL_SDM_FRA_EN		BIT(0)
+#define RG_DSI_MPPLL_SDM_SSC_PH_INIT	BIT(1)
+#define RG_DSI_MPPLL_SDM_SSC_EN		BIT(2)
+#define RG_DSI_MPPLL_SDM_SSC_PRD	(0xffff << 16)
+
+#define MIPITX_DSI_PLL_CON2	0x58
+
+#define MIPITX_DSI_PLL_PWR	0x68
+#define RG_DSI_MPPLL_SDM_PWR_ON		BIT(0)
+#define RG_DSI_MPPLL_SDM_ISO_EN		BIT(1)
+#define RG_DSI_MPPLL_SDM_PWR_ACK	BIT(8)
+
+#define MIPITX_DSI_SW_CTRL	0x80
+#define SW_CTRL_EN			BIT(0)
+
+#define MIPITX_DSI_SW_CTRL_CON0	0x84
+#define SW_LNTC_LPTX_PRE_OE		BIT(0)
+#define SW_LNTC_LPTX_OE			BIT(1)
+#define SW_LNTC_LPTX_P			BIT(2)
+#define SW_LNTC_LPTX_N			BIT(3)
+#define SW_LNTC_HSTX_PRE_OE		BIT(4)
+#define SW_LNTC_HSTX_OE			BIT(5)
+#define SW_LNTC_HSTX_ZEROCLK		BIT(6)
+#define SW_LNT0_LPTX_PRE_OE		BIT(7)
+#define SW_LNT0_LPTX_OE			BIT(8)
+#define SW_LNT0_LPTX_P			BIT(9)
+#define SW_LNT0_LPTX_N			BIT(10)
+#define SW_LNT0_HSTX_PRE_OE		BIT(11)
+#define SW_LNT0_HSTX_OE			BIT(12)
+#define SW_LNT0_LPRX_EN			BIT(13)
+#define SW_LNT1_LPTX_PRE_OE		BIT(14)
+#define SW_LNT1_LPTX_OE			BIT(15)
+#define SW_LNT1_LPTX_P			BIT(16)
+#define SW_LNT1_LPTX_N			BIT(17)
+#define SW_LNT1_HSTX_PRE_OE		BIT(18)
+#define SW_LNT1_HSTX_OE			BIT(19)
+#define SW_LNT2_LPTX_PRE_OE		BIT(20)
+#define SW_LNT2_LPTX_OE			BIT(21)
+#define SW_LNT2_LPTX_P			BIT(22)
+#define SW_LNT2_LPTX_N			BIT(23)
+#define SW_LNT2_HSTX_PRE_OE		BIT(24)
+#define SW_LNT2_HSTX_OE			BIT(25)
+
+struct mtk_mipi_tx {
+	struct device *dev;
+	void __iomem *regs;
+	unsigned int data_rate;
+	struct clk_hw pll_hw;
+	struct clk *pll;
+};
+
+static void mtk_mipi_tx_mask(struct mtk_mipi_tx *mipi_tx, u32 offset, u32 mask,
+			     u32 data)
+{
+	u32 temp = readl(mipi_tx->regs + offset);
+
+	writel((temp & ~mask) | (data & mask), mipi_tx->regs + offset);
+}
+
+static int mtk_mipi_tx_pll_prepare(struct clk_hw *hw)
+{
+	struct mtk_mipi_tx *mipi_tx = container_of(hw, struct mtk_mipi_tx,
+						   pll_hw);
+	unsigned int txdiv, txdiv0, txdiv1;
+	u64 pcw;
+
+	dev_dbg(mipi_tx->dev, "prepare: %u Hz\n", mipi_tx->data_rate);
+
+	if (mipi_tx->data_rate >= 500000000) {
+		txdiv = 1;
+		txdiv0 = 0;
+		txdiv1 = 0;
+	} else if (mipi_tx->data_rate >= 250000000) {
+		txdiv = 2;
+		txdiv0 = 1;
+		txdiv1 = 0;
+	} else if (mipi_tx->data_rate >= 125000000) {
+		txdiv = 4;
+		txdiv0 = 2;
+		txdiv1 = 0;
+	} else if (mipi_tx->data_rate > 62000000) {
+		txdiv = 8;
+		txdiv0 = 2;
+		txdiv1 = 1;
+	} else if (mipi_tx->data_rate >= 50000000) {
+		txdiv = 16;
+		txdiv0 = 2;
+		txdiv1 = 2;
+	} else {
+		return -EINVAL;
+	}
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_BG_CON,
+			 RG_DSI_VOUT_MSK | RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN,
+			 (4 << 20) | (4 << 17) | (4 << 14) |
+			 (4 << 11) | (4 << 8) | (4 << 5) |
+			 RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN);
+
+	usleep_range(30, 100);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON,
+			 RG_DSI_LNT_IMP_CAL_CODE | RG_DSI_LNT_HS_BIAS_EN,
+			 (8 << 4) | RG_DSI_LNT_HS_BIAS_EN);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CON,
+			 RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN,
+			 RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_PWR,
+			 RG_DSI_MPPLL_SDM_PWR_ON | RG_DSI_MPPLL_SDM_ISO_EN,
+			 RG_DSI_MPPLL_SDM_PWR_ON);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN, 0);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0,
+			 RG_DSI_MPPLL_TXDIV0 | RG_DSI_MPPLL_TXDIV1 |
+			 RG_DSI_MPPLL_PREDIV,
+			 (txdiv0 << 3) | (txdiv1 << 5));
+
+	/*
+	 * PLL PCW config
+	 * PCW bit 24~30 = integer part of pcw
+	 * PCW bit 0~23 = fractional part of pcw
+	 * pcw = data_Rate*4*txdiv/(Ref_clk*2);
+	 * Post DIV =4, so need data_Rate*4
+	 * Ref_clk is 26MHz
+	 */
+	pcw = ((u64)mipi_tx->data_rate * 2 * txdiv) << 24;
+	pcw /= 26000000;
+	writel(pcw, mipi_tx->regs + MIPITX_DSI_PLL_CON2);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON1,
+			 RG_DSI_MPPLL_SDM_FRA_EN, RG_DSI_MPPLL_SDM_FRA_EN);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0,
+			 RG_DSI_MPPLL_PLL_EN, RG_DSI_MPPLL_PLL_EN);
+
+	usleep_range(20, 100);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON1,
+			 RG_DSI_MPPLL_SDM_SSC_EN, 0);
+
+	return 0;
+}
+
+static void mtk_mipi_tx_pll_unprepare(struct clk_hw *hw)
+{
+	struct mtk_mipi_tx *mipi_tx = container_of(hw, struct mtk_mipi_tx,
+						   pll_hw);
+
+	dev_dbg(mipi_tx->dev, "unprepare\n");
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN, 0);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_PWR,
+			 RG_DSI_MPPLL_SDM_ISO_EN | RG_DSI_MPPLL_SDM_PWR_ON,
+			 RG_DSI_MPPLL_SDM_ISO_EN);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_LNT_HS_BIAS_EN, 0);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CON,
+			 RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN, 0);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_BG_CON,
+			 RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN, 0);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_DIV_MSK, 0);
+}
+
+static long mtk_mipi_tx_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				       unsigned long *prate)
+{
+	return clamp_val(rate, 50000000, 1250000000);
+}
+
+static int mtk_mipi_tx_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long parent_rate)
+{
+	struct mtk_mipi_tx *mipi_tx = container_of(hw, struct mtk_mipi_tx,
+						   pll_hw);
+
+	dev_dbg(mipi_tx->dev, "set rate: %lu Hz\n", rate);
+
+	mipi_tx->data_rate = rate;
+
+	return 0;
+}
+
+static unsigned long mtk_mipi_tx_pll_recalc_rate(struct clk_hw *hw,
+						 unsigned long parent_rate)
+{
+	struct mtk_mipi_tx *mipi_tx = container_of(hw, struct mtk_mipi_tx,
+						   pll_hw);
+	return mipi_tx->data_rate;
+}
+
+static const struct clk_ops mtk_mipi_tx_pll_ops = {
+	.prepare = mtk_mipi_tx_pll_prepare,
+	.unprepare = mtk_mipi_tx_pll_unprepare,
+	.round_rate = mtk_mipi_tx_pll_round_rate,
+	.set_rate = mtk_mipi_tx_pll_set_rate,
+	.recalc_rate = mtk_mipi_tx_pll_recalc_rate,
+};
+
+static int mtk_mipi_tx_power_on_signal(struct phy *phy)
+{
+	struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CLOCK_LANE,
+			 RG_DSI_LNTC_LDOOUT_EN, RG_DSI_LNTC_LDOOUT_EN);
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE0,
+			 RG_DSI_LNT0_LDOOUT_EN, RG_DSI_LNT0_LDOOUT_EN);
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE1,
+			 RG_DSI_LNT1_LDOOUT_EN, RG_DSI_LNT1_LDOOUT_EN);
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE2,
+			 RG_DSI_LNT2_LDOOUT_EN, RG_DSI_LNT2_LDOOUT_EN);
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE3,
+			 RG_DSI_LNT3_LDOOUT_EN, RG_DSI_LNT3_LDOOUT_EN);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_PAD_TIE_LOW_EN, 0);
+
+	return 0;
+}
+
+static int mtk_mipi_tx_power_on(struct phy *phy)
+{
+	struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
+	int ret;
+
+	/* Power up core and enable PLL */
+	ret = clk_prepare_enable(mipi_tx->pll);
+	if (ret < 0)
+		return ret;
+
+	/* Enable DSI Lane LDO outputs, disable pad tie low */
+	mtk_mipi_tx_power_on_signal(phy);
+
+	return 0;
+}
+
+static void mtk_mipi_tx_power_off_signal(struct phy *phy)
+{
+	struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_PAD_TIE_LOW_EN,
+			 RG_DSI_PAD_TIE_LOW_EN);
+
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CLOCK_LANE,
+			 RG_DSI_LNTC_LDOOUT_EN, 0);
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE0,
+			 RG_DSI_LNT0_LDOOUT_EN, 0);
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE1,
+			 RG_DSI_LNT1_LDOOUT_EN, 0);
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE2,
+			 RG_DSI_LNT2_LDOOUT_EN, 0);
+	mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE3,
+			 RG_DSI_LNT3_LDOOUT_EN, 0);
+}
+
+static int mtk_mipi_tx_power_off(struct phy *phy)
+{
+	struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
+
+	/* Enable pad tie low, disable DSI Lane LDO outputs */
+	mtk_mipi_tx_power_off_signal(phy);
+
+	/* Disable PLL and power down core */
+	clk_disable_unprepare(mipi_tx->pll);
+
+	return 0;
+}
+
+static const struct phy_ops mtk_mipi_tx_ops = {
+	.power_on = mtk_mipi_tx_power_on,
+	.power_off = mtk_mipi_tx_power_off,
+	.owner = THIS_MODULE,
+};
+
+static int mtk_mipi_tx_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_mipi_tx *mipi_tx;
+	struct resource *mem;
+	struct clk *ref_clk;
+	const char *ref_clk_name;
+	struct clk_init_data clk_init = {
+		.ops = &mtk_mipi_tx_pll_ops,
+		.num_parents = 1,
+		.parent_names = (const char * const *)&ref_clk_name,
+		.flags = CLK_SET_RATE_GATE,
+	};
+	struct phy *phy;
+	struct phy_provider *phy_provider;
+	int ret;
+
+	mipi_tx = devm_kzalloc(dev, sizeof(*mipi_tx), GFP_KERNEL);
+	if (!mipi_tx)
+		return -ENOMEM;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	mipi_tx->regs = devm_ioremap_resource(dev, mem);
+	if (IS_ERR(mipi_tx->regs)) {
+		ret = PTR_ERR(mipi_tx->regs);
+		dev_err(dev, "Failed to get memory resource: %d\n", ret);
+		return ret;
+	}
+
+	ref_clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(ref_clk)) {
+		ret = PTR_ERR(ref_clk);
+		dev_err(dev, "Failed to get reference clock: %d\n", ret);
+		return ret;
+	}
+	ref_clk_name = __clk_get_name(ref_clk);
+
+	ret = of_property_read_string(dev->of_node, "clock-output-names",
+				      &clk_init.name);
+	if (ret < 0) {
+		dev_err(dev, "Failed to read clock-output-names: %d\n", ret);
+		return ret;
+	}
+
+	mipi_tx->pll_hw.init = &clk_init;
+	mipi_tx->pll = devm_clk_register(dev, &mipi_tx->pll_hw);
+	if (IS_ERR(mipi_tx->pll)) {
+		ret = PTR_ERR(mipi_tx->pll);
+		dev_err(dev, "Failed to register PLL: %d\n", ret);
+		return ret;
+	}
+
+	phy = devm_phy_create(dev, NULL, &mtk_mipi_tx_ops);
+	if (IS_ERR(phy)) {
+		ret = PTR_ERR(phy);
+		dev_err(dev, "Failed to create MIPI D-PHY: %d\n", ret);
+		return ret;
+	}
+	phy_set_drvdata(phy, mipi_tx);
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (IS_ERR(phy)) {
+		ret = PTR_ERR(phy_provider);
+		return ret;
+	}
+
+	mipi_tx->dev = dev;
+
+	return of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
+				   mipi_tx->pll);
+}
+
+static int mtk_mipi_tx_remove(struct platform_device *pdev)
+{
+	of_clk_del_provider(pdev->dev.of_node);
+	return 0;
+}
+
+static const struct of_device_id mtk_mipi_tx_match[] = {
+	{ .compatible = "mediatek,mt8173-mipi-tx", },
+	{},
+};
+
+struct platform_driver mtk_mipi_tx_driver = {
+	.probe = mtk_mipi_tx_probe,
+	.remove = mtk_mipi_tx_remove,
+	.driver = {
+		.name = "mediatek-mipi-tx",
+		.of_match_table = mtk_mipi_tx_match,
+	},
+};
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 04/14] drm/mediatek: Add DPI sub driver
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
                   ` (2 preceding siblings ...)
  2016-03-08 13:27 ` [PATCH v13 03/14] drm/mediatek: Add DSI sub driver Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
       [not found]   ` <1457443649-12133-5-git-send-email-p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
       [not found] ` <1457443649-12133-1-git-send-email-p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
                   ` (9 subsequent siblings)
  13 siblings, 1 reply; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

From: Jie Qiu <jie.qiu@mediatek.com>

Add DPI connector/encoder to support HDMI output via the
attached HDMI bridge.

Signed-off-by: Jie Qiu <jie.qiu@mediatek.com>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
---
 drivers/gpu/drm/mediatek/Makefile       |   3 +-
 drivers/gpu/drm/mediatek/mtk_dpi.c      | 757 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_dpi.h      |  85 ++++
 drivers/gpu/drm/mediatek/mtk_dpi_regs.h | 228 ++++++++++
 drivers/gpu/drm/mediatek/mtk_drm_drv.c  |   1 +
 drivers/gpu/drm/mediatek/mtk_drm_drv.h  |   1 +
 6 files changed, 1074 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi_regs.h

diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile
index e781db5a..5fcf58e 100644
--- a/drivers/gpu/drm/mediatek/Makefile
+++ b/drivers/gpu/drm/mediatek/Makefile
@@ -8,6 +8,7 @@ mediatek-drm-y := mtk_disp_ovl.o \
 		  mtk_drm_gem.o \
 		  mtk_drm_plane.o \
 		  mtk_dsi.o \
-		  mtk_mipi_tx.o
+		  mtk_mipi_tx.o \
+		  mtk_dpi.o
 
 obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.c b/drivers/gpu/drm/mediatek/mtk_dpi.c
new file mode 100644
index 0000000..ae81906
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_dpi.c
@@ -0,0 +1,757 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <linux/kernel.h>
+#include <linux/component.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+
+#include "mtk_dpi.h"
+#include "mtk_dpi_regs.h"
+
+enum mtk_dpi_polarity {
+	MTK_DPI_POLARITY_RISING,
+	MTK_DPI_POLARITY_FALLING,
+};
+
+enum mtk_dpi_power_ctl {
+	DPI_POWER_START = BIT(0),
+	DPI_POWER_ENABLE = BIT(1),
+	DPI_POWER_RESUME = BIT(2),
+};
+
+struct mtk_dpi_polarities {
+	enum mtk_dpi_polarity de_pol;
+	enum mtk_dpi_polarity ck_pol;
+	enum mtk_dpi_polarity hsync_pol;
+	enum mtk_dpi_polarity vsync_pol;
+};
+
+struct mtk_dpi_sync_param {
+	u32 sync_width;
+	u32 front_porch;
+	u32 back_porch;
+	bool shift_half_line;
+};
+
+struct mtk_dpi_yc_limit {
+	u16 y_top;
+	u16 y_bottom;
+	u16 c_top;
+	u16 c_bottom;
+};
+
+static void mtk_dpi_mask(struct mtk_dpi *dpi, u32 offset, u32 val, u32 mask)
+{
+	u32 tmp = readl(dpi->regs + offset) & ~mask;
+
+	tmp |= (val & mask);
+	writel(tmp, dpi->regs + offset);
+}
+
+static void mtk_dpi_sw_reset(struct mtk_dpi *dpi, bool reset)
+{
+	mtk_dpi_mask(dpi, DPI_RET, reset ? RST : 0, RST);
+}
+
+static void mtk_dpi_enable(struct mtk_dpi *dpi)
+{
+	mtk_dpi_mask(dpi, DPI_EN, EN, EN);
+}
+
+static void mtk_dpi_disable(struct mtk_dpi *dpi)
+{
+	mtk_dpi_mask(dpi, DPI_EN, 0, EN);
+}
+
+static void mtk_dpi_config_hsync(struct mtk_dpi *dpi,
+				 struct mtk_dpi_sync_param *sync)
+{
+	mtk_dpi_mask(dpi, DPI_TGEN_HWIDTH,
+		     sync->sync_width << HPW, HPW_MASK);
+	mtk_dpi_mask(dpi, DPI_TGEN_HPORCH,
+		     sync->back_porch << HBP, HBP_MASK);
+	mtk_dpi_mask(dpi, DPI_TGEN_HPORCH, sync->front_porch << HFP,
+		     HFP_MASK);
+}
+
+static void mtk_dpi_config_vsync(struct mtk_dpi *dpi,
+				 struct mtk_dpi_sync_param *sync,
+				 u32 width_addr, u32 porch_addr)
+{
+	mtk_dpi_mask(dpi, width_addr,
+		     sync->sync_width << VSYNC_WIDTH_SHIFT,
+		     VSYNC_WIDTH_MASK);
+	mtk_dpi_mask(dpi, width_addr,
+		     sync->shift_half_line << VSYNC_HALF_LINE_SHIFT,
+		     VSYNC_HALF_LINE_MASK);
+	mtk_dpi_mask(dpi, porch_addr,
+		     sync->back_porch << VSYNC_BACK_PORCH_SHIFT,
+		     VSYNC_BACK_PORCH_MASK);
+	mtk_dpi_mask(dpi, porch_addr,
+		     sync->front_porch << VSYNC_FRONT_PORCH_SHIFT,
+		     VSYNC_FRONT_PORCH_MASK);
+}
+
+static void mtk_dpi_config_vsync_lodd(struct mtk_dpi *dpi,
+				      struct mtk_dpi_sync_param *sync)
+{
+	mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH, DPI_TGEN_VPORCH);
+}
+
+static void mtk_dpi_config_vsync_leven(struct mtk_dpi *dpi,
+				       struct mtk_dpi_sync_param *sync)
+{
+	mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_LEVEN,
+			     DPI_TGEN_VPORCH_LEVEN);
+}
+
+static void mtk_dpi_config_vsync_rodd(struct mtk_dpi *dpi,
+				      struct mtk_dpi_sync_param *sync)
+{
+	mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_RODD,
+			     DPI_TGEN_VPORCH_RODD);
+}
+
+static void mtk_dpi_config_vsync_reven(struct mtk_dpi *dpi,
+				       struct mtk_dpi_sync_param *sync)
+{
+	mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_REVEN,
+			     DPI_TGEN_VPORCH_REVEN);
+}
+
+static void mtk_dpi_config_pol(struct mtk_dpi *dpi,
+			       struct mtk_dpi_polarities *dpi_pol)
+{
+	unsigned int pol;
+
+	pol = (dpi_pol->ck_pol == MTK_DPI_POLARITY_RISING ? 0 : CK_POL) |
+	      (dpi_pol->de_pol == MTK_DPI_POLARITY_RISING ? 0 : DE_POL) |
+	      (dpi_pol->hsync_pol == MTK_DPI_POLARITY_RISING ? 0 : HSYNC_POL) |
+	      (dpi_pol->vsync_pol == MTK_DPI_POLARITY_RISING ? 0 : VSYNC_POL);
+	mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, pol,
+		     CK_POL | DE_POL | HSYNC_POL | VSYNC_POL);
+}
+
+static void mtk_dpi_config_3d(struct mtk_dpi *dpi, bool en_3d)
+{
+	mtk_dpi_mask(dpi, DPI_CON, en_3d ? TDFP_EN : 0, TDFP_EN);
+}
+
+static void mtk_dpi_config_interface(struct mtk_dpi *dpi, bool inter)
+{
+	mtk_dpi_mask(dpi, DPI_CON, inter ? INTL_EN : 0, INTL_EN);
+}
+
+static void mtk_dpi_config_fb_size(struct mtk_dpi *dpi, u32 width, u32 height)
+{
+	mtk_dpi_mask(dpi, DPI_SIZE, width << HSIZE, HSIZE_MASK);
+	mtk_dpi_mask(dpi, DPI_SIZE, height << VSIZE, VSIZE_MASK);
+}
+
+static void mtk_dpi_config_channel_limit(struct mtk_dpi *dpi,
+					 struct mtk_dpi_yc_limit *limit)
+{
+	mtk_dpi_mask(dpi, DPI_Y_LIMIT, limit->y_bottom << Y_LIMINT_BOT,
+		     Y_LIMINT_BOT_MASK);
+	mtk_dpi_mask(dpi, DPI_Y_LIMIT, limit->y_top << Y_LIMINT_TOP,
+		     Y_LIMINT_TOP_MASK);
+	mtk_dpi_mask(dpi, DPI_C_LIMIT, limit->c_bottom << C_LIMIT_BOT,
+		     C_LIMIT_BOT_MASK);
+	mtk_dpi_mask(dpi, DPI_C_LIMIT, limit->c_top << C_LIMIT_TOP,
+		     C_LIMIT_TOP_MASK);
+}
+
+static void mtk_dpi_config_bit_num(struct mtk_dpi *dpi,
+				   enum mtk_dpi_out_bit_num num)
+{
+	u32 val;
+
+	switch (num) {
+	case MTK_DPI_OUT_BIT_NUM_8BITS:
+		val = OUT_BIT_8;
+		break;
+	case MTK_DPI_OUT_BIT_NUM_10BITS:
+		val = OUT_BIT_10;
+		break;
+	case MTK_DPI_OUT_BIT_NUM_12BITS:
+		val = OUT_BIT_12;
+		break;
+	case MTK_DPI_OUT_BIT_NUM_16BITS:
+		val = OUT_BIT_16;
+		break;
+	default:
+		val = OUT_BIT_8;
+		break;
+	}
+	mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << OUT_BIT,
+		     OUT_BIT_MASK);
+}
+
+static void mtk_dpi_config_yc_map(struct mtk_dpi *dpi,
+				  enum mtk_dpi_out_yc_map map)
+{
+	u32 val;
+
+	switch (map) {
+	case MTK_DPI_OUT_YC_MAP_RGB:
+		val = YC_MAP_RGB;
+		break;
+	case MTK_DPI_OUT_YC_MAP_CYCY:
+		val = YC_MAP_CYCY;
+		break;
+	case MTK_DPI_OUT_YC_MAP_YCYC:
+		val = YC_MAP_YCYC;
+		break;
+	case MTK_DPI_OUT_YC_MAP_CY:
+		val = YC_MAP_CY;
+		break;
+	case MTK_DPI_OUT_YC_MAP_YC:
+		val = YC_MAP_YC;
+		break;
+	default:
+		val = YC_MAP_RGB;
+		break;
+	}
+
+	mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << YC_MAP, YC_MAP_MASK);
+}
+
+static void mtk_dpi_config_channel_swap(struct mtk_dpi *dpi,
+					enum mtk_dpi_out_channel_swap swap)
+{
+	u32 val;
+
+	switch (swap) {
+	case MTK_DPI_OUT_CHANNEL_SWAP_RGB:
+		val = SWAP_RGB;
+		break;
+	case MTK_DPI_OUT_CHANNEL_SWAP_GBR:
+		val = SWAP_GBR;
+		break;
+	case MTK_DPI_OUT_CHANNEL_SWAP_BRG:
+		val = SWAP_BRG;
+		break;
+	case MTK_DPI_OUT_CHANNEL_SWAP_RBG:
+		val = SWAP_RBG;
+		break;
+	case MTK_DPI_OUT_CHANNEL_SWAP_GRB:
+		val = SWAP_GRB;
+		break;
+	case MTK_DPI_OUT_CHANNEL_SWAP_BGR:
+		val = SWAP_BGR;
+		break;
+	default:
+		val = SWAP_RGB;
+		break;
+	}
+
+	mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << CH_SWAP, CH_SWAP_MASK);
+}
+
+static void mtk_dpi_config_yuv422_enable(struct mtk_dpi *dpi, bool enable)
+{
+	mtk_dpi_mask(dpi, DPI_CON, enable ? YUV422_EN : 0, YUV422_EN);
+}
+
+static void mtk_dpi_config_csc_enable(struct mtk_dpi *dpi, bool enable)
+{
+	mtk_dpi_mask(dpi, DPI_CON, enable ? CSC_ENABLE : 0, CSC_ENABLE);
+}
+
+static void mtk_dpi_config_swap_input(struct mtk_dpi *dpi, bool enable)
+{
+	mtk_dpi_mask(dpi, DPI_CON, enable ? IN_RB_SWAP : 0, IN_RB_SWAP);
+}
+
+static void mtk_dpi_config_2n_h_fre(struct mtk_dpi *dpi)
+{
+	mtk_dpi_mask(dpi, DPI_H_FRE_CON, H_FRE_2N, H_FRE_2N);
+}
+
+static void mtk_dpi_config_color_format(struct mtk_dpi *dpi,
+					enum mtk_dpi_out_color_format format)
+{
+	if ((format == MTK_DPI_COLOR_FORMAT_YCBCR_444) ||
+	    (format == MTK_DPI_COLOR_FORMAT_YCBCR_444_FULL)) {
+		mtk_dpi_config_yuv422_enable(dpi, false);
+		mtk_dpi_config_csc_enable(dpi, true);
+		mtk_dpi_config_swap_input(dpi, false);
+		mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_BGR);
+	} else if ((format == MTK_DPI_COLOR_FORMAT_YCBCR_422) ||
+		   (format == MTK_DPI_COLOR_FORMAT_YCBCR_422_FULL)) {
+		mtk_dpi_config_yuv422_enable(dpi, true);
+		mtk_dpi_config_csc_enable(dpi, true);
+		mtk_dpi_config_swap_input(dpi, true);
+		mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB);
+	} else {
+		mtk_dpi_config_yuv422_enable(dpi, false);
+		mtk_dpi_config_csc_enable(dpi, false);
+		mtk_dpi_config_swap_input(dpi, false);
+		mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB);
+	}
+}
+
+static void mtk_dpi_power_off(struct mtk_dpi *dpi, enum mtk_dpi_power_ctl pctl)
+{
+	dpi->power_ctl &= ~pctl;
+
+	if ((dpi->power_ctl & DPI_POWER_START) ||
+	    ((dpi->power_ctl & DPI_POWER_ENABLE) &&
+	     (dpi->power_ctl & DPI_POWER_RESUME)))
+		return;
+
+	if (!dpi->power_sta)
+		return;
+
+	mtk_dpi_disable(dpi);
+	clk_disable_unprepare(dpi->pixel_clk);
+	clk_disable_unprepare(dpi->engine_clk);
+	dpi->power_sta = false;
+}
+
+static int mtk_dpi_power_on(struct mtk_dpi *dpi, enum mtk_dpi_power_ctl pctl)
+{
+	int ret;
+
+	dpi->power_ctl |= pctl;
+
+	if (!(dpi->power_ctl & DPI_POWER_START) &&
+	    !((dpi->power_ctl & DPI_POWER_ENABLE) &&
+	      ((dpi->power_ctl & DPI_POWER_RESUME))))
+		return 0;
+
+	if (dpi->power_sta)
+		return 0;
+
+	ret = clk_prepare_enable(dpi->engine_clk);
+	if (ret) {
+		dev_err(dpi->dev, "Failed to enable engine clock: %d\n", ret);
+		goto err_eng;
+	}
+
+	ret = clk_prepare_enable(dpi->pixel_clk);
+	if (ret) {
+		dev_err(dpi->dev, "Failed to enable pixel clock: %d\n", ret);
+		goto err_pixel;
+	}
+
+	mtk_dpi_enable(dpi);
+	dpi->power_sta = true;
+	return 0;
+
+err_pixel:
+	clk_disable_unprepare(dpi->engine_clk);
+err_eng:
+	dpi->power_ctl &= ~pctl;
+	return ret;
+}
+
+int mtk_dpi_set_display_mode(struct mtk_dpi *dpi, struct drm_display_mode *mode)
+{
+	struct mtk_dpi_yc_limit limit;
+	struct mtk_dpi_polarities dpi_pol;
+	struct mtk_dpi_sync_param hsync;
+	struct mtk_dpi_sync_param vsync_lodd = { 0 };
+	struct mtk_dpi_sync_param vsync_leven = { 0 };
+	struct mtk_dpi_sync_param vsync_rodd = { 0 };
+	struct mtk_dpi_sync_param vsync_reven = { 0 };
+	unsigned long pix_rate;
+	unsigned long pll_rate;
+	unsigned int factor;
+
+	if (!dpi) {
+		dev_err(dpi->dev, "invalid argument\n");
+		return -EINVAL;
+	}
+
+	pix_rate = 1000UL * mode->clock;
+	if (mode->clock <= 74000)
+		factor = 8 * 3;
+	else
+		factor = 4 * 3;
+	pll_rate = pix_rate * factor;
+
+	dev_dbg(dpi->dev, "Want PLL %lu Hz, pixel clock %lu Hz\n",
+		pll_rate, pix_rate);
+
+	clk_set_rate(dpi->tvd_clk, pll_rate);
+	pll_rate = clk_get_rate(dpi->tvd_clk);
+
+	pix_rate = pll_rate / factor;
+	clk_set_rate(dpi->pixel_clk, pix_rate);
+	pix_rate = clk_get_rate(dpi->pixel_clk);
+
+	dev_dbg(dpi->dev, "Got  PLL %lu Hz, pixel clock %lu Hz\n",
+		pll_rate, pix_rate);
+
+	limit.c_bottom = 0x0010;
+	limit.c_top = 0x0FE0;
+	limit.y_bottom = 0x0010;
+	limit.y_top = 0x0FE0;
+
+	dpi_pol.ck_pol = MTK_DPI_POLARITY_FALLING;
+	dpi_pol.de_pol = MTK_DPI_POLARITY_RISING;
+	dpi_pol.hsync_pol = mode->flags & DRM_MODE_FLAG_PHSYNC ?
+			    MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING;
+	dpi_pol.vsync_pol = mode->flags & DRM_MODE_FLAG_PVSYNC ?
+			    MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING;
+
+	hsync.sync_width = mode->hsync_end - mode->hsync_start;
+	hsync.back_porch = mode->htotal - mode->hsync_end;
+	hsync.front_porch = mode->hsync_start - mode->hdisplay;
+	hsync.shift_half_line = false;
+
+	vsync_lodd.sync_width = mode->vsync_end - mode->vsync_start;
+	vsync_lodd.back_porch = mode->vtotal - mode->vsync_end;
+	vsync_lodd.front_porch = mode->vsync_start - mode->vdisplay;
+	vsync_lodd.shift_half_line = false;
+
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE &&
+	    mode->flags & DRM_MODE_FLAG_3D_MASK) {
+		vsync_leven = vsync_lodd;
+		vsync_rodd = vsync_lodd;
+		vsync_reven = vsync_lodd;
+		vsync_leven.shift_half_line = true;
+		vsync_reven.shift_half_line = true;
+	} else if (mode->flags & DRM_MODE_FLAG_INTERLACE &&
+		   !(mode->flags & DRM_MODE_FLAG_3D_MASK)) {
+		vsync_leven = vsync_lodd;
+		vsync_leven.shift_half_line = true;
+	} else if (!(mode->flags & DRM_MODE_FLAG_INTERLACE) &&
+		   mode->flags & DRM_MODE_FLAG_3D_MASK) {
+		vsync_rodd = vsync_lodd;
+	}
+	mtk_dpi_sw_reset(dpi, true);
+	mtk_dpi_config_pol(dpi, &dpi_pol);
+
+	mtk_dpi_config_hsync(dpi, &hsync);
+	mtk_dpi_config_vsync_lodd(dpi, &vsync_lodd);
+	mtk_dpi_config_vsync_rodd(dpi, &vsync_rodd);
+	mtk_dpi_config_vsync_leven(dpi, &vsync_leven);
+	mtk_dpi_config_vsync_reven(dpi, &vsync_reven);
+
+	mtk_dpi_config_3d(dpi, !!(mode->flags & DRM_MODE_FLAG_3D_MASK));
+	mtk_dpi_config_interface(dpi, !!(mode->flags &
+					 DRM_MODE_FLAG_INTERLACE));
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		mtk_dpi_config_fb_size(dpi, mode->hdisplay, mode->vdisplay / 2);
+	else
+		mtk_dpi_config_fb_size(dpi, mode->hdisplay, mode->vdisplay);
+
+	mtk_dpi_config_channel_limit(dpi, &limit);
+	mtk_dpi_config_bit_num(dpi, dpi->bit_num);
+	mtk_dpi_config_channel_swap(dpi, dpi->channel_swap);
+	mtk_dpi_config_yc_map(dpi, dpi->yc_map);
+	mtk_dpi_config_color_format(dpi, dpi->color_format);
+	mtk_dpi_config_2n_h_fre(dpi);
+	mtk_dpi_sw_reset(dpi, false);
+
+	return 0;
+}
+
+static void mtk_dpi_encoder_destroy(struct drm_encoder *encoder)
+{
+	drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs mtk_dpi_encoder_funcs = {
+	.destroy = mtk_dpi_encoder_destroy,
+};
+
+static bool mtk_dpi_encoder_mode_fixup(struct drm_encoder *encoder,
+				       const struct drm_display_mode *mode,
+				       struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static void mtk_dpi_encoder_mode_set(struct drm_encoder *encoder,
+				     struct drm_display_mode *mode,
+				     struct drm_display_mode *adjusted_mode)
+{
+	struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder);
+
+	drm_mode_copy(&dpi->mode, adjusted_mode);
+}
+
+static void mtk_dpi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder);
+
+	mtk_dpi_power_off(dpi, DPI_POWER_ENABLE);
+}
+
+static void mtk_dpi_encoder_enable(struct drm_encoder *encoder)
+{
+	struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder);
+
+	mtk_dpi_power_on(dpi, DPI_POWER_ENABLE);
+	mtk_dpi_set_display_mode(dpi, &dpi->mode);
+}
+
+static int mtk_dpi_atomic_check(struct drm_encoder *encoder,
+				struct drm_crtc_state *crtc_state,
+				struct drm_connector_state *conn_state)
+{
+	return 0;
+}
+
+static const struct drm_encoder_helper_funcs mtk_dpi_encoder_helper_funcs = {
+	.mode_fixup = mtk_dpi_encoder_mode_fixup,
+	.mode_set = mtk_dpi_encoder_mode_set,
+	.disable = mtk_dpi_encoder_disable,
+	.enable = mtk_dpi_encoder_enable,
+	.atomic_check = mtk_dpi_atomic_check,
+};
+
+static void mtk_dpi_start(struct mtk_ddp_comp *comp)
+{
+	struct mtk_dpi *dpi = container_of(comp, struct mtk_dpi, ddp_comp);
+
+	mtk_dpi_power_on(dpi, DPI_POWER_START);
+}
+
+static void mtk_dpi_stop(struct mtk_ddp_comp *comp)
+{
+	struct mtk_dpi *dpi = container_of(comp, struct mtk_dpi, ddp_comp);
+
+	mtk_dpi_power_off(dpi, DPI_POWER_START);
+}
+
+static const struct mtk_ddp_comp_funcs mtk_dpi_funcs = {
+	.start = mtk_dpi_start,
+	.stop = mtk_dpi_stop,
+};
+
+static int mtk_dpi_bind(struct device *dev, struct device *master, void *data)
+{
+	struct mtk_dpi *dpi = dev_get_drvdata(dev);
+	struct drm_device *drm_dev = data;
+	int ret;
+
+	ret = mtk_ddp_comp_register(drm_dev, &dpi->ddp_comp);
+	if (ret < 0) {
+		dev_err(dev, "Failed to register component %s: %d\n",
+			dev->of_node->full_name, ret);
+		return ret;
+	}
+
+	ret = drm_encoder_init(drm_dev, &dpi->encoder, &mtk_dpi_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
+	if (ret) {
+		dev_err(dev, "Failed to initialize decoder: %d\n", ret);
+		goto err_unregister;
+	}
+	drm_encoder_helper_add(&dpi->encoder, &mtk_dpi_encoder_helper_funcs);
+
+	/* Currently DPI0 is fixed to be driven by OVL1 */
+	dpi->encoder.possible_crtcs = BIT(1);
+
+	dpi->encoder.bridge->encoder = &dpi->encoder;
+	ret = drm_bridge_attach(dpi->encoder.dev, dpi->encoder.bridge);
+	if (ret) {
+		dev_err(dev, "Failed to attach bridge: %d\n", ret);
+		goto err_cleanup;
+	}
+
+	dpi->bit_num = MTK_DPI_OUT_BIT_NUM_8BITS;
+	dpi->channel_swap = MTK_DPI_OUT_CHANNEL_SWAP_RGB;
+	dpi->yc_map = MTK_DPI_OUT_YC_MAP_RGB;
+	dpi->color_format = MTK_DPI_COLOR_FORMAT_RGB;
+
+	return 0;
+
+err_cleanup:
+	drm_encoder_cleanup(&dpi->encoder);
+err_unregister:
+	mtk_ddp_comp_unregister(drm_dev, &dpi->ddp_comp);
+	return ret;
+}
+
+static void mtk_dpi_unbind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct mtk_dpi *dpi = dev_get_drvdata(dev);
+	struct drm_device *drm_dev = data;
+
+	drm_encoder_cleanup(&dpi->encoder);
+	mtk_ddp_comp_unregister(drm_dev, &dpi->ddp_comp);
+}
+
+static const struct component_ops mtk_dpi_component_ops = {
+	.bind = mtk_dpi_bind,
+	.unbind = mtk_dpi_unbind,
+};
+
+static int mtk_dpi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_dpi *dpi;
+	struct resource *mem;
+	struct device_node *ep, *bridge_node = NULL;
+	int comp_id;
+	int ret;
+
+	dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL);
+	if (!dpi)
+		return -ENOMEM;
+
+	dpi->dev = dev;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dpi->regs = devm_ioremap_resource(dev, mem);
+	if (IS_ERR(dpi->regs)) {
+		ret = PTR_ERR(dpi->regs);
+		dev_err(dev, "Failed to ioremap mem resource: %d\n", ret);
+		return ret;
+	}
+
+	dpi->engine_clk = devm_clk_get(dev, "engine");
+	if (IS_ERR(dpi->engine_clk)) {
+		ret = PTR_ERR(dpi->engine_clk);
+		dev_err(dev, "Failed to get engine clock: %d\n", ret);
+		return ret;
+	}
+
+	dpi->pixel_clk = devm_clk_get(dev, "pixel");
+	if (IS_ERR(dpi->pixel_clk)) {
+		ret = PTR_ERR(dpi->pixel_clk);
+		dev_err(dev, "Failed to get pixel clock: %d\n", ret);
+		return ret;
+	}
+
+	dpi->tvd_clk = devm_clk_get(dev, "pll");
+	if (IS_ERR(dpi->tvd_clk)) {
+		ret = PTR_ERR(dpi->tvd_clk);
+		dev_err(dev, "Failed to get tvdpll clock: %d\n", ret);
+		return ret;
+	}
+
+	dpi->irq = platform_get_irq(pdev, 0);
+	if (dpi->irq <= 0) {
+		dev_err(dev, "Failed to get irq: %d\n", dpi->irq);
+		return -EINVAL;
+	}
+
+	ep = of_graph_get_next_endpoint(dev->of_node, NULL);
+	if (ep) {
+		bridge_node = of_graph_get_remote_port_parent(ep);
+		of_node_put(ep);
+	}
+	if (!bridge_node) {
+		dev_err(dev, "Failed to find bridge node: %d\n", ret);
+		return ret;
+	}
+
+	dev_info(dev, "Found bridge node: %s\n", bridge_node->full_name);
+
+	dpi->encoder.bridge = of_drm_find_bridge(bridge_node);
+	of_node_put(bridge_node);
+	if (!dpi->encoder.bridge)
+		return -EPROBE_DEFER;
+
+	comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DPI);
+	if (comp_id < 0) {
+		dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
+		return comp_id;
+	}
+
+	ret = mtk_ddp_comp_init(dev, dev->of_node, &dpi->ddp_comp, comp_id,
+				&mtk_dpi_funcs);
+	if (ret) {
+		dev_err(dev, "Failed to initialize component: %d\n", ret);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, dpi);
+
+	ret = component_add(dev, &mtk_dpi_component_ops);
+	if (ret) {
+		dev_err(dev, "Failed to add component: %d\n", ret);
+		return ret;
+	}
+
+	dpi->power_ctl |= DPI_POWER_RESUME;
+
+	return 0;
+}
+
+static int mtk_dpi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &mtk_dpi_component_ops);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mtk_dpi_suspend(struct device *dev)
+{
+	struct mtk_dpi *dpi = dev_get_drvdata(dev);
+
+	if (IS_ERR(dpi)) {
+		dev_info(dev, "dpi suspend failed!\n");
+		return PTR_ERR(dpi);
+	}
+
+	mtk_dpi_power_off(dpi, DPI_POWER_RESUME);
+
+	dev_info(dev, "dpi suspend success!\n");
+
+	return 0;
+}
+
+static int mtk_dpi_resume(struct device *dev)
+{
+	struct mtk_dpi *dpi = dev_get_drvdata(dev);
+	int ret;
+
+	if (IS_ERR(dpi)) {
+		dev_err(dev, "dpi resume failed!\n");
+		return PTR_ERR(dpi);
+	}
+
+	ret = mtk_dpi_power_on(dpi, DPI_POWER_RESUME);
+	if (ret) {
+		dev_err(dev, "dpi resume failed!\n");
+		return ret;
+	}
+
+	dev_info(dev, "dpi resume success!\n");
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(mtk_dpi_pm_ops, mtk_dpi_suspend, mtk_dpi_resume);
+
+static const struct of_device_id mtk_dpi_of_ids[] = {
+	{ .compatible = "mediatek,mt8173-dpi", },
+	{}
+};
+
+struct platform_driver mtk_dpi_driver = {
+	.probe = mtk_dpi_probe,
+	.remove = mtk_dpi_remove,
+	.driver = {
+		.name = "mediatek-dpi",
+		.of_match_table = mtk_dpi_of_ids,
+		.pm = &mtk_dpi_pm_ops,
+	},
+};
diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.h b/drivers/gpu/drm/mediatek/mtk_dpi.h
new file mode 100644
index 0000000..4fa4114
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_dpi.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _MTK_DPI_H
+#define _MTK_DPI_H
+
+#include <linux/types.h>
+#include <drm/drm_crtc.h>
+
+#include "mtk_drm_ddp_comp.h"
+
+struct device;
+struct clk;
+
+enum mtk_dpi_out_bit_num {
+	MTK_DPI_OUT_BIT_NUM_8BITS,
+	MTK_DPI_OUT_BIT_NUM_10BITS,
+	MTK_DPI_OUT_BIT_NUM_12BITS,
+	MTK_DPI_OUT_BIT_NUM_16BITS
+};
+
+enum mtk_dpi_out_yc_map {
+	MTK_DPI_OUT_YC_MAP_RGB,
+	MTK_DPI_OUT_YC_MAP_CYCY,
+	MTK_DPI_OUT_YC_MAP_YCYC,
+	MTK_DPI_OUT_YC_MAP_CY,
+	MTK_DPI_OUT_YC_MAP_YC
+};
+
+enum mtk_dpi_out_channel_swap {
+	MTK_DPI_OUT_CHANNEL_SWAP_RGB,
+	MTK_DPI_OUT_CHANNEL_SWAP_GBR,
+	MTK_DPI_OUT_CHANNEL_SWAP_BRG,
+	MTK_DPI_OUT_CHANNEL_SWAP_RBG,
+	MTK_DPI_OUT_CHANNEL_SWAP_GRB,
+	MTK_DPI_OUT_CHANNEL_SWAP_BGR
+};
+
+enum mtk_dpi_out_color_format {
+	MTK_DPI_COLOR_FORMAT_RGB,
+	MTK_DPI_COLOR_FORMAT_RGB_FULL,
+	MTK_DPI_COLOR_FORMAT_YCBCR_444,
+	MTK_DPI_COLOR_FORMAT_YCBCR_422,
+	MTK_DPI_COLOR_FORMAT_XV_YCC,
+	MTK_DPI_COLOR_FORMAT_YCBCR_444_FULL,
+	MTK_DPI_COLOR_FORMAT_YCBCR_422_FULL
+};
+
+struct mtk_dpi {
+	struct mtk_ddp_comp ddp_comp;
+	struct drm_encoder encoder;
+	void __iomem *regs;
+	struct device *dev;
+	struct clk *engine_clk;
+	struct clk *pixel_clk;
+	struct clk *tvd_clk;
+	int irq;
+	struct drm_display_mode mode;
+	enum mtk_dpi_out_color_format color_format;
+	enum mtk_dpi_out_yc_map yc_map;
+	enum mtk_dpi_out_bit_num bit_num;
+	enum mtk_dpi_out_channel_swap channel_swap;
+	bool power_sta;
+	u8 power_ctl;
+};
+
+static inline struct mtk_dpi *mtk_dpi_from_encoder(struct drm_encoder *e)
+{
+	return container_of(e, struct mtk_dpi, encoder);
+}
+
+int mtk_dpi_set_display_mode(struct mtk_dpi *dpi,
+			     struct drm_display_mode *mode);
+
+#endif /* _MTK_DPI_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_dpi_regs.h b/drivers/gpu/drm/mediatek/mtk_dpi_regs.h
new file mode 100644
index 0000000..4b6ad47
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_dpi_regs.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef __MTK_DPI_REGS_H
+#define __MTK_DPI_REGS_H
+
+#define DPI_EN			0x00
+#define EN				BIT(0)
+
+#define DPI_RET			0x04
+#define RST				BIT(0)
+
+#define DPI_INTEN		0x08
+#define INT_VSYNC_EN			BIT(0)
+#define INT_VDE_EN			BIT(1)
+#define INT_UNDERFLOW_EN		BIT(2)
+
+#define DPI_INTSTA		0x0C
+#define INT_VSYNC_STA			BIT(0)
+#define INT_VDE_STA			BIT(1)
+#define INT_UNDERFLOW_STA		BIT(2)
+
+#define DPI_CON			0x10
+#define BG_ENABLE			BIT(0)
+#define IN_RB_SWAP			BIT(1)
+#define INTL_EN				BIT(2)
+#define TDFP_EN				BIT(3)
+#define CLPF_EN				BIT(4)
+#define YUV422_EN			BIT(5)
+#define CSC_ENABLE			BIT(6)
+#define R601_SEL			BIT(7)
+#define EMBSYNC_EN			BIT(8)
+#define VS_LODD_EN			BIT(16)
+#define VS_LEVEN_EN			BIT(17)
+#define VS_RODD_EN			BIT(18)
+#define VS_REVEN			BIT(19)
+#define FAKE_DE_LODD			BIT(20)
+#define FAKE_DE_LEVEN			BIT(21)
+#define FAKE_DE_RODD			BIT(22)
+#define FAKE_DE_REVEN			BIT(23)
+
+#define DPI_OUTPUT_SETTING	0x14
+#define CH_SWAP				0
+#define CH_SWAP_MASK			(0x7 << 0)
+#define SWAP_RGB			0x00
+#define SWAP_GBR			0x01
+#define SWAP_BRG			0x02
+#define SWAP_RBG			0x03
+#define SWAP_GRB			0x04
+#define SWAP_BGR			0x05
+#define BIT_SWAP			BIT(3)
+#define B_MASK				BIT(4)
+#define G_MASK				BIT(5)
+#define R_MASK				BIT(6)
+#define DE_MASK				BIT(8)
+#define HS_MASK				BIT(9)
+#define VS_MASK				BIT(10)
+#define DE_POL				BIT(12)
+#define HSYNC_POL			BIT(13)
+#define VSYNC_POL			BIT(14)
+#define CK_POL				BIT(15)
+#define OEN_OFF				BIT(16)
+#define EDGE_SEL			BIT(17)
+#define OUT_BIT				18
+#define OUT_BIT_MASK			(0x3 << 18)
+#define OUT_BIT_8			0x00
+#define OUT_BIT_10			0x01
+#define OUT_BIT_12			0x02
+#define OUT_BIT_16			0x03
+#define YC_MAP				20
+#define YC_MAP_MASK			(0x7 << 20)
+#define YC_MAP_RGB			0x00
+#define YC_MAP_CYCY			0x04
+#define YC_MAP_YCYC			0x05
+#define YC_MAP_CY			0x06
+#define YC_MAP_YC			0x07
+
+#define DPI_SIZE		0x18
+#define HSIZE				0
+#define HSIZE_MASK			(0x1FFF << 0)
+#define VSIZE				16
+#define VSIZE_MASK			(0x1FFF << 16)
+
+#define DPI_DDR_SETTING		0x1C
+#define DDR_EN				BIT(0)
+#define DDDR_SEL			BIT(1)
+#define DDR_4PHASE			BIT(2)
+#define DDR_WIDTH			(0x3 << 4)
+#define DDR_PAD_MODE			(0x1 << 8)
+
+#define DPI_TGEN_HWIDTH		0x20
+#define HPW				0
+#define HPW_MASK			(0xFFF << 0)
+
+#define DPI_TGEN_HPORCH		0x24
+#define HBP				0
+#define HBP_MASK			(0xFFF << 0)
+#define HFP				16
+#define HFP_MASK			(0xFFF << 16)
+
+#define DPI_TGEN_VWIDTH		0x28
+#define DPI_TGEN_VPORCH		0x2C
+
+#define VSYNC_WIDTH_SHIFT		0
+#define VSYNC_WIDTH_MASK		(0xFFF << 0)
+#define VSYNC_HALF_LINE_SHIFT		16
+#define VSYNC_HALF_LINE_MASK		BIT(16)
+#define VSYNC_BACK_PORCH_SHIFT		0
+#define VSYNC_BACK_PORCH_MASK		(0xFFF << 0)
+#define VSYNC_FRONT_PORCH_SHIFT		16
+#define VSYNC_FRONT_PORCH_MASK		(0xFFF << 16)
+
+#define DPI_BG_HCNTL		0x30
+#define BG_RIGHT			(0x1FFF << 0)
+#define BG_LEFT				(0x1FFF << 16)
+
+#define DPI_BG_VCNTL		0x34
+#define BG_BOT				(0x1FFF << 0)
+#define BG_TOP				(0x1FFF << 16)
+
+#define DPI_BG_COLOR		0x38
+#define BG_B				(0xF << 0)
+#define BG_G				(0xF << 8)
+#define BG_R				(0xF << 16)
+
+#define DPI_FIFO_CTL		0x3C
+#define FIFO_VALID_SET			(0x1F << 0)
+#define FIFO_RST_SEL			(0x1 << 8)
+
+#define DPI_STATUS		0x40
+#define VCOUNTER			(0x1FFF << 0)
+#define DPI_BUSY			BIT(16)
+#define OUTEN				BIT(17)
+#define FIELD				BIT(20)
+#define TDLR				BIT(21)
+
+#define DPI_TMODE		0x44
+#define DPI_OEN_ON			BIT(0)
+
+#define DPI_CHECKSUM		0x48
+#define DPI_CHECKSUM_MASK		(0xFFFFFF << 0)
+#define DPI_CHECKSUM_READY		BIT(30)
+#define DPI_CHECKSUM_EN			BIT(31)
+
+#define DPI_DUMMY		0x50
+#define DPI_DUMMY_MASK			(0xFFFFFFFF << 0)
+
+#define DPI_TGEN_VWIDTH_LEVEN	0x68
+#define DPI_TGEN_VPORCH_LEVEN	0x6C
+#define DPI_TGEN_VWIDTH_RODD	0x70
+#define DPI_TGEN_VPORCH_RODD	0x74
+#define DPI_TGEN_VWIDTH_REVEN	0x78
+#define DPI_TGEN_VPORCH_REVEN	0x7C
+
+#define DPI_ESAV_VTIMING_LODD	0x80
+#define ESAV_VOFST_LODD			(0xFFF << 0)
+#define ESAV_VWID_LODD			(0xFFF << 16)
+
+#define DPI_ESAV_VTIMING_LEVEN	0x84
+#define ESAV_VOFST_LEVEN		(0xFFF << 0)
+#define ESAV_VWID_LEVEN			(0xFFF << 16)
+
+#define DPI_ESAV_VTIMING_RODD	0x88
+#define ESAV_VOFST_RODD			(0xFFF << 0)
+#define ESAV_VWID_RODD			(0xFFF << 16)
+
+#define DPI_ESAV_VTIMING_REVEN	0x8C
+#define ESAV_VOFST_REVEN		(0xFFF << 0)
+#define ESAV_VWID_REVEN			(0xFFF << 16)
+
+#define DPI_ESAV_FTIMING	0x90
+#define ESAV_FOFST_ODD			(0xFFF << 0)
+#define ESAV_FOFST_EVEN			(0xFFF << 16)
+
+#define DPI_CLPF_SETTING	0x94
+#define CLPF_TYPE			(0x3 << 0)
+#define ROUND_EN			BIT(4)
+
+#define DPI_Y_LIMIT		0x98
+#define Y_LIMINT_BOT			0
+#define Y_LIMINT_BOT_MASK		(0xFFF << 0)
+#define Y_LIMINT_TOP			16
+#define Y_LIMINT_TOP_MASK		(0xFFF << 16)
+
+#define DPI_C_LIMIT		0x9C
+#define C_LIMIT_BOT			0
+#define C_LIMIT_BOT_MASK		(0xFFF << 0)
+#define C_LIMIT_TOP			16
+#define C_LIMIT_TOP_MASK		(0xFFF << 16)
+
+#define DPI_YUV422_SETTING	0xA0
+#define UV_SWAP				BIT(0)
+#define CR_DELSEL			BIT(4)
+#define CB_DELSEL			BIT(5)
+#define Y_DELSEL			BIT(6)
+#define DE_DELSEL			BIT(7)
+
+#define DPI_EMBSYNC_SETTING	0xA4
+#define EMBSYNC_R_CR_EN			BIT(0)
+#define EMPSYNC_G_Y_EN			BIT(1)
+#define EMPSYNC_B_CB_EN			BIT(2)
+#define ESAV_F_INV			BIT(4)
+#define ESAV_V_INV			BIT(5)
+#define ESAV_H_INV			BIT(6)
+#define ESAV_CODE_MAN			BIT(8)
+#define VS_OUT_SEL			(0x7 << 12)
+
+#define DPI_ESAV_CODE_SET0	0xA8
+#define ESAV_CODE0			(0xFFF << 0)
+#define ESAV_CODE1			(0xFFF << 16)
+
+#define DPI_ESAV_CODE_SET1	0xAC
+#define ESAV_CODE2			(0xFFF << 0)
+#define ESAV_CODE3_MSB			BIT(16)
+
+#define DPI_H_FRE_CON		0xE0
+#define H_FRE_2N			BIT(25)
+#endif /* __MTK_DPI_REGS_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
index 4fcc0e0..a69958c 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
@@ -553,6 +553,7 @@ static struct platform_driver * const mtk_drm_drivers[] = {
 	&mtk_disp_rdma_driver,
 	&mtk_dsi_driver,
 	&mtk_mipi_tx_driver,
+	&mtk_dpi_driver,
 };
 
 static int __init mtk_drm_init(void)
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
index 161a362..e0fff2c 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_drv.h
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
@@ -52,5 +52,6 @@ extern struct platform_driver mtk_disp_ovl_driver;
 extern struct platform_driver mtk_disp_rdma_driver;
 extern struct platform_driver mtk_dsi_driver;
 extern struct platform_driver mtk_mipi_tx_driver;
+extern struct platform_driver mtk_dpi_driver;
 
 #endif /* MTK_DRM_DRV_H */
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 05/14] dt-bindings: drm/mediatek: Add Mediatek HDMI dts binding
       [not found] ` <1457443649-12133-1-git-send-email-p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
@ 2016-03-08 13:27   ` Philipp Zabel
  0 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW
  Cc: Rob Herring, Daniel Vetter, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Dave Airlie, Matthias Brugger,
	Cawa Cheng, CK Hu, YT Shen, Jitao Shi, Jie Qiu, Paul Bolle,
	Daniel Stone, Daniel Kurtz, Tomasz Figa, Yingjoe Chen,
	Michael Turquette, Stephen Boyd, Emil Velikov,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	kernel-bIcnvbaLZ9MEGnE8C9+IrQ, Philipp Zabel

Add the device tree binding documentation for Mediatek HDMI,
HDMI PHY and HDMI DDC devices.

Signed-off-by: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
 .../bindings/display/mediatek/mediatek,hdmi.txt    | 148 +++++++++++++++++++++
 1 file changed, 148 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt

diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt
new file mode 100644
index 0000000..7b12424
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt
@@ -0,0 +1,148 @@
+Mediatek HDMI Encoder
+=====================
+
+The Mediatek HDMI encoder can generate HDMI 1.4a or MHL 2.0 signals from
+its parallel input.
+
+Required properties:
+- compatible: Should be "mediatek,<chip>-hdmi".
+- reg: Physical base address and length of the controller's registers
+- interrupts: The interrupt signal from the function block.
+- clocks: device clocks
+  See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+- clock-names: must contain "pixel", "pll", "bclk", and "spdif".
+- phys: phandle link to the HDMI PHY node.
+  See Documentation/devicetree/bindings/phy/phy-bindings.txt for details.
+- phy-names: must contain "hdmi"
+- mediatek,syscon-hdmi: phandle link and register offset to the system
+  configuration registers. For mt8173 this must be offset 0x900 into the
+  MMSYS_CONFIG region: <&mmsys 0x900>.
+- ports: A node containing input and output port nodes with endpoint
+  definitions as documented in Documentation/devicetree/bindings/graph.txt.
+- port@0: The input port in the ports node should be connected to a DPI output
+  port.
+- port@1: The output port in the ports node should be connected to the input
+  port of a connector node that contains a ddc-i2c-bus property, or to the
+  input port of an attached bridge chip, such as a SlimPort transmitter.
+
+HDMI CEC
+========
+
+The HDMI CEC controller handles hotplug detection and CEC communication.
+
+Required properties:
+- compatible: Should be "mediatek,<chip>-cec"
+- reg: Physical base address and length of the controller's registers
+- interrupts: The interrupt signal from the function block.
+- clocks: device clock
+
+HDMI DDC
+========
+
+The HDMI DDC i2c controller is used to interface with the HDMI DDC pins.
+The Mediatek's I2C controller is used to interface with I2C devices.
+
+Required properties:
+- compatible: Should be "mediatek,<chip>-hdmi-ddc"
+- reg: Physical base address and length of the controller's registers
+- clocks: device clock
+- clock-names: Should be "ddc-i2c".
+
+HDMI PHY
+========
+
+The HDMI PHY serializes the HDMI encoder's three channel 10-bit parallel
+output and drives the HDMI pads.
+
+Required properties:
+- compatible: "mediatek,<chip>-hdmi-phy"
+- reg: Physical base address and length of the module's registers
+- clocks: PLL reference clock
+- clock-names: must contain "pll_ref"
+- clock-output-names: must be "hdmitx_dig_cts" on mt8173
+- #phy-cells: must be <0>
+- #clock-cells: must be <0>
+
+Optional properties:
+- mediatek,ibias: TX DRV bias current for <1.65Gbps, defaults to 0xa
+- mediatek,ibias_up: TX DRV bias current for >1.65Gbps, defaults to 0x1c
+
+Example:
+
+cec: cec@10013000 {
+	compatible = "mediatek,mt8173-cec";
+	reg = <0 0x10013000 0 0xbc>;
+	interrupts = <GIC_SPI 167 IRQ_TYPE_LEVEL_LOW>;
+	clocks = <&infracfg CLK_INFRA_CEC>;
+};
+
+hdmi_phy: hdmi-phy@10209100 {
+	compatible = "mediatek,mt8173-hdmi-phy";
+	reg = <0 0x10209100 0 0x24>;
+	clocks = <&apmixedsys CLK_APMIXED_HDMI_REF>;
+	clock-names = "pll_ref";
+	clock-output-names = "hdmitx_dig_cts";
+	mediatek,ibias = <0xa>;
+	mediatek,ibias_up = <0x1c>;
+	#clock-cells = <0>;
+	#phy-cells = <0>;
+};
+
+hdmi_ddc0: i2c@11012000 {
+	compatible = "mediatek,mt8173-hdmi-ddc";
+	reg = <0 0x11012000 0 0x1c>;
+	interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_LOW>;
+	clocks = <&pericfg CLK_PERI_I2C5>;
+	clock-names = "ddc-i2c";
+};
+
+hdmi0: hdmi@14025000 {
+	compatible = "mediatek,mt8173-hdmi";
+	reg = <0 0x14025000 0 0x400>;
+	interrupts = <GIC_SPI 206 IRQ_TYPE_LEVEL_LOW>;
+	clocks = <&mmsys CLK_MM_HDMI_PIXEL>,
+		 <&mmsys CLK_MM_HDMI_PLLCK>,
+		 <&mmsys CLK_MM_HDMI_AUDIO>,
+		 <&mmsys CLK_MM_HDMI_SPDIF>;
+	clock-names = "pixel", "pll", "bclk", "spdif";
+	pinctrl-names = "default";
+	pinctrl-0 = <&hdmi_pin>;
+	phys = <&hdmi_phy>;
+	phy-names = "hdmi";
+	mediatek,syscon-hdmi = <&mmsys 0x900>;
+	assigned-clocks = <&topckgen CLK_TOP_HDMI_SEL>;
+	assigned-clock-parents = <&hdmi_phy>;
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@0 {
+			reg = <0>;
+
+			hdmi0_in: endpoint {
+				remote-endpoint = <&dpi0_out>;
+			};
+		};
+
+		port@1 {
+			reg = <1>;
+
+			hdmi0_out: endpoint {
+				remote-endpoint = <&hdmi_con_in>;
+			};
+		};
+	};
+};
+
+connector {
+	compatible = "hdmi-connector";
+	type = "a";
+	ddc-i2c-bus = <&hdmiddc0>;
+
+	port {
+		hdmi_con_in: endpoint {
+			remote-endpoint = <&hdmi0_out>;
+		};
+	};
+};
-- 
2.7.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 06/14] drm/mediatek: Add HDMI support
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
                   ` (4 preceding siblings ...)
       [not found] ` <1457443649-12133-1-git-send-email-p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
@ 2016-03-08 13:27 ` Philipp Zabel
  2016-03-09 13:52   ` Daniel Kurtz
  2016-03-08 13:27 ` [PATCH v13 07/14] drm/mediatek: enable hdmi output control bit Philipp Zabel
                   ` (7 subsequent siblings)
  13 siblings, 1 reply; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

From: Jie Qiu <jie.qiu@mediatek.com>

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@mediatek.com>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
---
 drivers/gpu/drm/mediatek/Kconfig               |   7 +
 drivers/gpu/drm/mediatek/Makefile              |   9 +
 drivers/gpu/drm/mediatek/mtk_cec.c             | 245 ++++++++++
 drivers/gpu/drm/mediatek/mtk_cec.h             |  25 +
 drivers/gpu/drm/mediatek/mtk_drm_drv.c         |   1 +
 drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c    | 579 ++++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi.c            | 479 ++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi.h            | 221 +++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c    | 362 ++++++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi_hw.c         | 652 +++++++++++++++++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi_hw.h         |  73 +++
 drivers/gpu/drm/mediatek/mtk_hdmi_regs.h       | 221 +++++++++
 drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c | 505 +++++++++++++++++++
 13 files changed, 3379 insertions(+)
 create mode 100644 drivers/gpu/drm/mediatek/mtk_cec.c
 create mode 100644 drivers/gpu/drm/mediatek/mtk_cec.h
 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_regs.h
 create mode 100644 drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c

diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig
index 0c49a94..e2ff158 100644
--- a/drivers/gpu/drm/mediatek/Kconfig
+++ b/drivers/gpu/drm/mediatek/Kconfig
@@ -12,3 +12,10 @@ config DRM_MEDIATEK
 	  The module will be called mediatek-drm
 	  This driver provides kernel mode setting and
 	  buffer management to userspace.
+
+config DRM_MEDIATEK_HDMI
+	tristate "DRM HDMI Support for Mediatek SoCs"
+	depends on DRM_MEDIATEK
+	select 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 5fcf58e..6d53bee 100644
--- a/drivers/gpu/drm/mediatek/Makefile
+++ b/drivers/gpu/drm/mediatek/Makefile
@@ -12,3 +12,12 @@ mediatek-drm-y := mtk_disp_ovl.o \
 		  mtk_dpi.o
 
 obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
+
+mediatek-drm-hdmi-objs := mtk_cec.o \
+			  mtk_drm_hdmi_drv.o \
+			  mtk_hdmi.o \
+			  mtk_hdmi_ddc_drv.o \
+			  mtk_hdmi_hw.o \
+			  mtk_mt8173_hdmi_phy.o
+
+obj-$(CONFIG_DRM_MEDIATEK_HDMI) += mediatek-drm-hdmi.o
diff --git a/drivers/gpu/drm/mediatek/mtk_cec.c b/drivers/gpu/drm/mediatek/mtk_cec.c
new file mode 100644
index 0000000..cba3647
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_cec.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include "mtk_cec.h"
+
+#define TR_CONFIG		0x00
+#define CLEAR_CEC_IRQ			BIT(15)
+
+#define CEC_CKGEN		0x04
+#define CEC_32K_PDN			BIT(19)
+#define PDN				BIT(16)
+
+#define RX_EVENT		0x54
+#define HDMI_PORD			BIT(25)
+#define HDMI_HTPLG			BIT(24)
+#define HDMI_PORD_INT_EN		BIT(9)
+#define HDMI_HTPLG_INT_EN		BIT(8)
+
+#define RX_GEN_WD		0x58
+#define HDMI_PORD_INT_32K_STATUS	BIT(26)
+#define RX_RISC_INT_32K_STATUS		BIT(25)
+#define HDMI_HTPLG_INT_32K_STATUS	BIT(24)
+#define HDMI_PORD_INT_32K_CLR		BIT(18)
+#define RX_INT_32K_CLR			BIT(17)
+#define HDMI_HTPLG_INT_32K_CLR		BIT(16)
+#define HDMI_PORD_INT_32K_STA_MASK	BIT(10)
+#define RX_RISC_INT_32K_STA_MASK	BIT(9)
+#define HDMI_HTPLG_INT_32K_STA_MASK	BIT(8)
+#define HDMI_PORD_INT_32K_EN		BIT(2)
+#define RX_INT_32K_EN			BIT(1)
+#define HDMI_HTPLG_INT_32K_EN		BIT(0)
+
+#define NORMAL_INT_CTRL		0x5C
+#define HDMI_HTPLG_INT_STA		BIT(0)
+#define HDMI_PORD_INT_STA		BIT(1)
+#define HDMI_HTPLG_INT_CLR		BIT(16)
+#define HDMI_PORD_INT_CLR		BIT(17)
+#define HDMI_FULL_INT_CLR		BIT(20)
+
+struct mtk_cec {
+	void __iomem *regs;
+	struct clk *clk;
+	int irq;
+	bool hpd;
+	void (*hpd_event)(bool hpd, struct device *dev);
+	struct device *hdmi_dev;
+};
+
+static void mtk_cec_mask(struct mtk_cec *cec, unsigned int offset,
+			 unsigned int val, unsigned int mask)
+{
+	u32 tmp = readl(cec->regs + offset) & ~mask;
+
+	tmp |= val & mask;
+	writel(val, cec->regs + offset);
+}
+
+void mtk_cec_set_hpd_event(struct device *dev,
+			   void (*hpd_event)(bool hpd, struct device *dev),
+			   struct device *hdmi_dev)
+{
+	struct mtk_cec *cec = dev_get_drvdata(dev);
+
+	cec->hdmi_dev = hdmi_dev;
+	cec->hpd_event = hpd_event;
+}
+
+bool mtk_cec_hpd_high(struct device *dev)
+{
+	struct mtk_cec *cec = dev_get_drvdata(dev);
+	unsigned int status;
+
+	status = readl(cec->regs + RX_EVENT);
+
+	return (status & (HDMI_PORD | HDMI_HTPLG)) == (HDMI_PORD | HDMI_HTPLG);
+}
+
+int mtk_cec_irq(struct device *dev)
+{
+	struct mtk_cec *cec = dev_get_drvdata(dev);
+
+	return cec->irq;
+}
+
+static void mtk_cec_htplg_irq_enable(struct mtk_cec *cec)
+{
+	mtk_cec_mask(cec, CEC_CKGEN, 0, PDN);
+	mtk_cec_mask(cec, CEC_CKGEN, CEC_32K_PDN, CEC_32K_PDN);
+	mtk_cec_mask(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR,
+		     HDMI_PORD_INT_32K_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, RX_INT_32K_CLR, RX_INT_32K_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, HDMI_HTPLG_INT_32K_CLR,
+		     HDMI_HTPLG_INT_32K_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_EN);
+	mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_EN);
+	mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_EN);
+
+	mtk_cec_mask(cec, RX_EVENT, HDMI_PORD_INT_EN, HDMI_PORD_INT_EN);
+	mtk_cec_mask(cec, RX_EVENT, HDMI_HTPLG_INT_EN, HDMI_HTPLG_INT_EN);
+}
+
+static void mtk_cec_htplg_irq_disable(struct mtk_cec *cec)
+{
+	mtk_cec_mask(cec, RX_EVENT, 0, HDMI_PORD_INT_EN);
+	mtk_cec_mask(cec, RX_EVENT, 0, HDMI_HTPLG_INT_EN);
+}
+
+static void mtk_cec_clear_htplg_irq(struct mtk_cec *cec)
+{
+	mtk_cec_mask(cec, TR_CONFIG, CLEAR_CEC_IRQ, CLEAR_CEC_IRQ);
+	mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_HTPLG_INT_CLR,
+		     HDMI_HTPLG_INT_CLR);
+	mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_PORD_INT_CLR,
+		     HDMI_PORD_INT_CLR);
+	mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_FULL_INT_CLR,
+		     HDMI_FULL_INT_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR,
+		     HDMI_PORD_INT_32K_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, RX_INT_32K_CLR, RX_INT_32K_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, HDMI_HTPLG_INT_32K_CLR,
+		     HDMI_HTPLG_INT_32K_CLR);
+	udelay(5);
+	mtk_cec_mask(cec, NORMAL_INT_CTRL, 0, HDMI_HTPLG_INT_CLR);
+	mtk_cec_mask(cec, NORMAL_INT_CTRL, 0, HDMI_PORD_INT_CLR);
+	mtk_cec_mask(cec, TR_CONFIG, 0, CLEAR_CEC_IRQ);
+	mtk_cec_mask(cec, NORMAL_INT_CTRL, 0, HDMI_FULL_INT_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_CLR);
+	mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_CLR);
+}
+
+static irqreturn_t mtk_cec_htplg_isr_thread(int irq, void *arg)
+{
+	struct device *dev = arg;
+	struct mtk_cec *cec = dev_get_drvdata(dev);
+	bool hpd;
+
+	mtk_cec_clear_htplg_irq(cec);
+	hpd = mtk_cec_hpd_high(dev);
+
+	if (cec->hpd != hpd) {
+		dev_info(dev, "hotplug event!,cur hpd = %d, hpd = %d\n",
+			 cec->hpd, hpd);
+		cec->hpd = hpd;
+		if (cec->hpd_event)
+			cec->hpd_event(hpd, cec->hdmi_dev);
+	}
+	return IRQ_HANDLED;
+}
+
+static int mtk_cec_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_cec *cec;
+	struct resource *res;
+	int ret;
+
+	cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL);
+	if (!cec)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, cec);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	cec->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(cec->regs)) {
+		ret = PTR_ERR(cec->regs);
+		dev_err(dev, "Failed to ioremap cec: %d\n", ret);
+		return ret;
+	}
+
+	cec->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(cec->clk)) {
+		ret = PTR_ERR(cec->clk);
+		dev_err(dev, "Failed to get cec clock: %d\n", ret);
+		return ret;
+	}
+
+	cec->irq = platform_get_irq(pdev, 0);
+	if (cec->irq < 0) {
+		dev_err(dev, "Failed to get cec irq: %d\n", cec->irq);
+		return cec->irq;
+	}
+
+	ret = devm_request_threaded_irq(dev, cec->irq, NULL,
+					mtk_cec_htplg_isr_thread,
+					IRQF_SHARED | IRQF_TRIGGER_LOW |
+					IRQF_ONESHOT, "hdmi hpd", dev);
+	if (ret) {
+		dev_err(dev, "Failed to register cec irq: %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(cec->clk);
+	if (ret) {
+		dev_err(dev, "Failed to enable cec clock: %d\n", ret);
+		return ret;
+	}
+
+	mtk_cec_htplg_irq_enable(cec);
+
+	return 0;
+}
+
+static int mtk_cec_remove(struct platform_device *pdev)
+{
+	struct mtk_cec *cec = platform_get_drvdata(pdev);
+
+	mtk_cec_htplg_irq_disable(cec);
+	clk_disable_unprepare(cec->clk);
+	return 0;
+}
+
+static const struct of_device_id mtk_cec_of_ids[] = {
+	{ .compatible = "mediatek,mt8173-cec", },
+	{}
+};
+
+struct platform_driver mtk_cec_driver = {
+	.probe = mtk_cec_probe,
+	.remove = mtk_cec_remove,
+	.driver = {
+		.name = "mediatek-cec",
+		.of_match_table = mtk_cec_of_ids,
+	},
+};
diff --git a/drivers/gpu/drm/mediatek/mtk_cec.h b/drivers/gpu/drm/mediatek/mtk_cec.h
new file mode 100644
index 0000000..fe6e8fd
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_cec.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _MTK_CEC_H
+#define _MTK_CEC_H
+
+struct device;
+
+void mtk_cec_set_hpd_event(struct device *dev,
+			   void (*hotplug_event)(bool hpd, struct device *dev),
+			   struct device *hdmi_dev);
+bool mtk_cec_hpd_high(struct device *dev);
+int mtk_cec_irq(struct device *dev);
+
+#endif /* _MTK_CEC_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
index a69958c..80056d1 100644
--- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
+++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
@@ -23,6 +23,7 @@
 #include <linux/of_platform.h>
 #include <linux/pm_runtime.h>
 
+#include "mtk_cec.h"
 #include "mtk_drm_crtc.h"
 #include "mtk_drm_ddp.h"
 #include "mtk_drm_ddp_comp.h"
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..ff661e0
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c
@@ -0,0 +1,579 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_platform.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_graph.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include "mtk_cec.h"
+#include "mtk_hdmi.h"
+#include "mtk_hdmi_hw.h"
+
+static const char * const mtk_hdmi_clk_names[MTK_HDMI_CLK_COUNT] = {
+	[MTK_HDMI_CLK_HDMI_PIXEL] = "pixel",
+	[MTK_HDMI_CLK_HDMI_PLL] = "pll",
+	[MTK_HDMI_CLK_AUD_BCLK] = "bclk",
+	[MTK_HDMI_CLK_AUD_SPDIF] = "spdif",
+};
+
+static const enum mtk_hdmi_clk_id mtk_hdmi_enable_clocks[] = {
+	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_enable_audio(struct mtk_hdmi *hdmi)
+{
+	int ret;
+
+	ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_AUD_SPDIF]);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]);
+	return ret;
+}
+
+static void mtk_hdmi_clk_disable_audio(struct mtk_hdmi *hdmi)
+{
+	clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]);
+	clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_SPDIF]);
+}
+
+static enum drm_connector_status hdmi_conn_detect(struct drm_connector *conn,
+						  bool force)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
+
+	return mtk_hdmi_hpd_high(hdmi) ?
+	       connector_status_connected : connector_status_disconnected;
+}
+
+static void hdmi_conn_destroy(struct drm_connector *conn)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
+
+	mtk_cec_set_hpd_event(hdmi->cec_dev, NULL, NULL);
+
+	drm_connector_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_monitor_audio(edid);
+
+	drm_mode_connector_update_edid_property(conn, edid);
+
+	ret = drm_add_edid_modes(conn, edid);
+	drm_edid_to_eld(conn, edid);
+	kfree(edid);
+	return ret;
+}
+
+static int mtk_hdmi_conn_mode_valid(struct drm_connector *conn,
+				    struct drm_display_mode *mode)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
+
+	dev_dbg(hdmi->dev, "xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n",
+		mode->hdisplay, mode->vdisplay, mode->vrefresh,
+		!!(mode->flags & DRM_MODE_FLAG_INTERLACE), mode->clock * 1000);
+
+	if (hdmi->bridge.next) {
+		struct drm_display_mode adjusted_mode;
+
+		drm_mode_copy(&adjusted_mode, mode);
+		if (!drm_bridge_mode_fixup(hdmi->bridge.next, mode,
+					   &adjusted_mode))
+			return MODE_BAD;
+	}
+
+	if (mode->clock < 27000)
+		return MODE_CLOCK_LOW;
+	if (mode->clock > 297000)
+		return MODE_CLOCK_HIGH;
+
+	return drm_mode_validate_size(mode, 0x1fff, 0x1fff);
+}
+
+static struct drm_encoder *mtk_hdmi_conn_best_enc(struct drm_connector *conn)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
+
+	return hdmi->bridge.encoder;
+}
+
+static const struct drm_connector_funcs mtk_hdmi_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.detect = hdmi_conn_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = hdmi_conn_destroy,
+	.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,
+};
+
+static void mtk_hdmi_hpd_event(bool hpd, struct device *dev)
+{
+	struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
+
+	if (hdmi && hdmi->bridge.encoder && hdmi->bridge.encoder->dev)
+		drm_helper_hpd_irq_event(hdmi->bridge.encoder->dev);
+}
+
+/*
+ * Bridge callbacks
+ */
+
+static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+	int ret;
+
+	ret = drm_connector_init(bridge->encoder->dev, &hdmi->conn,
+				 &mtk_hdmi_connector_funcs,
+				 DRM_MODE_CONNECTOR_HDMIA);
+	if (ret) {
+		dev_err(hdmi->dev, "Failed to initialize connector: %d\n", ret);
+		return ret;
+	}
+	drm_connector_helper_add(&hdmi->conn, &mtk_hdmi_connector_helper_funcs);
+
+	hdmi->conn.polled = DRM_CONNECTOR_POLL_HPD;
+	hdmi->conn.interlace_allowed = true;
+	hdmi->conn.doublescan_allowed = false;
+
+	ret = drm_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;
+		}
+	}
+
+	mtk_cec_set_hpd_event(hdmi->cec_dev, mtk_hdmi_hpd_event, hdmi->dev);
+
+	return 0;
+}
+
+static bool mtk_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
+				       const struct drm_display_mode *mode,
+				       struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static void mtk_hdmi_bridge_disable(struct drm_bridge *bridge)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+	phy_power_off(hdmi->phy);
+	clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]);
+	clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]);
+}
+
+static void mtk_hdmi_bridge_post_disable(struct drm_bridge *bridge)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+	mtk_hdmi_power_off(hdmi);
+}
+
+static void mtk_hdmi_bridge_mode_set(struct drm_bridge *bridge,
+				     struct drm_display_mode *mode,
+				     struct drm_display_mode *adjusted_mode)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+	dev_dbg(hdmi->dev, "cur info: name:%s, hdisplay:%d\n",
+		adjusted_mode->name, adjusted_mode->hdisplay);
+	dev_dbg(hdmi->dev, "hsync_start:%d,hsync_end:%d, htotal:%d",
+		adjusted_mode->hsync_start, adjusted_mode->hsync_end,
+		adjusted_mode->htotal);
+	dev_dbg(hdmi->dev, "hskew:%d, vdisplay:%d\n",
+		adjusted_mode->hskew, adjusted_mode->vdisplay);
+	dev_dbg(hdmi->dev, "vsync_start:%d, vsync_end:%d, vtotal:%d",
+		adjusted_mode->vsync_start, adjusted_mode->vsync_end,
+		adjusted_mode->vtotal);
+	dev_dbg(hdmi->dev, "vscan:%d, flag:%d\n",
+		adjusted_mode->vscan, adjusted_mode->flags);
+
+	drm_mode_copy(&hdmi->mode, adjusted_mode);
+}
+
+static void mtk_hdmi_bridge_pre_enable(struct drm_bridge *bridge)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+	mtk_hdmi_power_on(hdmi);
+}
+
+static void mtk_hdmi_bridge_enable(struct drm_bridge *bridge)
+{
+	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
+
+	mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode);
+	clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]);
+	clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]);
+	phy_power_on(hdmi->phy);
+}
+
+static const struct drm_bridge_funcs mtk_hdmi_bridge_funcs = {
+	.attach = mtk_hdmi_bridge_attach,
+	.mode_fixup = mtk_hdmi_bridge_mode_fixup,
+	.disable = mtk_hdmi_bridge_disable,
+	.post_disable = mtk_hdmi_bridge_post_disable,
+	.mode_set = mtk_hdmi_bridge_mode_set,
+	.pre_enable = mtk_hdmi_bridge_pre_enable,
+	.enable = mtk_hdmi_bridge_enable,
+};
+
+static int mtk_hdmi_dt_parse_pdata(struct mtk_hdmi *hdmi,
+				   struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct device_node *cec_np, *port, *ep, *remote, *i2c_np;
+	struct platform_device *cec_pdev;
+	struct regmap *regmap;
+	struct resource *mem;
+	int ret;
+
+	ret = mtk_hdmi_get_all_clk(hdmi, np);
+	if (ret) {
+		dev_err(dev, "Failed to get clocks: %d\n", ret);
+		return ret;
+	}
+
+	/* The CEC module handles HDMI hotplug detection */
+	cec_np = of_find_compatible_node(np->parent, NULL,
+					 "mediatek,mt8173-cec");
+	if (!cec_np) {
+		dev_err(dev, "Failed to find CEC node\n");
+		return -EINVAL;
+	}
+
+	cec_pdev = of_find_device_by_node(cec_np);
+	if (!cec_pdev) {
+		dev_err(hdmi->dev, "Waiting for CEC device %s\n",
+			cec_np->full_name);
+		return -EPROBE_DEFER;
+	}
+	hdmi->cec_dev = &cec_pdev->dev;
+
+	/*
+	 * The mediatek,syscon-hdmi property contains a phandle link to the
+	 * MMSYS_CONFIG device and the register offset of the HDMI_SYS_CFG
+	 * registers it contains.
+	 */
+	regmap = syscon_regmap_lookup_by_phandle(np, "mediatek,syscon-hdmi");
+	ret = of_property_read_u32_index(np, "mediatek,syscon-hdmi", 1,
+					 &hdmi->sys_offset);
+	if (IS_ERR(regmap))
+		ret = PTR_ERR(regmap);
+	if (ret) {
+		ret = PTR_ERR(regmap);
+		dev_err(dev,
+			"Failed to get system configuration registers: %d\n",
+			ret);
+		return ret;
+	}
+	hdmi->sys_regmap = regmap;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	hdmi->regs = devm_ioremap_resource(dev, mem);
+	if (IS_ERR(hdmi->regs)) {
+		dev_err(dev, "Failed to ioremap hdmi_shell: %ld\n",
+			PTR_ERR(hdmi->regs));
+		return PTR_ERR(hdmi->regs);
+	}
+
+	port = of_graph_get_port_by_id(np, 1);
+	if (!port) {
+		dev_err(dev, "Missing output port node\n");
+		return -EINVAL;
+	}
+
+	ep = of_get_child_by_name(port, "endpoint");
+	if (!ep) {
+		dev_err(dev, "Missing endpoint node in port %s\n",
+			port->full_name);
+		of_node_put(port);
+		return -EINVAL;
+	}
+	of_node_put(port);
+
+	remote = of_graph_get_remote_port_parent(ep);
+	if (!remote) {
+		dev_err(dev, "Missing connector/bridge node for endpoint %s\n",
+			ep->full_name);
+		of_node_put(ep);
+		return -EINVAL;
+	}
+	of_node_put(ep);
+
+	if (!of_device_is_compatible(remote, "hdmi-connector")) {
+		hdmi->bridge.next = of_drm_find_bridge(remote);
+		if (!hdmi->bridge.next) {
+			dev_err(dev, "Waiting for external bridge\n");
+			of_node_put(remote);
+			return -EPROBE_DEFER;
+		}
+	}
+
+	i2c_np = of_parse_phandle(remote, "ddc-i2c-bus", 0);
+	if (!i2c_np) {
+		dev_err(dev, "Failed to find ddc-i2c-bus node in %s\n",
+			remote->full_name);
+		of_node_put(remote);
+		return -EINVAL;
+	}
+	of_node_put(remote);
+
+	hdmi->ddc_adpt = of_find_i2c_adapter_by_node(i2c_np);
+	if (!hdmi->ddc_adpt) {
+		dev_err(dev, "Failed to get ddc i2c adapter by node\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int mtk_drm_hdmi_probe(struct platform_device *pdev)
+{
+	struct mtk_hdmi *hdmi;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+
+	hdmi->dev = dev;
+
+	ret = mtk_hdmi_dt_parse_pdata(hdmi, pdev);
+	if (ret)
+		return ret;
+
+	hdmi->phy = devm_phy_get(dev, "hdmi");
+	if (IS_ERR(hdmi->phy)) {
+		ret = PTR_ERR(hdmi->phy);
+		dev_err(dev, "Failed to get HDMI PHY: %d\n", ret);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, hdmi);
+
+	ret = mtk_hdmi_output_init(hdmi);
+	if (ret) {
+		dev_err(dev, "Failed to initialize hdmi output\n");
+		return ret;
+	}
+
+	hdmi->bridge.funcs = &mtk_hdmi_bridge_funcs;
+	hdmi->bridge.of_node = pdev->dev.of_node;
+	ret = drm_bridge_add(&hdmi->bridge);
+	if (ret) {
+		dev_err(dev, "failed to add bridge, ret = %d\n", ret);
+		return ret;
+	}
+
+	ret = mtk_hdmi_clk_enable_audio(hdmi);
+	if (ret) {
+		dev_err(dev, "Failed to enable audio clocks: %d\n", ret);
+		goto err_bridge_remove;
+	}
+
+	dev_dbg(dev, "mediatek hdmi probe success\n");
+	return 0;
+
+err_bridge_remove:
+	drm_bridge_remove(&hdmi->bridge);
+	return ret;
+}
+
+static int mtk_drm_hdmi_remove(struct platform_device *pdev)
+{
+	struct mtk_hdmi *hdmi = platform_get_drvdata(pdev);
+
+	drm_bridge_remove(&hdmi->bridge);
+	platform_device_unregister(hdmi->audio_pdev);
+	platform_set_drvdata(pdev, NULL);
+	mtk_hdmi_clk_disable_audio(hdmi);
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mtk_hdmi_suspend(struct device *dev)
+{
+	struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
+
+	mtk_hdmi_power_off(hdmi);
+	mtk_hdmi_clk_disable_audio(hdmi);
+	phy_power_off(hdmi->phy);
+	dev_dbg(dev, "hdmi suspend success!\n");
+	return 0;
+}
+
+static int mtk_hdmi_resume(struct device *dev)
+{
+	struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
+	int ret = 0;
+
+	ret = mtk_hdmi_clk_enable_audio(hdmi);
+	if (ret) {
+		dev_err(dev, "hdmi resume failed!\n");
+		return ret;
+	}
+
+	mtk_hdmi_power_on(hdmi);
+	mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode);
+	phy_power_on(hdmi->phy);
+	dev_dbg(dev, "hdmi resume success!\n");
+	return 0;
+}
+#endif
+static SIMPLE_DEV_PM_OPS(mtk_hdmi_pm_ops,
+			 mtk_hdmi_suspend, mtk_hdmi_resume);
+
+static const struct of_device_id mtk_drm_hdmi_of_ids[] = {
+	{ .compatible = "mediatek,mt8173-hdmi", },
+	{}
+};
+
+static struct platform_driver mtk_hdmi_driver = {
+	.probe = mtk_drm_hdmi_probe,
+	.remove = mtk_drm_hdmi_remove,
+	.driver = {
+		.name = "mediatek-drm-hdmi",
+		.of_match_table = mtk_drm_hdmi_of_ids,
+		.pm = &mtk_hdmi_pm_ops,
+	},
+};
+
+static struct platform_driver * const mtk_hdmi_drivers[] = {
+	&mtk_hdmi_phy_driver,
+	&mtk_hdmi_ddc_driver,
+	&mtk_cec_driver,
+	&mtk_hdmi_driver,
+};
+
+static int __init mtk_hdmitx_init(void)
+{
+	int ret;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(mtk_hdmi_drivers); i++) {
+		ret = platform_driver_register(mtk_hdmi_drivers[i]);
+		if (ret < 0) {
+			pr_err("Failed to register %s driver: %d\n",
+			       mtk_hdmi_drivers[i]->driver.name, ret);
+			goto err;
+		}
+	}
+
+	return 0;
+
+err:
+	while (--i >= 0)
+		platform_driver_unregister(mtk_hdmi_drivers[i]);
+
+	return ret;
+}
+
+static void __exit mtk_hdmitx_exit(void)
+{
+	int i;
+
+	for (i = ARRAY_SIZE(mtk_hdmi_drivers) - 1; i >= 0; i--)
+		platform_driver_unregister(mtk_hdmi_drivers[i]);
+}
+
+module_init(mtk_hdmitx_init);
+module_exit(mtk_hdmitx_exit);
+
+MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek HDMI Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c
new file mode 100644
index 0000000..30ec7b5
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c
@@ -0,0 +1,479 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <drm/drm_edid.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/phy/phy.h>
+#include "mtk_cec.h"
+#include "mtk_hdmi.h"
+#include "mtk_hdmi_hw.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)
+{
+	unsigned long rate;
+	int ret;
+
+	/* The DPI driver already should have set TVDPLL to the correct rate */
+	ret = clk_set_rate(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL], clock);
+	if (ret) {
+		dev_err(hdmi->dev, "Failed to set PLL to %u Hz: %d\n", clock,
+			ret);
+		return ret;
+	}
+
+	rate = clk_get_rate(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]);
+
+	if (DIV_ROUND_CLOSEST(rate, 1000) != DIV_ROUND_CLOSEST(clock, 1000))
+		dev_warn(hdmi->dev, "Want PLL %u Hz, got %lu Hz\n", clock,
+			 rate);
+	else
+		dev_dbg(hdmi->dev, "Want PLL %u Hz, got %lu Hz\n", clock, rate);
+
+	mtk_hdmi_hw_config_sys(hdmi);
+	mtk_hdmi_hw_set_deep_color_mode(hdmi);
+	return 0;
+}
+
+static void mtk_hdmi_video_set_display_mode(struct mtk_hdmi *hdmi,
+					    struct drm_display_mode *mode)
+{
+	mtk_hdmi_hw_reset(hdmi);
+	mtk_hdmi_hw_enable_notice(hdmi, true);
+	mtk_hdmi_hw_write_int_mask(hdmi, 0xff);
+	mtk_hdmi_hw_enable_dvi_mode(hdmi, hdmi->dvi_mode);
+	mtk_hdmi_hw_ncts_auto_write_enable(hdmi, true);
+
+	mtk_hdmi_hw_msic_setting(hdmi, mode);
+}
+
+static int mtk_hdmi_aud_enable_packet(struct mtk_hdmi *hdmi, bool enable)
+{
+	mtk_hdmi_hw_send_aud_packet(hdmi, enable);
+	return 0;
+}
+
+static int mtk_hdmi_aud_on_off_hw_ncts(struct mtk_hdmi *hdmi, bool on)
+{
+	mtk_hdmi_hw_ncts_enable(hdmi, on);
+	return 0;
+}
+
+static int mtk_hdmi_aud_set_input(struct mtk_hdmi *hdmi)
+{
+	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->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: %zd\n", err);
+		return err;
+	}
+
+	err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
+	if (err < 0) {
+		dev_err(hdmi->dev, "Failed to pack AVI infoframe: %zd\n", err);
+		return err;
+	}
+
+	mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
+	return 0;
+}
+
+static int mtk_hdmi_setup_spd_infoframe(struct mtk_hdmi *hdmi,
+					const char *vendor,
+					const char *product)
+{
+	struct hdmi_spd_infoframe frame;
+	u8 buffer[29];
+	ssize_t err;
+
+	err = hdmi_spd_infoframe_init(&frame, vendor, product);
+	if (err < 0) {
+		dev_err(hdmi->dev, "Failed to initialize SPD infoframe: %zd\n",
+			err);
+		return err;
+	}
+
+	err = hdmi_spd_infoframe_pack(&frame, buffer, sizeof(buffer));
+	if (err < 0) {
+		dev_err(hdmi->dev, "Failed to pack SDP infoframe: %zd\n", err);
+		return err;
+	}
+
+	mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
+	return 0;
+}
+
+static int mtk_hdmi_setup_audio_infoframe(struct mtk_hdmi *hdmi)
+{
+	struct hdmi_audio_infoframe frame;
+	u8 buffer[14];
+	ssize_t err;
+
+	err = hdmi_audio_infoframe_init(&frame);
+	if (err < 0) {
+		dev_err(hdmi->dev, "Failed to setup audio infoframe: %zd\n",
+			err);
+		return err;
+	}
+
+	frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
+	frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
+	frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
+	frame.channels =
+	    mtk_hdmi_aud_get_chnl_count(
+	    hdmi->aud_param.aud_input_chan_type);
+
+	err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
+	if (err < 0) {
+		dev_err(hdmi->dev, "Failed to pack audio infoframe: %zd\n",
+			err);
+		return err;
+	}
+
+	mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
+	return 0;
+}
+
+static int mtk_hdmi_setup_vendor_specific_infoframe(struct mtk_hdmi *hdmi,
+						struct drm_display_mode *mode)
+{
+	struct hdmi_vendor_infoframe frame;
+	u8 buffer[10];
+	ssize_t err;
+
+	err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, mode);
+	if (err) {
+		dev_err(hdmi->dev,
+			"Failed to get vendor infoframe from mode: %zd\n", err);
+		return err;
+	}
+
+	err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer));
+	if (err) {
+		dev_err(hdmi->dev, "Failed to pack vendor infoframe: %zd\n",
+			err);
+		return err;
+	}
+
+	mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
+	return 0;
+}
+
+int mtk_hdmi_hpd_high(struct mtk_hdmi *hdmi)
+{
+	return hdmi->cec_dev ? mtk_cec_hpd_high(hdmi->cec_dev) : false;
+}
+
+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->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);
+}
+
+void mtk_hdmi_power_off(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_hw_1p4_version_enable(hdmi, true);
+	mtk_hdmi_hw_make_reg_writable(hdmi, false);
+}
+
+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);
+	if (ret) {
+		dev_err(hdmi->dev, "Failed to set vpll: %d\n", ret);
+		return ret;
+	}
+	mtk_hdmi_video_set_display_mode(hdmi, mode);
+
+	phy_power_on(hdmi->phy);
+	mtk_hdmi_aud_output_config(hdmi, mode);
+
+	mtk_hdmi_setup_audio_infoframe(hdmi);
+	mtk_hdmi_setup_avi_infoframe(hdmi, mode);
+	mtk_hdmi_setup_spd_infoframe(hdmi, "mediatek", "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..9403915
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _MTK_HDMI_CTRL_H
+#define _MTK_HDMI_CTRL_H
+
+#include <drm/drm_crtc.h>
+#include <linux/hdmi.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+struct clk;
+struct device;
+struct device_node;
+struct i2c_adapter;
+struct platform_device;
+struct phy;
+struct regmap;
+
+enum mtk_hdmi_clk_id {
+	MTK_HDMI_CLK_HDMI_PIXEL,
+	MTK_HDMI_CLK_HDMI_PLL,
+	MTK_HDMI_CLK_AUD_BCLK,
+	MTK_HDMI_CLK_AUD_SPDIF,
+	MTK_HDMI_CLK_COUNT
+};
+
+enum hdmi_aud_input_type {
+	HDMI_AUD_INPUT_I2S = 0,
+	HDMI_AUD_INPUT_SPDIF,
+};
+
+enum hdmi_aud_i2s_fmt {
+	HDMI_I2S_MODE_RJT_24BIT = 0,
+	HDMI_I2S_MODE_RJT_16BIT,
+	HDMI_I2S_MODE_LJT_24BIT,
+	HDMI_I2S_MODE_LJT_16BIT,
+	HDMI_I2S_MODE_I2S_24BIT,
+	HDMI_I2S_MODE_I2S_16BIT
+};
+
+enum hdmi_aud_mclk {
+	HDMI_AUD_MCLK_128FS,
+	HDMI_AUD_MCLK_192FS,
+	HDMI_AUD_MCLK_256FS,
+	HDMI_AUD_MCLK_384FS,
+	HDMI_AUD_MCLK_512FS,
+	HDMI_AUD_MCLK_768FS,
+	HDMI_AUD_MCLK_1152FS,
+};
+
+enum hdmi_aud_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 drm_bridge bridge;
+	struct drm_connector conn;
+	struct device *dev;
+	struct phy *phy;
+	struct device *cec_dev;
+	struct i2c_adapter *ddc_adpt;
+	struct clk *clk[MTK_HDMI_CLK_COUNT];
+#if defined(CONFIG_DEBUG_FS)
+	struct dentry *debugfs;
+#endif
+	struct platform_device *audio_pdev;
+	struct drm_display_mode mode;
+	bool dvi_mode;
+	u32 min_clock;
+	u32 max_clock;
+	u32 max_hdisplay;
+	u32 max_vdisplay;
+	u32 ibias;
+	u32 ibias_up;
+	struct regmap *sys_regmap;
+	unsigned int sys_offset;
+	void __iomem *regs;
+	bool init;
+	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);
+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);
+#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_cec_driver;
+extern struct platform_driver mtk_hdmi_ddc_driver;
+extern struct platform_driver mtk_hdmi_phy_driver;
+#endif /* _MTK_HDMI_CTRL_H */
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_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@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@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..99c7ffc
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c
@@ -0,0 +1,652 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include "mtk_hdmi_hw.h"
+#include "mtk_hdmi_regs.h"
+#include "mtk_hdmi.h"
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/hdmi.h>
+#include <linux/io.h>
+#include <linux/regmap.h>
+
+static u32 mtk_hdmi_read(struct mtk_hdmi *hdmi, u32 offset)
+{
+	return readl(hdmi->regs + offset);
+}
+
+static void mtk_hdmi_write(struct mtk_hdmi *hdmi, u32 offset, u32 val)
+{
+	writel(val, hdmi->regs + offset);
+}
+
+static void mtk_hdmi_mask(struct mtk_hdmi *hdmi, u32 offset, u32 val, u32 mask)
+{
+	u32 tmp = mtk_hdmi_read(hdmi, offset) & ~mask;
+
+	tmp |= (val & mask);
+	mtk_hdmi_write(hdmi, offset, tmp);
+}
+
+#define NCTS_BYTES          0x07
+
+void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi,
+			   bool black)
+{
+	mtk_hdmi_mask(hdmi, VIDEO_CFG_4, black ? GEN_RGB : NORMAL_PATH,
+		      VIDEO_SOURCE_SEL);
+}
+
+void mtk_hdmi_hw_make_reg_writable(struct mtk_hdmi *hdmi, bool enable)
+{
+	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
+			   HDMI_PCLK_FREE_RUN, enable ? HDMI_PCLK_FREE_RUN : 0);
+	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
+			   HDMI_ON | ANLG_ON, enable ? (HDMI_ON | ANLG_ON) : 0);
+}
+
+void mtk_hdmi_hw_1p4_version_enable(struct mtk_hdmi *hdmi, bool enable)
+{
+	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
+			   HDMI2P0_EN, enable ? 0 : HDMI2P0_EN);
+}
+
+void mtk_hdmi_hw_aud_mute(struct mtk_hdmi *hdmi, bool mute)
+{
+	mtk_hdmi_mask(hdmi, GRL_AUDIO_CFG, mute ? AUDIO_ZERO : 0, AUDIO_ZERO);
+}
+
+void mtk_hdmi_hw_reset(struct mtk_hdmi *hdmi)
+{
+	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
+			   HDMI_RST, HDMI_RST);
+	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
+			   HDMI_RST, 0);
+	mtk_hdmi_mask(hdmi, GRL_CFG3, 0, CFG3_CONTROL_PACKET_DELAY);
+	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
+			   ANLG_ON, ANLG_ON);
+}
+
+void mtk_hdmi_hw_enable_notice(struct mtk_hdmi *hdmi, bool enable_notice)
+{
+	mtk_hdmi_mask(hdmi, GRL_CFG2, enable_notice ? CFG2_NOTICE_EN : 0,
+		      CFG2_NOTICE_EN);
+}
+
+void mtk_hdmi_hw_write_int_mask(struct mtk_hdmi *hdmi, u32 int_mask)
+{
+	mtk_hdmi_write(hdmi, GRL_INT_MASK, int_mask);
+}
+
+void mtk_hdmi_hw_enable_dvi_mode(struct mtk_hdmi *hdmi, bool enable)
+{
+	mtk_hdmi_mask(hdmi, GRL_CFG1, enable ? CFG1_DVI : 0, CFG1_DVI);
+}
+
+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_dbg(hdmi->dev,
+		"frame_type:0x%x,frame_ver:0x%x,frame_len:0x%x,checksum:0x%x\n",
+		frame_type, frame_ver, frame_len, checksum);
+
+	switch (frame_type) {
+	case HDMI_INFOFRAME_TYPE_AVI:
+		ctrl_frame_en = CTRL_AVI_EN;
+		ctrl_reg = GRL_CTRL;
+		break;
+	case HDMI_INFOFRAME_TYPE_SPD:
+		ctrl_frame_en = CTRL_SPD_EN;
+		ctrl_reg = GRL_CTRL;
+		break;
+	case HDMI_INFOFRAME_TYPE_AUDIO:
+		ctrl_frame_en = CTRL_AUDIO_EN;
+		ctrl_reg = GRL_CTRL;
+		break;
+	case HDMI_INFOFRAME_TYPE_VENDOR:
+		ctrl_frame_en = VS_EN;
+		ctrl_reg = GRL_ACP_ISRC_CTRL;
+		break;
+	default:
+		break;
+	}
+	mtk_hdmi_mask(hdmi, ctrl_reg, 0, ctrl_frame_en);
+	mtk_hdmi_write(hdmi, GRL_INFOFRM_TYPE, frame_type);
+	mtk_hdmi_write(hdmi, GRL_INFOFRM_VER, frame_ver);
+	mtk_hdmi_write(hdmi, GRL_INFOFRM_LNG, frame_len);
+
+	mtk_hdmi_write(hdmi, GRL_IFM_PORT, checksum);
+	for (i = 0; i < frame_len; i++)
+		mtk_hdmi_write(hdmi, GRL_IFM_PORT, frame_data[i]);
+
+	mtk_hdmi_mask(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(hdmi, GRL_SHIFT_R2, enable ? 0 : AUDIO_PACKET_OFF,
+		      AUDIO_PACKET_OFF);
+}
+
+void mtk_hdmi_hw_config_sys(struct mtk_hdmi *hdmi)
+{
+	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
+			   HDMI_OUT_FIFO_EN | MHL_MODE_ON, 0);
+	mdelay(2);
+	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
+			   HDMI_OUT_FIFO_EN | MHL_MODE_ON, HDMI_OUT_FIFO_EN);
+}
+
+void mtk_hdmi_hw_set_deep_color_mode(struct mtk_hdmi *hdmi)
+{
+	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
+			   DEEP_COLOR_MODE_MASK | DEEP_COLOR_EN, COLOR_8BIT_MODE);
+}
+
+void mtk_hdmi_hw_send_av_mute(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_mask(hdmi, GRL_CFG4, 0, CTRL_AVMUTE);
+	mdelay(2);
+	mtk_hdmi_mask(hdmi, GRL_CFG4, CTRL_AVMUTE, CTRL_AVMUTE);
+}
+
+void mtk_hdmi_hw_send_av_unmute(struct mtk_hdmi *hdmi)
+{
+	mtk_hdmi_mask(hdmi, GRL_CFG4, CFG4_AV_UNMUTE_EN,
+		      CFG4_AV_UNMUTE_EN | CFG4_AV_UNMUTE_SET);
+	mdelay(2);
+	mtk_hdmi_mask(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(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(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(hdmi, GRL_CFG4, 0, CFG_MHL_MODE);
+
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE &&
+	    mode->clock == 74250 &&
+	    mode->vdisplay == 1080)
+		mtk_hdmi_mask(hdmi, GRL_CFG2, 0, MHL_DE_SEL);
+	else
+		mtk_hdmi_mask(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(hdmi, GRL_CH_SWAP, swap_bit, 0xff);
+}
+
+void mtk_hdmi_hw_aud_raw_data_enable(struct mtk_hdmi *hdmi, bool enable)
+{
+	mtk_hdmi_mask(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(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(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(hdmi, GRL_CFG0, val);
+}
+
+void mtk_hdmi_hw_aud_set_high_bitrate(struct mtk_hdmi *hdmi, bool enable)
+{
+	mtk_hdmi_mask(hdmi, GRL_AOUT_BNUM_SEL,
+		      enable ? HIGH_BIT_RATE_PACKET_ALIGN : 0,
+		      HIGH_BIT_RATE_PACKET_ALIGN);
+	mtk_hdmi_mask(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(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(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(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(hdmi, GRL_CH_SW0, val_2);
+	mtk_hdmi_write(hdmi, GRL_CH_SW1, val_3);
+	mtk_hdmi_write(hdmi, GRL_CH_SW2, val_4);
+	mtk_hdmi_write(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(hdmi, GRL_CFG1);
+	if (input_type == HDMI_AUD_INPUT_I2S &&
+	    (val & CFG1_SPDIF) == CFG1_SPDIF) {
+		val &= ~CFG1_SPDIF;
+	} else if (input_type == HDMI_AUD_INPUT_SPDIF &&
+		(val & CFG1_SPDIF) == 0) {
+		val |= CFG1_SPDIF;
+	}
+	mtk_hdmi_write(hdmi, GRL_CFG1, val);
+}
+
+void mtk_hdmi_hw_aud_set_channel_status(struct mtk_hdmi *hdmi,
+					u8 *l_chan_status, u8 *r_chan_status,
+					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_status[0];
+	r_status[1] = r_chan_status[1];
+	r_status[2] = r_chan_status[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(hdmi, GRL_I2S_C_STA0, val);
+	mtk_hdmi_write(hdmi, GRL_L_STATUS_0, val);
+
+	val = r_status[0];
+	mtk_hdmi_write(hdmi, GRL_R_STATUS_0, val);
+
+	val = l_status[1];
+	mtk_hdmi_write(hdmi, GRL_I2S_C_STA1, val);
+	mtk_hdmi_write(hdmi, GRL_L_STATUS_1, val);
+
+	val = r_status[1];
+	mtk_hdmi_write(hdmi, GRL_R_STATUS_1, val);
+
+	val = l_status[2];
+	mtk_hdmi_write(hdmi, GRL_I2S_C_STA2, val);
+	mtk_hdmi_write(hdmi, GRL_L_STATUS_2, val);
+
+	val = r_status[2];
+	mtk_hdmi_write(hdmi, GRL_R_STATUS_2, val);
+
+	val = l_status[3];
+	mtk_hdmi_write(hdmi, GRL_I2S_C_STA3, val);
+	mtk_hdmi_write(hdmi, GRL_L_STATUS_3, val);
+
+	val = r_status[3];
+	mtk_hdmi_write(hdmi, GRL_R_STATUS_3, val);
+
+	val = l_status[4];
+	mtk_hdmi_write(hdmi, GRL_I2S_C_STA4, val);
+	mtk_hdmi_write(hdmi, GRL_L_STATUS_4, val);
+
+	val = r_status[4];
+	mtk_hdmi_write(hdmi, GRL_R_STATUS_4, val);
+
+	for (val = 0; val < 19; val++) {
+		mtk_hdmi_write(hdmi, GRL_L_STATUS_5 + val * 4, 0);
+		mtk_hdmi_write(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(hdmi, GRL_MIX_CTRL);
+	if (val & MIX_CTRL_SRC_EN) {
+		val &= ~MIX_CTRL_SRC_EN;
+		mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
+		usleep_range(255, 512);
+		val |= MIX_CTRL_SRC_EN;
+		mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
+	}
+}
+
+void mtk_hdmi_hw_aud_src_off(struct mtk_hdmi *hdmi)
+{
+	u32 val;
+
+	val = mtk_hdmi_read(hdmi, GRL_MIX_CTRL);
+	val &= ~MIX_CTRL_SRC_EN;
+	mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
+	mtk_hdmi_write(hdmi, GRL_SHIFT_L1, 0x00);
+}
+
+void mtk_hdmi_hw_aud_set_mclk(struct mtk_hdmi *hdmi, enum hdmi_aud_mclk mclk)
+{
+	u32 val;
+
+	val = mtk_hdmi_read(hdmi, GRL_CFG5);
+	val &= CFG5_CD_RATIO_MASK;
+
+	switch (mclk) {
+	case HDMI_AUD_MCLK_128FS:
+		val |= CFG5_FS128;
+		break;
+	case HDMI_AUD_MCLK_256FS:
+		val |= CFG5_FS256;
+		break;
+	case HDMI_AUD_MCLK_384FS:
+		val |= CFG5_FS384;
+		break;
+	case HDMI_AUD_MCLK_512FS:
+		val |= CFG5_FS512;
+		break;
+	case HDMI_AUD_MCLK_768FS:
+		val |= CFG5_FS768;
+		break;
+	default:
+		val |= CFG5_FS256;
+		break;
+	}
+	mtk_hdmi_write(hdmi, GRL_CFG5, val);
+}
+
+void mtk_hdmi_hw_aud_aclk_inv_enable(struct mtk_hdmi *hdmi, bool enable)
+{
+	u32 val;
+
+	val = mtk_hdmi_read(hdmi, GRL_CFG2);
+	if (enable)
+		val |= 0x80;
+	else
+		val &= ~0x80;
+	mtk_hdmi_write(hdmi, GRL_CFG2, val);
+}
+
+struct hdmi_acr_n {
+	unsigned int clock;
+	unsigned int n[3];
+};
+
+/* Recommended N values from HDMI specification, tables 7-1 to 7-3 */
+static const struct hdmi_acr_n hdmi_rec_n_table[] = {
+	/* Clock, N: 32kHz 44.1kHz 48kHz */
+	{  25175, {  4576,  7007,  6864 } },
+	{  74176, { 11648, 17836, 11648 } },
+	{ 148352, { 11648,  8918,  5824 } },
+	{ 296703, {  5824,  4459,  5824 } },
+	{ 297000, {  3072,  4704,  5120 } },
+	{      0, {  4096,  6272,  6144 } }, /* all other TMDS clocks */
+};
+
+/**
+ * hdmi_recommended_n() - Return N value recommended by HDMI specification
+ * @freq: audio sample rate in Hz
+ * @clock: rounded TMDS clock in kHz
+ */
+static unsigned int hdmi_recommended_n(unsigned int freq, unsigned int clock)
+{
+	const struct hdmi_acr_n *recommended;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(hdmi_rec_n_table) - 1; i++) {
+		if (clock == hdmi_rec_n_table[i].clock)
+			break;
+	}
+	recommended = hdmi_rec_n_table + i;
+
+	switch (freq) {
+	case 32000:
+		return recommended->n[0];
+	case 44100:
+		return recommended->n[1];
+	case 48000:
+		return recommended->n[2];
+	case 88200:
+		return recommended->n[1] * 2;
+	case 96000:
+		return recommended->n[2] * 2;
+	case 176400:
+		return recommended->n[1] * 4;
+	case 192000:
+		return recommended->n[2] * 4;
+	default:
+		return (128 * freq) / 1000;
+	}
+}
+
+static unsigned int hdmi_mode_clock_to_hz(unsigned int clock)
+{
+	switch (clock) {
+	case 25175:
+		return 25174825;	/* 25.2/1.001 MHz */
+	case 74176:
+		return 74175824;	/* 74.25/1.001 MHz */
+	case 148352:
+		return 148351648;	/* 148.5/1.001 MHz */
+	case 296703:
+		return 296703297;	/* 297/1.001 MHz */
+	default:
+		return clock * 1000;
+	}
+}
+
+static unsigned int hdmi_expected_cts(unsigned int audio_sample_rate,
+				      unsigned int tmds_clock, unsigned int n)
+{
+	return DIV_ROUND_CLOSEST_ULL((u64)hdmi_mode_clock_to_hz(tmds_clock) * n,
+				     128 * audio_sample_rate);
+}
+
+static void do_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, unsigned int n,
+				    unsigned int cts)
+{
+	unsigned char val[NCTS_BYTES];
+	int i;
+
+	mtk_hdmi_write(hdmi, GRL_NCTS, 0);
+	mtk_hdmi_write(hdmi, GRL_NCTS, 0);
+	mtk_hdmi_write(hdmi, GRL_NCTS, 0);
+	memset(val, 0, sizeof(val));
+
+	val[0] = (cts >> 24) & 0xff;
+	val[1] = (cts >> 16) & 0xff;
+	val[2] = (cts >> 8) & 0xff;
+	val[3] = cts & 0xff;
+
+	val[4] = (n >> 16) & 0xff;
+	val[5] = (n >> 8) & 0xff;
+	val[6] = n & 0xff;
+
+	for (i = 0; i < NCTS_BYTES; i++)
+		mtk_hdmi_write(hdmi, GRL_NCTS, val[i]);
+}
+
+void mtk_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, unsigned int sample_rate,
+			      unsigned int clock)
+{
+	unsigned int n, cts;
+
+	n = hdmi_recommended_n(sample_rate, clock);
+	cts = hdmi_expected_cts(sample_rate, clock, n);
+
+	dev_dbg(hdmi->dev, "%s: sample_rate=%u, clock=%d, cts=%u, n=%u\n",
+		__func__, sample_rate, clock, n, cts);
+
+	mtk_hdmi_mask(hdmi, DUMMY_304, AUDIO_I2S_NCTS_SEL_64,
+		      AUDIO_I2S_NCTS_SEL);
+	do_hdmi_hw_aud_set_ncts(hdmi, n, cts);
+}
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..9013219
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _MTK_HDMI_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);
+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, unsigned int sample_rate,
+			      unsigned 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_regs.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
new file mode 100644
index 0000000..8c1d318
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _MTK_HDMI_REGS_H
+#define _MTK_HDMI_REGS_H
+
+#define GRL_INT_MASK		0x18
+#define GRL_IFM_PORT		0x188
+#define GRL_CH_SWAP		0x198
+#define LR_SWAP				BIT(0)
+#define LFE_CC_SWAP			BIT(1)
+#define LSRS_SWAP			BIT(2)
+#define RLS_RRS_SWAP			BIT(3)
+#define LR_STATUS_SWAP			BIT(4)
+#define GRL_I2S_C_STA0		0x140
+#define GRL_I2S_C_STA1		0x144
+#define GRL_I2S_C_STA2		0x148
+#define GRL_I2S_C_STA3		0x14C
+#define GRL_I2S_C_STA4		0x150
+#define GRL_I2S_UV		0x154
+#define 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			(0x1 << 4)
+#define CFG5_FS256			(0x2 << 4)
+#define CFG5_FS384			(0x3 << 4)
+#define CFG5_FS512			(0x4 << 4)
+#define CFG5_FS768			(0x6 << 4)
+#define DUMMY_304		0x304
+#define CHMO_SEL			(0x3 << 2)
+#define CHM1_SEL			(0x3 << 4)
+#define CHM2_SEL			(0x3 << 6)
+#define AUDIO_I2S_NCTS_SEL		BIT(1)
+#define AUDIO_I2S_NCTS_SEL_64		(1 << 1)
+#define AUDIO_I2S_NCTS_SEL_128		(0 << 1)
+#define NEW_GCP_CTRL			BIT(0)
+#define NEW_GCP_CTRL_MERGE		BIT(0)
+#define GRL_L_STATUS_0		0x200
+#define GRL_L_STATUS_1		0x204
+#define GRL_L_STATUS_2		0x208
+#define GRL_L_STATUS_3		0x20c
+#define GRL_L_STATUS_4		0x210
+#define GRL_L_STATUS_5		0x214
+#define GRL_L_STATUS_6		0x218
+#define GRL_L_STATUS_7		0x21c
+#define GRL_L_STATUS_8		0x220
+#define GRL_L_STATUS_9		0x224
+#define GRL_L_STATUS_10		0x228
+#define GRL_L_STATUS_11		0x22c
+#define GRL_L_STATUS_12		0x230
+#define GRL_L_STATUS_13		0x234
+#define GRL_L_STATUS_14		0x238
+#define GRL_L_STATUS_15		0x23c
+#define GRL_L_STATUS_16		0x240
+#define GRL_L_STATUS_17		0x244
+#define GRL_L_STATUS_18		0x248
+#define GRL_L_STATUS_19		0x24c
+#define GRL_L_STATUS_20		0x250
+#define GRL_L_STATUS_21		0x254
+#define GRL_L_STATUS_22		0x258
+#define GRL_L_STATUS_23		0x25c
+#define GRL_R_STATUS_0		0x260
+#define GRL_R_STATUS_1		0x264
+#define GRL_R_STATUS_2		0x268
+#define GRL_R_STATUS_3		0x26c
+#define GRL_R_STATUS_4		0x270
+#define GRL_R_STATUS_5		0x274
+#define GRL_R_STATUS_6		0x278
+#define GRL_R_STATUS_7		0x27c
+#define GRL_R_STATUS_8		0x280
+#define GRL_R_STATUS_9		0x284
+#define GRL_R_STATUS_10		0x288
+#define GRL_R_STATUS_11		0x28c
+#define GRL_R_STATUS_12		0x290
+#define GRL_R_STATUS_13		0x294
+#define GRL_R_STATUS_14		0x298
+#define GRL_R_STATUS_15		0x29c
+#define GRL_R_STATUS_16		0x2a0
+#define GRL_R_STATUS_17		0x2a4
+#define GRL_R_STATUS_18		0x2a8
+#define GRL_R_STATUS_19		0x2ac
+#define GRL_R_STATUS_20		0x2b0
+#define GRL_R_STATUS_21		0x2b4
+#define GRL_R_STATUS_22		0x2b8
+#define GRL_R_STATUS_23		0x2bc
+#define GRL_ABIST_CTRL0		0x2D4
+#define GRL_ABIST_CTRL1		0x2D8
+#define ABIST_EN			BIT(7)
+#define ABIST_DATA_FMT			(0x7 << 0)
+#define VIDEO_CFG_0		0x380
+#define VIDEO_CFG_1		0x384
+#define VIDEO_CFG_2		0x388
+#define VIDEO_CFG_3		0x38c
+#define VIDEO_CFG_4		0x390
+#define VIDEO_SOURCE_SEL		BIT(7)
+#define NORMAL_PATH			(1 << 7)
+#define GEN_RGB				(0 << 7)
+
+#define HDMI_SYS_CFG1C		0x000
+#define HDMI_ON				BIT(0)
+#define HDMI_RST			BIT(1)
+#define ANLG_ON				BIT(2)
+#define CFG10_DVI			BIT(3)
+#define HDMI_TST			BIT(3)
+#define SYS_KEYMASK1			(0xff << 8)
+#define SYS_KEYMASK2			(0xff << 16)
+#define AUD_OUTSYNC_EN			BIT(24)
+#define AUD_OUTSYNC_PRE_EN		BIT(25)
+#define I2CM_ON				BIT(26)
+#define E2PROM_TYPE_8BIT		BIT(27)
+#define MCM_E2PROM_ON			BIT(28)
+#define EXT_E2PROM_ON			BIT(29)
+#define HTPLG_PIN_SEL_OFF		BIT(30)
+#define AES_EFUSE_ENABLE		BIT(31)
+#define HDMI_SYS_CFG20		0x004
+#define DEEP_COLOR_MODE_MASK		(3 << 1)
+#define COLOR_8BIT_MODE			(0 << 1)
+#define COLOR_10BIT_MODE		(1 << 1)
+#define COLOR_12BIT_MODE		(2 << 1)
+#define COLOR_16BIT_MODE		(3 << 1)
+#define DEEP_COLOR_EN			BIT(0)
+#define HDMI_AUDIO_TEST_SEL		BIT(8)
+#define HDMI2P0_EN			BIT(11)
+#define HDMI_OUT_FIFO_EN		BIT(16)
+#define HDMI_OUT_FIFO_CLK_INV		BIT(17)
+#define MHL_MODE_ON			BIT(28)
+#define MHL_PP_MODE			BIT(29)
+#define MHL_SYNC_AUTO_EN		BIT(30)
+#define HDMI_PCLK_FREE_RUN		BIT(31)
+
+#endif
diff --git a/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c b/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c
new file mode 100644
index 0000000..5d9f07f
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#define HDMI_CON0		0x00
+#define RG_HDMITX_PLL_EN		BIT(31)
+#define RG_HDMITX_PLL_FBKDIV		(0x7f << 24)
+#define PLL_FBKDIV_SHIFT		24
+#define RG_HDMITX_PLL_FBKSEL		(0x3 << 22)
+#define PLL_FBKSEL_SHIFT		22
+#define RG_HDMITX_PLL_PREDIV		(0x3 << 20)
+#define PREDIV_SHIFT			20
+#define RG_HDMITX_PLL_POSDIV		(0x3 << 18)
+#define POSDIV_SHIFT			18
+#define RG_HDMITX_PLL_RST_DLY		(0x3 << 16)
+#define RG_HDMITX_PLL_IR		(0xf << 12)
+#define PLL_IR_SHIFT			12
+#define RG_HDMITX_PLL_IC		(0xf << 8)
+#define PLL_IC_SHIFT			8
+#define RG_HDMITX_PLL_BP		(0xf << 4)
+#define PLL_BP_SHIFT			4
+#define RG_HDMITX_PLL_BR		(0x3 << 2)
+#define PLL_BR_SHIFT			2
+#define RG_HDMITX_PLL_BC		(0x3 << 0)
+#define PLL_BC_SHIFT			0
+#define HDMI_CON1		0x04
+#define RG_HDMITX_PLL_DIVEN		(0x7 << 29)
+#define PLL_DIVEN_SHIFT			29
+#define RG_HDMITX_PLL_AUTOK_EN		BIT(28)
+#define RG_HDMITX_PLL_AUTOK_KF		(0x3 << 26)
+#define RG_HDMITX_PLL_AUTOK_KS		(0x3 << 24)
+#define RG_HDMITX_PLL_AUTOK_LOAD	BIT(23)
+#define RG_HDMITX_PLL_BAND		(0x3f << 16)
+#define RG_HDMITX_PLL_REF_SEL		BIT(15)
+#define RG_HDMITX_PLL_BIAS_EN		BIT(14)
+#define RG_HDMITX_PLL_BIAS_LPF_EN	BIT(13)
+#define RG_HDMITX_PLL_TXDIV_EN		BIT(12)
+#define RG_HDMITX_PLL_TXDIV		(0x3 << 10)
+#define PLL_TXDIV_SHIFT			10
+#define RG_HDMITX_PLL_LVROD_EN		BIT(9)
+#define RG_HDMITX_PLL_MONVC_EN		BIT(8)
+#define RG_HDMITX_PLL_MONCK_EN		BIT(7)
+#define RG_HDMITX_PLL_MONREF_EN		BIT(6)
+#define RG_HDMITX_PLL_TST_EN		BIT(5)
+#define RG_HDMITX_PLL_TST_CK_EN		BIT(4)
+#define RG_HDMITX_PLL_TST_SEL		(0xf << 0)
+#define HDMI_CON2		0x08
+#define RGS_HDMITX_PLL_AUTOK_BAND	(0x7f << 8)
+#define RGS_HDMITX_PLL_AUTOK_FAIL	BIT(1)
+#define RG_HDMITX_EN_TX_CKLDO		BIT(0)
+#define HDMI_CON3		0x0c
+#define RG_HDMITX_SER_EN		(0xf << 28)
+#define RG_HDMITX_PRD_EN		(0xf << 24)
+#define RG_HDMITX_PRD_IMP_EN		(0xf << 20)
+#define RG_HDMITX_DRV_EN		(0xf << 16)
+#define RG_HDMITX_DRV_IMP_EN		(0xf << 12)
+#define DRV_IMP_EN_SHIFT		12
+#define RG_HDMITX_MHLCK_FORCE		BIT(10)
+#define RG_HDMITX_MHLCK_PPIX_EN		BIT(9)
+#define RG_HDMITX_MHLCK_EN		BIT(8)
+#define RG_HDMITX_SER_DIN_SEL		(0xf << 4)
+#define RG_HDMITX_SER_5T1_BIST_EN	BIT(3)
+#define RG_HDMITX_SER_BIST_TOG		BIT(2)
+#define RG_HDMITX_SER_DIN_TOG		BIT(1)
+#define RG_HDMITX_SER_CLKDIG_INV	BIT(0)
+#define HDMI_CON4		0x10
+#define RG_HDMITX_PRD_IBIAS_CLK		(0xf << 24)
+#define RG_HDMITX_PRD_IBIAS_D2		(0xf << 16)
+#define RG_HDMITX_PRD_IBIAS_D1		(0xf << 8)
+#define RG_HDMITX_PRD_IBIAS_D0		(0xf << 0)
+#define PRD_IBIAS_CLK_SHIFT		24
+#define PRD_IBIAS_D2_SHIFT		16
+#define PRD_IBIAS_D1_SHIFT		8
+#define PRD_IBIAS_D0_SHIFT		0
+#define HDMI_CON5		0x14
+#define RG_HDMITX_DRV_IBIAS_CLK		(0x3f << 24)
+#define RG_HDMITX_DRV_IBIAS_D2		(0x3f << 16)
+#define RG_HDMITX_DRV_IBIAS_D1		(0x3f << 8)
+#define RG_HDMITX_DRV_IBIAS_D0		(0x3f << 0)
+#define DRV_IBIAS_CLK_SHIFT		24
+#define DRV_IBIAS_D2_SHIFT		16
+#define DRV_IBIAS_D1_SHIFT		8
+#define DRV_IBIAS_D0_SHIFT		0
+#define HDMI_CON6		0x18
+#define RG_HDMITX_DRV_IMP_CLK		(0x3f << 24)
+#define RG_HDMITX_DRV_IMP_D2		(0x3f << 16)
+#define RG_HDMITX_DRV_IMP_D1		(0x3f << 8)
+#define RG_HDMITX_DRV_IMP_D0		(0x3f << 0)
+#define DRV_IMP_CLK_SHIFT		24
+#define DRV_IMP_D2_SHIFT		16
+#define DRV_IMP_D1_SHIFT		8
+#define DRV_IMP_D0_SHIFT		0
+#define HDMI_CON7		0x1c
+#define RG_HDMITX_MHLCK_DRV_IBIAS	(0x1f << 27)
+#define RG_HDMITX_SER_DIN		(0x3ff << 16)
+#define RG_HDMITX_CHLDC_TST		(0xf << 12)
+#define RG_HDMITX_CHLCK_TST		(0xf << 8)
+#define RG_HDMITX_RESERVE		(0xff << 0)
+#define HDMI_CON8		0x20
+#define RGS_HDMITX_2T1_LEV		(0xf << 16)
+#define RGS_HDMITX_2T1_EDG		(0xf << 12)
+#define RGS_HDMITX_5T1_LEV		(0xf << 8)
+#define RGS_HDMITX_5T1_EDG		(0xf << 4)
+#define RGS_HDMITX_PLUG_TST		BIT(0)
+
+struct mtk_hdmi_phy {
+	void __iomem *regs;
+	struct device *dev;
+	struct clk *pll;
+	struct clk_hw pll_hw;
+	unsigned long pll_rate;
+	u8 drv_imp_clk;
+	u8 drv_imp_d2;
+	u8 drv_imp_d1;
+	u8 drv_imp_d0;
+	u32 ibias;
+	u32 ibias_up;
+};
+
+static const u8 PREDIV[3][4] = {
+	{0x0, 0x0, 0x0, 0x0},	/* 27Mhz */
+	{0x1, 0x1, 0x1, 0x1},	/* 74Mhz */
+	{0x1, 0x1, 0x1, 0x1}	/* 148Mhz */
+};
+
+static const u8 TXDIV[3][4] = {
+	{0x3, 0x3, 0x3, 0x2},	/* 27Mhz */
+	{0x2, 0x1, 0x1, 0x1},	/* 74Mhz */
+	{0x1, 0x0, 0x0, 0x0}	/* 148Mhz */
+};
+
+static const u8 FBKSEL[3][4] = {
+	{0x1, 0x1, 0x1, 0x1},	/* 27Mhz */
+	{0x1, 0x0, 0x1, 0x1},	/* 74Mhz */
+	{0x1, 0x0, 0x1, 0x1}	/* 148Mhz */
+};
+
+static const u8 FBKDIV[3][4] = {
+	{19, 24, 29, 19},	/* 27Mhz */
+	{19, 24, 14, 19},	/* 74Mhz */
+	{19, 24, 14, 19}	/* 148Mhz */
+};
+
+static const u8 DIVEN[3][4] = {
+	{0x2, 0x1, 0x1, 0x2},	/* 27Mhz */
+	{0x2, 0x2, 0x2, 0x2},	/* 74Mhz */
+	{0x2, 0x2, 0x2, 0x2}	/* 148Mhz */
+};
+
+static const u8 HTPLLBP[3][4] = {
+	{0xc, 0xc, 0x8, 0xc},	/* 27Mhz */
+	{0xc, 0xf, 0xf, 0xc},	/* 74Mhz */
+	{0xc, 0xf, 0xf, 0xc}	/* 148Mhz */
+};
+
+static const u8 HTPLLBC[3][4] = {
+	{0x2, 0x3, 0x3, 0x2},	/* 27Mhz */
+	{0x2, 0x3, 0x3, 0x2},	/* 74Mhz */
+	{0x2, 0x3, 0x3, 0x2}	/* 148Mhz */
+};
+
+static const u8 HTPLLBR[3][4] = {
+	{0x1, 0x1, 0x0, 0x1},	/* 27Mhz */
+	{0x1, 0x2, 0x2, 0x1},	/* 74Mhz */
+	{0x1, 0x2, 0x2, 0x1}	/* 148Mhz */
+};
+
+static void mtk_hdmi_phy_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 inline struct mtk_hdmi_phy *to_mtk_hdmi_phy(struct clk_hw *hw)
+{
+	return container_of(hw, struct mtk_hdmi_phy, pll_hw);
+}
+
+static int mtk_hdmi_pll_prepare(struct clk_hw *hw)
+{
+	struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+	dev_dbg(hdmi_phy->dev, "prepare\n");
+
+	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);
+
+	return 0;
+}
+
+static void mtk_hdmi_pll_unprepare(struct clk_hw *hw)
+{
+	struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+	dev_dbg(hdmi_phy->dev, "prepare\n");
+
+	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);
+}
+
+static int mtk_hdmi_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+				 unsigned long parent_rate)
+{
+	struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+	unsigned int pre_div;
+	unsigned int div;
+
+	dev_dbg(hdmi_phy->dev, "set rate : %lu, parent: %lu\n", rate,
+		parent_rate);
+
+	if (rate <= 27000000) {
+		pre_div = 0;
+		div = 3;
+	} else if (rate <= 74250000) {
+		pre_div = 1;
+		div = 2;
+	} else {
+		pre_div = 1;
+		div = 1;
+	}
+
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+			  (pre_div << PREDIV_SHIFT),
+			  RG_HDMITX_PLL_PREDIV);
+	mtk_hdmi_phy_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,
+			  (div << PLL_TXDIV_SHIFT),
+			  RG_HDMITX_PLL_TXDIV);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+			  (0x1 << PLL_FBKSEL_SHIFT) | (19 << PLL_FBKDIV_SHIFT),
+			  RG_HDMITX_PLL_FBKSEL | RG_HDMITX_PLL_FBKDIV);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1,
+			  (0x2 << PLL_DIVEN_SHIFT),
+			  RG_HDMITX_PLL_DIVEN);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+			  (0xc << PLL_BP_SHIFT) | (0x2 << PLL_BC_SHIFT) |
+			  (0x1 << PLL_BR_SHIFT),
+			  RG_HDMITX_PLL_BP | RG_HDMITX_PLL_BC |
+			  RG_HDMITX_PLL_BR);
+	mtk_hdmi_phy_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);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6,
+			  (hdmi_phy->drv_imp_clk << DRV_IMP_CLK_SHIFT) |
+			  (hdmi_phy->drv_imp_d2 << DRV_IMP_D2_SHIFT) |
+			  (hdmi_phy->drv_imp_d1 << DRV_IMP_D1_SHIFT) |
+			  (hdmi_phy->drv_imp_d0 << DRV_IMP_D0_SHIFT),
+			  RG_HDMITX_DRV_IMP_CLK | RG_HDMITX_DRV_IMP_D2 |
+			  RG_HDMITX_DRV_IMP_D1 | RG_HDMITX_DRV_IMP_D0);
+	mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON5,
+			  (hdmi_phy->ibias << DRV_IBIAS_CLK_SHIFT) |
+			  (hdmi_phy->ibias << DRV_IBIAS_D2_SHIFT) |
+			  (hdmi_phy->ibias << DRV_IBIAS_D1_SHIFT) |
+			  (hdmi_phy->ibias << DRV_IBIAS_D0_SHIFT),
+			  RG_HDMITX_DRV_IBIAS_CLK | RG_HDMITX_DRV_IBIAS_D2 |
+			  RG_HDMITX_DRV_IBIAS_D1 | RG_HDMITX_DRV_IBIAS_D0);
+	return 0;
+}
+
+static long mtk_hdmi_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long *parent_rate)
+{
+	struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+	hdmi_phy->pll_rate = rate;
+	if (rate <= 74250000)
+		*parent_rate = rate;
+	else
+		*parent_rate = rate / 2;
+
+	return rate;
+}
+
+static unsigned long mtk_hdmi_pll_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+	return hdmi_phy->pll_rate;
+}
+
+static const struct clk_ops mtk_hdmi_pll_ops = {
+	.prepare = mtk_hdmi_pll_prepare,
+	.unprepare = mtk_hdmi_pll_unprepare,
+	.set_rate = mtk_hdmi_pll_set_rate,
+	.round_rate = mtk_hdmi_pll_round_rate,
+	.recalc_rate = mtk_hdmi_pll_recalc_rate,
+};
+
+static void mtk_hdmi_phy_enable_tmds(struct mtk_hdmi_phy *hdmi_phy)
+{
+	mtk_hdmi_phy_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);
+}
+
+static int mtk_hdmi_phy_power_on(struct phy *phy)
+{
+	struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy);
+	int ret;
+
+	ret = clk_prepare_enable(hdmi_phy->pll);
+	if (ret < 0)
+		return ret;
+
+	mtk_hdmi_phy_enable_tmds(hdmi_phy);
+
+	return 0;
+}
+
+static int mtk_hdmi_phy_power_off(struct phy *phy)
+{
+	struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy);
+
+	mtk_hdmi_phy_disable_tmds(hdmi_phy);
+	clk_disable_unprepare(hdmi_phy->pll);
+
+	return 0;
+}
+
+static struct phy_ops mtk_hdmi_phy_ops = {
+	.power_on = mtk_hdmi_phy_power_on,
+	.power_off = mtk_hdmi_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static int mtk_hdmi_phy_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_hdmi_phy *hdmi_phy;
+	struct resource *mem;
+	struct clk *ref_clk;
+	const char *ref_clk_name;
+	struct clk_init_data clk_init = {
+		.ops = &mtk_hdmi_pll_ops,
+		.num_parents = 1,
+		.parent_names = (const char * const *)&ref_clk_name,
+		.flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE,
+	};
+	struct phy *phy;
+	struct phy_provider *phy_provider;
+	int ret;
+
+	hdmi_phy = devm_kzalloc(dev, sizeof(*hdmi_phy), GFP_KERNEL);
+	if (!hdmi_phy)
+		return -ENOMEM;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	hdmi_phy->regs = devm_ioremap_resource(dev, mem);
+	if (IS_ERR(hdmi_phy->regs)) {
+		ret = PTR_ERR(hdmi_phy->regs);
+		dev_err(dev, "Failed to get memory resource: %d\n", ret);
+		return ret;
+	}
+
+	ref_clk = devm_clk_get(dev, "pll_ref");
+	if (IS_ERR(ref_clk)) {
+		ret = PTR_ERR(ref_clk);
+		dev_err(&pdev->dev, "Failed to get PLL reference clock: %d\n",
+			ret);
+		return ret;
+	}
+	ref_clk_name = __clk_get_name(ref_clk);
+
+	ret = of_property_read_string(dev->of_node, "clock-output-names",
+				      &clk_init.name);
+	if (ret < 0) {
+		dev_err(dev, "Failed to read clock-output-names: %d\n", ret);
+		return ret;
+	}
+
+	hdmi_phy->pll_hw.init = &clk_init;
+	hdmi_phy->pll = devm_clk_register(dev, &hdmi_phy->pll_hw);
+	if (IS_ERR(hdmi_phy->pll)) {
+		ret = PTR_ERR(hdmi_phy->pll);
+		dev_err(dev, "Failed to register PLL: %d\n", ret);
+		return ret;
+	}
+
+	ret = of_property_read_u32(dev->of_node, "mediatek,ibias",
+				   &hdmi_phy->ibias);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to get ibias: %d\n", ret);
+		return ret;
+	}
+
+	ret = of_property_read_u32(dev->of_node, "mediatek,ibias_up",
+				   &hdmi_phy->ibias_up);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to get ibias up: %d\n", ret);
+		return ret;
+	}
+
+	dev_info(dev, "Using default TX DRV impedance: 4.2k/36\n");
+	hdmi_phy->drv_imp_clk = 0x30;
+	hdmi_phy->drv_imp_d2 = 0x30;
+	hdmi_phy->drv_imp_d1 = 0x30;
+	hdmi_phy->drv_imp_d0 = 0x30;
+
+	phy = devm_phy_create(dev, NULL, &mtk_hdmi_phy_ops);
+	if (IS_ERR(phy)) {
+		dev_err(dev, "Failed to create HDMI PHY\n");
+		return PTR_ERR(phy);
+	}
+	phy_set_drvdata(phy, hdmi_phy);
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (IS_ERR(phy_provider))
+		return PTR_ERR(phy_provider);
+
+	hdmi_phy->dev = dev;
+	return of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
+				   hdmi_phy->pll);
+}
+
+static int mtk_hdmi_phy_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static const struct of_device_id mtk_hdmi_phy_match[] = {
+	{ .compatible = "mediatek,mt8173-hdmi-phy", },
+	{},
+};
+
+struct platform_driver mtk_hdmi_phy_driver = {
+	.probe = mtk_hdmi_phy_probe,
+	.remove = mtk_hdmi_phy_remove,
+	.driver = {
+		.name = "mediatek-hdmi-phy",
+		.of_match_table = mtk_hdmi_phy_match,
+	},
+};
+
+MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek MT8173 HDMI PHY Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 07/14] drm/mediatek: enable hdmi output control bit
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
                   ` (5 preceding siblings ...)
  2016-03-08 13:27 ` [PATCH v13 06/14] drm/mediatek: Add HDMI support Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 08/14] arm64: dts: mt8173: Add display subsystem related nodes Philipp Zabel
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

From: Jie Qiu <jie.qiu@mediatek.com>

MT8173 HDMI hardware has a output control bit to enable/disable HDMI
output. Because of security reason, so this bit can ONLY be controlled
in ARM supervisor mode. Now the only way to enter ARM supervisor is the
ARM trusted firmware. So atf provides a API for HDMI driver to call to
setup this HDMI control bit to enable HDMI output in supervisor mode.

Signed-off-by: Jie Qiu <jie.qiu@mediatek.com>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
---
 drivers/gpu/drm/mediatek/mtk_hdmi_hw.c   | 12 ++++++++++++
 drivers/gpu/drm/mediatek/mtk_hdmi_regs.h |  1 +
 2 files changed, 13 insertions(+)

diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c
index 99c7ffc..ea4e35f 100644
--- a/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c
@@ -15,6 +15,7 @@
 #include "mtk_hdmi_regs.h"
 #include "mtk_hdmi.h"
 
+#include <linux/arm-smccc.h>
 #include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/hdmi.h>
@@ -50,6 +51,17 @@ void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi,
 
 void mtk_hdmi_hw_make_reg_writable(struct mtk_hdmi *hdmi, bool enable)
 {
+	struct arm_smccc_res res;
+
+	/*
+	 * MT8173 HDMI hardware has an output control bit to enable/disable HDMI
+	 * output. This bit can only be controlled in ARM supervisor mode.
+	 * The ARM trusted firmware provides an API for the HDMI driver to set
+	 * this control bit to enable HDMI output in supervisor mode.
+	 */
+	arm_smccc_smc(MTK_SIP_SET_AUTHORIZED_SECURE_REG, 0x14000904, 0x80000000,
+		      0, 0, 0, 0, 0, &res);
+
 	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
 			   HDMI_PCLK_FREE_RUN, enable ? HDMI_PCLK_FREE_RUN : 0);
 	regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
index 8c1d318..d88279f 100644
--- a/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
@@ -218,4 +218,5 @@
 #define MHL_SYNC_AUTO_EN		BIT(30)
 #define HDMI_PCLK_FREE_RUN		BIT(31)
 
+#define MTK_SIP_SET_AUTHORIZED_SECURE_REG 0x82000001
 #endif
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 08/14] arm64: dts: mt8173: Add display subsystem related nodes
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
                   ` (6 preceding siblings ...)
  2016-03-08 13:27 ` [PATCH v13 07/14] drm/mediatek: enable hdmi output control bit Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 09/14] arm64: dts: mt8173: Add HDMI " Philipp Zabel
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

From: CK Hu <ck.hu@mediatek.com>

This patch adds the device nodes for the DISP function blocks
comprising the display subsystem.

Signed-off-by: CK Hu <ck.hu@mediatek.com>
Signed-off-by: Cawa Cheng <cawa.cheng@mediatek.com>
Signed-off-by: Jie Qiu <jie.qiu@mediatek.com>
Signed-off-by: Daniel Kurtz <djkurtz@chromium.org>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
---
Changes since v12:
 - Remove iommus property from mmsys node
---
 arch/arm64/boot/dts/mediatek/mt8173.dtsi | 231 +++++++++++++++++++++++++++++++
 1 file changed, 231 insertions(+)

diff --git a/arch/arm64/boot/dts/mediatek/mt8173.dtsi b/arch/arm64/boot/dts/mediatek/mt8173.dtsi
index 8048811..6fb996f 100644
--- a/arch/arm64/boot/dts/mediatek/mt8173.dtsi
+++ b/arch/arm64/boot/dts/mediatek/mt8173.dtsi
@@ -26,6 +26,23 @@
 	#address-cells = <2>;
 	#size-cells = <2>;
 
+	aliases {
+		ovl0 = &ovl0;
+		ovl1 = &ovl1;
+		rdma0 = &rdma0;
+		rdma1 = &rdma1;
+		rdma2 = &rdma2;
+		wdma0 = &wdma0;
+		wdma1 = &wdma1;
+		color0 = &color0;
+		color1 = &color1;
+		split0 = &split0;
+		split1 = &split1;
+		dpi0 = &dpi0;
+		dsi0 = &dsi0;
+		dsi1 = &dsi1;
+	};
+
 	cpus {
 		#address-cells = <1>;
 		#size-cells = <0>;
@@ -295,6 +312,26 @@
 			#clock-cells = <1>;
 		};
 
+		mipi_tx0: mipi-dphy@10215000 {
+			compatible = "mediatek,mt8173-mipi-tx";
+			reg = <0 0x10215000 0 0x1000>;
+			clocks = <&clk26m>;
+			clock-output-names = "mipi_tx0_pll";
+			#clock-cells = <0>;
+			#phy-cells = <0>;
+			status = "disabled";
+		};
+
+		mipi_tx1: mipi-dphy@10216000 {
+			compatible = "mediatek,mt8173-mipi-tx";
+			reg = <0 0x10216000 0 0x1000>;
+			clocks = <&clk26m>;
+			clock-output-names = "mipi_tx1_pll";
+			#clock-cells = <0>;
+			#phy-cells = <0>;
+			status = "disabled";
+		};
+
 		gic: interrupt-controller@10220000 {
 			compatible = "arm,gic-400";
 			#interrupt-cells = <3>;
@@ -441,6 +478,14 @@
 			status = "disabled";
 		};
 
+		hdmiddc0: i2c@11012000 {
+			compatible = "mediatek,mt8173-hdmi-ddc";
+			interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_LOW>;
+			reg = <0 0x11012000 0 0x1C>;
+			clocks = <&pericfg CLK_PERI_I2C5>;
+			clock-names = "ddc-i2c";
+		};
+
 		i2c6: i2c@11013000 {
 			compatible = "mediatek,mt8173-i2c";
 			reg = <0 0x11013000 0 0x70>,
@@ -576,9 +621,181 @@
 		mmsys: clock-controller@14000000 {
 			compatible = "mediatek,mt8173-mmsys", "syscon";
 			reg = <0 0x14000000 0 0x1000>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
 			#clock-cells = <1>;
 		};
 
+		ovl0: ovl@1400c000 {
+			compatible = "mediatek,mt8173-disp-ovl";
+			reg = <0 0x1400c000 0 0x1000>;
+			interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_OVL0>;
+			iommus = <&iommu M4U_PORT_DISP_OVL0>;
+			mediatek,larb = <&larb0>;
+		};
+
+		ovl1: ovl@1400d000 {
+			compatible = "mediatek,mt8173-disp-ovl";
+			reg = <0 0x1400d000 0 0x1000>;
+			interrupts = <GIC_SPI 181 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_OVL1>;
+			iommus = <&iommu M4U_PORT_DISP_OVL1>;
+			mediatek,larb = <&larb4>;
+		};
+
+		rdma0: rdma@1400e000 {
+			compatible = "mediatek,mt8173-disp-rdma";
+			reg = <0 0x1400e000 0 0x1000>;
+			interrupts = <GIC_SPI 182 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_RDMA0>;
+			iommus = <&iommu M4U_PORT_DISP_RDMA0>;
+			mediatek,larb = <&larb0>;
+		};
+
+		rdma1: rdma@1400f000 {
+			compatible = "mediatek,mt8173-disp-rdma";
+			reg = <0 0x1400f000 0 0x1000>;
+			interrupts = <GIC_SPI 183 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_RDMA1>;
+			iommus = <&iommu M4U_PORT_DISP_RDMA1>;
+			mediatek,larb = <&larb4>;
+		};
+
+		rdma2: rdma@14010000 {
+			compatible = "mediatek,mt8173-disp-rdma";
+			reg = <0 0x14010000 0 0x1000>;
+			interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_RDMA2>;
+			iommus = <&iommu M4U_PORT_DISP_RDMA2>;
+			mediatek,larb = <&larb4>;
+		};
+
+		wdma0: wdma@14011000 {
+			compatible = "mediatek,mt8173-disp-wdma";
+			reg = <0 0x14011000 0 0x1000>;
+			interrupts = <GIC_SPI 185 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_WDMA0>;
+			iommus = <&iommu M4U_PORT_DISP_WDMA0>;
+			mediatek,larb = <&larb0>;
+		};
+
+		wdma1: wdma@14012000 {
+			compatible = "mediatek,mt8173-disp-wdma";
+			reg = <0 0x14012000 0 0x1000>;
+			interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_WDMA1>;
+			iommus = <&iommu M4U_PORT_DISP_WDMA1>;
+			mediatek,larb = <&larb4>;
+		};
+
+		color0: color@14013000 {
+			compatible = "mediatek,mt8173-disp-color";
+			reg = <0 0x14013000 0 0x1000>;
+			interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_COLOR0>;
+		};
+
+		color1: color@14014000 {
+			compatible = "mediatek,mt8173-disp-color";
+			reg = <0 0x14014000 0 0x1000>;
+			interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_COLOR1>;
+		};
+
+		aal@14015000 {
+			compatible = "mediatek,mt8173-disp-aal";
+			reg = <0 0x14015000 0 0x1000>;
+			interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_AAL>;
+		};
+
+		gamma@14016000 {
+			compatible = "mediatek,mt8173-disp-gamma";
+			reg = <0 0x14016000 0 0x1000>;
+			interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_GAMMA>;
+		};
+
+		merge@14017000 {
+			compatible = "mediatek,mt8173-disp-merge";
+			reg = <0 0x14017000 0 0x1000>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_MERGE>;
+		};
+
+		split0: split@14018000 {
+			compatible = "mediatek,mt8173-disp-split";
+			reg = <0 0x14018000 0 0x1000>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_SPLIT0>;
+		};
+
+		split1: split@14019000 {
+			compatible = "mediatek,mt8173-disp-split";
+			reg = <0 0x14019000 0 0x1000>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_SPLIT1>;
+		};
+
+		ufoe@1401a000 {
+			compatible = "mediatek,mt8173-disp-ufoe";
+			reg = <0 0x1401a000 0 0x1000>;
+			interrupts = <GIC_SPI 191 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DISP_UFOE>;
+		};
+
+		dsi0: dsi@1401b000 {
+			compatible = "mediatek,mt8173-dsi";
+			reg = <0 0x1401b000 0 0x1000>;
+			interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DSI0_ENGINE>,
+				 <&mmsys CLK_MM_DSI0_DIGITAL>,
+				 <&mipi_tx0>;
+			clock-names = "engine", "digital", "hs";
+			phys = <&mipi_tx0>;
+			phy-names = "dphy";
+			status = "disabled";
+		};
+
+		dsi1: dsi@1401c000 {
+			compatible = "mediatek,mt8173-dsi";
+			reg = <0 0x1401c000 0 0x1000>;
+			interrupts = <GIC_SPI 193 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DSI1_ENGINE>,
+				 <&mmsys CLK_MM_DSI1_DIGITAL>,
+				 <&mipi_tx1>;
+			clock-names = "engine", "digital", "hs";
+			phy = <&mipi_tx1>;
+			phy-names = "dphy";
+			status = "disabled";
+		};
+
+		dpi0: dpi@1401d000 {
+			compatible = "mediatek,mt8173-dpi";
+			reg = <0 0x1401d000 0 0x1000>;
+			interrupts = <GIC_SPI 194 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_DPI_PIXEL>,
+				 <&mmsys CLK_MM_DPI_ENGINE>,
+				 <&apmixedsys CLK_APMIXED_TVDPLL>;
+			clock-names = "pixel", "engine", "pll";
+			status = "disabled";
+		};
+
 		pwm0: pwm@1401e000 {
 			compatible = "mediatek,mt8173-disp-pwm",
 				     "mediatek,mt6595-disp-pwm";
@@ -601,6 +818,14 @@
 			status = "disabled";
 		};
 
+		mutex: mutex@14020000 {
+			compatible = "mediatek,mt8173-disp-mutex";
+			reg = <0 0x14020000 0 0x1000>;
+			interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_LOW>;
+			power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
+			clocks = <&mmsys CLK_MM_MUTEX_32K>;
+		};
+
 		larb0: larb@14021000 {
 			compatible = "mediatek,mt8173-smi-larb";
 			reg = <0 0x14021000 0 0x1000>;
@@ -620,6 +845,12 @@
 			clock-names = "apb", "smi";
 		};
 
+		od@14023000 {
+			compatible = "mediatek,mt8173-disp-od";
+			reg = <0 0x14023000 0 0x1000>;
+			clocks = <&mmsys CLK_MM_DISP_OD>;
+		};
+
 		larb4: larb@14027000 {
 			compatible = "mediatek,mt8173-smi-larb";
 			reg = <0 0x14027000 0 0x1000>;
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 09/14] arm64: dts: mt8173: Add HDMI related nodes
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
                   ` (7 preceding siblings ...)
  2016-03-08 13:27 ` [PATCH v13 08/14] arm64: dts: mt8173: Add display subsystem related nodes Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 10/14] clk: mediatek: make dpi0_sel propagate rate changes Philipp Zabel
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

From: CK Hu <ck.hu@mediatek.com>

This patch adds the device nodes for the HDMI encoder, HDMI PHY,
and HDMI CEC modules.

Signed-off-by: CK Hu <ck.hu@mediatek.com>
Signed-off-by: Cawa Cheng <cawa.cheng@mediatek.com>
Signed-off-by: Jie Qiu <jie.qiu@mediatek.com>
Signed-off-by: Daniel Kurtz <djkurtz@chromium.org>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
---
 arch/arm64/boot/dts/mediatek/mt8173.dtsi | 69 ++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/arch/arm64/boot/dts/mediatek/mt8173.dtsi b/arch/arm64/boot/dts/mediatek/mt8173.dtsi
index 6fb996f..78c121f 100644
--- a/arch/arm64/boot/dts/mediatek/mt8173.dtsi
+++ b/arch/arm64/boot/dts/mediatek/mt8173.dtsi
@@ -199,6 +199,16 @@
 				     <GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>,
 				     <GIC_SPI 147 IRQ_TYPE_LEVEL_HIGH>;
 
+			hdmi_pin: xxx {
+
+				/*hdmi htplg pin*/
+				pins1 {
+					pinmux = <MT8173_PIN_21_HTPLG__FUNC_HTPLG>;
+					input-enable;
+					bias-pull-down;
+				};
+			};
+
 			i2c0_pins_a: i2c0 {
 				pins1 {
 					pinmux = <MT8173_PIN_45_SDA0__FUNC_SDA0>,
@@ -286,6 +296,14 @@
 			clock-names = "spi", "wrap";
 		};
 
+		cec: cec@10013000 {
+			compatible = "mediatek,mt8173-cec";
+			reg = <0 0x10013000 0 0xbc>;
+			interrupts = <GIC_SPI 167 IRQ_TYPE_LEVEL_LOW>;
+			clocks = <&infracfg CLK_INFRA_CEC>;
+			status = "disabled";
+		};
+
 		sysirq: intpol-controller@10200620 {
 			compatible = "mediatek,mt8173-sysirq",
 				     "mediatek,mt6577-sysirq";
@@ -312,6 +330,19 @@
 			#clock-cells = <1>;
 		};
 
+		hdmi_phy: hdmi-phy@10209100 {
+			compatible = "mediatek,mt8173-hdmi-phy";
+			reg = <0 0x10209100 0 0x24>;
+			clocks = <&apmixedsys CLK_APMIXED_HDMI_REF>;
+			clock-names = "pll_ref";
+			clock-output-names = "hdmitx_dig_cts";
+			mediatek,ibias = <0xa>;
+			mediatek,ibias_up = <0x1c>;
+			#clock-cells = <0>;
+			#phy-cells = <0>;
+			status = "disabled";
+		};
+
 		mipi_tx0: mipi-dphy@10215000 {
 			compatible = "mediatek,mt8173-mipi-tx";
 			reg = <0 0x10215000 0 0x1000>;
@@ -794,6 +825,12 @@
 				 <&apmixedsys CLK_APMIXED_TVDPLL>;
 			clock-names = "pixel", "engine", "pll";
 			status = "disabled";
+
+			port {
+				dpi0_out: endpoint {
+					remote-endpoint = <&hdmi0_in>;
+				};
+			};
 		};
 
 		pwm0: pwm@1401e000 {
@@ -851,6 +888,38 @@
 			clocks = <&mmsys CLK_MM_DISP_OD>;
 		};
 
+		hdmi0: hdmi@14025000 {
+			compatible = "mediatek,mt8173-hdmi";
+			reg = <0 0x14025000 0 0x400>;
+			interrupts = <GIC_SPI 206 IRQ_TYPE_LEVEL_LOW>;
+			clocks = <&mmsys CLK_MM_HDMI_PIXEL>,
+				 <&mmsys CLK_MM_HDMI_PLLCK>,
+				 <&mmsys CLK_MM_HDMI_AUDIO>,
+				 <&mmsys CLK_MM_HDMI_SPDIF>;
+			clock-names = "pixel", "pll", "bclk", "spdif";
+			pinctrl-names = "default";
+			pinctrl-0 = <&hdmi_pin>;
+			phys = <&hdmi_phy>;
+			phy-names = "hdmi";
+			mediatek,syscon-hdmi = <&mmsys 0x900>;
+			assigned-clocks = <&topckgen CLK_TOP_HDMI_SEL>;
+			assigned-clock-parents = <&hdmi_phy>;
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				port@0 {
+					reg = <0>;
+
+					hdmi0_in: endpoint {
+						remote-endpoint = <&dpi0_out>;
+					};
+				};
+			};
+		};
+
 		larb4: larb@14027000 {
 			compatible = "mediatek,mt8173-smi-larb";
 			reg = <0 0x14027000 0 0x1000>;
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 10/14] clk: mediatek: make dpi0_sel propagate rate changes
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
                   ` (8 preceding siblings ...)
  2016-03-08 13:27 ` [PATCH v13 09/14] arm64: dts: mt8173: Add HDMI " Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 11/14] clk: mediatek: Add hdmi_ref HDMI PHY PLL reference clock output Philipp Zabel
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

This mux is supposed to select a fitting divider after the PLL
is already set to the correct rate.

Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
Acked-by: James Liao <jamesjj.liao@mediatek.com>
Acked-by: Stephen Boyd <sboyd@codeaurora.org>
---
 drivers/clk/mediatek/clk-mt8173.c |  6 +++++-
 drivers/clk/mediatek/clk-mtk.h    | 15 +++++++++++++--
 2 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/drivers/clk/mediatek/clk-mt8173.c b/drivers/clk/mediatek/clk-mt8173.c
index 227e356..85c0bfc 100644
--- a/drivers/clk/mediatek/clk-mt8173.c
+++ b/drivers/clk/mediatek/clk-mt8173.c
@@ -558,7 +558,11 @@ static const struct mtk_composite top_muxes[] __initconst = {
 	MUX_GATE(CLK_TOP_ATB_SEL, "atb_sel", atb_parents, 0x0090, 16, 2, 23),
 	MUX_GATE(CLK_TOP_VENC_LT_SEL, "venclt_sel", venc_lt_parents, 0x0090, 24, 4, 31),
 	/* CLK_CFG_6 */
-	MUX_GATE(CLK_TOP_DPI0_SEL, "dpi0_sel", dpi0_parents, 0x00a0, 0, 3, 7),
+	/*
+	 * The dpi0_sel clock should not propagate rate changes to its parent
+	 * clock so the dpi driver can have full control over PLL and divider.
+	 */
+	MUX_GATE_FLAGS(CLK_TOP_DPI0_SEL, "dpi0_sel", dpi0_parents, 0x00a0, 0, 3, 7, 0),
 	MUX_GATE(CLK_TOP_IRDA_SEL, "irda_sel", irda_parents, 0x00a0, 8, 2, 15),
 	MUX_GATE(CLK_TOP_CCI400_SEL, "cci400_sel", cci400_parents, 0x00a0, 16, 3, 23),
 	MUX_GATE(CLK_TOP_AUD_1_SEL, "aud_1_sel", aud_1_parents, 0x00a0, 24, 2, 31),
diff --git a/drivers/clk/mediatek/clk-mtk.h b/drivers/clk/mediatek/clk-mtk.h
index 32d2e45..9f24fcf 100644
--- a/drivers/clk/mediatek/clk-mtk.h
+++ b/drivers/clk/mediatek/clk-mtk.h
@@ -83,7 +83,11 @@ struct mtk_composite {
 	signed char num_parents;
 };
 
-#define MUX_GATE(_id, _name, _parents, _reg, _shift, _width, _gate) {	\
+/*
+ * In case the rate change propagation to parent clocks is undesirable,
+ * this macro allows to specify the clock flags manually.
+ */
+#define MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width, _gate, _flags) {	\
 		.id = _id,						\
 		.name = _name,						\
 		.mux_reg = _reg,					\
@@ -94,9 +98,16 @@ struct mtk_composite {
 		.divider_shift = -1,					\
 		.parent_names = _parents,				\
 		.num_parents = ARRAY_SIZE(_parents),			\
-		.flags = CLK_SET_RATE_PARENT,				\
+		.flags = _flags,					\
 	}
 
+/*
+ * Unless necessary, all MUX_GATE clocks propagate rate changes to their
+ * parent clock by default.
+ */
+#define MUX_GATE(_id, _name, _parents, _reg, _shift, _width, _gate)	\
+	MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width, _gate, CLK_SET_RATE_PARENT)
+
 #define MUX(_id, _name, _parents, _reg, _shift, _width) {		\
 		.id = _id,						\
 		.name = _name,						\
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 11/14] clk: mediatek: Add hdmi_ref HDMI PHY PLL reference clock output
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
                   ` (9 preceding siblings ...)
  2016-03-08 13:27 ` [PATCH v13 10/14] clk: mediatek: make dpi0_sel propagate rate changes Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 12/14] dt-bindings: hdmi-connector: add DDC I2C bus phandle documentation Philipp Zabel
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

The configurable hdmi_ref output of the PLL block is derived from
the tvdpll_594m clock signal via a configurable PLL post-divider.
It is used as the PLL reference input to the HDMI PHY module.

Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
Acked-by: James Liao <jamesjj.liao@mediatek.com>
Acked-by: Stephen Boyd <sboyd@codeaurora.org>
---
 drivers/clk/mediatek/clk-mt8173.c      | 5 +++++
 include/dt-bindings/clock/mt8173-clk.h | 3 ++-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/clk/mediatek/clk-mt8173.c b/drivers/clk/mediatek/clk-mt8173.c
index 85c0bfc..cf4fcb6 100644
--- a/drivers/clk/mediatek/clk-mt8173.c
+++ b/drivers/clk/mediatek/clk-mt8173.c
@@ -1095,6 +1095,11 @@ static void __init mtk_apmixedsys_init(struct device_node *node)
 		clk_data->clks[cku->id] = clk;
 	}
 
+	clk = clk_register_divider(NULL, "hdmi_ref", "tvdpll_594m", 0,
+				   base + 0x40, 16, 3, CLK_DIVIDER_POWER_OF_TWO,
+				   NULL);
+	clk_data->clks[CLK_APMIXED_HDMI_REF] = clk;
+
 	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
 	if (r)
 		pr_err("%s(): could not register clock provider: %d\n",
diff --git a/include/dt-bindings/clock/mt8173-clk.h b/include/dt-bindings/clock/mt8173-clk.h
index 7956ba1..6094bf7 100644
--- a/include/dt-bindings/clock/mt8173-clk.h
+++ b/include/dt-bindings/clock/mt8173-clk.h
@@ -176,7 +176,8 @@
 #define CLK_APMIXED_LVDSPLL		13
 #define CLK_APMIXED_MSDCPLL2		14
 #define CLK_APMIXED_REF2USB_TX		15
-#define CLK_APMIXED_NR_CLK		16
+#define CLK_APMIXED_HDMI_REF		16
+#define CLK_APMIXED_NR_CLK		17
 
 /* INFRA_SYS */
 
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 12/14] dt-bindings: hdmi-connector: add DDC I2C bus phandle documentation
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
                   ` (10 preceding siblings ...)
  2016-03-08 13:27 ` [PATCH v13 11/14] clk: mediatek: Add hdmi_ref HDMI PHY PLL reference clock output Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 13/14] clk: mediatek: remove hdmitx_dig_cts from TOP clocks Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 14/14] arm64: dts: mt8173-evb: enable HDMI output Philipp Zabel
  13 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

Add an optional ddc-i2c-bus phandle property that points to
an I2C master controller that handles the connector DDC pins.

Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
Acked-by: Rob Herring <robh@kernel.org>
---
 Documentation/devicetree/bindings/display/connector/hdmi-connector.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/display/connector/hdmi-connector.txt b/Documentation/devicetree/bindings/display/connector/hdmi-connector.txt
index acd5668..508aee4 100644
--- a/Documentation/devicetree/bindings/display/connector/hdmi-connector.txt
+++ b/Documentation/devicetree/bindings/display/connector/hdmi-connector.txt
@@ -8,6 +8,7 @@ Required properties:
 Optional properties:
 - label: a symbolic name for the connector
 - hpd-gpios: HPD GPIO number
+- ddc-i2c-bus: phandle link to the I2C controller used for DDC EDID probing
 
 Required nodes:
 - Video port for HDMI input
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 13/14] clk: mediatek: remove hdmitx_dig_cts from TOP clocks
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
                   ` (11 preceding siblings ...)
  2016-03-08 13:27 ` [PATCH v13 12/14] dt-bindings: hdmi-connector: add DDC I2C bus phandle documentation Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
  2016-03-08 13:27 ` [PATCH v13 14/14] arm64: dts: mt8173-evb: enable HDMI output Philipp Zabel
  13 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

The hdmitx_dig_cts clock signal is not a child of tvdpll_445p5m,
but is routed out of the HDMI PHY module.

Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
Acked-by: Stephen Boyd <sboyd@codeaurora.org>
---
 drivers/clk/mediatek/clk-mt8173.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/clk/mediatek/clk-mt8173.c b/drivers/clk/mediatek/clk-mt8173.c
index cf4fcb6..10c9860 100644
--- a/drivers/clk/mediatek/clk-mt8173.c
+++ b/drivers/clk/mediatek/clk-mt8173.c
@@ -61,7 +61,6 @@ static const struct mtk_fixed_factor top_divs[] __initconst = {
 	FACTOR(CLK_TOP_CLKRTC_INT, "clkrtc_int", "clk26m", 1, 793),
 	FACTOR(CLK_TOP_FPC, "fpc_ck", "clk26m", 1, 1),
 
-	FACTOR(CLK_TOP_HDMITX_DIG_CTS, "hdmitx_dig_cts", "tvdpll_445p5m", 1, 3),
 	FACTOR(CLK_TOP_HDMITXPLL_D2, "hdmitxpll_d2", "hdmitx_dig_cts", 1, 2),
 	FACTOR(CLK_TOP_HDMITXPLL_D3, "hdmitxpll_d3", "hdmitx_dig_cts", 1, 3),
 
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v13 14/14] arm64: dts: mt8173-evb: enable HDMI output
  2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
                   ` (12 preceding siblings ...)
  2016-03-08 13:27 ` [PATCH v13 13/14] clk: mediatek: remove hdmitx_dig_cts from TOP clocks Philipp Zabel
@ 2016-03-08 13:27 ` Philipp Zabel
  13 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-08 13:27 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, Jie Qiu,
	Cawa Cheng, YT Shen, Yingjoe Chen, devicetree, Jitao Shi, kernel,
	Pawel Moll, Ian Campbell, Rob Herring, linux-mediatek,
	Matthias Brugger, Paul Bolle, Emil Velikov, Tomasz Figa,
	Kumar Gala

Add an HDMI connector node and enable the devices that are part of the
HDMI display path: cec, dpi0, hdmi_phy, and hdmi0.

Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
---
 arch/arm64/boot/dts/mediatek/mt8173-evb.dts | 38 +++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/arch/arm64/boot/dts/mediatek/mt8173-evb.dts b/arch/arm64/boot/dts/mediatek/mt8173-evb.dts
index e427f04..5ee7f24 100644
--- a/arch/arm64/boot/dts/mediatek/mt8173-evb.dts
+++ b/arch/arm64/boot/dts/mediatek/mt8173-evb.dts
@@ -42,6 +42,44 @@
 		gpio = <&pio 130 GPIO_ACTIVE_HIGH>;
 		enable-active-high;
 	};
+
+	connector {
+		compatible = "hdmi-connector";
+		label = "hdmi";
+		type = "d";
+
+		port {
+			hdmi_connector_in: endpoint {
+				remote-endpoint = <&hdmi0_out>;
+			};
+		};
+	};
+};
+
+&cec {
+	status = "okay";
+};
+
+&dpi0 {
+	status = "okay";
+};
+
+&hdmi_phy {
+	status = "okay";
+};
+
+&hdmi0 {
+	status = "okay";
+
+	ports {
+		port@1 {
+			reg = <1>;
+
+			hdmi0_out: endpoint {
+				remote-endpoint = <&hdmi_connector_in>;
+			};
+		};
+	};
 };
 
 &i2c1 {
-- 
2.7.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v13 06/14] drm/mediatek: Add HDMI support
  2016-03-08 13:27 ` [PATCH v13 06/14] drm/mediatek: Add HDMI support Philipp Zabel
@ 2016-03-09 13:52   ` Daniel Kurtz
       [not found]     ` <CAGS+omD6_hNMVYCnmsHheyxpGZXDf7H+XVHT5C4wVCKDvgpQ7w-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 22+ messages in thread
From: Daniel Kurtz @ 2016-03-09 13:52 UTC (permalink / raw)
  To: Philipp Zabel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, dri-devel,
	Jie Qiu, Cawa Cheng, YT Shen, Yingjoe Chen,
	open list:OPEN FIRMWARE AND...,
	Jitao Shi, Sasha Hauer, Pawel Moll, Ian Campbell, Rob Herring,
	moderated list:ARM/Mediatek SoC support, Matthias Brugger,
	Paul Bolle, Emil Velikov, Tomasz Figa, Kumar Gala

Hi Philipp & Jie,

Sorry I only now had a chance to dig deeper and review the HDMI driver.

Lots of comments inline below...

On Tue, Mar 8, 2016 at 9:27 PM, Philipp Zabel <p.zabel@pengutronix.de> wrote:
> From: Jie Qiu <jie.qiu@mediatek.com>
>
> 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@mediatek.com>
> Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
> ---
>  drivers/gpu/drm/mediatek/Kconfig               |   7 +
>  drivers/gpu/drm/mediatek/Makefile              |   9 +
>  drivers/gpu/drm/mediatek/mtk_cec.c             | 245 ++++++++++
>  drivers/gpu/drm/mediatek/mtk_cec.h             |  25 +
>  drivers/gpu/drm/mediatek/mtk_drm_drv.c         |   1 +
>  drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c    | 579 ++++++++++++++++++++++
>  drivers/gpu/drm/mediatek/mtk_hdmi.c            | 479 ++++++++++++++++++
>  drivers/gpu/drm/mediatek/mtk_hdmi.h            | 221 +++++++++
>  drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c    | 362 ++++++++++++++
>  drivers/gpu/drm/mediatek/mtk_hdmi_hw.c         | 652 +++++++++++++++++++++++++
>  drivers/gpu/drm/mediatek/mtk_hdmi_hw.h         |  73 +++
>  drivers/gpu/drm/mediatek/mtk_hdmi_regs.h       | 221 +++++++++
>  drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c | 505 +++++++++++++++++++
>  13 files changed, 3379 insertions(+)
>  create mode 100644 drivers/gpu/drm/mediatek/mtk_cec.c
>  create mode 100644 drivers/gpu/drm/mediatek/mtk_cec.h
>  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_regs.h
>  create mode 100644 drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c
>
> diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig
> index 0c49a94..e2ff158 100644
> --- a/drivers/gpu/drm/mediatek/Kconfig
> +++ b/drivers/gpu/drm/mediatek/Kconfig
> @@ -12,3 +12,10 @@ config DRM_MEDIATEK
>           The module will be called mediatek-drm
>           This driver provides kernel mode setting and
>           buffer management to userspace.
> +
> +config DRM_MEDIATEK_HDMI
> +       tristate "DRM HDMI Support for Mediatek SoCs"
> +       depends on DRM_MEDIATEK
> +       select 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 5fcf58e..6d53bee 100644
> --- a/drivers/gpu/drm/mediatek/Makefile
> +++ b/drivers/gpu/drm/mediatek/Makefile
> @@ -12,3 +12,12 @@ mediatek-drm-y := mtk_disp_ovl.o \
>                   mtk_dpi.o
>
>  obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
> +
> +mediatek-drm-hdmi-objs := mtk_cec.o \
> +                         mtk_drm_hdmi_drv.o \
> +                         mtk_hdmi.o \
> +                         mtk_hdmi_ddc_drv.o \
> +                         mtk_hdmi_hw.o \
> +                         mtk_mt8173_hdmi_phy.o
> +
> +obj-$(CONFIG_DRM_MEDIATEK_HDMI) += mediatek-drm-hdmi.o
> diff --git a/drivers/gpu/drm/mediatek/mtk_cec.c b/drivers/gpu/drm/mediatek/mtk_cec.c
> new file mode 100644
> index 0000000..cba3647
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_cec.c
> @@ -0,0 +1,245 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu@mediatek.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +
> +#include "mtk_cec.h"
> +
> +#define TR_CONFIG              0x00
> +#define CLEAR_CEC_IRQ                  BIT(15)
> +
> +#define CEC_CKGEN              0x04
> +#define CEC_32K_PDN                    BIT(19)
> +#define PDN                            BIT(16)
> +
> +#define RX_EVENT               0x54
> +#define HDMI_PORD                      BIT(25)
> +#define HDMI_HTPLG                     BIT(24)
> +#define HDMI_PORD_INT_EN               BIT(9)
> +#define HDMI_HTPLG_INT_EN              BIT(8)
> +
> +#define RX_GEN_WD              0x58
> +#define HDMI_PORD_INT_32K_STATUS       BIT(26)
> +#define RX_RISC_INT_32K_STATUS         BIT(25)
> +#define HDMI_HTPLG_INT_32K_STATUS      BIT(24)
> +#define HDMI_PORD_INT_32K_CLR          BIT(18)
> +#define RX_INT_32K_CLR                 BIT(17)
> +#define HDMI_HTPLG_INT_32K_CLR         BIT(16)
> +#define HDMI_PORD_INT_32K_STA_MASK     BIT(10)
> +#define RX_RISC_INT_32K_STA_MASK       BIT(9)
> +#define HDMI_HTPLG_INT_32K_STA_MASK    BIT(8)
> +#define HDMI_PORD_INT_32K_EN           BIT(2)
> +#define RX_INT_32K_EN                  BIT(1)
> +#define HDMI_HTPLG_INT_32K_EN          BIT(0)
> +
> +#define NORMAL_INT_CTRL                0x5C
> +#define HDMI_HTPLG_INT_STA             BIT(0)
> +#define HDMI_PORD_INT_STA              BIT(1)
> +#define HDMI_HTPLG_INT_CLR             BIT(16)
> +#define HDMI_PORD_INT_CLR              BIT(17)
> +#define HDMI_FULL_INT_CLR              BIT(20)
> +
> +struct mtk_cec {
> +       void __iomem *regs;
> +       struct clk *clk;
> +       int irq;
> +       bool hpd;
> +       void (*hpd_event)(bool hpd, struct device *dev);
> +       struct device *hdmi_dev;
> +};
> +
> +static void mtk_cec_mask(struct mtk_cec *cec, unsigned int offset,
> +                        unsigned int val, unsigned int mask)
> +{
> +       u32 tmp = readl(cec->regs + offset) & ~mask;
> +
> +       tmp |= val & mask;
> +       writel(val, cec->regs + offset);
> +}
> +
> +void mtk_cec_set_hpd_event(struct device *dev,
> +                          void (*hpd_event)(bool hpd, struct device *dev),
> +                          struct device *hdmi_dev)
> +{
> +       struct mtk_cec *cec = dev_get_drvdata(dev);
> +
> +       cec->hdmi_dev = hdmi_dev;
> +       cec->hpd_event = hpd_event;

Lock this so to prevent race with the irq?

> +}
> +
> +bool mtk_cec_hpd_high(struct device *dev)
> +{
> +       struct mtk_cec *cec = dev_get_drvdata(dev);
> +       unsigned int status;
> +
> +       status = readl(cec->regs + RX_EVENT);
> +
> +       return (status & (HDMI_PORD | HDMI_HTPLG)) == (HDMI_PORD | HDMI_HTPLG);
> +}
> +
> +int mtk_cec_irq(struct device *dev)

AFAICT, this function is never used.

> +{
> +       struct mtk_cec *cec = dev_get_drvdata(dev);
> +
> +       return cec->irq;
> +}
> +
> +static void mtk_cec_htplg_irq_enable(struct mtk_cec *cec)
> +{
> +       mtk_cec_mask(cec, CEC_CKGEN, 0, PDN);
> +       mtk_cec_mask(cec, CEC_CKGEN, CEC_32K_PDN, CEC_32K_PDN);
> +       mtk_cec_mask(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR,
> +                    HDMI_PORD_INT_32K_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, RX_INT_32K_CLR, RX_INT_32K_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, HDMI_HTPLG_INT_32K_CLR,
> +                    HDMI_HTPLG_INT_32K_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_EN);
> +       mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_EN);
> +       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_EN);

This is a bit wasteful.  Can you just clear all of these bits in a single write?
(this applies to this entire file).

> +
> +       mtk_cec_mask(cec, RX_EVENT, HDMI_PORD_INT_EN, HDMI_PORD_INT_EN);
> +       mtk_cec_mask(cec, RX_EVENT, HDMI_HTPLG_INT_EN, HDMI_HTPLG_INT_EN);
> +}
> +
> +static void mtk_cec_htplg_irq_disable(struct mtk_cec *cec)
> +{

Why does irq_enable do so much more work than irq_disable?

> +       mtk_cec_mask(cec, RX_EVENT, 0, HDMI_PORD_INT_EN);
> +       mtk_cec_mask(cec, RX_EVENT, 0, HDMI_HTPLG_INT_EN);
> +}
> +
> +static void mtk_cec_clear_htplg_irq(struct mtk_cec *cec)
> +{
> +       mtk_cec_mask(cec, TR_CONFIG, CLEAR_CEC_IRQ, CLEAR_CEC_IRQ);
> +       mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_HTPLG_INT_CLR,
> +                    HDMI_HTPLG_INT_CLR);
> +       mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_PORD_INT_CLR,
> +                    HDMI_PORD_INT_CLR);
> +       mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_FULL_INT_CLR,
> +                    HDMI_FULL_INT_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR,
> +                    HDMI_PORD_INT_32K_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, RX_INT_32K_CLR, RX_INT_32K_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, HDMI_HTPLG_INT_32K_CLR,
> +                    HDMI_HTPLG_INT_32K_CLR);
> +       udelay(5);

Do you really need this delay in the middle of the isr handler?

> +       mtk_cec_mask(cec, NORMAL_INT_CTRL, 0, HDMI_HTPLG_INT_CLR);
> +       mtk_cec_mask(cec, NORMAL_INT_CTRL, 0, HDMI_PORD_INT_CLR);
> +       mtk_cec_mask(cec, TR_CONFIG, 0, CLEAR_CEC_IRQ);
> +       mtk_cec_mask(cec, NORMAL_INT_CTRL, 0, HDMI_FULL_INT_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_CLR);
> +       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_CLR);
> +}
> +
> +static irqreturn_t mtk_cec_htplg_isr_thread(int irq, void *arg)
> +{
> +       struct device *dev = arg;
> +       struct mtk_cec *cec = dev_get_drvdata(dev);
> +       bool hpd;
> +
> +       mtk_cec_clear_htplg_irq(cec);
> +       hpd = mtk_cec_hpd_high(dev);
> +
> +       if (cec->hpd != hpd) {
> +               dev_info(dev, "hotplug event!,cur hpd = %d, hpd = %d\n",
> +                        cec->hpd, hpd);

dev_dbg if anything

> +               cec->hpd = hpd;
> +               if (cec->hpd_event)
> +                       cec->hpd_event(hpd, cec->hdmi_dev);
> +       }
> +       return IRQ_HANDLED;
> +}
> +
> +static int mtk_cec_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct mtk_cec *cec;
> +       struct resource *res;
> +       int ret;
> +
> +       cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL);
> +       if (!cec)
> +               return -ENOMEM;
> +
> +       platform_set_drvdata(pdev, cec);
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       cec->regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(cec->regs)) {
> +               ret = PTR_ERR(cec->regs);
> +               dev_err(dev, "Failed to ioremap cec: %d\n", ret);
> +               return ret;
> +       }
> +
> +       cec->clk = devm_clk_get(dev, NULL);
> +       if (IS_ERR(cec->clk)) {
> +               ret = PTR_ERR(cec->clk);
> +               dev_err(dev, "Failed to get cec clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       cec->irq = platform_get_irq(pdev, 0);
> +       if (cec->irq < 0) {
> +               dev_err(dev, "Failed to get cec irq: %d\n", cec->irq);
> +               return cec->irq;
> +       }
> +
> +       ret = devm_request_threaded_irq(dev, cec->irq, NULL,
> +                                       mtk_cec_htplg_isr_thread,
> +                                       IRQF_SHARED | IRQF_TRIGGER_LOW |
> +                                       IRQF_ONESHOT, "hdmi hpd", dev);
> +       if (ret) {
> +               dev_err(dev, "Failed to register cec irq: %d\n", ret);
> +               return ret;
> +       }
> +
> +       ret = clk_prepare_enable(cec->clk);
> +       if (ret) {
> +               dev_err(dev, "Failed to enable cec clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       mtk_cec_htplg_irq_enable(cec);
> +
> +       return 0;
> +}
> +
> +static int mtk_cec_remove(struct platform_device *pdev)
> +{
> +       struct mtk_cec *cec = platform_get_drvdata(pdev);
> +
> +       mtk_cec_htplg_irq_disable(cec);
> +       clk_disable_unprepare(cec->clk);
> +       return 0;
> +}
> +
> +static const struct of_device_id mtk_cec_of_ids[] = {
> +       { .compatible = "mediatek,mt8173-cec", },
> +       {}
> +};
> +
> +struct platform_driver mtk_cec_driver = {
> +       .probe = mtk_cec_probe,
> +       .remove = mtk_cec_remove,
> +       .driver = {
> +               .name = "mediatek-cec",
> +               .of_match_table = mtk_cec_of_ids,
> +       },
> +};
> diff --git a/drivers/gpu/drm/mediatek/mtk_cec.h b/drivers/gpu/drm/mediatek/mtk_cec.h
> new file mode 100644
> index 0000000..fe6e8fd
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_cec.h
> @@ -0,0 +1,25 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu@mediatek.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#ifndef _MTK_CEC_H
> +#define _MTK_CEC_H
> +
> +struct device;
> +
> +void mtk_cec_set_hpd_event(struct device *dev,
> +                          void (*hotplug_event)(bool hpd, struct device *dev),
> +                          struct device *hdmi_dev);
> +bool mtk_cec_hpd_high(struct device *dev);
> +int mtk_cec_irq(struct device *dev);
> +
> +#endif /* _MTK_CEC_H */
> diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> index a69958c..80056d1 100644
> --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> @@ -23,6 +23,7 @@
>  #include <linux/of_platform.h>
>  #include <linux/pm_runtime.h>
>
> +#include "mtk_cec.h"
>  #include "mtk_drm_crtc.h"
>  #include "mtk_drm_ddp.h"
>  #include "mtk_drm_ddp_comp.h"
> 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..ff661e0
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c
> @@ -0,0 +1,579 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu@mediatek.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <linux/clk.h>
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/of_platform.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_graph.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include "mtk_cec.h"
> +#include "mtk_hdmi.h"
> +#include "mtk_hdmi_hw.h"
> +
> +static const char * const mtk_hdmi_clk_names[MTK_HDMI_CLK_COUNT] = {
> +       [MTK_HDMI_CLK_HDMI_PIXEL] = "pixel",
> +       [MTK_HDMI_CLK_HDMI_PLL] = "pll",
> +       [MTK_HDMI_CLK_AUD_BCLK] = "bclk",
> +       [MTK_HDMI_CLK_AUD_SPDIF] = "spdif",
> +};
> +
> +static const enum mtk_hdmi_clk_id mtk_hdmi_enable_clocks[] = {
> +       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_enable_audio(struct mtk_hdmi *hdmi)
> +{
> +       int ret;
> +
> +       ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]);
> +       if (ret)
> +               return ret;
> +
> +       ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_AUD_SPDIF]);
> +       if (ret)
> +               goto err;
> +
> +       return 0;
> +err:
> +       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]);
> +       return ret;
> +}
> +
> +static void mtk_hdmi_clk_disable_audio(struct mtk_hdmi *hdmi)
> +{
> +       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]);
> +       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_SPDIF]);
> +}
> +
> +static enum drm_connector_status hdmi_conn_detect(struct drm_connector *conn,
> +                                                 bool force)
> +{
> +       struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
> +
> +       return mtk_hdmi_hpd_high(hdmi) ?
> +              connector_status_connected : connector_status_disconnected;
> +}
> +
> +static void hdmi_conn_destroy(struct drm_connector *conn)
> +{
> +       struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
> +
> +       mtk_cec_set_hpd_event(hdmi->cec_dev, NULL, NULL);
> +
> +       drm_connector_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_monitor_audio(edid);
> +
> +       drm_mode_connector_update_edid_property(conn, edid);
> +
> +       ret = drm_add_edid_modes(conn, edid);
> +       drm_edid_to_eld(conn, edid);
> +       kfree(edid);
> +       return ret;
> +}
> +
> +static int mtk_hdmi_conn_mode_valid(struct drm_connector *conn,
> +                                   struct drm_display_mode *mode)
> +{
> +       struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
> +
> +       dev_dbg(hdmi->dev, "xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n",
> +               mode->hdisplay, mode->vdisplay, mode->vrefresh,
> +               !!(mode->flags & DRM_MODE_FLAG_INTERLACE), mode->clock * 1000);
> +
> +       if (hdmi->bridge.next) {
> +               struct drm_display_mode adjusted_mode;
> +
> +               drm_mode_copy(&adjusted_mode, mode);
> +               if (!drm_bridge_mode_fixup(hdmi->bridge.next, mode,
> +                                          &adjusted_mode))
> +                       return MODE_BAD;
> +       }
> +
> +       if (mode->clock < 27000)
> +               return MODE_CLOCK_LOW;
> +       if (mode->clock > 297000)
> +               return MODE_CLOCK_HIGH;
> +
> +       return drm_mode_validate_size(mode, 0x1fff, 0x1fff);
> +}
> +
> +static struct drm_encoder *mtk_hdmi_conn_best_enc(struct drm_connector *conn)
> +{
> +       struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn);
> +
> +       return hdmi->bridge.encoder;
> +}
> +
> +static const struct drm_connector_funcs mtk_hdmi_connector_funcs = {
> +       .dpms = drm_atomic_helper_connector_dpms,
> +       .detect = hdmi_conn_detect,
> +       .fill_modes = drm_helper_probe_single_connector_modes,
> +       .destroy = hdmi_conn_destroy,
> +       .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,
> +};
> +
> +static void mtk_hdmi_hpd_event(bool hpd, struct device *dev)
> +{
> +       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
> +
> +       if (hdmi && hdmi->bridge.encoder && hdmi->bridge.encoder->dev)
> +               drm_helper_hpd_irq_event(hdmi->bridge.encoder->dev);
> +}
> +
> +/*
> + * Bridge callbacks
> + */
> +
> +static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
> +{
> +       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
> +       int ret;
> +
> +       ret = drm_connector_init(bridge->encoder->dev, &hdmi->conn,
> +                                &mtk_hdmi_connector_funcs,
> +                                DRM_MODE_CONNECTOR_HDMIA);
> +       if (ret) {
> +               dev_err(hdmi->dev, "Failed to initialize connector: %d\n", ret);
> +               return ret;
> +       }
> +       drm_connector_helper_add(&hdmi->conn, &mtk_hdmi_connector_helper_funcs);
> +
> +       hdmi->conn.polled = DRM_CONNECTOR_POLL_HPD;
> +       hdmi->conn.interlace_allowed = true;
> +       hdmi->conn.doublescan_allowed = false;
> +
> +       ret = drm_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;
> +               }
> +       }
> +
> +       mtk_cec_set_hpd_event(hdmi->cec_dev, mtk_hdmi_hpd_event, hdmi->dev);
> +
> +       return 0;
> +}
> +
> +static bool mtk_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
> +                                      const struct drm_display_mode *mode,
> +                                      struct drm_display_mode *adjusted_mode)
> +{
> +       return true;
> +}
> +
> +static void mtk_hdmi_bridge_disable(struct drm_bridge *bridge)
> +{
> +       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
> +
> +       phy_power_off(hdmi->phy);
> +       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]);
> +       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]);

As far as I can tell, __drm_helper_disable_unused_functions() doesn't
check if crtc/encoder/bridge are disabled before calling the
->*en/disable*() callbacks.

So, these clk_disable_unprepare() may be called with the HDMI already disabled,
trigerring their WARN_ON().

So, perhaps we also need to track enabled/disabled state separately here in
the driver.

> +}
> +
> +static void mtk_hdmi_bridge_post_disable(struct drm_bridge *bridge)
> +{
> +       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
> +
> +       mtk_hdmi_power_off(hdmi);
> +}
> +
> +static void mtk_hdmi_bridge_mode_set(struct drm_bridge *bridge,
> +                                    struct drm_display_mode *mode,
> +                                    struct drm_display_mode *adjusted_mode)
> +{
> +       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
> +
> +       dev_dbg(hdmi->dev, "cur info: name:%s, hdisplay:%d\n",
> +               adjusted_mode->name, adjusted_mode->hdisplay);
> +       dev_dbg(hdmi->dev, "hsync_start:%d,hsync_end:%d, htotal:%d",
> +               adjusted_mode->hsync_start, adjusted_mode->hsync_end,
> +               adjusted_mode->htotal);
> +       dev_dbg(hdmi->dev, "hskew:%d, vdisplay:%d\n",
> +               adjusted_mode->hskew, adjusted_mode->vdisplay);
> +       dev_dbg(hdmi->dev, "vsync_start:%d, vsync_end:%d, vtotal:%d",
> +               adjusted_mode->vsync_start, adjusted_mode->vsync_end,
> +               adjusted_mode->vtotal);
> +       dev_dbg(hdmi->dev, "vscan:%d, flag:%d\n",
> +               adjusted_mode->vscan, adjusted_mode->flags);
> +
> +       drm_mode_copy(&hdmi->mode, adjusted_mode);
> +}
> +
> +static void mtk_hdmi_bridge_pre_enable(struct drm_bridge *bridge)
> +{
> +       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
> +
> +       mtk_hdmi_power_on(hdmi);
> +}
> +
> +static void mtk_hdmi_bridge_enable(struct drm_bridge *bridge)
> +{
> +       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
> +
> +       mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode);
> +       clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]);
> +       clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]);
> +       phy_power_on(hdmi->phy);
> +}
> +
> +static const struct drm_bridge_funcs mtk_hdmi_bridge_funcs = {
> +       .attach = mtk_hdmi_bridge_attach,
> +       .mode_fixup = mtk_hdmi_bridge_mode_fixup,
> +       .disable = mtk_hdmi_bridge_disable,
> +       .post_disable = mtk_hdmi_bridge_post_disable,
> +       .mode_set = mtk_hdmi_bridge_mode_set,
> +       .pre_enable = mtk_hdmi_bridge_pre_enable,
> +       .enable = mtk_hdmi_bridge_enable,
> +};
> +
> +static int mtk_hdmi_dt_parse_pdata(struct mtk_hdmi *hdmi,
> +                                  struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct device_node *np = dev->of_node;
> +       struct device_node *cec_np, *port, *ep, *remote, *i2c_np;
> +       struct platform_device *cec_pdev;
> +       struct regmap *regmap;
> +       struct resource *mem;
> +       int ret;
> +
> +       ret = mtk_hdmi_get_all_clk(hdmi, np);
> +       if (ret) {
> +               dev_err(dev, "Failed to get clocks: %d\n", ret);
> +               return ret;
> +       }
> +
> +       /* The CEC module handles HDMI hotplug detection */
> +       cec_np = of_find_compatible_node(np->parent, NULL,
> +                                        "mediatek,mt8173-cec");
> +       if (!cec_np) {
> +               dev_err(dev, "Failed to find CEC node\n");
> +               return -EINVAL;
> +       }
> +
> +       cec_pdev = of_find_device_by_node(cec_np);
> +       if (!cec_pdev) {
> +               dev_err(hdmi->dev, "Waiting for CEC device %s\n",
> +                       cec_np->full_name);
> +               return -EPROBE_DEFER;
> +       }
> +       hdmi->cec_dev = &cec_pdev->dev;
> +
> +       /*
> +        * The mediatek,syscon-hdmi property contains a phandle link to the
> +        * MMSYS_CONFIG device and the register offset of the HDMI_SYS_CFG
> +        * registers it contains.
> +        */
> +       regmap = syscon_regmap_lookup_by_phandle(np, "mediatek,syscon-hdmi");
> +       ret = of_property_read_u32_index(np, "mediatek,syscon-hdmi", 1,
> +                                        &hdmi->sys_offset);
> +       if (IS_ERR(regmap))
> +               ret = PTR_ERR(regmap);
> +       if (ret) {
> +               ret = PTR_ERR(regmap);
> +               dev_err(dev,
> +                       "Failed to get system configuration registers: %d\n",
> +                       ret);
> +               return ret;
> +       }
> +       hdmi->sys_regmap = regmap;
> +
> +       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       hdmi->regs = devm_ioremap_resource(dev, mem);
> +       if (IS_ERR(hdmi->regs)) {
> +               dev_err(dev, "Failed to ioremap hdmi_shell: %ld\n",
> +                       PTR_ERR(hdmi->regs));

What is hdmi_shell?
In any case, I don't think you need to print anything here.

> +               return PTR_ERR(hdmi->regs);
> +       }
> +
> +       port = of_graph_get_port_by_id(np, 1);
> +       if (!port) {
> +               dev_err(dev, "Missing output port node\n");
> +               return -EINVAL;
> +       }
> +
> +       ep = of_get_child_by_name(port, "endpoint");
> +       if (!ep) {
> +               dev_err(dev, "Missing endpoint node in port %s\n",
> +                       port->full_name);
> +               of_node_put(port);
> +               return -EINVAL;
> +       }
> +       of_node_put(port);
> +
> +       remote = of_graph_get_remote_port_parent(ep);
> +       if (!remote) {
> +               dev_err(dev, "Missing connector/bridge node for endpoint %s\n",
> +                       ep->full_name);
> +               of_node_put(ep);
> +               return -EINVAL;
> +       }
> +       of_node_put(ep);
> +
> +       if (!of_device_is_compatible(remote, "hdmi-connector")) {
> +               hdmi->bridge.next = of_drm_find_bridge(remote);
> +               if (!hdmi->bridge.next) {
> +                       dev_err(dev, "Waiting for external bridge\n");
> +                       of_node_put(remote);
> +                       return -EPROBE_DEFER;
> +               }
> +       }
> +
> +       i2c_np = of_parse_phandle(remote, "ddc-i2c-bus", 0);
> +       if (!i2c_np) {
> +               dev_err(dev, "Failed to find ddc-i2c-bus node in %s\n",
> +                       remote->full_name);
> +               of_node_put(remote);
> +               return -EINVAL;
> +       }
> +       of_node_put(remote);
> +
> +       hdmi->ddc_adpt = of_find_i2c_adapter_by_node(i2c_np);
> +       if (!hdmi->ddc_adpt) {
> +               dev_err(dev, "Failed to get ddc i2c adapter by node\n");
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static int mtk_drm_hdmi_probe(struct platform_device *pdev)
> +{
> +       struct mtk_hdmi *hdmi;
> +       struct device *dev = &pdev->dev;
> +       int ret;
> +
> +       hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
> +       if (!hdmi)
> +               return -ENOMEM;
> +
> +       hdmi->dev = dev;
> +
> +       ret = mtk_hdmi_dt_parse_pdata(hdmi, pdev);
> +       if (ret)
> +               return ret;
> +
> +       hdmi->phy = devm_phy_get(dev, "hdmi");
> +       if (IS_ERR(hdmi->phy)) {
> +               ret = PTR_ERR(hdmi->phy);
> +               dev_err(dev, "Failed to get HDMI PHY: %d\n", ret);
> +               return ret;
> +       }
> +
> +       platform_set_drvdata(pdev, hdmi);
> +
> +       ret = mtk_hdmi_output_init(hdmi);
> +       if (ret) {
> +               dev_err(dev, "Failed to initialize hdmi output\n");
> +               return ret;
> +       }
> +
> +       hdmi->bridge.funcs = &mtk_hdmi_bridge_funcs;
> +       hdmi->bridge.of_node = pdev->dev.of_node;
> +       ret = drm_bridge_add(&hdmi->bridge);
> +       if (ret) {
> +               dev_err(dev, "failed to add bridge, ret = %d\n", ret);
> +               return ret;
> +       }
> +
> +       ret = mtk_hdmi_clk_enable_audio(hdmi);
> +       if (ret) {
> +               dev_err(dev, "Failed to enable audio clocks: %d\n", ret);
> +               goto err_bridge_remove;
> +       }
> +
> +       dev_dbg(dev, "mediatek hdmi probe success\n");
> +       return 0;
> +
> +err_bridge_remove:
> +       drm_bridge_remove(&hdmi->bridge);
> +       return ret;
> +}
> +
> +static int mtk_drm_hdmi_remove(struct platform_device *pdev)
> +{
> +       struct mtk_hdmi *hdmi = platform_get_drvdata(pdev);
> +
> +       drm_bridge_remove(&hdmi->bridge);
> +       platform_device_unregister(hdmi->audio_pdev);

audio_pdev is not set in this patch.
Is there more audio stuff that should be removed from this patch?

> +       platform_set_drvdata(pdev, NULL);

I don't think this is necessary.

> +       mtk_hdmi_clk_disable_audio(hdmi);
> +       return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int mtk_hdmi_suspend(struct device *dev)
> +{
> +       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
> +
> +       mtk_hdmi_power_off(hdmi);
> +       mtk_hdmi_clk_disable_audio(hdmi);
> +       phy_power_off(hdmi->phy);
> +       dev_dbg(dev, "hdmi suspend success!\n");
> +       return 0;
> +}
> +
> +static int mtk_hdmi_resume(struct device *dev)
> +{
> +       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
> +       int ret = 0;
> +
> +       ret = mtk_hdmi_clk_enable_audio(hdmi);
> +       if (ret) {
> +               dev_err(dev, "hdmi resume failed!\n");
> +               return ret;
> +       }
> +
> +       mtk_hdmi_power_on(hdmi);
> +       mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode);
> +       phy_power_on(hdmi->phy);
> +       dev_dbg(dev, "hdmi resume success!\n");
> +       return 0;
> +}
> +#endif
> +static SIMPLE_DEV_PM_OPS(mtk_hdmi_pm_ops,
> +                        mtk_hdmi_suspend, mtk_hdmi_resume);

I do not think these suspend/resume ops are needed.
The MTK DRM driver turn off connectors at suspend, and re-enables them
at resume.

> +
> +static const struct of_device_id mtk_drm_hdmi_of_ids[] = {
> +       { .compatible = "mediatek,mt8173-hdmi", },
> +       {}
> +};
> +
> +static struct platform_driver mtk_hdmi_driver = {
> +       .probe = mtk_drm_hdmi_probe,
> +       .remove = mtk_drm_hdmi_remove,
> +       .driver = {
> +               .name = "mediatek-drm-hdmi",
> +               .of_match_table = mtk_drm_hdmi_of_ids,
> +               .pm = &mtk_hdmi_pm_ops,
> +       },
> +};
> +
> +static struct platform_driver * const mtk_hdmi_drivers[] = {
> +       &mtk_hdmi_phy_driver,
> +       &mtk_hdmi_ddc_driver,
> +       &mtk_cec_driver,
> +       &mtk_hdmi_driver,
> +};
> +
> +static int __init mtk_hdmitx_init(void)
> +{
> +       int ret;
> +       int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(mtk_hdmi_drivers); i++) {
> +               ret = platform_driver_register(mtk_hdmi_drivers[i]);
> +               if (ret < 0) {
> +                       pr_err("Failed to register %s driver: %d\n",
> +                              mtk_hdmi_drivers[i]->driver.name, ret);
> +                       goto err;
> +               }
> +       }
> +
> +       return 0;
> +
> +err:
> +       while (--i >= 0)
> +               platform_driver_unregister(mtk_hdmi_drivers[i]);
> +
> +       return ret;
> +}
> +
> +static void __exit mtk_hdmitx_exit(void)
> +{
> +       int i;
> +
> +       for (i = ARRAY_SIZE(mtk_hdmi_drivers) - 1; i >= 0; i--)
> +               platform_driver_unregister(mtk_hdmi_drivers[i]);
> +}
> +
> +module_init(mtk_hdmitx_init);
> +module_exit(mtk_hdmitx_exit);
> +
> +MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>");
> +MODULE_DESCRIPTION("MediaTek HDMI Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c
> new file mode 100644
> index 0000000..30ec7b5
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c

Hmm... what is the value in splitting this out into its own separate mtk_hdmi.c?

> @@ -0,0 +1,479 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu@mediatek.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#include <drm/drm_edid.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/phy/phy.h>
> +#include "mtk_cec.h"
> +#include "mtk_hdmi.h"
> +#include "mtk_hdmi_hw.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)
> +{
> +       unsigned long rate;
> +       int ret;
> +
> +       /* The DPI driver already should have set TVDPLL to the correct rate */
> +       ret = clk_set_rate(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL], clock);
> +       if (ret) {
> +               dev_err(hdmi->dev, "Failed to set PLL to %u Hz: %d\n", clock,
> +                       ret);
> +               return ret;
> +       }
> +
> +       rate = clk_get_rate(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]);
> +
> +       if (DIV_ROUND_CLOSEST(rate, 1000) != DIV_ROUND_CLOSEST(clock, 1000))
> +               dev_warn(hdmi->dev, "Want PLL %u Hz, got %lu Hz\n", clock,
> +                        rate);
> +       else
> +               dev_dbg(hdmi->dev, "Want PLL %u Hz, got %lu Hz\n", clock, rate);
> +
> +       mtk_hdmi_hw_config_sys(hdmi);
> +       mtk_hdmi_hw_set_deep_color_mode(hdmi);
> +       return 0;
> +}
> +
> +static void mtk_hdmi_video_set_display_mode(struct mtk_hdmi *hdmi,
> +                                           struct drm_display_mode *mode)
> +{
> +       mtk_hdmi_hw_reset(hdmi);
> +       mtk_hdmi_hw_enable_notice(hdmi, true);
> +       mtk_hdmi_hw_write_int_mask(hdmi, 0xff);
> +       mtk_hdmi_hw_enable_dvi_mode(hdmi, hdmi->dvi_mode);
> +       mtk_hdmi_hw_ncts_auto_write_enable(hdmi, true);
> +
> +       mtk_hdmi_hw_msic_setting(hdmi, mode);
> +}
> +
> +static int mtk_hdmi_aud_enable_packet(struct mtk_hdmi *hdmi, bool enable)
> +{
> +       mtk_hdmi_hw_send_aud_packet(hdmi, enable);
> +       return 0;
> +}
> +
> +static int mtk_hdmi_aud_on_off_hw_ncts(struct mtk_hdmi *hdmi, bool on)
> +{
> +       mtk_hdmi_hw_ncts_enable(hdmi, on);
> +       return 0;
> +}
> +
> +static int mtk_hdmi_aud_set_input(struct mtk_hdmi *hdmi)
> +{
> +       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);

These three all change the same register.  Combine them into a single helper
function that just writes GRL_AUDIO_CFG once.

> +
> +       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); */

why is this commented out?

> +                       mtk_hdmi_hw_aud_set_mclk(
> +                       hdmi,
> +                       hdmi->aud_param.aud_mclk);

indentation is funny here

> +                       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;

indentation is funny here

> +                       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;

indentation is funny here

> +                       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;

indentation is funny here

> +                       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);

These clauses all seem to do the same thing; only the mclk parameter is
different.  Can you refactor them into a helper function?

> +                       break;
> +               default:
> +                       break;
> +               }
> +       }
> +       mtk_hdmi_hw_aud_set_ncts(hdmi, 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);

indentation is funny here

> +       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: %zd\n", err);
> +               return err;
> +       }
> +
> +       err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
> +       if (err < 0) {
> +               dev_err(hdmi->dev, "Failed to pack AVI infoframe: %zd\n", err);
> +               return err;
> +       }
> +
> +       mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
> +       return 0;
> +}
> +
> +static int mtk_hdmi_setup_spd_infoframe(struct mtk_hdmi *hdmi,
> +                                       const char *vendor,
> +                                       const char *product)
> +{
> +       struct hdmi_spd_infoframe frame;
> +       u8 buffer[29];
> +       ssize_t err;
> +
> +       err = hdmi_spd_infoframe_init(&frame, vendor, product);
> +       if (err < 0) {
> +               dev_err(hdmi->dev, "Failed to initialize SPD infoframe: %zd\n",
> +                       err);
> +               return err;
> +       }
> +
> +       err = hdmi_spd_infoframe_pack(&frame, buffer, sizeof(buffer));
> +       if (err < 0) {
> +               dev_err(hdmi->dev, "Failed to pack SDP infoframe: %zd\n", err);
> +               return err;
> +       }
> +
> +       mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
> +       return 0;
> +}
> +
> +static int mtk_hdmi_setup_audio_infoframe(struct mtk_hdmi *hdmi)
> +{
> +       struct hdmi_audio_infoframe frame;
> +       u8 buffer[14];
> +       ssize_t err;
> +
> +       err = hdmi_audio_infoframe_init(&frame);
> +       if (err < 0) {
> +               dev_err(hdmi->dev, "Failed to setup audio infoframe: %zd\n",
> +                       err);
> +               return err;
> +       }
> +
> +       frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
> +       frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
> +       frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
> +       frame.channels =
> +           mtk_hdmi_aud_get_chnl_count(
> +           hdmi->aud_param.aud_input_chan_type);

indentation is funny

> +
> +       err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
> +       if (err < 0) {
> +               dev_err(hdmi->dev, "Failed to pack audio infoframe: %zd\n",
> +                       err);
> +               return err;
> +       }
> +
> +       mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
> +       return 0;
> +}
> +
> +static int mtk_hdmi_setup_vendor_specific_infoframe(struct mtk_hdmi *hdmi,
> +                                               struct drm_display_mode *mode)
> +{
> +       struct hdmi_vendor_infoframe frame;
> +       u8 buffer[10];
> +       ssize_t err;
> +
> +       err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, mode);
> +       if (err) {
> +               dev_err(hdmi->dev,
> +                       "Failed to get vendor infoframe from mode: %zd\n", err);
> +               return err;
> +       }
> +
> +       err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer));
> +       if (err) {
> +               dev_err(hdmi->dev, "Failed to pack vendor infoframe: %zd\n",
> +                       err);
> +               return err;
> +       }
> +
> +       mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer));
> +       return 0;
> +}
> +
> +int mtk_hdmi_hpd_high(struct mtk_hdmi *hdmi)
> +{
> +       return hdmi->cec_dev ? mtk_cec_hpd_high(hdmi->cec_dev) : false;

I don't think we would get this far if cec_dev was NULL.

> +}
> +
> +int mtk_hdmi_output_init(struct mtk_hdmi *hdmi)
> +{
> +       struct hdmi_audio_param *aud_param = &hdmi->aud_param;
> +
> +       if (hdmi->init)
> +               return -EINVAL;

This check is not needed; this function is only called once, during probe.
In fact, I don't think the "->init" field is needed at all.

> +
> +       hdmi->csp = HDMI_COLORSPACE_RGB;
> +       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);
> +}
> +
> +void mtk_hdmi_power_off(struct mtk_hdmi *hdmi)
> +{
> +       mtk_hdmi_hw_1p4_version_enable(hdmi, true);
> +       mtk_hdmi_hw_make_reg_writable(hdmi, false);
> +}
> +
> +int mtk_hdmi_output_set_display_mode(struct mtk_hdmi *hdmi,
> +                                    struct drm_display_mode *mode)
> +{
> +       int ret;
> +
> +       if (!hdmi->init) {

Is this possible?

> +               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);
> +       if (ret) {
> +               dev_err(hdmi->dev, "Failed to set vpll: %d\n", ret);

cleanup on error?

> +               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");

what?  No.  The "product" should refer to the MTK HDMI block.

> +       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..9403915
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.h
> @@ -0,0 +1,221 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu@mediatek.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#ifndef _MTK_HDMI_CTRL_H
> +#define _MTK_HDMI_CTRL_H
> +
> +#include <drm/drm_crtc.h>
> +#include <linux/hdmi.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/types.h>
> +
> +struct clk;
> +struct device;
> +struct device_node;
> +struct i2c_adapter;
> +struct platform_device;
> +struct phy;
> +struct regmap;
> +
> +enum mtk_hdmi_clk_id {
> +       MTK_HDMI_CLK_HDMI_PIXEL,
> +       MTK_HDMI_CLK_HDMI_PLL,
> +       MTK_HDMI_CLK_AUD_BCLK,
> +       MTK_HDMI_CLK_AUD_SPDIF,
> +       MTK_HDMI_CLK_COUNT
> +};
> +
> +enum hdmi_aud_input_type {
> +       HDMI_AUD_INPUT_I2S = 0,
> +       HDMI_AUD_INPUT_SPDIF,
> +};
> +
> +enum hdmi_aud_i2s_fmt {
> +       HDMI_I2S_MODE_RJT_24BIT = 0,
> +       HDMI_I2S_MODE_RJT_16BIT,
> +       HDMI_I2S_MODE_LJT_24BIT,
> +       HDMI_I2S_MODE_LJT_16BIT,
> +       HDMI_I2S_MODE_I2S_24BIT,
> +       HDMI_I2S_MODE_I2S_16BIT
> +};
> +
> +enum hdmi_aud_mclk {
> +       HDMI_AUD_MCLK_128FS,
> +       HDMI_AUD_MCLK_192FS,
> +       HDMI_AUD_MCLK_256FS,
> +       HDMI_AUD_MCLK_384FS,
> +       HDMI_AUD_MCLK_512FS,
> +       HDMI_AUD_MCLK_768FS,
> +       HDMI_AUD_MCLK_1152FS,
> +};
> +
> +enum hdmi_aud_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 drm_bridge bridge;
> +       struct drm_connector conn;
> +       struct device *dev;
> +       struct phy *phy;
> +       struct device *cec_dev;
> +       struct i2c_adapter *ddc_adpt;
> +       struct clk *clk[MTK_HDMI_CLK_COUNT];
> +#if defined(CONFIG_DEBUG_FS)
> +       struct dentry *debugfs;
> +#endif

Remove all of the debugfs stuff from this patch, since it isn't implemented.

> +       struct platform_device *audio_pdev;
> +       struct drm_display_mode mode;
> +       bool dvi_mode;
> +       u32 min_clock;
> +       u32 max_clock;
> +       u32 max_hdisplay;
> +       u32 max_vdisplay;
> +       u32 ibias;
> +       u32 ibias_up;
> +       struct regmap *sys_regmap;
> +       unsigned int sys_offset;
> +       void __iomem *regs;
> +       bool init;
> +       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);
> +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);
> +#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_cec_driver;
> +extern struct platform_driver mtk_hdmi_ddc_driver;
> +extern struct platform_driver mtk_hdmi_phy_driver;
> +#endif /* _MTK_HDMI_CTRL_H */
> diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_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@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 {

Throughout this driver, I think we should:

s/mtk_hdmi_i2c/mtk_hdmi_ddc

> +       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;
> +       }

Use iopoll?


> +}
> +
> +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);

nit, no space before ':'

> +               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");

nit: don't really need to print anything here.

> +               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.class = I2C_CLASS_DDC;

> +       i2c->adap.algo = &mtk_hdmi_i2c_algorithm;
> +       i2c->adap.retries = 3;

why set this?

> +       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);

remove these debugging lines.

> +
> +       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);

To match probe order, call i2c_del_adapter() first.

> +
> +       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@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..99c7ffc
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c

What is the value in having this as a separate mtk_hdmi_hw.c file?
If these functions were in mtk_hdmi.c, they could all be static, and
the compiler
could inline them away.


> @@ -0,0 +1,652 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu@mediatek.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#include "mtk_hdmi_hw.h"
> +#include "mtk_hdmi_regs.h"
> +#include "mtk_hdmi.h"

I think these usually go after system includes.

> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/hdmi.h>
> +#include <linux/io.h>
> +#include <linux/regmap.h>
> +
> +static u32 mtk_hdmi_read(struct mtk_hdmi *hdmi, u32 offset)
> +{
> +       return readl(hdmi->regs + offset);
> +}
> +
> +static void mtk_hdmi_write(struct mtk_hdmi *hdmi, u32 offset, u32 val)
> +{
> +       writel(val, hdmi->regs + offset);
> +}
> +
> +static void mtk_hdmi_mask(struct mtk_hdmi *hdmi, u32 offset, u32 val, u32 mask)
> +{
> +       u32 tmp = mtk_hdmi_read(hdmi, offset) & ~mask;
> +
> +       tmp |= (val & mask);
> +       mtk_hdmi_write(hdmi, offset, tmp);
> +}
> +
> +#define NCTS_BYTES          0x07

move above the functions

> +
> +void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi,
> +                          bool black)
> +{
> +       mtk_hdmi_mask(hdmi, VIDEO_CFG_4, black ? GEN_RGB : NORMAL_PATH,
> +                     VIDEO_SOURCE_SEL);
> +}
> +
> +void mtk_hdmi_hw_make_reg_writable(struct mtk_hdmi *hdmi, bool enable)
> +{
> +       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
> +                          HDMI_PCLK_FREE_RUN, enable ? HDMI_PCLK_FREE_RUN : 0);
> +       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
> +                          HDMI_ON | ANLG_ON, enable ? (HDMI_ON | ANLG_ON) : 0);
> +}
> +
> +void mtk_hdmi_hw_1p4_version_enable(struct mtk_hdmi *hdmi, bool enable)
> +{
> +       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
> +                          HDMI2P0_EN, enable ? 0 : HDMI2P0_EN);
> +}
> +
> +void mtk_hdmi_hw_aud_mute(struct mtk_hdmi *hdmi, bool mute)
> +{
> +       mtk_hdmi_mask(hdmi, GRL_AUDIO_CFG, mute ? AUDIO_ZERO : 0, AUDIO_ZERO);
> +}
> +
> +void mtk_hdmi_hw_reset(struct mtk_hdmi *hdmi)
> +{
> +       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
> +                          HDMI_RST, HDMI_RST);
> +       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
> +                          HDMI_RST, 0);
> +       mtk_hdmi_mask(hdmi, GRL_CFG3, 0, CFG3_CONTROL_PACKET_DELAY);
> +       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C,
> +                          ANLG_ON, ANLG_ON);
> +}
> +
> +void mtk_hdmi_hw_enable_notice(struct mtk_hdmi *hdmi, bool enable_notice)
> +{
> +       mtk_hdmi_mask(hdmi, GRL_CFG2, enable_notice ? CFG2_NOTICE_EN : 0,
> +                     CFG2_NOTICE_EN);
> +}
> +
> +void mtk_hdmi_hw_write_int_mask(struct mtk_hdmi *hdmi, u32 int_mask)
> +{
> +       mtk_hdmi_write(hdmi, GRL_INT_MASK, int_mask);
> +}
> +
> +void mtk_hdmi_hw_enable_dvi_mode(struct mtk_hdmi *hdmi, bool enable)
> +{
> +       mtk_hdmi_mask(hdmi, GRL_CFG1, enable ? CFG1_DVI : 0, CFG1_DVI);
> +}
> +
> +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_dbg(hdmi->dev,
> +               "frame_type:0x%x,frame_ver:0x%x,frame_len:0x%x,checksum:0x%x\n",
> +               frame_type, frame_ver, frame_len, checksum);
> +
> +       switch (frame_type) {
> +       case HDMI_INFOFRAME_TYPE_AVI:
> +               ctrl_frame_en = CTRL_AVI_EN;
> +               ctrl_reg = GRL_CTRL;
> +               break;
> +       case HDMI_INFOFRAME_TYPE_SPD:
> +               ctrl_frame_en = CTRL_SPD_EN;
> +               ctrl_reg = GRL_CTRL;
> +               break;
> +       case HDMI_INFOFRAME_TYPE_AUDIO:
> +               ctrl_frame_en = CTRL_AUDIO_EN;
> +               ctrl_reg = GRL_CTRL;
> +               break;
> +       case HDMI_INFOFRAME_TYPE_VENDOR:
> +               ctrl_frame_en = VS_EN;
> +               ctrl_reg = GRL_ACP_ISRC_CTRL;
> +               break;
> +       default:

Just fall through if none of the above?

> +               break;
> +       }
> +       mtk_hdmi_mask(hdmi, ctrl_reg, 0, ctrl_frame_en);
> +       mtk_hdmi_write(hdmi, GRL_INFOFRM_TYPE, frame_type);
> +       mtk_hdmi_write(hdmi, GRL_INFOFRM_VER, frame_ver);
> +       mtk_hdmi_write(hdmi, GRL_INFOFRM_LNG, frame_len);
> +
> +       mtk_hdmi_write(hdmi, GRL_IFM_PORT, checksum);
> +       for (i = 0; i < frame_len; i++)
> +               mtk_hdmi_write(hdmi, GRL_IFM_PORT, frame_data[i]);
> +
> +       mtk_hdmi_mask(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(hdmi, GRL_SHIFT_R2, enable ? 0 : AUDIO_PACKET_OFF,
> +                     AUDIO_PACKET_OFF);
> +}
> +
> +void mtk_hdmi_hw_config_sys(struct mtk_hdmi *hdmi)
> +{
> +       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
> +                          HDMI_OUT_FIFO_EN | MHL_MODE_ON, 0);
> +       mdelay(2);

Can this be msleep instead of mdelay?
It is a bit rude to hog the CPU for 2 msec.

> +       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
> +                          HDMI_OUT_FIFO_EN | MHL_MODE_ON, HDMI_OUT_FIFO_EN);
> +}
> +
> +void mtk_hdmi_hw_set_deep_color_mode(struct mtk_hdmi *hdmi)
> +{
> +       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
> +                          DEEP_COLOR_MODE_MASK | DEEP_COLOR_EN, COLOR_8BIT_MODE);
> +}
> +
> +void mtk_hdmi_hw_send_av_mute(struct mtk_hdmi *hdmi)
> +{
> +       mtk_hdmi_mask(hdmi, GRL_CFG4, 0, CTRL_AVMUTE);
> +       mdelay(2);

Can this be msleep instead of mdelay?

> +       mtk_hdmi_mask(hdmi, GRL_CFG4, CTRL_AVMUTE, CTRL_AVMUTE);
> +}
> +
> +void mtk_hdmi_hw_send_av_unmute(struct mtk_hdmi *hdmi)
> +{
> +       mtk_hdmi_mask(hdmi, GRL_CFG4, CFG4_AV_UNMUTE_EN,
> +                     CFG4_AV_UNMUTE_EN | CFG4_AV_UNMUTE_SET);
> +       mdelay(2);

Can this be msleep instead of mdelay?

> +       mtk_hdmi_mask(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(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(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(hdmi, GRL_CFG4, 0, CFG_MHL_MODE);
> +
> +       if (mode->flags & DRM_MODE_FLAG_INTERLACE &&
> +           mode->clock == 74250 &&
> +           mode->vdisplay == 1080)
> +               mtk_hdmi_mask(hdmi, GRL_CFG2, 0, MHL_DE_SEL);
> +       else
> +               mtk_hdmi_mask(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(hdmi, GRL_CH_SWAP, swap_bit, 0xff);
> +}
> +
> +void mtk_hdmi_hw_aud_raw_data_enable(struct mtk_hdmi *hdmi, bool enable)
> +{
> +       mtk_hdmi_mask(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;

no 0 init

> +
> +       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(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;

no 0 init

> +
> +       val = mtk_hdmi_read(hdmi, GRL_CFG0);
> +       val &= ~0x33;

#define this mask

> +
> +       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(hdmi, GRL_CFG0, val);
> +}
> +
> +void mtk_hdmi_hw_aud_set_high_bitrate(struct mtk_hdmi *hdmi, bool enable)
> +{
> +       mtk_hdmi_mask(hdmi, GRL_AOUT_BNUM_SEL,
> +                     enable ? HIGH_BIT_RATE_PACKET_ALIGN : 0,
> +                     HIGH_BIT_RATE_PACKET_ALIGN);
> +       mtk_hdmi_mask(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(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(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(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;

better:
  u8 sw[3], uv;

> +
> +       if (channel_count == 2) {
> +               val_1 = 0x04;
> +               val_2 = 0x50;

Some #defines with meaningful names would be nice here.


> +       } 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(hdmi, GRL_CH_SW0, val_2);
> +       mtk_hdmi_write(hdmi, GRL_CH_SW1, val_3);
> +       mtk_hdmi_write(hdmi, GRL_CH_SW2, val_4);
> +       mtk_hdmi_write(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;

no need to 0 init

> +
> +       val = mtk_hdmi_read(hdmi, GRL_CFG1);
> +       if (input_type == HDMI_AUD_INPUT_I2S &&
> +           (val & CFG1_SPDIF) == CFG1_SPDIF) {
> +               val &= ~CFG1_SPDIF;
> +       } else if (input_type == HDMI_AUD_INPUT_SPDIF &&
> +               (val & CFG1_SPDIF) == 0) {
> +               val |= CFG1_SPDIF;
> +       }
> +       mtk_hdmi_write(hdmi, GRL_CFG1, val);
> +}
> +
> +void mtk_hdmi_hw_aud_set_channel_status(struct mtk_hdmi *hdmi,
> +                                       u8 *l_chan_status, u8 *r_chan_status,
> +                                       enum hdmi_audio_sample_frequency
> +                                       aud_hdmi_fs)
> +{
> +       u8 l_status[5];
> +       u8 r_status[5];
> +       u8 val = 0;

no need to 0 init

> +
> +       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_status[0];
> +       r_status[1] = r_chan_status[1];
> +       r_status[2] = r_chan_status[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];

nit: You don't need to bounce through val here.
You can just write the *_status[n] value directly.

> +       mtk_hdmi_write(hdmi, GRL_I2S_C_STA0, val);
> +       mtk_hdmi_write(hdmi, GRL_L_STATUS_0, val);
> +
> +       val = r_status[0];
> +       mtk_hdmi_write(hdmi, GRL_R_STATUS_0, val);
> +
> +       val = l_status[1];
> +       mtk_hdmi_write(hdmi, GRL_I2S_C_STA1, val);
> +       mtk_hdmi_write(hdmi, GRL_L_STATUS_1, val);
> +
> +       val = r_status[1];
> +       mtk_hdmi_write(hdmi, GRL_R_STATUS_1, val);
> +
> +       val = l_status[2];
> +       mtk_hdmi_write(hdmi, GRL_I2S_C_STA2, val);
> +       mtk_hdmi_write(hdmi, GRL_L_STATUS_2, val);
> +
> +       val = r_status[2];
> +       mtk_hdmi_write(hdmi, GRL_R_STATUS_2, val);
> +
> +       val = l_status[3];
> +       mtk_hdmi_write(hdmi, GRL_I2S_C_STA3, val);
> +       mtk_hdmi_write(hdmi, GRL_L_STATUS_3, val);
> +
> +       val = r_status[3];
> +       mtk_hdmi_write(hdmi, GRL_R_STATUS_3, val);
> +
> +       val = l_status[4];
> +       mtk_hdmi_write(hdmi, GRL_I2S_C_STA4, val);
> +       mtk_hdmi_write(hdmi, GRL_L_STATUS_4, val);
> +
> +       val = r_status[4];
> +       mtk_hdmi_write(hdmi, GRL_R_STATUS_4, val);
> +
> +       for (val = 0; val < 19; val++) {
> +               mtk_hdmi_write(hdmi, GRL_L_STATUS_5 + val * 4, 0);
> +               mtk_hdmi_write(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(hdmi, GRL_MIX_CTRL);
> +       if (val & MIX_CTRL_SRC_EN) {
> +               val &= ~MIX_CTRL_SRC_EN;
> +               mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
> +               usleep_range(255, 512);

Those are some very precise values for a range...

> +               val |= MIX_CTRL_SRC_EN;
> +               mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
> +       }
> +}
> +
> +void mtk_hdmi_hw_aud_src_off(struct mtk_hdmi *hdmi)

Perhaps *_disable() would be more consistent.

> +{
> +       u32 val;
> +
> +       val = mtk_hdmi_read(hdmi, GRL_MIX_CTRL);
> +       val &= ~MIX_CTRL_SRC_EN;
> +       mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
> +       mtk_hdmi_write(hdmi, GRL_SHIFT_L1, 0x00);
> +}
> +
> +void mtk_hdmi_hw_aud_set_mclk(struct mtk_hdmi *hdmi, enum hdmi_aud_mclk mclk)
> +{
> +       u32 val;
> +
> +       val = mtk_hdmi_read(hdmi, GRL_CFG5);
> +       val &= CFG5_CD_RATIO_MASK;
> +
> +       switch (mclk) {
> +       case HDMI_AUD_MCLK_128FS:
> +               val |= CFG5_FS128;
> +               break;
> +       case HDMI_AUD_MCLK_256FS:
> +               val |= CFG5_FS256;
> +               break;
> +       case HDMI_AUD_MCLK_384FS:
> +               val |= CFG5_FS384;
> +               break;
> +       case HDMI_AUD_MCLK_512FS:
> +               val |= CFG5_FS512;
> +               break;
> +       case HDMI_AUD_MCLK_768FS:
> +               val |= CFG5_FS768;
> +               break;
> +       default:
> +               val |= CFG5_FS256;
> +               break;
> +       }
> +       mtk_hdmi_write(hdmi, GRL_CFG5, val);
> +}
> +
> +void mtk_hdmi_hw_aud_aclk_inv_enable(struct mtk_hdmi *hdmi, bool enable)

nit: I prefer explicit _enable() and _disable() functions w/out the
'enable' parameter.

> +{
> +       u32 val;
> +
> +       val = mtk_hdmi_read(hdmi, GRL_CFG2);
> +       if (enable)
> +               val |= 0x80;
> +       else
> +               val &= ~0x80;
> +       mtk_hdmi_write(hdmi, GRL_CFG2, val);
> +}
> +
> +struct hdmi_acr_n {
> +       unsigned int clock;
> +       unsigned int n[3];
> +};
> +
> +/* Recommended N values from HDMI specification, tables 7-1 to 7-3 */
> +static const struct hdmi_acr_n hdmi_rec_n_table[] = {
> +       /* Clock, N: 32kHz 44.1kHz 48kHz */
> +       {  25175, {  4576,  7007,  6864 } },
> +       {  74176, { 11648, 17836, 11648 } },
> +       { 148352, { 11648,  8918,  5824 } },
> +       { 296703, {  5824,  4459,  5824 } },
> +       { 297000, {  3072,  4704,  5120 } },
> +       {      0, {  4096,  6272,  6144 } }, /* all other TMDS clocks */
> +};
> +
> +/**
> + * hdmi_recommended_n() - Return N value recommended by HDMI specification
> + * @freq: audio sample rate in Hz
> + * @clock: rounded TMDS clock in kHz
> + */
> +static unsigned int hdmi_recommended_n(unsigned int freq, unsigned int clock)
> +{
> +       const struct hdmi_acr_n *recommended;
> +       unsigned int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(hdmi_rec_n_table) - 1; i++) {
> +               if (clock == hdmi_rec_n_table[i].clock)
> +                       break;
> +       }
> +       recommended = hdmi_rec_n_table + i;
> +
> +       switch (freq) {
> +       case 32000:
> +               return recommended->n[0];
> +       case 44100:
> +               return recommended->n[1];
> +       case 48000:
> +               return recommended->n[2];
> +       case 88200:
> +               return recommended->n[1] * 2;
> +       case 96000:
> +               return recommended->n[2] * 2;
> +       case 176400:
> +               return recommended->n[1] * 4;
> +       case 192000:
> +               return recommended->n[2] * 4;
> +       default:
> +               return (128 * freq) / 1000;
> +       }
> +}
> +
> +static unsigned int hdmi_mode_clock_to_hz(unsigned int clock)
> +{
> +       switch (clock) {
> +       case 25175:
> +               return 25174825;        /* 25.2/1.001 MHz */
> +       case 74176:
> +               return 74175824;        /* 74.25/1.001 MHz */
> +       case 148352:
> +               return 148351648;       /* 148.5/1.001 MHz */
> +       case 296703:
> +               return 296703297;       /* 297/1.001 MHz */
> +       default:
> +               return clock * 1000;
> +       }
> +}
> +
> +static unsigned int hdmi_expected_cts(unsigned int audio_sample_rate,
> +                                     unsigned int tmds_clock, unsigned int n)
> +{
> +       return DIV_ROUND_CLOSEST_ULL((u64)hdmi_mode_clock_to_hz(tmds_clock) * n,
> +                                    128 * audio_sample_rate);
> +}

Doug Anderson may have some opinions about how N & CTS are computed.


> +
> +static void do_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, unsigned int n,
> +                                   unsigned int cts)
> +{
> +       unsigned char val[NCTS_BYTES];
> +       int i;
> +
> +       mtk_hdmi_write(hdmi, GRL_NCTS, 0);
> +       mtk_hdmi_write(hdmi, GRL_NCTS, 0);
> +       mtk_hdmi_write(hdmi, GRL_NCTS, 0);
> +       memset(val, 0, sizeof(val));

not necessary, since you fill in all 7 bytes anyway.

> +
> +       val[0] = (cts >> 24) & 0xff;
> +       val[1] = (cts >> 16) & 0xff;
> +       val[2] = (cts >> 8) & 0xff;
> +       val[3] = cts & 0xff;
> +
> +       val[4] = (n >> 16) & 0xff;
> +       val[5] = (n >> 8) & 0xff;
> +       val[6] = n & 0xff;

all of these "& 0xff" are not needed, since val is an unsigned char array.

> +
> +       for (i = 0; i < NCTS_BYTES; i++)
> +               mtk_hdmi_write(hdmi, GRL_NCTS, val[i]);

What an interesting design.  We write all 10 bytes to the same register address?
In this case, why bother with val at all?
Just directly call mtk_hdmi_write() for each of the bytes above.


> +}
> +
> +void mtk_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, unsigned int sample_rate,
> +                             unsigned int clock)
> +{
> +       unsigned int n, cts;
> +
> +       n = hdmi_recommended_n(sample_rate, clock);
> +       cts = hdmi_expected_cts(sample_rate, clock, n);
> +
> +       dev_dbg(hdmi->dev, "%s: sample_rate=%u, clock=%d, cts=%u, n=%u\n",
> +               __func__, sample_rate, clock, n, cts);
> +
> +       mtk_hdmi_mask(hdmi, DUMMY_304, AUDIO_I2S_NCTS_SEL_64,
> +                     AUDIO_I2S_NCTS_SEL);
> +       do_hdmi_hw_aud_set_ncts(hdmi, n, cts);
> +}
> 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..9013219
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.h
> @@ -0,0 +1,73 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu@mediatek.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#ifndef _MTK_HDMI_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);
> +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, unsigned int sample_rate,
> +                             unsigned 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_regs.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
> new file mode 100644
> index 0000000..8c1d318
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h
> @@ -0,0 +1,221 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu@mediatek.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#ifndef _MTK_HDMI_REGS_H
> +#define _MTK_HDMI_REGS_H
> +
> +#define GRL_INT_MASK           0x18
> +#define GRL_IFM_PORT           0x188
> +#define GRL_CH_SWAP            0x198
> +#define LR_SWAP                                BIT(0)
> +#define LFE_CC_SWAP                    BIT(1)
> +#define LSRS_SWAP                      BIT(2)
> +#define RLS_RRS_SWAP                   BIT(3)
> +#define LR_STATUS_SWAP                 BIT(4)
> +#define GRL_I2S_C_STA0         0x140
> +#define GRL_I2S_C_STA1         0x144
> +#define GRL_I2S_C_STA2         0x148
> +#define GRL_I2S_C_STA3         0x14C
> +#define GRL_I2S_C_STA4         0x150
> +#define GRL_I2S_UV             0x154
> +#define 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                     (0x1 << 4)
> +#define CFG5_FS256                     (0x2 << 4)
> +#define CFG5_FS384                     (0x3 << 4)
> +#define CFG5_FS512                     (0x4 << 4)
> +#define CFG5_FS768                     (0x6 << 4)
> +#define DUMMY_304              0x304
> +#define CHMO_SEL                       (0x3 << 2)
> +#define CHM1_SEL                       (0x3 << 4)
> +#define CHM2_SEL                       (0x3 << 6)
> +#define AUDIO_I2S_NCTS_SEL             BIT(1)
> +#define AUDIO_I2S_NCTS_SEL_64          (1 << 1)
> +#define AUDIO_I2S_NCTS_SEL_128         (0 << 1)
> +#define NEW_GCP_CTRL                   BIT(0)
> +#define NEW_GCP_CTRL_MERGE             BIT(0)
> +#define GRL_L_STATUS_0         0x200
> +#define GRL_L_STATUS_1         0x204
> +#define GRL_L_STATUS_2         0x208
> +#define GRL_L_STATUS_3         0x20c
> +#define GRL_L_STATUS_4         0x210
> +#define GRL_L_STATUS_5         0x214
> +#define GRL_L_STATUS_6         0x218
> +#define GRL_L_STATUS_7         0x21c
> +#define GRL_L_STATUS_8         0x220
> +#define GRL_L_STATUS_9         0x224
> +#define GRL_L_STATUS_10                0x228
> +#define GRL_L_STATUS_11                0x22c
> +#define GRL_L_STATUS_12                0x230
> +#define GRL_L_STATUS_13                0x234
> +#define GRL_L_STATUS_14                0x238
> +#define GRL_L_STATUS_15                0x23c
> +#define GRL_L_STATUS_16                0x240
> +#define GRL_L_STATUS_17                0x244
> +#define GRL_L_STATUS_18                0x248
> +#define GRL_L_STATUS_19                0x24c
> +#define GRL_L_STATUS_20                0x250
> +#define GRL_L_STATUS_21                0x254
> +#define GRL_L_STATUS_22                0x258
> +#define GRL_L_STATUS_23                0x25c
> +#define GRL_R_STATUS_0         0x260
> +#define GRL_R_STATUS_1         0x264
> +#define GRL_R_STATUS_2         0x268
> +#define GRL_R_STATUS_3         0x26c
> +#define GRL_R_STATUS_4         0x270
> +#define GRL_R_STATUS_5         0x274
> +#define GRL_R_STATUS_6         0x278
> +#define GRL_R_STATUS_7         0x27c
> +#define GRL_R_STATUS_8         0x280
> +#define GRL_R_STATUS_9         0x284
> +#define GRL_R_STATUS_10                0x288
> +#define GRL_R_STATUS_11                0x28c
> +#define GRL_R_STATUS_12                0x290
> +#define GRL_R_STATUS_13                0x294
> +#define GRL_R_STATUS_14                0x298
> +#define GRL_R_STATUS_15                0x29c
> +#define GRL_R_STATUS_16                0x2a0
> +#define GRL_R_STATUS_17                0x2a4
> +#define GRL_R_STATUS_18                0x2a8
> +#define GRL_R_STATUS_19                0x2ac
> +#define GRL_R_STATUS_20                0x2b0
> +#define GRL_R_STATUS_21                0x2b4
> +#define GRL_R_STATUS_22                0x2b8
> +#define GRL_R_STATUS_23                0x2bc
> +#define GRL_ABIST_CTRL0                0x2D4
> +#define GRL_ABIST_CTRL1                0x2D8
> +#define ABIST_EN                       BIT(7)
> +#define ABIST_DATA_FMT                 (0x7 << 0)
> +#define VIDEO_CFG_0            0x380
> +#define VIDEO_CFG_1            0x384
> +#define VIDEO_CFG_2            0x388
> +#define VIDEO_CFG_3            0x38c
> +#define VIDEO_CFG_4            0x390
> +#define VIDEO_SOURCE_SEL               BIT(7)
> +#define NORMAL_PATH                    (1 << 7)
> +#define GEN_RGB                                (0 << 7)
> +
> +#define HDMI_SYS_CFG1C         0x000
> +#define HDMI_ON                                BIT(0)
> +#define HDMI_RST                       BIT(1)
> +#define ANLG_ON                                BIT(2)
> +#define CFG10_DVI                      BIT(3)
> +#define HDMI_TST                       BIT(3)
> +#define SYS_KEYMASK1                   (0xff << 8)
> +#define SYS_KEYMASK2                   (0xff << 16)
> +#define AUD_OUTSYNC_EN                 BIT(24)
> +#define AUD_OUTSYNC_PRE_EN             BIT(25)
> +#define I2CM_ON                                BIT(26)
> +#define E2PROM_TYPE_8BIT               BIT(27)
> +#define MCM_E2PROM_ON                  BIT(28)
> +#define EXT_E2PROM_ON                  BIT(29)
> +#define HTPLG_PIN_SEL_OFF              BIT(30)
> +#define AES_EFUSE_ENABLE               BIT(31)
> +#define HDMI_SYS_CFG20         0x004
> +#define DEEP_COLOR_MODE_MASK           (3 << 1)
> +#define COLOR_8BIT_MODE                        (0 << 1)
> +#define COLOR_10BIT_MODE               (1 << 1)
> +#define COLOR_12BIT_MODE               (2 << 1)
> +#define COLOR_16BIT_MODE               (3 << 1)
> +#define DEEP_COLOR_EN                  BIT(0)
> +#define HDMI_AUDIO_TEST_SEL            BIT(8)
> +#define HDMI2P0_EN                     BIT(11)
> +#define HDMI_OUT_FIFO_EN               BIT(16)
> +#define HDMI_OUT_FIFO_CLK_INV          BIT(17)
> +#define MHL_MODE_ON                    BIT(28)
> +#define MHL_PP_MODE                    BIT(29)
> +#define MHL_SYNC_AUTO_EN               BIT(30)
> +#define HDMI_PCLK_FREE_RUN             BIT(31)
> +
> +#endif
> diff --git a/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c b/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c
> new file mode 100644
> index 0000000..5d9f07f
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c
> @@ -0,0 +1,505 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu@mediatek.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/types.h>
> +
> +#define HDMI_CON0              0x00
> +#define RG_HDMITX_PLL_EN               BIT(31)
> +#define RG_HDMITX_PLL_FBKDIV           (0x7f << 24)
> +#define PLL_FBKDIV_SHIFT               24
> +#define RG_HDMITX_PLL_FBKSEL           (0x3 << 22)
> +#define PLL_FBKSEL_SHIFT               22
> +#define RG_HDMITX_PLL_PREDIV           (0x3 << 20)
> +#define PREDIV_SHIFT                   20
> +#define RG_HDMITX_PLL_POSDIV           (0x3 << 18)
> +#define POSDIV_SHIFT                   18
> +#define RG_HDMITX_PLL_RST_DLY          (0x3 << 16)
> +#define RG_HDMITX_PLL_IR               (0xf << 12)
> +#define PLL_IR_SHIFT                   12
> +#define RG_HDMITX_PLL_IC               (0xf << 8)
> +#define PLL_IC_SHIFT                   8
> +#define RG_HDMITX_PLL_BP               (0xf << 4)
> +#define PLL_BP_SHIFT                   4
> +#define RG_HDMITX_PLL_BR               (0x3 << 2)
> +#define PLL_BR_SHIFT                   2
> +#define RG_HDMITX_PLL_BC               (0x3 << 0)
> +#define PLL_BC_SHIFT                   0
> +#define HDMI_CON1              0x04
> +#define RG_HDMITX_PLL_DIVEN            (0x7 << 29)
> +#define PLL_DIVEN_SHIFT                        29
> +#define RG_HDMITX_PLL_AUTOK_EN         BIT(28)
> +#define RG_HDMITX_PLL_AUTOK_KF         (0x3 << 26)
> +#define RG_HDMITX_PLL_AUTOK_KS         (0x3 << 24)
> +#define RG_HDMITX_PLL_AUTOK_LOAD       BIT(23)
> +#define RG_HDMITX_PLL_BAND             (0x3f << 16)
> +#define RG_HDMITX_PLL_REF_SEL          BIT(15)
> +#define RG_HDMITX_PLL_BIAS_EN          BIT(14)
> +#define RG_HDMITX_PLL_BIAS_LPF_EN      BIT(13)
> +#define RG_HDMITX_PLL_TXDIV_EN         BIT(12)
> +#define RG_HDMITX_PLL_TXDIV            (0x3 << 10)
> +#define PLL_TXDIV_SHIFT                        10
> +#define RG_HDMITX_PLL_LVROD_EN         BIT(9)
> +#define RG_HDMITX_PLL_MONVC_EN         BIT(8)
> +#define RG_HDMITX_PLL_MONCK_EN         BIT(7)
> +#define RG_HDMITX_PLL_MONREF_EN                BIT(6)
> +#define RG_HDMITX_PLL_TST_EN           BIT(5)
> +#define RG_HDMITX_PLL_TST_CK_EN                BIT(4)
> +#define RG_HDMITX_PLL_TST_SEL          (0xf << 0)
> +#define HDMI_CON2              0x08
> +#define RGS_HDMITX_PLL_AUTOK_BAND      (0x7f << 8)
> +#define RGS_HDMITX_PLL_AUTOK_FAIL      BIT(1)
> +#define RG_HDMITX_EN_TX_CKLDO          BIT(0)
> +#define HDMI_CON3              0x0c
> +#define RG_HDMITX_SER_EN               (0xf << 28)
> +#define RG_HDMITX_PRD_EN               (0xf << 24)
> +#define RG_HDMITX_PRD_IMP_EN           (0xf << 20)
> +#define RG_HDMITX_DRV_EN               (0xf << 16)
> +#define RG_HDMITX_DRV_IMP_EN           (0xf << 12)
> +#define DRV_IMP_EN_SHIFT               12
> +#define RG_HDMITX_MHLCK_FORCE          BIT(10)
> +#define RG_HDMITX_MHLCK_PPIX_EN                BIT(9)
> +#define RG_HDMITX_MHLCK_EN             BIT(8)
> +#define RG_HDMITX_SER_DIN_SEL          (0xf << 4)
> +#define RG_HDMITX_SER_5T1_BIST_EN      BIT(3)
> +#define RG_HDMITX_SER_BIST_TOG         BIT(2)
> +#define RG_HDMITX_SER_DIN_TOG          BIT(1)
> +#define RG_HDMITX_SER_CLKDIG_INV       BIT(0)
> +#define HDMI_CON4              0x10
> +#define RG_HDMITX_PRD_IBIAS_CLK                (0xf << 24)
> +#define RG_HDMITX_PRD_IBIAS_D2         (0xf << 16)
> +#define RG_HDMITX_PRD_IBIAS_D1         (0xf << 8)
> +#define RG_HDMITX_PRD_IBIAS_D0         (0xf << 0)
> +#define PRD_IBIAS_CLK_SHIFT            24
> +#define PRD_IBIAS_D2_SHIFT             16
> +#define PRD_IBIAS_D1_SHIFT             8
> +#define PRD_IBIAS_D0_SHIFT             0
> +#define HDMI_CON5              0x14
> +#define RG_HDMITX_DRV_IBIAS_CLK                (0x3f << 24)
> +#define RG_HDMITX_DRV_IBIAS_D2         (0x3f << 16)
> +#define RG_HDMITX_DRV_IBIAS_D1         (0x3f << 8)
> +#define RG_HDMITX_DRV_IBIAS_D0         (0x3f << 0)
> +#define DRV_IBIAS_CLK_SHIFT            24
> +#define DRV_IBIAS_D2_SHIFT             16
> +#define DRV_IBIAS_D1_SHIFT             8
> +#define DRV_IBIAS_D0_SHIFT             0
> +#define HDMI_CON6              0x18
> +#define RG_HDMITX_DRV_IMP_CLK          (0x3f << 24)
> +#define RG_HDMITX_DRV_IMP_D2           (0x3f << 16)
> +#define RG_HDMITX_DRV_IMP_D1           (0x3f << 8)
> +#define RG_HDMITX_DRV_IMP_D0           (0x3f << 0)
> +#define DRV_IMP_CLK_SHIFT              24
> +#define DRV_IMP_D2_SHIFT               16
> +#define DRV_IMP_D1_SHIFT               8
> +#define DRV_IMP_D0_SHIFT               0
> +#define HDMI_CON7              0x1c
> +#define RG_HDMITX_MHLCK_DRV_IBIAS      (0x1f << 27)
> +#define RG_HDMITX_SER_DIN              (0x3ff << 16)
> +#define RG_HDMITX_CHLDC_TST            (0xf << 12)
> +#define RG_HDMITX_CHLCK_TST            (0xf << 8)
> +#define RG_HDMITX_RESERVE              (0xff << 0)
> +#define HDMI_CON8              0x20
> +#define RGS_HDMITX_2T1_LEV             (0xf << 16)
> +#define RGS_HDMITX_2T1_EDG             (0xf << 12)
> +#define RGS_HDMITX_5T1_LEV             (0xf << 8)
> +#define RGS_HDMITX_5T1_EDG             (0xf << 4)
> +#define RGS_HDMITX_PLUG_TST            BIT(0)
> +
> +struct mtk_hdmi_phy {
> +       void __iomem *regs;
> +       struct device *dev;
> +       struct clk *pll;
> +       struct clk_hw pll_hw;
> +       unsigned long pll_rate;
> +       u8 drv_imp_clk;
> +       u8 drv_imp_d2;
> +       u8 drv_imp_d1;
> +       u8 drv_imp_d0;
> +       u32 ibias;
> +       u32 ibias_up;
> +};
> +
> +static const u8 PREDIV[3][4] = {
> +       {0x0, 0x0, 0x0, 0x0},   /* 27Mhz */
> +       {0x1, 0x1, 0x1, 0x1},   /* 74Mhz */
> +       {0x1, 0x1, 0x1, 0x1}    /* 148Mhz */
> +};
> +
> +static const u8 TXDIV[3][4] = {
> +       {0x3, 0x3, 0x3, 0x2},   /* 27Mhz */
> +       {0x2, 0x1, 0x1, 0x1},   /* 74Mhz */
> +       {0x1, 0x0, 0x0, 0x0}    /* 148Mhz */
> +};
> +
> +static const u8 FBKSEL[3][4] = {
> +       {0x1, 0x1, 0x1, 0x1},   /* 27Mhz */
> +       {0x1, 0x0, 0x1, 0x1},   /* 74Mhz */
> +       {0x1, 0x0, 0x1, 0x1}    /* 148Mhz */
> +};
> +
> +static const u8 FBKDIV[3][4] = {
> +       {19, 24, 29, 19},       /* 27Mhz */
> +       {19, 24, 14, 19},       /* 74Mhz */
> +       {19, 24, 14, 19}        /* 148Mhz */
> +};
> +
> +static const u8 DIVEN[3][4] = {
> +       {0x2, 0x1, 0x1, 0x2},   /* 27Mhz */
> +       {0x2, 0x2, 0x2, 0x2},   /* 74Mhz */
> +       {0x2, 0x2, 0x2, 0x2}    /* 148Mhz */
> +};
> +
> +static const u8 HTPLLBP[3][4] = {
> +       {0xc, 0xc, 0x8, 0xc},   /* 27Mhz */
> +       {0xc, 0xf, 0xf, 0xc},   /* 74Mhz */
> +       {0xc, 0xf, 0xf, 0xc}    /* 148Mhz */
> +};
> +
> +static const u8 HTPLLBC[3][4] = {
> +       {0x2, 0x3, 0x3, 0x2},   /* 27Mhz */
> +       {0x2, 0x3, 0x3, 0x2},   /* 74Mhz */
> +       {0x2, 0x3, 0x3, 0x2}    /* 148Mhz */
> +};
> +
> +static const u8 HTPLLBR[3][4] = {
> +       {0x1, 0x1, 0x0, 0x1},   /* 27Mhz */
> +       {0x1, 0x2, 0x2, 0x1},   /* 74Mhz */
> +       {0x1, 0x2, 0x2, 0x1}    /* 148Mhz */
> +};
> +
> +static void mtk_hdmi_phy_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 inline struct mtk_hdmi_phy *to_mtk_hdmi_phy(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct mtk_hdmi_phy, pll_hw);
> +}
> +
> +static int mtk_hdmi_pll_prepare(struct clk_hw *hw)
> +{
> +       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
> +
> +       dev_dbg(hdmi_phy->dev, "prepare\n");
> +
> +       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);
> +
> +       return 0;
> +}
> +
> +static void mtk_hdmi_pll_unprepare(struct clk_hw *hw)
> +{
> +       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
> +
> +       dev_dbg(hdmi_phy->dev, "prepare\n");

nit: "unprepare" (or just '"%s\n", __func__)', here and everywhere.

> +
> +       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);
> +}
> +
> +static int mtk_hdmi_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                unsigned long parent_rate)
> +{
> +       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
> +       unsigned int pre_div;
> +       unsigned int div;
> +
> +       dev_dbg(hdmi_phy->dev, "set rate : %lu, parent: %lu\n", rate,

nit, no space before the first ':'.

> +               parent_rate);
> +
> +       if (rate <= 27000000) {
> +               pre_div = 0;
> +               div = 3;
> +       } else if (rate <= 74250000) {
> +               pre_div = 1;
> +               div = 2;
> +       } else {
> +               pre_div = 1;
> +               div = 1;
> +       }
> +
> +       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
> +                         (pre_div << PREDIV_SHIFT),
> +                         RG_HDMITX_PLL_PREDIV);
> +       mtk_hdmi_phy_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,
> +                         (div << PLL_TXDIV_SHIFT),
> +                         RG_HDMITX_PLL_TXDIV);
> +       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
> +                         (0x1 << PLL_FBKSEL_SHIFT) | (19 << PLL_FBKDIV_SHIFT),
> +                         RG_HDMITX_PLL_FBKSEL | RG_HDMITX_PLL_FBKDIV);
> +       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1,
> +                         (0x2 << PLL_DIVEN_SHIFT),
> +                         RG_HDMITX_PLL_DIVEN);
> +       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
> +                         (0xc << PLL_BP_SHIFT) | (0x2 << PLL_BC_SHIFT) |
> +                         (0x1 << PLL_BR_SHIFT),
> +                         RG_HDMITX_PLL_BP | RG_HDMITX_PLL_BC |
> +                         RG_HDMITX_PLL_BR);
> +       mtk_hdmi_phy_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);
> +       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6,
> +                         (hdmi_phy->drv_imp_clk << DRV_IMP_CLK_SHIFT) |
> +                         (hdmi_phy->drv_imp_d2 << DRV_IMP_D2_SHIFT) |
> +                         (hdmi_phy->drv_imp_d1 << DRV_IMP_D1_SHIFT) |
> +                         (hdmi_phy->drv_imp_d0 << DRV_IMP_D0_SHIFT),
> +                         RG_HDMITX_DRV_IMP_CLK | RG_HDMITX_DRV_IMP_D2 |
> +                         RG_HDMITX_DRV_IMP_D1 | RG_HDMITX_DRV_IMP_D0);
> +       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON5,
> +                         (hdmi_phy->ibias << DRV_IBIAS_CLK_SHIFT) |
> +                         (hdmi_phy->ibias << DRV_IBIAS_D2_SHIFT) |
> +                         (hdmi_phy->ibias << DRV_IBIAS_D1_SHIFT) |
> +                         (hdmi_phy->ibias << DRV_IBIAS_D0_SHIFT),
> +                         RG_HDMITX_DRV_IBIAS_CLK | RG_HDMITX_DRV_IBIAS_D2 |
> +                         RG_HDMITX_DRV_IBIAS_D1 | RG_HDMITX_DRV_IBIAS_D0);
> +       return 0;
> +}
> +
> +static long mtk_hdmi_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                   unsigned long *parent_rate)
> +{
> +       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
> +
> +       hdmi_phy->pll_rate = rate;
> +       if (rate <= 74250000)
> +               *parent_rate = rate;
> +       else
> +               *parent_rate = rate / 2;
> +
> +       return rate;
> +}
> +
> +static unsigned long mtk_hdmi_pll_recalc_rate(struct clk_hw *hw,
> +                                             unsigned long parent_rate)
> +{
> +       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
> +
> +       return hdmi_phy->pll_rate;
> +}
> +
> +static const struct clk_ops mtk_hdmi_pll_ops = {
> +       .prepare = mtk_hdmi_pll_prepare,
> +       .unprepare = mtk_hdmi_pll_unprepare,
> +       .set_rate = mtk_hdmi_pll_set_rate,
> +       .round_rate = mtk_hdmi_pll_round_rate,
> +       .recalc_rate = mtk_hdmi_pll_recalc_rate,
> +};
> +
> +static void mtk_hdmi_phy_enable_tmds(struct mtk_hdmi_phy *hdmi_phy)
> +{
> +       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, RG_HDMITX_SER_EN,
> +                         RG_HDMITX_SER_EN);

nit: lots of these calls might be more readable (and easier to maintain &
review) if we used two helper functions:

mtk_hdmi_phy_set_bits(struct mtk_hdmi_phy *hdmi_phy, u32 offset, u32 mask);
mtk_hdmi_phy_clr_bits(struct mtk_hdmi_phy *hdmi_phy, u32 offset, u32 mask);

and

mtk_hdmi_set_bits()
mtk_hdmi_clr_bits()


> +       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);
> +}
> +
> +static int mtk_hdmi_phy_power_on(struct phy *phy)
> +{
> +       struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy);
> +       int ret;
> +
> +       ret = clk_prepare_enable(hdmi_phy->pll);
> +       if (ret < 0)
> +               return ret;
> +
> +       mtk_hdmi_phy_enable_tmds(hdmi_phy);
> +
> +       return 0;
> +}
> +
> +static int mtk_hdmi_phy_power_off(struct phy *phy)
> +{
> +       struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy);
> +
> +       mtk_hdmi_phy_disable_tmds(hdmi_phy);
> +       clk_disable_unprepare(hdmi_phy->pll);
> +
> +       return 0;
> +}
> +
> +static struct phy_ops mtk_hdmi_phy_ops = {

static const

> +       .power_on = mtk_hdmi_phy_power_on,
> +       .power_off = mtk_hdmi_phy_power_off,
> +       .owner = THIS_MODULE,
> +};
> +
> +static int mtk_hdmi_phy_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct mtk_hdmi_phy *hdmi_phy;
> +       struct resource *mem;
> +       struct clk *ref_clk;
> +       const char *ref_clk_name;
> +       struct clk_init_data clk_init = {
> +               .ops = &mtk_hdmi_pll_ops,
> +               .num_parents = 1,
> +               .parent_names = (const char * const *)&ref_clk_name,
> +               .flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE,
> +       };
> +       struct phy *phy;
> +       struct phy_provider *phy_provider;
> +       int ret;
> +
> +       hdmi_phy = devm_kzalloc(dev, sizeof(*hdmi_phy), GFP_KERNEL);
> +       if (!hdmi_phy)
> +               return -ENOMEM;
> +
> +       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       hdmi_phy->regs = devm_ioremap_resource(dev, mem);
> +       if (IS_ERR(hdmi_phy->regs)) {
> +               ret = PTR_ERR(hdmi_phy->regs);
> +               dev_err(dev, "Failed to get memory resource: %d\n", ret);
> +               return ret;
> +       }
> +
> +       ref_clk = devm_clk_get(dev, "pll_ref");
> +       if (IS_ERR(ref_clk)) {
> +               ret = PTR_ERR(ref_clk);
> +               dev_err(&pdev->dev, "Failed to get PLL reference clock: %d\n",
> +                       ret);
> +               return ret;
> +       }
> +       ref_clk_name = __clk_get_name(ref_clk);
> +
> +       ret = of_property_read_string(dev->of_node, "clock-output-names",
> +                                     &clk_init.name);
> +       if (ret < 0) {
> +               dev_err(dev, "Failed to read clock-output-names: %d\n", ret);
> +               return ret;
> +       }
> +
> +       hdmi_phy->pll_hw.init = &clk_init;
> +       hdmi_phy->pll = devm_clk_register(dev, &hdmi_phy->pll_hw);
> +       if (IS_ERR(hdmi_phy->pll)) {
> +               ret = PTR_ERR(hdmi_phy->pll);
> +               dev_err(dev, "Failed to register PLL: %d\n", ret);
> +               return ret;
> +       }
> +
> +       ret = of_property_read_u32(dev->of_node, "mediatek,ibias",
> +                                  &hdmi_phy->ibias);
> +       if (ret < 0) {
> +               dev_err(&pdev->dev, "Failed to get ibias: %d\n", ret);
> +               return ret;
> +       }
> +
> +       ret = of_property_read_u32(dev->of_node, "mediatek,ibias_up",
> +                                  &hdmi_phy->ibias_up);
> +       if (ret < 0) {
> +               dev_err(&pdev->dev, "Failed to get ibias up: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dev_info(dev, "Using default TX DRV impedance: 4.2k/36\n");
> +       hdmi_phy->drv_imp_clk = 0x30;
> +       hdmi_phy->drv_imp_d2 = 0x30;
> +       hdmi_phy->drv_imp_d1 = 0x30;
> +       hdmi_phy->drv_imp_d0 = 0x30;
> +
> +       phy = devm_phy_create(dev, NULL, &mtk_hdmi_phy_ops);
> +       if (IS_ERR(phy)) {
> +               dev_err(dev, "Failed to create HDMI PHY\n");
> +               return PTR_ERR(phy);
> +       }
> +       phy_set_drvdata(phy, hdmi_phy);
> +
> +       phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
> +       if (IS_ERR(phy_provider))
> +               return PTR_ERR(phy_provider);
> +
> +       hdmi_phy->dev = dev;
> +       return of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
> +                                  hdmi_phy->pll);
> +}
> +
> +static int mtk_hdmi_phy_remove(struct platform_device *pdev)
> +{
> +       return 0;
> +}
> +
> +static const struct of_device_id mtk_hdmi_phy_match[] = {
> +       { .compatible = "mediatek,mt8173-hdmi-phy", },
> +       {},
> +};
> +
> +struct platform_driver mtk_hdmi_phy_driver = {
> +       .probe = mtk_hdmi_phy_probe,
> +       .remove = mtk_hdmi_phy_remove,
> +       .driver = {
> +               .name = "mediatek-hdmi-phy",
> +               .of_match_table = mtk_hdmi_phy_match,
> +       },
> +};
> +
> +MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>");
> +MODULE_DESCRIPTION("MediaTek MT8173 HDMI PHY Driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.7.0
>
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v13 03/14] drm/mediatek: Add DSI sub driver
       [not found]   ` <1457443649-12133-4-git-send-email-p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
@ 2016-03-09 14:07     ` Daniel Kurtz
  2016-03-15 11:49       ` Philipp Zabel
  0 siblings, 1 reply; 22+ messages in thread
From: Daniel Kurtz @ 2016-03-09 14:07 UTC (permalink / raw)
  To: Philipp Zabel
  Cc: dri-devel, Rob Herring, Daniel Vetter, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Dave Airlie, Matthias Brugger,
	Cawa Cheng, CK Hu, YT Shen, Jitao Shi, Jie Qiu, Paul Bolle,
	Daniel Stone, Tomasz Figa, Yingjoe Chen, Michael Turquette,
	Stephen Boyd, Emil Velikov, open list:OPEN FIRMWARE AND...,
	ARM/Mediatek

Hi Philipp, CK,

Some small comments.
Nothing that couldn't be addressed after merging, if you prefer.

On Tue, Mar 8, 2016 at 9:27 PM, Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> wrote:
> From: CK Hu <ck.hu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
>
> This patch add a drm encoder/connector driver for the MIPI DSI function
> block of the Mediatek display subsystem and a phy driver for the MIPI TX
> D-PHY control module.
>
> Signed-off-by: Jitao Shi <jitao.shi-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> Signed-off-by: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> --
>  drivers/gpu/drm/mediatek/Kconfig       |   2 +
>  drivers/gpu/drm/mediatek/Makefile      |   4 +-
>  drivers/gpu/drm/mediatek/mtk_drm_drv.c |   2 +
>  drivers/gpu/drm/mediatek/mtk_drm_drv.h |   2 +
>  drivers/gpu/drm/mediatek/mtk_dsi.c     | 942 +++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/mediatek/mtk_mipi_tx.c | 487 +++++++++++++++++
>  6 files changed, 1438 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/gpu/drm/mediatek/mtk_dsi.c
>  create mode 100644 drivers/gpu/drm/mediatek/mtk_mipi_tx.c
>
> diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig
> index 8dad892..0c49a94 100644
> --- a/drivers/gpu/drm/mediatek/Kconfig
> +++ b/drivers/gpu/drm/mediatek/Kconfig
> @@ -3,6 +3,8 @@ config DRM_MEDIATEK
>         depends on DRM
>         depends on ARCH_MEDIATEK || (ARM && COMPILE_TEST)
>         select DRM_KMS_HELPER
> +       select DRM_MIPI_DSI
> +       select DRM_PANEL
>         select IOMMU_DMA
>         select MTK_SMI
>         help
> diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile
> index d4bde7c..e781db5a 100644
> --- a/drivers/gpu/drm/mediatek/Makefile
> +++ b/drivers/gpu/drm/mediatek/Makefile
> @@ -6,6 +6,8 @@ mediatek-drm-y := mtk_disp_ovl.o \
>                   mtk_drm_drv.o \
>                   mtk_drm_fb.o \
>                   mtk_drm_gem.o \
> -                 mtk_drm_plane.o
> +                 mtk_drm_plane.o \
> +                 mtk_dsi.o \
> +                 mtk_mipi_tx.o
>
>  obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
> diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> index 8a21ca7..4fcc0e0 100644
> --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> @@ -551,6 +551,8 @@ static struct platform_driver * const mtk_drm_drivers[] = {
>         &mtk_drm_platform_driver,
>         &mtk_disp_ovl_driver,
>         &mtk_disp_rdma_driver,
> +       &mtk_dsi_driver,
> +       &mtk_mipi_tx_driver,
>  };
>
>  static int __init mtk_drm_init(void)
> diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
> index efb744c..161a362 100644
> --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.h
> +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
> @@ -50,5 +50,7 @@ struct mtk_drm_private {
>
>  extern struct platform_driver mtk_disp_ovl_driver;
>  extern struct platform_driver mtk_disp_rdma_driver;
> +extern struct platform_driver mtk_dsi_driver;
> +extern struct platform_driver mtk_mipi_tx_driver;
>
>  #endif /* MTK_DRM_DRV_H */
> diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
> new file mode 100644
> index 0000000..463d389
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
> @@ -0,0 +1,942 @@
> +/*
> + * Copyright (c) 2015 MediaTek Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_graph.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <video/videomode.h>
> +
> +#include "mtk_drm_ddp_comp.h"
> +
> +#define DSI_VIDEO_FIFO_DEPTH   (1920 / 4)
> +#define DSI_HOST_FIFO_DEPTH    64
> +
> +#define DSI_START              0x00
> +
> +#define DSI_CON_CTRL           0x10
> +#define DSI_RESET                      BIT(0)
> +#define DSI_EN                         BIT(1)
> +
> +#define DSI_MODE_CTRL          0x14
> +#define MODE                           (3)
> +#define CMD_MODE                       0
> +#define SYNC_PULSE_MODE                        1
> +#define SYNC_EVENT_MODE                        2
> +#define BURST_MODE                     3
> +#define FRM_MODE                       BIT(16)
> +#define MIX_MODE                       BIT(17)
> +
> +#define DSI_TXRX_CTRL          0x18
> +#define VC_NUM                         (2 << 0)
> +#define LANE_NUM                       (0xf << 2)
> +#define DIS_EOT                                BIT(6)
> +#define NULL_EN                                BIT(7)
> +#define TE_FREERUN                     BIT(8)
> +#define EXT_TE_EN                      BIT(9)
> +#define EXT_TE_EDGE                    BIT(10)
> +#define MAX_RTN_SIZE                   (0xf << 12)
> +#define HSTX_CKLP_EN                   BIT(16)
> +
> +#define DSI_PSCTRL             0x1c
> +#define DSI_PS_WC                      0x3fff
> +#define DSI_PS_SEL                     (3 << 16)
> +#define PACKED_PS_16BIT_RGB565         (0 << 16)
> +#define LOOSELY_PS_18BIT_RGB666                (1 << 16)
> +#define PACKED_PS_18BIT_RGB666         (2 << 16)
> +#define PACKED_PS_24BIT_RGB888         (3 << 16)
> +
> +#define DSI_VSA_NL             0x20
> +#define DSI_VBP_NL             0x24
> +#define DSI_VFP_NL             0x28
> +#define DSI_VACT_NL            0x2C
> +#define DSI_HSA_WC             0x50
> +#define DSI_HBP_WC             0x54
> +#define DSI_HFP_WC             0x58
> +
> +#define DSI_HSTX_CKL_WC                0x64
> +
> +#define DSI_PHY_LCCON          0x104
> +#define LC_HS_TX_EN                    BIT(0)
> +#define LC_ULPM_EN                     BIT(1)
> +#define LC_WAKEUP_EN                   BIT(2)
> +
> +#define DSI_PHY_LD0CON         0x108
> +#define LD0_HS_TX_EN                   BIT(0)
> +#define LD0_ULPM_EN                    BIT(1)
> +#define LD0_WAKEUP_EN                  BIT(2)
> +
> +#define DSI_PHY_TIMECON0       0x110
> +#define LPX                            (0xff << 0)
> +#define HS_PRPR                                (0xff << 8)
> +#define HS_ZERO                                (0xff << 16)
> +#define HS_TRAIL                       (0xff << 24)
> +
> +#define DSI_PHY_TIMECON1       0x114
> +#define TA_GO                          (0xff << 0)
> +#define TA_SURE                                (0xff << 8)
> +#define TA_GET                         (0xff << 16)
> +#define DA_HS_EXIT                     (0xff << 24)
> +
> +#define DSI_PHY_TIMECON2       0x118
> +#define CONT_DET                       (0xff << 0)
> +#define CLK_ZERO                       (0xff << 16)
> +#define CLK_TRAIL                      (0xff << 24)
> +
> +#define DSI_PHY_TIMECON3       0x11c
> +#define CLK_HS_PRPR                    (0xff << 0)
> +#define CLK_HS_POST                    (0xff << 8)
> +#define CLK_HS_EXIT                    (0xff << 16)
> +
> +#define NS_TO_CYCLE(n, c)    ((n) / (c) + (((n) % (c)) ? 1 : 0))
> +
> +struct phy;
> +
> +struct mtk_dsi {
> +       struct mtk_ddp_comp ddp_comp;
> +       struct device *dev;
> +       struct mipi_dsi_host host;
> +       struct drm_encoder encoder;
> +       struct drm_connector conn;
> +       struct drm_panel *panel;
> +       struct drm_bridge *bridge;
> +       struct phy *phy;
> +
> +       void __iomem *regs;
> +
> +       struct clk *engine_clk;
> +       struct clk *digital_clk;
> +       struct clk *hs_clk;
> +
> +       u32 data_rate;
> +
> +       unsigned long mode_flags;
> +       enum mipi_dsi_pixel_format format;
> +       unsigned int lanes;
> +       struct videomode vm;
> +       int refcount;
> +       bool enabled;
> +};
> +
> +static inline struct mtk_dsi *encoder_to_dsi(struct drm_encoder *e)
> +{
> +       return container_of(e, struct mtk_dsi, encoder);
> +}
> +
> +static inline struct mtk_dsi *connector_to_dsi(struct drm_connector *c)
> +{
> +       return container_of(c, struct mtk_dsi, conn);
> +}
> +
> +static inline struct mtk_dsi *host_to_dsi(struct mipi_dsi_host *h)
> +{
> +       return container_of(h, struct mtk_dsi, host);
> +}
> +
> +static void mtk_dsi_mask(struct mtk_dsi *dsi, u32 offset, u32 mask, u32 data)
> +{
> +       u32 temp = readl(dsi->regs + offset);
> +
> +       writel((temp & ~mask) | (data & mask), dsi->regs + offset);
> +}
> +
> +static void dsi_phy_timconfig(struct mtk_dsi *dsi)
> +{
> +       u32 timcon0, timcon1, timcon2, timcon3;
> +       unsigned int ui, cycle_time;
> +       unsigned int lpx;
> +
> +       ui = 1000 / dsi->data_rate + 0x01;
> +       cycle_time = 8000 / dsi->data_rate + 0x01;
> +       lpx = 5;
> +
> +       timcon0 = (8 << 24) | (0xa << 16) | (0x6 << 8) | lpx;
> +       timcon1 = (7 << 24) | (5 * lpx << 16) | ((3 * lpx) / 2) << 8 |
> +                 (4 * lpx);
> +       timcon2 = ((NS_TO_CYCLE(0x64, cycle_time) + 0xa) << 24) |
> +                 (NS_TO_CYCLE(0x150, cycle_time) << 16);
> +       timcon3 = (2 * lpx) << 16 | NS_TO_CYCLE(80 + 52 * ui, cycle_time) << 8 |
> +                  NS_TO_CYCLE(0x40, cycle_time);
> +
> +       writel(timcon0, dsi->regs + DSI_PHY_TIMECON0);
> +       writel(timcon1, dsi->regs + DSI_PHY_TIMECON1);
> +       writel(timcon2, dsi->regs + DSI_PHY_TIMECON2);
> +       writel(timcon3, dsi->regs + DSI_PHY_TIMECON3);
> +}
> +
> +static void mtk_dsi_enable(struct mtk_dsi *dsi)
> +{
> +       mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, DSI_EN);
> +}
> +
> +static void mtk_dsi_disable(struct mtk_dsi *dsi)
> +{
> +       mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, 0);
> +}
> +
> +static void mtk_dsi_reset(struct mtk_dsi *dsi)
> +{
> +       mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_RESET, DSI_RESET);
> +       mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_RESET, 0);
> +}
> +
> +static int mtk_dsi_poweron(struct mtk_dsi *dsi)
> +{
> +       struct device *dev = dsi->dev;
> +       int ret;
> +
> +       if (++dsi->refcount != 1)
> +               return 0;

What is the point of this refcount?
I believe dsi->enabled already ensures poweron/poweroff calls are paired.

> +
> +       /**
> +        * data_rate = (pixel_clock / 1000) * pixel_dipth * mipi_ratio;
> +        * pixel_clock unit is Khz, data_rata unit is MHz, so need divide 1000.
> +        * mipi_ratio is mipi clk coefficient for balance the pixel clk in mipi.
> +        * we set mipi_ratio is 1.05.
> +        */
> +       dsi->data_rate = dsi->vm.pixelclock * 3 * 21 / (1 * 1000 * 10);
> +
> +       ret = clk_set_rate(dsi->hs_clk, dsi->data_rate * 1000000);
> +       if (ret < 0) {
> +               dev_err(dev, "Failed to set data rate: %d\n", ret);
> +               goto err_refcount;
> +       }
> +
> +       phy_power_on(dsi->phy);
> +
> +       ret = clk_prepare_enable(dsi->engine_clk);
> +       if (ret < 0) {
> +               dev_err(dev, "Failed to enable engine clock: %d\n", ret);
> +               goto err_phy_power_off;
> +       }
> +
> +       ret = clk_prepare_enable(dsi->digital_clk);
> +       if (ret < 0) {
> +               dev_err(dev, "Failed to enable digital clock: %d\n", ret);
> +               goto err_disable_engine_clk;
> +       }
> +
> +       mtk_dsi_enable(dsi);
> +       mtk_dsi_reset(dsi);
> +       dsi_phy_timconfig(dsi);
> +
> +       return 0;
> +
> +err_disable_engine_clk:
> +       clk_disable_unprepare(dsi->engine_clk);
> +err_phy_power_off:
> +       phy_power_off(dsi->phy);
> +err_refcount:
> +       dsi->refcount--;
> +       return ret;
> +}
> +
> +static void dsi_clk_ulp_mode_enter(struct mtk_dsi *dsi)
> +{
> +       mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0);
> +       mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_ULPM_EN, 0);
> +}
> +
> +static void dsi_clk_ulp_mode_leave(struct mtk_dsi *dsi)
> +{
> +       mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_ULPM_EN, 0);
> +       mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_WAKEUP_EN, LC_WAKEUP_EN);
> +       mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_WAKEUP_EN, 0);
> +}
> +
> +static void dsi_lane0_ulp_mode_enter(struct mtk_dsi *dsi)
> +{
> +       mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_HS_TX_EN, 0);
> +       mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_ULPM_EN, 0);
> +}
> +
> +static void dsi_lane0_ulp_mode_leave(struct mtk_dsi *dsi)
> +{
> +       mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_ULPM_EN, 0);
> +       mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_WAKEUP_EN, LD0_WAKEUP_EN);
> +       mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_WAKEUP_EN, 0);
> +}
> +
> +static bool dsi_clk_hs_state(struct mtk_dsi *dsi)
> +{
> +       u32 tmp_reg1;
> +
> +       tmp_reg1 = readl(dsi->regs + DSI_PHY_LCCON);
> +       return ((tmp_reg1 & LC_HS_TX_EN) == 1) ? true : false;
> +}
> +
> +static void dsi_clk_hs_mode(struct mtk_dsi *dsi, bool enter)
> +{
> +       if (enter && !dsi_clk_hs_state(dsi))
> +               mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, LC_HS_TX_EN);
> +       else if (!enter && dsi_clk_hs_state(dsi))
> +               mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0);
> +}
> +
> +static void dsi_set_mode(struct mtk_dsi *dsi)
> +{
> +       u32 vid_mode = CMD_MODE;
> +
> +       if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
> +               vid_mode = SYNC_PULSE_MODE;
> +
> +               if ((dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) &&
> +                   !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))
> +                       vid_mode = BURST_MODE;
> +       }
> +
> +       writel(vid_mode, dsi->regs + DSI_MODE_CTRL);
> +}
> +
> +static void dsi_ps_control_vact(struct mtk_dsi *dsi)
> +{
> +       struct videomode *vm = &dsi->vm;
> +       u32 dsi_buf_bpp, ps_wc;
> +       u32 ps_bpp_mode;
> +
> +       if (dsi->format == MIPI_DSI_FMT_RGB565)
> +               dsi_buf_bpp = 2;
> +       else
> +               dsi_buf_bpp = 3;
> +
> +       ps_wc = vm->hactive * dsi_buf_bpp;
> +       ps_bpp_mode = ps_wc;
> +
> +       switch (dsi->format) {
> +       case MIPI_DSI_FMT_RGB888:
> +               ps_bpp_mode |= PACKED_PS_24BIT_RGB888;
> +               break;
> +       case MIPI_DSI_FMT_RGB666:
> +               ps_bpp_mode |= PACKED_PS_18BIT_RGB666;
> +               break;
> +       case MIPI_DSI_FMT_RGB666_PACKED:
> +               ps_bpp_mode |= LOOSELY_PS_18BIT_RGB666;
> +               break;
> +       case MIPI_DSI_FMT_RGB565:
> +               ps_bpp_mode |= PACKED_PS_16BIT_RGB565;
> +               break;
> +       }
> +
> +       writel(vm->vactive, dsi->regs + DSI_VACT_NL);
> +       writel(ps_bpp_mode, dsi->regs + DSI_PSCTRL);
> +       writel(ps_wc, dsi->regs + DSI_HSTX_CKL_WC);
> +}
> +
> +static void dsi_rxtx_control(struct mtk_dsi *dsi)
> +{
> +       u32 tmp_reg;
> +
> +       switch (dsi->lanes) {
> +       case 1:
> +               tmp_reg = 1 << 2;
> +               break;
> +       case 2:
> +               tmp_reg = 3 << 2;
> +               break;
> +       case 3:
> +               tmp_reg = 7 << 2;
> +               break;
> +       case 4:
> +               tmp_reg = 0xf << 2;
> +               break;
> +       default:
> +               tmp_reg = 0xf << 2;
> +               break;
> +       }
> +
> +       writel(tmp_reg, dsi->regs + DSI_TXRX_CTRL);
> +}
> +
> +static void dsi_ps_control(struct mtk_dsi *dsi)
> +{
> +       unsigned int dsi_tmp_buf_bpp;
> +       u32 tmp_reg;
> +
> +       switch (dsi->format) {
> +       case MIPI_DSI_FMT_RGB888:
> +               tmp_reg = PACKED_PS_24BIT_RGB888;
> +               dsi_tmp_buf_bpp = 3;
> +               break;
> +       case MIPI_DSI_FMT_RGB666:
> +               tmp_reg = LOOSELY_PS_18BIT_RGB666;
> +               dsi_tmp_buf_bpp = 3;
> +               break;
> +       case MIPI_DSI_FMT_RGB666_PACKED:
> +               tmp_reg = PACKED_PS_18BIT_RGB666;
> +               dsi_tmp_buf_bpp = 3;
> +               break;
> +       case MIPI_DSI_FMT_RGB565:
> +               tmp_reg = PACKED_PS_16BIT_RGB565;
> +               dsi_tmp_buf_bpp = 2;
> +               break;
> +       default:
> +               tmp_reg = PACKED_PS_24BIT_RGB888;
> +               dsi_tmp_buf_bpp = 3;
> +               break;
> +       }
> +
> +       tmp_reg += dsi->vm.hactive * dsi_tmp_buf_bpp & DSI_PS_WC;
> +       writel(tmp_reg, dsi->regs + DSI_PSCTRL);
> +}
> +
> +static void dsi_config_vdo_timing(struct mtk_dsi *dsi)
> +{
> +       unsigned int horizontal_sync_active_byte;
> +       unsigned int horizontal_backporch_byte;
> +       unsigned int horizontal_frontporch_byte;
> +       unsigned int dsi_tmp_buf_bpp;
> +
> +       struct videomode *vm = &dsi->vm;
> +
> +       if (dsi->format == MIPI_DSI_FMT_RGB565)
> +               dsi_tmp_buf_bpp = 2;
> +       else
> +               dsi_tmp_buf_bpp = 3;
> +
> +       writel(vm->vsync_len, dsi->regs + DSI_VSA_NL);
> +       writel(vm->vback_porch, dsi->regs + DSI_VBP_NL);
> +       writel(vm->vfront_porch, dsi->regs + DSI_VFP_NL);
> +       writel(vm->vactive, dsi->regs + DSI_VACT_NL);
> +
> +       horizontal_sync_active_byte = (vm->hsync_len * dsi_tmp_buf_bpp - 10);
> +
> +       if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
> +               horizontal_backporch_byte =
> +                       (vm->hback_porch * dsi_tmp_buf_bpp - 10);
> +       else
> +               horizontal_backporch_byte = ((vm->hback_porch + vm->hsync_len) *
> +                       dsi_tmp_buf_bpp - 10);
> +
> +       horizontal_frontporch_byte = (vm->hfront_porch * dsi_tmp_buf_bpp - 12);
> +
> +       writel(horizontal_sync_active_byte, dsi->regs + DSI_HSA_WC);
> +       writel(horizontal_backporch_byte, dsi->regs + DSI_HBP_WC);
> +       writel(horizontal_frontporch_byte, dsi->regs + DSI_HFP_WC);
> +
> +       dsi_ps_control(dsi);
> +}
> +
> +static void mtk_dsi_start(struct mtk_dsi *dsi)
> +{
> +       writel(0, dsi->regs + DSI_START);
> +       writel(1, dsi->regs + DSI_START);
> +}
> +
> +static void mtk_dsi_poweroff(struct mtk_dsi *dsi)
> +{
> +       if (WARN_ON(dsi->refcount == 0))
> +               return;
> +
> +       if (--dsi->refcount != 0)
> +               return;
> +
> +       dsi_lane0_ulp_mode_enter(dsi);
> +       dsi_clk_ulp_mode_enter(dsi);
> +
> +       mtk_dsi_disable(dsi);
> +
> +       clk_disable_unprepare(dsi->engine_clk);
> +       clk_disable_unprepare(dsi->digital_clk);
> +
> +       phy_power_off(dsi->phy);
> +}
> +
> +static void mtk_output_dsi_enable(struct mtk_dsi *dsi)
> +{
> +       int ret;
> +
> +       if (dsi->enabled)
> +               return;
> +
> +       if (dsi->panel) {
> +               if (drm_panel_prepare(dsi->panel)) {
> +                       DRM_ERROR("failed to setup the panel\n");
> +                       return;
> +               }
> +       }
> +
> +       ret = mtk_dsi_poweron(dsi);
> +       if (ret < 0) {
> +               DRM_ERROR("failed to power on dsi\n");
> +               return;
> +       }
> +
> +       dsi_rxtx_control(dsi);
> +
> +       dsi_clk_ulp_mode_leave(dsi);
> +       dsi_lane0_ulp_mode_leave(dsi);
> +       dsi_clk_hs_mode(dsi, 0);
> +       dsi_set_mode(dsi);
> +
> +       dsi_ps_control_vact(dsi);
> +       dsi_config_vdo_timing(dsi);
> +
> +       dsi_set_mode(dsi);
> +       dsi_clk_hs_mode(dsi, 1);
> +
> +       mtk_dsi_start(dsi);
> +
> +       dsi->enabled = true;
> +}
> +
> +static void mtk_output_dsi_disable(struct mtk_dsi *dsi)
> +{
> +       if (!dsi->enabled)
> +               return;
> +
> +       if (dsi->panel) {
> +               if (drm_panel_disable(dsi->panel)) {
> +                       DRM_ERROR("failed to disable the panel\n");
> +                       return;
> +               }
> +       }
> +
> +       mtk_dsi_poweroff(dsi);
> +
> +       dsi->enabled = false;
> +}
> +
> +static void mtk_dsi_encoder_destroy(struct drm_encoder *encoder)
> +{
> +       drm_encoder_cleanup(encoder);
> +}
> +
> +static const struct drm_encoder_funcs mtk_dsi_encoder_funcs = {
> +       .destroy = mtk_dsi_encoder_destroy,
> +};
> +
> +static bool mtk_dsi_encoder_mode_fixup(struct drm_encoder *encoder,
> +                                      const struct drm_display_mode *mode,
> +                                      struct drm_display_mode *adjusted_mode)
> +{
> +       return true;
> +}
> +
> +static void mtk_dsi_encoder_mode_set(struct drm_encoder *encoder,
> +                                    struct drm_display_mode *mode,
> +                                    struct drm_display_mode *adjusted)
> +{
> +       struct mtk_dsi *dsi = encoder_to_dsi(encoder);
> +
> +       dsi->vm.pixelclock = adjusted->clock;
> +       dsi->vm.hactive = adjusted->hdisplay;
> +       dsi->vm.hback_porch = adjusted->htotal - adjusted->hsync_end;
> +       dsi->vm.hfront_porch = adjusted->hsync_start - adjusted->hdisplay;
> +       dsi->vm.hsync_len = adjusted->hsync_end - adjusted->hsync_start;
> +
> +       dsi->vm.vactive = adjusted->vdisplay;
> +       dsi->vm.vback_porch = adjusted->vtotal - adjusted->vsync_end;
> +       dsi->vm.vfront_porch = adjusted->vsync_start - adjusted->vdisplay;
> +       dsi->vm.vsync_len = adjusted->vsync_end - adjusted->vsync_start;
> +}
> +
> +static void mtk_dsi_encoder_disable(struct drm_encoder *encoder)
> +{
> +       struct mtk_dsi *dsi = encoder_to_dsi(encoder);
> +
> +       mtk_output_dsi_disable(dsi);
> +}
> +
> +static void mtk_dsi_encoder_enable(struct drm_encoder *encoder)
> +{
> +       struct mtk_dsi *dsi = encoder_to_dsi(encoder);
> +
> +       mtk_output_dsi_enable(dsi);
> +}
> +
> +static enum drm_connector_status mtk_dsi_connector_detect(
> +       struct drm_connector *connector, bool force)
> +{
> +       return connector_status_connected;
> +}
> +
> +static void mtk_dsi_connector_destroy(struct drm_connector *connector)
> +{
> +       drm_connector_unregister(connector);
> +       drm_connector_cleanup(connector);
> +}
> +
> +static int mtk_dsi_connector_get_modes(struct drm_connector *connector)
> +{
> +       struct mtk_dsi *dsi = connector_to_dsi(connector);
> +
> +       return drm_panel_get_modes(dsi->panel);
> +}
> +
> +static struct drm_encoder *mtk_dsi_connector_best_encoder(
> +               struct drm_connector *connector)
> +{
> +       struct mtk_dsi *dsi = connector_to_dsi(connector);
> +
> +       return &dsi->encoder;
> +}
> +
> +static const struct drm_encoder_helper_funcs mtk_dsi_encoder_helper_funcs = {
> +       .mode_fixup = mtk_dsi_encoder_mode_fixup,
> +       .mode_set = mtk_dsi_encoder_mode_set,
> +       .disable = mtk_dsi_encoder_disable,
> +       .enable = mtk_dsi_encoder_enable,
> +};
> +
> +static const struct drm_connector_funcs mtk_dsi_connector_funcs = {
> +       .dpms = drm_atomic_helper_connector_dpms,
> +       .detect = mtk_dsi_connector_detect,
> +       .fill_modes = drm_helper_probe_single_connector_modes,
> +       .destroy = mtk_dsi_connector_destroy,
> +       .reset = drm_atomic_helper_connector_reset,
> +       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static const struct drm_connector_helper_funcs
> +       mtk_dsi_connector_helper_funcs = {
> +       .get_modes = mtk_dsi_connector_get_modes,
> +       .best_encoder = mtk_dsi_connector_best_encoder,
> +};
> +
> +static int mtk_drm_attach_bridge(struct drm_bridge *bridge,
> +                                struct drm_encoder *encoder)
> +{
> +       int ret;
> +
> +       if (!bridge)
> +               return -ENOENT;
> +
> +       encoder->bridge = bridge;
> +       bridge->encoder = encoder;
> +       ret = drm_bridge_attach(encoder->dev, bridge);
> +       if (ret) {
> +               DRM_ERROR("Failed to attach bridge to drm\n");
> +               encoder->bridge = NULL;
> +               bridge->encoder = NULL;
> +       }
> +
> +       return ret;
> +}
> +
> +static int mtk_dsi_create_conn_enc(struct drm_device *drm, struct mtk_dsi *dsi)
> +{
> +       int ret;
> +
> +       ret = drm_encoder_init(drm, &dsi->encoder, &mtk_dsi_encoder_funcs,
> +                              DRM_MODE_ENCODER_DSI, NULL);
> +       if (ret) {
> +               DRM_ERROR("Failed to encoder init to drm\n");
> +               return ret;
> +       }
> +       drm_encoder_helper_add(&dsi->encoder, &mtk_dsi_encoder_helper_funcs);
> +
> +       /*
> +        * Currently display data paths are statically assigned to a crtc each.
> +        * crtc 0 is OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0
> +        */
> +       dsi->encoder.possible_crtcs = 1;
> +
> +       /* Pre-empt DP connector creation if there's a bridge */
> +       ret = mtk_drm_attach_bridge(dsi->bridge, &dsi->encoder);
> +       if (!ret)
> +               return 0;

nit: the above valid early termination of this function here is a bit unusual.
It might be more clear if the "connector init" part below was split out into its
own helper function.

> +
> +       ret = drm_connector_init(drm, &dsi->conn, &mtk_dsi_connector_funcs,
> +                                DRM_MODE_CONNECTOR_DSI);
> +       if (ret) {
> +               DRM_ERROR("Failed to connector init to drm\n");
> +               goto err_encoder_cleanup;
> +       }
> +
> +       drm_connector_helper_add(&dsi->conn, &mtk_dsi_connector_helper_funcs);
> +
> +       ret = drm_connector_register(&dsi->conn);
> +       if (ret) {
> +               DRM_ERROR("Failed to connector register to drm\n");
> +               goto err_connector_cleanup;
> +       }
> +
> +       dsi->conn.dpms = DRM_MODE_DPMS_OFF;
> +       drm_mode_connector_attach_encoder(&dsi->conn, &dsi->encoder);
> +
> +       if (dsi->panel) {
> +               ret = drm_panel_attach(dsi->panel, &dsi->conn);
> +               if (ret) {
> +                       DRM_ERROR("Failed to attach panel to drm\n");
> +                       goto err_connector_unregister;
> +               }
> +       }
> +       return 0;
> +
> +err_connector_unregister:
> +       drm_connector_unregister(&dsi->conn);
> +err_connector_cleanup:
> +       drm_connector_cleanup(&dsi->conn);
> +err_encoder_cleanup:
> +       drm_encoder_cleanup(&dsi->encoder);
> +
> +       return ret;
> +}
> +
> +static void mtk_dsi_destroy_conn_enc(struct mtk_dsi *dsi)
> +{
> +       drm_encoder_cleanup(&dsi->encoder);
> +       /* Skip connector cleanup if creation was delegated to the bridge */
> +       if (dsi->conn.dev) {
> +               drm_connector_unregister(&dsi->conn);
> +               drm_connector_cleanup(&dsi->conn);
> +       }
> +}
> +
> +static void mtk_dsi_ddp_start(struct mtk_ddp_comp *comp)
> +{
> +       struct mtk_dsi *dsi = container_of(comp, struct mtk_dsi, ddp_comp);
> +
> +       mtk_dsi_poweron(dsi);
> +}
> +
> +static void mtk_dsi_ddp_stop(struct mtk_ddp_comp *comp)
> +{
> +       struct mtk_dsi *dsi = container_of(comp, struct mtk_dsi, ddp_comp);
> +
> +       mtk_dsi_poweroff(dsi);
> +}
> +
> +static const struct mtk_ddp_comp_funcs mtk_dsi_funcs = {
> +       .start = mtk_dsi_ddp_start,
> +       .stop = mtk_dsi_ddp_stop,
> +};
> +
> +static int mtk_dsi_host_attach(struct mipi_dsi_host *host,
> +                              struct mipi_dsi_device *device)
> +{
> +       struct mtk_dsi *dsi = host_to_dsi(host);
> +
> +       dsi->lanes = device->lanes;
> +       dsi->format = device->format;
> +       dsi->mode_flags = device->mode_flags;
> +
> +       if (dsi->conn.dev)
> +               drm_helper_hpd_irq_event(dsi->conn.dev);
> +
> +       return 0;
> +}
> +
> +static int mtk_dsi_host_detach(struct mipi_dsi_host *host,
> +                              struct mipi_dsi_device *device)
> +{
> +       struct mtk_dsi *dsi = host_to_dsi(host);
> +
> +       if (dsi->conn.dev)
> +               drm_helper_hpd_irq_event(dsi->conn.dev);
> +
> +       return 0;
> +}
> +
> +static const struct mipi_dsi_host_ops mtk_dsi_ops = {
> +       .attach = mtk_dsi_host_attach,
> +       .detach = mtk_dsi_host_detach,
> +};
> +
> +static int mtk_dsi_bind(struct device *dev, struct device *master, void *data)
> +{
> +       int ret;
> +       struct drm_device *drm = data;
> +       struct mtk_dsi *dsi = dev_get_drvdata(dev);
> +
> +       ret = mtk_ddp_comp_register(drm, &dsi->ddp_comp);
> +       if (ret < 0) {
> +               dev_err(dev, "Failed to register component %s: %d\n",
> +                       dev->of_node->full_name, ret);
> +               return ret;
> +       }
> +
> +       ret = mipi_dsi_host_register(&dsi->host);
> +       if (ret < 0) {
> +               dev_err(dev, "failed to register DSI host: %d\n", ret);
> +               goto err_ddp_comp_unregister;
> +       }
> +
> +       ret = mtk_dsi_create_conn_enc(drm, dsi);
> +       if (ret) {
> +               DRM_ERROR("Encoder create failed with %d\n", ret);
> +               goto err_unregister;
> +       }
> +
> +       return 0;
> +
> +err_unregister:
> +       mipi_dsi_host_unregister(&dsi->host);
> +err_ddp_comp_unregister:
> +       mtk_ddp_comp_unregister(drm, &dsi->ddp_comp);
> +       return ret;
> +}
> +
> +static void mtk_dsi_unbind(struct device *dev, struct device *master,
> +                          void *data)
> +{
> +       struct drm_device *drm = data;
> +       struct mtk_dsi *dsi = dev_get_drvdata(dev);
> +
> +       mtk_dsi_destroy_conn_enc(dsi);
> +       mipi_dsi_host_unregister(&dsi->host);
> +       mtk_ddp_comp_unregister(drm, &dsi->ddp_comp);
> +}
> +
> +static const struct component_ops mtk_dsi_component_ops = {
> +       .bind = mtk_dsi_bind,
> +       .unbind = mtk_dsi_unbind,
> +};
> +
> +static int mtk_dsi_probe(struct platform_device *pdev)
> +{
> +       struct mtk_dsi *dsi;
> +       struct device *dev = &pdev->dev;
> +       struct device_node *remote_node, *endpoint;
> +       struct resource *regs;
> +       int comp_id;
> +       int ret;
> +
> +       dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
> +       if (!dsi)
> +               return -ENOMEM;
> +
> +       dsi->host.ops = &mtk_dsi_ops;
> +       dsi->host.dev = dev;
> +
> +       endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
> +       if (endpoint) {
> +               remote_node = of_graph_get_remote_port_parent(endpoint);
> +               if (!remote_node) {
> +                       dev_err(dev, "No panel connected\n");
> +                       return -ENODEV;
> +               }
> +
> +               dsi->bridge = of_drm_find_bridge(remote_node);
> +               dsi->panel = of_drm_find_panel(remote_node);
> +               of_node_put(remote_node);
> +               if (!dsi->bridge && !dsi->panel) {
> +                       dev_info(dev, "Waiting for bridge or panel driver\n");
> +                       return -EPROBE_DEFER;
> +               }
> +       }
> +
> +       dsi->engine_clk = devm_clk_get(dev, "engine");
> +       if (IS_ERR(dsi->engine_clk)) {
> +               ret = PTR_ERR(dsi->engine_clk);
> +               dev_err(dev, "Failed to get engine clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dsi->digital_clk = devm_clk_get(dev, "digital");
> +       if (IS_ERR(dsi->digital_clk)) {
> +               ret = PTR_ERR(dsi->digital_clk);
> +               dev_err(dev, "Failed to get digital clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dsi->hs_clk = devm_clk_get(dev, "hs");
> +       if (IS_ERR(dsi->hs_clk)) {
> +               ret = PTR_ERR(dsi->hs_clk);
> +               dev_err(dev, "Failed to get hs clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       dsi->regs = devm_ioremap_resource(dev, regs);
> +       if (IS_ERR(dsi->regs)) {
> +               ret = PTR_ERR(dsi->regs);
> +               dev_err(dev, "Failed to ioremap memory: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dsi->phy = devm_phy_get(dev, "dphy");
> +       if (IS_ERR(dsi->phy)) {
> +               ret = PTR_ERR(dsi->phy);
> +               dev_err(dev, "Failed to get MIPI-DPHY: %d\n", ret);
> +               return ret;
> +       }
> +
> +       comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DSI);
> +       if (comp_id < 0) {
> +               dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
> +               return comp_id;
> +       }
> +
> +       ret = mtk_ddp_comp_init(dev, dev->of_node, &dsi->ddp_comp, comp_id,
> +                               &mtk_dsi_funcs);
> +       if (ret) {
> +               dev_err(dev, "Failed to initialize component: %d\n", ret);
> +               return ret;
> +       }
> +
> +       platform_set_drvdata(pdev, dsi);
> +
> +       return component_add(&pdev->dev, &mtk_dsi_component_ops);
> +}
> +
> +static int mtk_dsi_remove(struct platform_device *pdev)
> +{
> +       struct mtk_dsi *dsi = platform_get_drvdata(pdev);
> +
> +       mtk_output_dsi_disable(dsi);

This one looks unmatched.

> +       component_del(&pdev->dev, &mtk_dsi_component_ops);
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int mtk_dsi_suspend(struct device *dev)
> +{
> +       struct mtk_dsi *dsi;
> +
> +       dsi = dev_get_drvdata(dev);
> +
> +       mtk_output_dsi_disable(dsi);
> +       DRM_DEBUG_DRIVER("dsi suspend success!\n");
> +
> +       return 0;
> +}
> +
> +static int mtk_dsi_resume(struct device *dev)
> +{
> +       struct mtk_dsi *dsi;
> +
> +       dsi = dev_get_drvdata(dev);
> +
> +       mtk_output_dsi_enable(dsi);
> +       DRM_DEBUG_DRIVER("dsi resume success!\n");
> +
> +       return 0;
> +}
> +#endif
> +static SIMPLE_DEV_PM_OPS(mtk_dsi_pm_ops, mtk_dsi_suspend, mtk_dsi_resume);

I don't think we want PM ops.
The MTK DRM driver disables/enables encoders during suspend/resume.

> +
> +static const struct of_device_id mtk_dsi_of_match[] = {
> +       { .compatible = "mediatek,mt8173-dsi" },
> +       { },
> +};
> +
> +struct platform_driver mtk_dsi_driver = {
> +       .probe = mtk_dsi_probe,
> +       .remove = mtk_dsi_remove,
> +       .driver = {
> +               .name = "mtk-dsi",
> +               .of_match_table = mtk_dsi_of_match,
> +               .pm = &mtk_dsi_pm_ops,
> +       },
> +};
> diff --git a/drivers/gpu/drm/mediatek/mtk_mipi_tx.c b/drivers/gpu/drm/mediatek/mtk_mipi_tx.c
> new file mode 100644
> index 0000000..b1e8e34
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_mipi_tx.c
> @@ -0,0 +1,487 @@
> +/*
> + * Copyright (c) 2015 MediaTek Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/phy/phy.h>
> +
> +#define MIPITX_DSI_CON         0x00
> +#define RG_DSI_LDOCORE_EN              BIT(0)
> +#define RG_DSI_CKG_LDOOUT_EN           BIT(1)
> +#define RG_DSI_BCLK_SEL                        (3 << 2)
> +#define RG_DSI_LD_IDX_SEL              (7 << 4)
> +#define RG_DSI_PHYCLK_SEL              (2 << 8)
> +#define RG_DSI_DSICLK_FREQ_SEL         BIT(10)
> +#define RG_DSI_LPTX_CLMP_EN            BIT(11)
> +
> +#define MIPITX_DSI_CLOCK_LANE  0x04
> +#define RG_DSI_LNTC_LDOOUT_EN          BIT(0)
> +#define RG_DSI_LNTC_CKLANE_EN          BIT(1)
> +#define RG_DSI_LNTC_LPTX_IPLUS1                BIT(2)
> +#define RG_DSI_LNTC_LPTX_IPLUS2                BIT(3)
> +#define RG_DSI_LNTC_LPTX_IMINUS                BIT(4)
> +#define RG_DSI_LNTC_LPCD_IPLUS         BIT(5)
> +#define RG_DSI_LNTC_LPCD_IMLUS         BIT(6)
> +#define RG_DSI_LNTC_RT_CODE            (0xf << 8)
> +
> +#define MIPITX_DSI_DATA_LANE0  0x08
> +#define RG_DSI_LNT0_LDOOUT_EN          BIT(0)
> +#define RG_DSI_LNT0_CKLANE_EN          BIT(1)
> +#define RG_DSI_LNT0_LPTX_IPLUS1                BIT(2)
> +#define RG_DSI_LNT0_LPTX_IPLUS2                BIT(3)
> +#define RG_DSI_LNT0_LPTX_IMINUS                BIT(4)
> +#define RG_DSI_LNT0_LPCD_IPLUS         BIT(5)
> +#define RG_DSI_LNT0_LPCD_IMINUS                BIT(6)
> +#define RG_DSI_LNT0_RT_CODE            (0xf << 8)
> +
> +#define MIPITX_DSI_DATA_LANE1  0x0c
> +#define RG_DSI_LNT1_LDOOUT_EN          BIT(0)
> +#define RG_DSI_LNT1_CKLANE_EN          BIT(1)
> +#define RG_DSI_LNT1_LPTX_IPLUS1                BIT(2)
> +#define RG_DSI_LNT1_LPTX_IPLUS2                BIT(3)
> +#define RG_DSI_LNT1_LPTX_IMINUS                BIT(4)
> +#define RG_DSI_LNT1_LPCD_IPLUS         BIT(5)
> +#define RG_DSI_LNT1_LPCD_IMINUS                BIT(6)
> +#define RG_DSI_LNT1_RT_CODE            (0xf << 8)
> +
> +#define MIPITX_DSI_DATA_LANE2  0x10
> +#define RG_DSI_LNT2_LDOOUT_EN          BIT(0)
> +#define RG_DSI_LNT2_CKLANE_EN          BIT(1)
> +#define RG_DSI_LNT2_LPTX_IPLUS1                BIT(2)
> +#define RG_DSI_LNT2_LPTX_IPLUS2                BIT(3)
> +#define RG_DSI_LNT2_LPTX_IMINUS                BIT(4)
> +#define RG_DSI_LNT2_LPCD_IPLUS         BIT(5)
> +#define RG_DSI_LNT2_LPCD_IMINUS                BIT(6)
> +#define RG_DSI_LNT2_RT_CODE            (0xf << 8)
> +
> +#define MIPITX_DSI_DATA_LANE3  0x14
> +#define RG_DSI_LNT3_LDOOUT_EN          BIT(0)
> +#define RG_DSI_LNT3_CKLANE_EN          BIT(1)
> +#define RG_DSI_LNT3_LPTX_IPLUS1                BIT(2)
> +#define RG_DSI_LNT3_LPTX_IPLUS2                BIT(3)
> +#define RG_DSI_LNT3_LPTX_IMINUS                BIT(4)
> +#define RG_DSI_LNT3_LPCD_IPLUS         BIT(5)
> +#define RG_DSI_LNT3_LPCD_IMINUS                BIT(6)
> +#define RG_DSI_LNT3_RT_CODE            (0xf << 8)
> +
> +#define MIPITX_DSI_TOP_CON     0x40
> +#define RG_DSI_LNT_INTR_EN             BIT(0)
> +#define RG_DSI_LNT_HS_BIAS_EN          BIT(1)
> +#define RG_DSI_LNT_IMP_CAL_EN          BIT(2)
> +#define RG_DSI_LNT_TESTMODE_EN         BIT(3)
> +#define RG_DSI_LNT_IMP_CAL_CODE                (0xf << 4)
> +#define RG_DSI_LNT_AIO_SEL             (7 << 8)
> +#define RG_DSI_PAD_TIE_LOW_EN          BIT(11)
> +#define RG_DSI_DEBUG_INPUT_EN          BIT(12)
> +#define RG_DSI_PRESERVE                        (7 << 13)
> +
> +#define MIPITX_DSI_BG_CON      0x44
> +#define RG_DSI_BG_CORE_EN              BIT(0)
> +#define RG_DSI_BG_CKEN                 BIT(1)
> +#define RG_DSI_BG_DIV                  (0x3 << 2)
> +#define RG_DSI_BG_FAST_CHARGE          BIT(4)
> +#define RG_DSI_VOUT_MSK                        (0x3ffff << 5)
> +#define RG_DSI_V12_SEL                 (7 << 5)
> +#define RG_DSI_V10_SEL                 (7 << 8)
> +#define RG_DSI_V072_SEL                        (7 << 11)
> +#define RG_DSI_V04_SEL                 (7 << 14)
> +#define RG_DSI_V032_SEL                        (7 << 17)
> +#define RG_DSI_V02_SEL                 (7 << 20)
> +#define RG_DSI_BG_R1_TRIM              (0xf << 24)
> +#define RG_DSI_BG_R2_TRIM              (0xf << 28)
> +
> +#define MIPITX_DSI_PLL_CON0    0x50
> +#define RG_DSI_MPPLL_PLL_EN            BIT(0)
> +#define RG_DSI_MPPLL_DIV_MSK           (0x1ff << 1)
> +#define RG_DSI_MPPLL_PREDIV            (3 << 1)
> +#define RG_DSI_MPPLL_TXDIV0            (3 << 3)
> +#define RG_DSI_MPPLL_TXDIV1            (3 << 5)
> +#define RG_DSI_MPPLL_POSDIV            (7 << 7)
> +#define RG_DSI_MPPLL_MONVC_EN          BIT(10)
> +#define RG_DSI_MPPLL_MONREF_EN         BIT(11)
> +#define RG_DSI_MPPLL_VOD_EN            BIT(12)
> +
> +#define MIPITX_DSI_PLL_CON1    0x54
> +#define RG_DSI_MPPLL_SDM_FRA_EN                BIT(0)
> +#define RG_DSI_MPPLL_SDM_SSC_PH_INIT   BIT(1)
> +#define RG_DSI_MPPLL_SDM_SSC_EN                BIT(2)
> +#define RG_DSI_MPPLL_SDM_SSC_PRD       (0xffff << 16)
> +
> +#define MIPITX_DSI_PLL_CON2    0x58
> +
> +#define MIPITX_DSI_PLL_PWR     0x68
> +#define RG_DSI_MPPLL_SDM_PWR_ON                BIT(0)
> +#define RG_DSI_MPPLL_SDM_ISO_EN                BIT(1)
> +#define RG_DSI_MPPLL_SDM_PWR_ACK       BIT(8)
> +
> +#define MIPITX_DSI_SW_CTRL     0x80
> +#define SW_CTRL_EN                     BIT(0)
> +
> +#define MIPITX_DSI_SW_CTRL_CON0        0x84
> +#define SW_LNTC_LPTX_PRE_OE            BIT(0)
> +#define SW_LNTC_LPTX_OE                        BIT(1)
> +#define SW_LNTC_LPTX_P                 BIT(2)
> +#define SW_LNTC_LPTX_N                 BIT(3)
> +#define SW_LNTC_HSTX_PRE_OE            BIT(4)
> +#define SW_LNTC_HSTX_OE                        BIT(5)
> +#define SW_LNTC_HSTX_ZEROCLK           BIT(6)
> +#define SW_LNT0_LPTX_PRE_OE            BIT(7)
> +#define SW_LNT0_LPTX_OE                        BIT(8)
> +#define SW_LNT0_LPTX_P                 BIT(9)
> +#define SW_LNT0_LPTX_N                 BIT(10)
> +#define SW_LNT0_HSTX_PRE_OE            BIT(11)
> +#define SW_LNT0_HSTX_OE                        BIT(12)
> +#define SW_LNT0_LPRX_EN                        BIT(13)
> +#define SW_LNT1_LPTX_PRE_OE            BIT(14)
> +#define SW_LNT1_LPTX_OE                        BIT(15)
> +#define SW_LNT1_LPTX_P                 BIT(16)
> +#define SW_LNT1_LPTX_N                 BIT(17)
> +#define SW_LNT1_HSTX_PRE_OE            BIT(18)
> +#define SW_LNT1_HSTX_OE                        BIT(19)
> +#define SW_LNT2_LPTX_PRE_OE            BIT(20)
> +#define SW_LNT2_LPTX_OE                        BIT(21)
> +#define SW_LNT2_LPTX_P                 BIT(22)
> +#define SW_LNT2_LPTX_N                 BIT(23)
> +#define SW_LNT2_HSTX_PRE_OE            BIT(24)
> +#define SW_LNT2_HSTX_OE                        BIT(25)
> +
> +struct mtk_mipi_tx {
> +       struct device *dev;
> +       void __iomem *regs;
> +       unsigned int data_rate;
> +       struct clk_hw pll_hw;
> +       struct clk *pll;
> +};
> +
> +static void mtk_mipi_tx_mask(struct mtk_mipi_tx *mipi_tx, u32 offset, u32 mask,
> +                            u32 data)
> +{
> +       u32 temp = readl(mipi_tx->regs + offset);
> +
> +       writel((temp & ~mask) | (data & mask), mipi_tx->regs + offset);
> +}
> +
> +static int mtk_mipi_tx_pll_prepare(struct clk_hw *hw)
> +{
> +       struct mtk_mipi_tx *mipi_tx = container_of(hw, struct mtk_mipi_tx,
> +                                                  pll_hw);
> +       unsigned int txdiv, txdiv0, txdiv1;
> +       u64 pcw;
> +
> +       dev_dbg(mipi_tx->dev, "prepare: %u Hz\n", mipi_tx->data_rate);
> +
> +       if (mipi_tx->data_rate >= 500000000) {
> +               txdiv = 1;
> +               txdiv0 = 0;
> +               txdiv1 = 0;
> +       } else if (mipi_tx->data_rate >= 250000000) {
> +               txdiv = 2;
> +               txdiv0 = 1;
> +               txdiv1 = 0;
> +       } else if (mipi_tx->data_rate >= 125000000) {
> +               txdiv = 4;
> +               txdiv0 = 2;
> +               txdiv1 = 0;
> +       } else if (mipi_tx->data_rate > 62000000) {
> +               txdiv = 8;
> +               txdiv0 = 2;
> +               txdiv1 = 1;
> +       } else if (mipi_tx->data_rate >= 50000000) {
> +               txdiv = 16;
> +               txdiv0 = 2;
> +               txdiv1 = 2;
> +       } else {
> +               return -EINVAL;
> +       }
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_BG_CON,
> +                        RG_DSI_VOUT_MSK | RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN,
> +                        (4 << 20) | (4 << 17) | (4 << 14) |
> +                        (4 << 11) | (4 << 8) | (4 << 5) |
> +                        RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN);
> +
> +       usleep_range(30, 100);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON,
> +                        RG_DSI_LNT_IMP_CAL_CODE | RG_DSI_LNT_HS_BIAS_EN,
> +                        (8 << 4) | RG_DSI_LNT_HS_BIAS_EN);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CON,
> +                        RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN,
> +                        RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_PWR,
> +                        RG_DSI_MPPLL_SDM_PWR_ON | RG_DSI_MPPLL_SDM_ISO_EN,
> +                        RG_DSI_MPPLL_SDM_PWR_ON);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN, 0);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0,
> +                        RG_DSI_MPPLL_TXDIV0 | RG_DSI_MPPLL_TXDIV1 |
> +                        RG_DSI_MPPLL_PREDIV,
> +                        (txdiv0 << 3) | (txdiv1 << 5));
> +
> +       /*
> +        * PLL PCW config
> +        * PCW bit 24~30 = integer part of pcw
> +        * PCW bit 0~23 = fractional part of pcw
> +        * pcw = data_Rate*4*txdiv/(Ref_clk*2);
> +        * Post DIV =4, so need data_Rate*4
> +        * Ref_clk is 26MHz
> +        */
> +       pcw = ((u64)mipi_tx->data_rate * 2 * txdiv) << 24;
> +       pcw /= 26000000;
> +       writel(pcw, mipi_tx->regs + MIPITX_DSI_PLL_CON2);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON1,
> +                        RG_DSI_MPPLL_SDM_FRA_EN, RG_DSI_MPPLL_SDM_FRA_EN);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0,
> +                        RG_DSI_MPPLL_PLL_EN, RG_DSI_MPPLL_PLL_EN);
> +
> +       usleep_range(20, 100);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON1,
> +                        RG_DSI_MPPLL_SDM_SSC_EN, 0);
> +
> +       return 0;
> +}
> +
> +static void mtk_mipi_tx_pll_unprepare(struct clk_hw *hw)
> +{
> +       struct mtk_mipi_tx *mipi_tx = container_of(hw, struct mtk_mipi_tx,
> +                                                  pll_hw);
> +
> +       dev_dbg(mipi_tx->dev, "unprepare\n");
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN, 0);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_PWR,
> +                        RG_DSI_MPPLL_SDM_ISO_EN | RG_DSI_MPPLL_SDM_PWR_ON,
> +                        RG_DSI_MPPLL_SDM_ISO_EN);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_LNT_HS_BIAS_EN, 0);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CON,
> +                        RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN, 0);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_BG_CON,
> +                        RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN, 0);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_DIV_MSK, 0);
> +}
> +
> +static long mtk_mipi_tx_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                      unsigned long *prate)
> +{
> +       return clamp_val(rate, 50000000, 1250000000);
> +}
> +
> +static int mtk_mipi_tx_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                   unsigned long parent_rate)
> +{
> +       struct mtk_mipi_tx *mipi_tx = container_of(hw, struct mtk_mipi_tx,
> +                                                  pll_hw);
> +
> +       dev_dbg(mipi_tx->dev, "set rate: %lu Hz\n", rate);
> +
> +       mipi_tx->data_rate = rate;
> +
> +       return 0;
> +}
> +
> +static unsigned long mtk_mipi_tx_pll_recalc_rate(struct clk_hw *hw,
> +                                                unsigned long parent_rate)
> +{
> +       struct mtk_mipi_tx *mipi_tx = container_of(hw, struct mtk_mipi_tx,
> +                                                  pll_hw);

An inline function / macro would help here.

> +       return mipi_tx->data_rate;
> +}
> +
> +static const struct clk_ops mtk_mipi_tx_pll_ops = {
> +       .prepare = mtk_mipi_tx_pll_prepare,
> +       .unprepare = mtk_mipi_tx_pll_unprepare,
> +       .round_rate = mtk_mipi_tx_pll_round_rate,
> +       .set_rate = mtk_mipi_tx_pll_set_rate,
> +       .recalc_rate = mtk_mipi_tx_pll_recalc_rate,
> +};
> +
> +static int mtk_mipi_tx_power_on_signal(struct phy *phy)
> +{
> +       struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CLOCK_LANE,
> +                        RG_DSI_LNTC_LDOOUT_EN, RG_DSI_LNTC_LDOOUT_EN);
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE0,
> +                        RG_DSI_LNT0_LDOOUT_EN, RG_DSI_LNT0_LDOOUT_EN);
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE1,
> +                        RG_DSI_LNT1_LDOOUT_EN, RG_DSI_LNT1_LDOOUT_EN);
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE2,
> +                        RG_DSI_LNT2_LDOOUT_EN, RG_DSI_LNT2_LDOOUT_EN);
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE3,
> +                        RG_DSI_LNT3_LDOOUT_EN, RG_DSI_LNT3_LDOOUT_EN);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_PAD_TIE_LOW_EN, 0);
> +
> +       return 0;
> +}
> +
> +static int mtk_mipi_tx_power_on(struct phy *phy)
> +{
> +       struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
> +       int ret;
> +
> +       /* Power up core and enable PLL */
> +       ret = clk_prepare_enable(mipi_tx->pll);
> +       if (ret < 0)
> +               return ret;
> +
> +       /* Enable DSI Lane LDO outputs, disable pad tie low */
> +       mtk_mipi_tx_power_on_signal(phy);
> +
> +       return 0;
> +}
> +
> +static void mtk_mipi_tx_power_off_signal(struct phy *phy)
> +{
> +       struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_PAD_TIE_LOW_EN,
> +                        RG_DSI_PAD_TIE_LOW_EN);

As mentioned in the HDMI review, _set_bits() / _clr_bits() is more readable than
_mask() if we are just setting/clearing groups of bits.

> +
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CLOCK_LANE,
> +                        RG_DSI_LNTC_LDOOUT_EN, 0);
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE0,
> +                        RG_DSI_LNT0_LDOOUT_EN, 0);
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE1,
> +                        RG_DSI_LNT1_LDOOUT_EN, 0);
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE2,
> +                        RG_DSI_LNT2_LDOOUT_EN, 0);
> +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE3,
> +                        RG_DSI_LNT3_LDOOUT_EN, 0);
> +}
> +
> +static int mtk_mipi_tx_power_off(struct phy *phy)
> +{
> +       struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
> +
> +       /* Enable pad tie low, disable DSI Lane LDO outputs */
> +       mtk_mipi_tx_power_off_signal(phy);
> +
> +       /* Disable PLL and power down core */
> +       clk_disable_unprepare(mipi_tx->pll);
> +
> +       return 0;
> +}
> +
> +static const struct phy_ops mtk_mipi_tx_ops = {
> +       .power_on = mtk_mipi_tx_power_on,
> +       .power_off = mtk_mipi_tx_power_off,
> +       .owner = THIS_MODULE,
> +};
> +
> +static int mtk_mipi_tx_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct mtk_mipi_tx *mipi_tx;
> +       struct resource *mem;
> +       struct clk *ref_clk;
> +       const char *ref_clk_name;
> +       struct clk_init_data clk_init = {
> +               .ops = &mtk_mipi_tx_pll_ops,
> +               .num_parents = 1,
> +               .parent_names = (const char * const *)&ref_clk_name,
> +               .flags = CLK_SET_RATE_GATE,
> +       };
> +       struct phy *phy;
> +       struct phy_provider *phy_provider;
> +       int ret;
> +
> +       mipi_tx = devm_kzalloc(dev, sizeof(*mipi_tx), GFP_KERNEL);
> +       if (!mipi_tx)
> +               return -ENOMEM;
> +
> +       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       mipi_tx->regs = devm_ioremap_resource(dev, mem);
> +       if (IS_ERR(mipi_tx->regs)) {
> +               ret = PTR_ERR(mipi_tx->regs);
> +               dev_err(dev, "Failed to get memory resource: %d\n", ret);
> +               return ret;
> +       }
> +
> +       ref_clk = devm_clk_get(dev, NULL);
> +       if (IS_ERR(ref_clk)) {
> +               ret = PTR_ERR(ref_clk);
> +               dev_err(dev, "Failed to get reference clock: %d\n", ret);
> +               return ret;
> +       }
> +       ref_clk_name = __clk_get_name(ref_clk);
> +
> +       ret = of_property_read_string(dev->of_node, "clock-output-names",
> +                                     &clk_init.name);
> +       if (ret < 0) {
> +               dev_err(dev, "Failed to read clock-output-names: %d\n", ret);
> +               return ret;
> +       }
> +
> +       mipi_tx->pll_hw.init = &clk_init;
> +       mipi_tx->pll = devm_clk_register(dev, &mipi_tx->pll_hw);
> +       if (IS_ERR(mipi_tx->pll)) {
> +               ret = PTR_ERR(mipi_tx->pll);
> +               dev_err(dev, "Failed to register PLL: %d\n", ret);
> +               return ret;
> +       }
> +
> +       phy = devm_phy_create(dev, NULL, &mtk_mipi_tx_ops);
> +       if (IS_ERR(phy)) {
> +               ret = PTR_ERR(phy);
> +               dev_err(dev, "Failed to create MIPI D-PHY: %d\n", ret);
> +               return ret;
> +       }
> +       phy_set_drvdata(phy, mipi_tx);
> +
> +       phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
> +       if (IS_ERR(phy)) {
> +               ret = PTR_ERR(phy_provider);
> +               return ret;
> +       }
> +
> +       mipi_tx->dev = dev;
> +
> +       return of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
> +                                  mipi_tx->pll);
> +}
> +
> +static int mtk_mipi_tx_remove(struct platform_device *pdev)
> +{
> +       of_clk_del_provider(pdev->dev.of_node);
> +       return 0;
> +}
> +
> +static const struct of_device_id mtk_mipi_tx_match[] = {
> +       { .compatible = "mediatek,mt8173-mipi-tx", },
> +       {},
> +};
> +
> +struct platform_driver mtk_mipi_tx_driver = {
> +       .probe = mtk_mipi_tx_probe,
> +       .remove = mtk_mipi_tx_remove,
> +       .driver = {
> +               .name = "mediatek-mipi-tx",
> +               .of_match_table = mtk_mipi_tx_match,
> +       },
> +};
> --
> 2.7.0
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v13 04/14] drm/mediatek: Add DPI sub driver
       [not found]   ` <1457443649-12133-5-git-send-email-p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
@ 2016-03-09 14:23     ` Daniel Kurtz
  2016-03-15 11:45       ` Philipp Zabel
  0 siblings, 1 reply; 22+ messages in thread
From: Daniel Kurtz @ 2016-03-09 14:23 UTC (permalink / raw)
  To: Philipp Zabel
  Cc: dri-devel, Rob Herring, Daniel Vetter, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Dave Airlie, Matthias Brugger,
	Cawa Cheng, CK Hu, YT Shen, Jitao Shi, Jie Qiu, Paul Bolle,
	Daniel Stone, Tomasz Figa, Yingjoe Chen, Michael Turquette,
	Stephen Boyd, Emil Velikov, open list:OPEN FIRMWARE AND...,
	ARM/Mediatek

Hi Philipp, Jie,

Some small comments.
Nothing that can't be fixed after merging if you prefer.

On Tue, Mar 8, 2016 at 9:27 PM, Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> wrote:
> From: Jie Qiu <jie.qiu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
>
> Add DPI connector/encoder to support HDMI output via the
> attached HDMI bridge.
>
> Signed-off-by: Jie Qiu <jie.qiu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> Signed-off-by: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> ---
>  drivers/gpu/drm/mediatek/Makefile       |   3 +-
>  drivers/gpu/drm/mediatek/mtk_dpi.c      | 757 ++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/mediatek/mtk_dpi.h      |  85 ++++
>  drivers/gpu/drm/mediatek/mtk_dpi_regs.h | 228 ++++++++++
>  drivers/gpu/drm/mediatek/mtk_drm_drv.c  |   1 +
>  drivers/gpu/drm/mediatek/mtk_drm_drv.h  |   1 +
>  6 files changed, 1074 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi.c
>  create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi.h
>  create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi_regs.h
>
> diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile
> index e781db5a..5fcf58e 100644
> --- a/drivers/gpu/drm/mediatek/Makefile
> +++ b/drivers/gpu/drm/mediatek/Makefile
> @@ -8,6 +8,7 @@ mediatek-drm-y := mtk_disp_ovl.o \
>                   mtk_drm_gem.o \
>                   mtk_drm_plane.o \
>                   mtk_dsi.o \
> -                 mtk_mipi_tx.o
> +                 mtk_mipi_tx.o \
> +                 mtk_dpi.o
>
>  obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
> diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.c b/drivers/gpu/drm/mediatek/mtk_dpi.c
> new file mode 100644
> index 0000000..ae81906
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_dpi.c
> @@ -0,0 +1,757 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> + *
> + * 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 <linux/kernel.h>
> +#include <linux/component.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/interrupt.h>
> +#include <linux/clk.h>
> +
> +#include "mtk_dpi.h"
> +#include "mtk_dpi_regs.h"
> +
> +enum mtk_dpi_polarity {
> +       MTK_DPI_POLARITY_RISING,
> +       MTK_DPI_POLARITY_FALLING,
> +};
> +
> +enum mtk_dpi_power_ctl {
> +       DPI_POWER_START = BIT(0),
> +       DPI_POWER_ENABLE = BIT(1),
> +       DPI_POWER_RESUME = BIT(2),
> +};
> +
> +struct mtk_dpi_polarities {
> +       enum mtk_dpi_polarity de_pol;
> +       enum mtk_dpi_polarity ck_pol;
> +       enum mtk_dpi_polarity hsync_pol;
> +       enum mtk_dpi_polarity vsync_pol;
> +};
> +
> +struct mtk_dpi_sync_param {
> +       u32 sync_width;
> +       u32 front_porch;
> +       u32 back_porch;
> +       bool shift_half_line;
> +};
> +
> +struct mtk_dpi_yc_limit {
> +       u16 y_top;
> +       u16 y_bottom;
> +       u16 c_top;
> +       u16 c_bottom;
> +};
> +
> +static void mtk_dpi_mask(struct mtk_dpi *dpi, u32 offset, u32 val, u32 mask)
> +{
> +       u32 tmp = readl(dpi->regs + offset) & ~mask;
> +
> +       tmp |= (val & mask);
> +       writel(tmp, dpi->regs + offset);
> +}
> +
> +static void mtk_dpi_sw_reset(struct mtk_dpi *dpi, bool reset)
> +{
> +       mtk_dpi_mask(dpi, DPI_RET, reset ? RST : 0, RST);
> +}
> +
> +static void mtk_dpi_enable(struct mtk_dpi *dpi)
> +{
> +       mtk_dpi_mask(dpi, DPI_EN, EN, EN);
> +}
> +
> +static void mtk_dpi_disable(struct mtk_dpi *dpi)
> +{
> +       mtk_dpi_mask(dpi, DPI_EN, 0, EN);
> +}
> +
> +static void mtk_dpi_config_hsync(struct mtk_dpi *dpi,
> +                                struct mtk_dpi_sync_param *sync)
> +{
> +       mtk_dpi_mask(dpi, DPI_TGEN_HWIDTH,
> +                    sync->sync_width << HPW, HPW_MASK);
> +       mtk_dpi_mask(dpi, DPI_TGEN_HPORCH,
> +                    sync->back_porch << HBP, HBP_MASK);
> +       mtk_dpi_mask(dpi, DPI_TGEN_HPORCH, sync->front_porch << HFP,
> +                    HFP_MASK);
> +}
> +
> +static void mtk_dpi_config_vsync(struct mtk_dpi *dpi,
> +                                struct mtk_dpi_sync_param *sync,
> +                                u32 width_addr, u32 porch_addr)
> +{
> +       mtk_dpi_mask(dpi, width_addr,
> +                    sync->sync_width << VSYNC_WIDTH_SHIFT,
> +                    VSYNC_WIDTH_MASK);
> +       mtk_dpi_mask(dpi, width_addr,
> +                    sync->shift_half_line << VSYNC_HALF_LINE_SHIFT,
> +                    VSYNC_HALF_LINE_MASK);
> +       mtk_dpi_mask(dpi, porch_addr,
> +                    sync->back_porch << VSYNC_BACK_PORCH_SHIFT,
> +                    VSYNC_BACK_PORCH_MASK);
> +       mtk_dpi_mask(dpi, porch_addr,
> +                    sync->front_porch << VSYNC_FRONT_PORCH_SHIFT,
> +                    VSYNC_FRONT_PORCH_MASK);
> +}
> +
> +static void mtk_dpi_config_vsync_lodd(struct mtk_dpi *dpi,
> +                                     struct mtk_dpi_sync_param *sync)
> +{
> +       mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH, DPI_TGEN_VPORCH);
> +}
> +
> +static void mtk_dpi_config_vsync_leven(struct mtk_dpi *dpi,
> +                                      struct mtk_dpi_sync_param *sync)
> +{
> +       mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_LEVEN,
> +                            DPI_TGEN_VPORCH_LEVEN);
> +}
> +
> +static void mtk_dpi_config_vsync_rodd(struct mtk_dpi *dpi,
> +                                     struct mtk_dpi_sync_param *sync)
> +{
> +       mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_RODD,
> +                            DPI_TGEN_VPORCH_RODD);
> +}
> +
> +static void mtk_dpi_config_vsync_reven(struct mtk_dpi *dpi,
> +                                      struct mtk_dpi_sync_param *sync)
> +{
> +       mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_REVEN,
> +                            DPI_TGEN_VPORCH_REVEN);
> +}
> +
> +static void mtk_dpi_config_pol(struct mtk_dpi *dpi,
> +                              struct mtk_dpi_polarities *dpi_pol)
> +{
> +       unsigned int pol;
> +
> +       pol = (dpi_pol->ck_pol == MTK_DPI_POLARITY_RISING ? 0 : CK_POL) |
> +             (dpi_pol->de_pol == MTK_DPI_POLARITY_RISING ? 0 : DE_POL) |
> +             (dpi_pol->hsync_pol == MTK_DPI_POLARITY_RISING ? 0 : HSYNC_POL) |
> +             (dpi_pol->vsync_pol == MTK_DPI_POLARITY_RISING ? 0 : VSYNC_POL);
> +       mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, pol,
> +                    CK_POL | DE_POL | HSYNC_POL | VSYNC_POL);
> +}
> +
> +static void mtk_dpi_config_3d(struct mtk_dpi *dpi, bool en_3d)
> +{
> +       mtk_dpi_mask(dpi, DPI_CON, en_3d ? TDFP_EN : 0, TDFP_EN);
> +}
> +
> +static void mtk_dpi_config_interface(struct mtk_dpi *dpi, bool inter)
> +{
> +       mtk_dpi_mask(dpi, DPI_CON, inter ? INTL_EN : 0, INTL_EN);
> +}
> +
> +static void mtk_dpi_config_fb_size(struct mtk_dpi *dpi, u32 width, u32 height)
> +{
> +       mtk_dpi_mask(dpi, DPI_SIZE, width << HSIZE, HSIZE_MASK);
> +       mtk_dpi_mask(dpi, DPI_SIZE, height << VSIZE, VSIZE_MASK);
> +}
> +
> +static void mtk_dpi_config_channel_limit(struct mtk_dpi *dpi,
> +                                        struct mtk_dpi_yc_limit *limit)
> +{
> +       mtk_dpi_mask(dpi, DPI_Y_LIMIT, limit->y_bottom << Y_LIMINT_BOT,
> +                    Y_LIMINT_BOT_MASK);
> +       mtk_dpi_mask(dpi, DPI_Y_LIMIT, limit->y_top << Y_LIMINT_TOP,
> +                    Y_LIMINT_TOP_MASK);
> +       mtk_dpi_mask(dpi, DPI_C_LIMIT, limit->c_bottom << C_LIMIT_BOT,
> +                    C_LIMIT_BOT_MASK);
> +       mtk_dpi_mask(dpi, DPI_C_LIMIT, limit->c_top << C_LIMIT_TOP,
> +                    C_LIMIT_TOP_MASK);
> +}
> +
> +static void mtk_dpi_config_bit_num(struct mtk_dpi *dpi,
> +                                  enum mtk_dpi_out_bit_num num)
> +{
> +       u32 val;
> +
> +       switch (num) {
> +       case MTK_DPI_OUT_BIT_NUM_8BITS:
> +               val = OUT_BIT_8;
> +               break;
> +       case MTK_DPI_OUT_BIT_NUM_10BITS:
> +               val = OUT_BIT_10;
> +               break;
> +       case MTK_DPI_OUT_BIT_NUM_12BITS:
> +               val = OUT_BIT_12;
> +               break;
> +       case MTK_DPI_OUT_BIT_NUM_16BITS:
> +               val = OUT_BIT_16;
> +               break;
> +       default:
> +               val = OUT_BIT_8;
> +               break;
> +       }
> +       mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << OUT_BIT,
> +                    OUT_BIT_MASK);
> +}
> +
> +static void mtk_dpi_config_yc_map(struct mtk_dpi *dpi,
> +                                 enum mtk_dpi_out_yc_map map)
> +{
> +       u32 val;
> +
> +       switch (map) {
> +       case MTK_DPI_OUT_YC_MAP_RGB:
> +               val = YC_MAP_RGB;
> +               break;
> +       case MTK_DPI_OUT_YC_MAP_CYCY:
> +               val = YC_MAP_CYCY;
> +               break;
> +       case MTK_DPI_OUT_YC_MAP_YCYC:
> +               val = YC_MAP_YCYC;
> +               break;
> +       case MTK_DPI_OUT_YC_MAP_CY:
> +               val = YC_MAP_CY;
> +               break;
> +       case MTK_DPI_OUT_YC_MAP_YC:
> +               val = YC_MAP_YC;
> +               break;
> +       default:
> +               val = YC_MAP_RGB;
> +               break;
> +       }
> +
> +       mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << YC_MAP, YC_MAP_MASK);
> +}
> +
> +static void mtk_dpi_config_channel_swap(struct mtk_dpi *dpi,
> +                                       enum mtk_dpi_out_channel_swap swap)
> +{
> +       u32 val;
> +
> +       switch (swap) {
> +       case MTK_DPI_OUT_CHANNEL_SWAP_RGB:
> +               val = SWAP_RGB;
> +               break;
> +       case MTK_DPI_OUT_CHANNEL_SWAP_GBR:
> +               val = SWAP_GBR;
> +               break;
> +       case MTK_DPI_OUT_CHANNEL_SWAP_BRG:
> +               val = SWAP_BRG;
> +               break;
> +       case MTK_DPI_OUT_CHANNEL_SWAP_RBG:
> +               val = SWAP_RBG;
> +               break;
> +       case MTK_DPI_OUT_CHANNEL_SWAP_GRB:
> +               val = SWAP_GRB;
> +               break;
> +       case MTK_DPI_OUT_CHANNEL_SWAP_BGR:
> +               val = SWAP_BGR;
> +               break;
> +       default:
> +               val = SWAP_RGB;
> +               break;
> +       }
> +
> +       mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << CH_SWAP, CH_SWAP_MASK);
> +}
> +
> +static void mtk_dpi_config_yuv422_enable(struct mtk_dpi *dpi, bool enable)
> +{
> +       mtk_dpi_mask(dpi, DPI_CON, enable ? YUV422_EN : 0, YUV422_EN);
> +}
> +
> +static void mtk_dpi_config_csc_enable(struct mtk_dpi *dpi, bool enable)
> +{
> +       mtk_dpi_mask(dpi, DPI_CON, enable ? CSC_ENABLE : 0, CSC_ENABLE);
> +}
> +
> +static void mtk_dpi_config_swap_input(struct mtk_dpi *dpi, bool enable)
> +{
> +       mtk_dpi_mask(dpi, DPI_CON, enable ? IN_RB_SWAP : 0, IN_RB_SWAP);
> +}
> +
> +static void mtk_dpi_config_2n_h_fre(struct mtk_dpi *dpi)
> +{
> +       mtk_dpi_mask(dpi, DPI_H_FRE_CON, H_FRE_2N, H_FRE_2N);
> +}
> +
> +static void mtk_dpi_config_color_format(struct mtk_dpi *dpi,
> +                                       enum mtk_dpi_out_color_format format)
> +{
> +       if ((format == MTK_DPI_COLOR_FORMAT_YCBCR_444) ||
> +           (format == MTK_DPI_COLOR_FORMAT_YCBCR_444_FULL)) {
> +               mtk_dpi_config_yuv422_enable(dpi, false);
> +               mtk_dpi_config_csc_enable(dpi, true);
> +               mtk_dpi_config_swap_input(dpi, false);
> +               mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_BGR);
> +       } else if ((format == MTK_DPI_COLOR_FORMAT_YCBCR_422) ||
> +                  (format == MTK_DPI_COLOR_FORMAT_YCBCR_422_FULL)) {
> +               mtk_dpi_config_yuv422_enable(dpi, true);
> +               mtk_dpi_config_csc_enable(dpi, true);
> +               mtk_dpi_config_swap_input(dpi, true);
> +               mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB);
> +       } else {
> +               mtk_dpi_config_yuv422_enable(dpi, false);
> +               mtk_dpi_config_csc_enable(dpi, false);
> +               mtk_dpi_config_swap_input(dpi, false);
> +               mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB);
> +       }
> +}
> +
> +static void mtk_dpi_power_off(struct mtk_dpi *dpi, enum mtk_dpi_power_ctl pctl)
> +{
> +       dpi->power_ctl &= ~pctl;
> +
> +       if ((dpi->power_ctl & DPI_POWER_START) ||
> +           ((dpi->power_ctl & DPI_POWER_ENABLE) &&
> +            (dpi->power_ctl & DPI_POWER_RESUME)))
> +               return;
> +
> +       if (!dpi->power_sta)
> +               return;
> +
> +       mtk_dpi_disable(dpi);
> +       clk_disable_unprepare(dpi->pixel_clk);
> +       clk_disable_unprepare(dpi->engine_clk);
> +       dpi->power_sta = false;
> +}
> +
> +static int mtk_dpi_power_on(struct mtk_dpi *dpi, enum mtk_dpi_power_ctl pctl)
> +{
> +       int ret;
> +
> +       dpi->power_ctl |= pctl;
> +
> +       if (!(dpi->power_ctl & DPI_POWER_START) &&
> +           !((dpi->power_ctl & DPI_POWER_ENABLE) &&
> +             ((dpi->power_ctl & DPI_POWER_RESUME))))

There one too many () on the POWER_RESUME clause.

> +               return 0;
> +
> +       if (dpi->power_sta)
> +               return 0;
> +
> +       ret = clk_prepare_enable(dpi->engine_clk);
> +       if (ret) {
> +               dev_err(dpi->dev, "Failed to enable engine clock: %d\n", ret);
> +               goto err_eng;
> +       }
> +
> +       ret = clk_prepare_enable(dpi->pixel_clk);
> +       if (ret) {
> +               dev_err(dpi->dev, "Failed to enable pixel clock: %d\n", ret);
> +               goto err_pixel;
> +       }
> +
> +       mtk_dpi_enable(dpi);
> +       dpi->power_sta = true;
> +       return 0;
> +
> +err_pixel:
> +       clk_disable_unprepare(dpi->engine_clk);
> +err_eng:
> +       dpi->power_ctl &= ~pctl;
> +       return ret;
> +}
> +
> +int mtk_dpi_set_display_mode(struct mtk_dpi *dpi, struct drm_display_mode *mode)
> +{
> +       struct mtk_dpi_yc_limit limit;
> +       struct mtk_dpi_polarities dpi_pol;
> +       struct mtk_dpi_sync_param hsync;
> +       struct mtk_dpi_sync_param vsync_lodd = { 0 };
> +       struct mtk_dpi_sync_param vsync_leven = { 0 };
> +       struct mtk_dpi_sync_param vsync_rodd = { 0 };
> +       struct mtk_dpi_sync_param vsync_reven = { 0 };
> +       unsigned long pix_rate;
> +       unsigned long pll_rate;
> +       unsigned int factor;
> +
> +       if (!dpi) {
> +               dev_err(dpi->dev, "invalid argument\n");
> +               return -EINVAL;
> +       }
> +
> +       pix_rate = 1000UL * mode->clock;
> +       if (mode->clock <= 74000)
> +               factor = 8 * 3;
> +       else
> +               factor = 4 * 3;
> +       pll_rate = pix_rate * factor;
> +
> +       dev_dbg(dpi->dev, "Want PLL %lu Hz, pixel clock %lu Hz\n",
> +               pll_rate, pix_rate);
> +
> +       clk_set_rate(dpi->tvd_clk, pll_rate);
> +       pll_rate = clk_get_rate(dpi->tvd_clk);
> +
> +       pix_rate = pll_rate / factor;
> +       clk_set_rate(dpi->pixel_clk, pix_rate);
> +       pix_rate = clk_get_rate(dpi->pixel_clk);
> +
> +       dev_dbg(dpi->dev, "Got  PLL %lu Hz, pixel clock %lu Hz\n",
> +               pll_rate, pix_rate);
> +
> +       limit.c_bottom = 0x0010;
> +       limit.c_top = 0x0FE0;
> +       limit.y_bottom = 0x0010;
> +       limit.y_top = 0x0FE0;
> +
> +       dpi_pol.ck_pol = MTK_DPI_POLARITY_FALLING;
> +       dpi_pol.de_pol = MTK_DPI_POLARITY_RISING;
> +       dpi_pol.hsync_pol = mode->flags & DRM_MODE_FLAG_PHSYNC ?
> +                           MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING;
> +       dpi_pol.vsync_pol = mode->flags & DRM_MODE_FLAG_PVSYNC ?
> +                           MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING;
> +
> +       hsync.sync_width = mode->hsync_end - mode->hsync_start;
> +       hsync.back_porch = mode->htotal - mode->hsync_end;
> +       hsync.front_porch = mode->hsync_start - mode->hdisplay;
> +       hsync.shift_half_line = false;
> +
> +       vsync_lodd.sync_width = mode->vsync_end - mode->vsync_start;
> +       vsync_lodd.back_porch = mode->vtotal - mode->vsync_end;
> +       vsync_lodd.front_porch = mode->vsync_start - mode->vdisplay;
> +       vsync_lodd.shift_half_line = false;
> +
> +       if (mode->flags & DRM_MODE_FLAG_INTERLACE &&
> +           mode->flags & DRM_MODE_FLAG_3D_MASK) {
> +               vsync_leven = vsync_lodd;
> +               vsync_rodd = vsync_lodd;
> +               vsync_reven = vsync_lodd;
> +               vsync_leven.shift_half_line = true;
> +               vsync_reven.shift_half_line = true;
> +       } else if (mode->flags & DRM_MODE_FLAG_INTERLACE &&
> +                  !(mode->flags & DRM_MODE_FLAG_3D_MASK)) {
> +               vsync_leven = vsync_lodd;
> +               vsync_leven.shift_half_line = true;
> +       } else if (!(mode->flags & DRM_MODE_FLAG_INTERLACE) &&
> +                  mode->flags & DRM_MODE_FLAG_3D_MASK) {
> +               vsync_rodd = vsync_lodd;
> +       }
> +       mtk_dpi_sw_reset(dpi, true);
> +       mtk_dpi_config_pol(dpi, &dpi_pol);
> +
> +       mtk_dpi_config_hsync(dpi, &hsync);
> +       mtk_dpi_config_vsync_lodd(dpi, &vsync_lodd);
> +       mtk_dpi_config_vsync_rodd(dpi, &vsync_rodd);
> +       mtk_dpi_config_vsync_leven(dpi, &vsync_leven);
> +       mtk_dpi_config_vsync_reven(dpi, &vsync_reven);
> +
> +       mtk_dpi_config_3d(dpi, !!(mode->flags & DRM_MODE_FLAG_3D_MASK));
> +       mtk_dpi_config_interface(dpi, !!(mode->flags &
> +                                        DRM_MODE_FLAG_INTERLACE));
> +       if (mode->flags & DRM_MODE_FLAG_INTERLACE)
> +               mtk_dpi_config_fb_size(dpi, mode->hdisplay, mode->vdisplay / 2);
> +       else
> +               mtk_dpi_config_fb_size(dpi, mode->hdisplay, mode->vdisplay);
> +
> +       mtk_dpi_config_channel_limit(dpi, &limit);
> +       mtk_dpi_config_bit_num(dpi, dpi->bit_num);
> +       mtk_dpi_config_channel_swap(dpi, dpi->channel_swap);
> +       mtk_dpi_config_yc_map(dpi, dpi->yc_map);
> +       mtk_dpi_config_color_format(dpi, dpi->color_format);
> +       mtk_dpi_config_2n_h_fre(dpi);
> +       mtk_dpi_sw_reset(dpi, false);
> +
> +       return 0;
> +}
> +
> +static void mtk_dpi_encoder_destroy(struct drm_encoder *encoder)
> +{
> +       drm_encoder_cleanup(encoder);
> +}
> +
> +static const struct drm_encoder_funcs mtk_dpi_encoder_funcs = {
> +       .destroy = mtk_dpi_encoder_destroy,
> +};
> +
> +static bool mtk_dpi_encoder_mode_fixup(struct drm_encoder *encoder,
> +                                      const struct drm_display_mode *mode,
> +                                      struct drm_display_mode *adjusted_mode)
> +{
> +       return true;
> +}
> +
> +static void mtk_dpi_encoder_mode_set(struct drm_encoder *encoder,
> +                                    struct drm_display_mode *mode,
> +                                    struct drm_display_mode *adjusted_mode)
> +{
> +       struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder);
> +
> +       drm_mode_copy(&dpi->mode, adjusted_mode);
> +}
> +
> +static void mtk_dpi_encoder_disable(struct drm_encoder *encoder)
> +{
> +       struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder);
> +
> +       mtk_dpi_power_off(dpi, DPI_POWER_ENABLE);
> +}
> +
> +static void mtk_dpi_encoder_enable(struct drm_encoder *encoder)
> +{
> +       struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder);
> +
> +       mtk_dpi_power_on(dpi, DPI_POWER_ENABLE);
> +       mtk_dpi_set_display_mode(dpi, &dpi->mode);
> +}
> +
> +static int mtk_dpi_atomic_check(struct drm_encoder *encoder,
> +                               struct drm_crtc_state *crtc_state,
> +                               struct drm_connector_state *conn_state)
> +{
> +       return 0;
> +}
> +
> +static const struct drm_encoder_helper_funcs mtk_dpi_encoder_helper_funcs = {
> +       .mode_fixup = mtk_dpi_encoder_mode_fixup,
> +       .mode_set = mtk_dpi_encoder_mode_set,
> +       .disable = mtk_dpi_encoder_disable,
> +       .enable = mtk_dpi_encoder_enable,
> +       .atomic_check = mtk_dpi_atomic_check,
> +};
> +
> +static void mtk_dpi_start(struct mtk_ddp_comp *comp)
> +{
> +       struct mtk_dpi *dpi = container_of(comp, struct mtk_dpi, ddp_comp);
> +
> +       mtk_dpi_power_on(dpi, DPI_POWER_START);
> +}
> +
> +static void mtk_dpi_stop(struct mtk_ddp_comp *comp)
> +{
> +       struct mtk_dpi *dpi = container_of(comp, struct mtk_dpi, ddp_comp);
> +
> +       mtk_dpi_power_off(dpi, DPI_POWER_START);
> +}
> +
> +static const struct mtk_ddp_comp_funcs mtk_dpi_funcs = {
> +       .start = mtk_dpi_start,
> +       .stop = mtk_dpi_stop,
> +};
> +
> +static int mtk_dpi_bind(struct device *dev, struct device *master, void *data)
> +{
> +       struct mtk_dpi *dpi = dev_get_drvdata(dev);
> +       struct drm_device *drm_dev = data;
> +       int ret;
> +
> +       ret = mtk_ddp_comp_register(drm_dev, &dpi->ddp_comp);
> +       if (ret < 0) {
> +               dev_err(dev, "Failed to register component %s: %d\n",
> +                       dev->of_node->full_name, ret);
> +               return ret;
> +       }
> +
> +       ret = drm_encoder_init(drm_dev, &dpi->encoder, &mtk_dpi_encoder_funcs,
> +                              DRM_MODE_ENCODER_TMDS, NULL);
> +       if (ret) {
> +               dev_err(dev, "Failed to initialize decoder: %d\n", ret);
> +               goto err_unregister;
> +       }
> +       drm_encoder_helper_add(&dpi->encoder, &mtk_dpi_encoder_helper_funcs);
> +
> +       /* Currently DPI0 is fixed to be driven by OVL1 */
> +       dpi->encoder.possible_crtcs = BIT(1);
> +
> +       dpi->encoder.bridge->encoder = &dpi->encoder;
> +       ret = drm_bridge_attach(dpi->encoder.dev, dpi->encoder.bridge);
> +       if (ret) {
> +               dev_err(dev, "Failed to attach bridge: %d\n", ret);
> +               goto err_cleanup;
> +       }
> +
> +       dpi->bit_num = MTK_DPI_OUT_BIT_NUM_8BITS;
> +       dpi->channel_swap = MTK_DPI_OUT_CHANNEL_SWAP_RGB;
> +       dpi->yc_map = MTK_DPI_OUT_YC_MAP_RGB;
> +       dpi->color_format = MTK_DPI_COLOR_FORMAT_RGB;
> +
> +       return 0;
> +
> +err_cleanup:
> +       drm_encoder_cleanup(&dpi->encoder);
> +err_unregister:
> +       mtk_ddp_comp_unregister(drm_dev, &dpi->ddp_comp);
> +       return ret;
> +}
> +
> +static void mtk_dpi_unbind(struct device *dev, struct device *master,
> +                          void *data)
> +{
> +       struct mtk_dpi *dpi = dev_get_drvdata(dev);
> +       struct drm_device *drm_dev = data;
> +
> +       drm_encoder_cleanup(&dpi->encoder);
> +       mtk_ddp_comp_unregister(drm_dev, &dpi->ddp_comp);
> +}
> +
> +static const struct component_ops mtk_dpi_component_ops = {
> +       .bind = mtk_dpi_bind,
> +       .unbind = mtk_dpi_unbind,
> +};
> +
> +static int mtk_dpi_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct mtk_dpi *dpi;
> +       struct resource *mem;
> +       struct device_node *ep, *bridge_node = NULL;
> +       int comp_id;
> +       int ret;
> +
> +       dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL);
> +       if (!dpi)
> +               return -ENOMEM;
> +
> +       dpi->dev = dev;
> +
> +       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       dpi->regs = devm_ioremap_resource(dev, mem);
> +       if (IS_ERR(dpi->regs)) {
> +               ret = PTR_ERR(dpi->regs);
> +               dev_err(dev, "Failed to ioremap mem resource: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dpi->engine_clk = devm_clk_get(dev, "engine");
> +       if (IS_ERR(dpi->engine_clk)) {
> +               ret = PTR_ERR(dpi->engine_clk);
> +               dev_err(dev, "Failed to get engine clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dpi->pixel_clk = devm_clk_get(dev, "pixel");
> +       if (IS_ERR(dpi->pixel_clk)) {
> +               ret = PTR_ERR(dpi->pixel_clk);
> +               dev_err(dev, "Failed to get pixel clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dpi->tvd_clk = devm_clk_get(dev, "pll");
> +       if (IS_ERR(dpi->tvd_clk)) {
> +               ret = PTR_ERR(dpi->tvd_clk);
> +               dev_err(dev, "Failed to get tvdpll clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dpi->irq = platform_get_irq(pdev, 0);
> +       if (dpi->irq <= 0) {
> +               dev_err(dev, "Failed to get irq: %d\n", dpi->irq);
> +               return -EINVAL;
> +       }
> +
> +       ep = of_graph_get_next_endpoint(dev->of_node, NULL);
> +       if (ep) {
> +               bridge_node = of_graph_get_remote_port_parent(ep);
> +               of_node_put(ep);
> +       }
> +       if (!bridge_node) {
> +               dev_err(dev, "Failed to find bridge node: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dev_info(dev, "Found bridge node: %s\n", bridge_node->full_name);
> +
> +       dpi->encoder.bridge = of_drm_find_bridge(bridge_node);
> +       of_node_put(bridge_node);
> +       if (!dpi->encoder.bridge)
> +               return -EPROBE_DEFER;
> +
> +       comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DPI);
> +       if (comp_id < 0) {
> +               dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
> +               return comp_id;
> +       }
> +
> +       ret = mtk_ddp_comp_init(dev, dev->of_node, &dpi->ddp_comp, comp_id,
> +                               &mtk_dpi_funcs);
> +       if (ret) {
> +               dev_err(dev, "Failed to initialize component: %d\n", ret);
> +               return ret;
> +       }
> +
> +       platform_set_drvdata(pdev, dpi);
> +
> +       ret = component_add(dev, &mtk_dpi_component_ops);
> +       if (ret) {
> +               dev_err(dev, "Failed to add component: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dpi->power_ctl |= DPI_POWER_RESUME;
> +
> +       return 0;
> +}
> +
> +static int mtk_dpi_remove(struct platform_device *pdev)
> +{
> +       component_del(&pdev->dev, &mtk_dpi_component_ops);
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int mtk_dpi_suspend(struct device *dev)
> +{
> +       struct mtk_dpi *dpi = dev_get_drvdata(dev);
> +
> +       if (IS_ERR(dpi)) {
> +               dev_info(dev, "dpi suspend failed!\n");
> +               return PTR_ERR(dpi);
> +       }
> +
> +       mtk_dpi_power_off(dpi, DPI_POWER_RESUME);
> +
> +       dev_info(dev, "dpi suspend success!\n");
> +
> +       return 0;
> +}
> +
> +static int mtk_dpi_resume(struct device *dev)
> +{
> +       struct mtk_dpi *dpi = dev_get_drvdata(dev);
> +       int ret;
> +
> +       if (IS_ERR(dpi)) {
> +               dev_err(dev, "dpi resume failed!\n");
> +               return PTR_ERR(dpi);
> +       }
> +
> +       ret = mtk_dpi_power_on(dpi, DPI_POWER_RESUME);
> +       if (ret) {
> +               dev_err(dev, "dpi resume failed!\n");
> +               return ret;
> +       }
> +
> +       dev_info(dev, "dpi resume success!\n");
> +
> +       return 0;
> +}
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(mtk_dpi_pm_ops, mtk_dpi_suspend, mtk_dpi_resume);

Like hdmi & dsi, I think we can remove these PM routines.
And maybe then we can remove the "DPI_POWER_RESUME" logic (which I
don't grok yet)?

> +
> +static const struct of_device_id mtk_dpi_of_ids[] = {
> +       { .compatible = "mediatek,mt8173-dpi", },
> +       {}
> +};
> +
> +struct platform_driver mtk_dpi_driver = {
> +       .probe = mtk_dpi_probe,
> +       .remove = mtk_dpi_remove,
> +       .driver = {
> +               .name = "mediatek-dpi",
> +               .of_match_table = mtk_dpi_of_ids,
> +               .pm = &mtk_dpi_pm_ops,
> +       },
> +};
> diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.h b/drivers/gpu/drm/mediatek/mtk_dpi.h
> new file mode 100644
> index 0000000..4fa4114
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_dpi.h

All of this can be moved to mtk_dpi.c.

> @@ -0,0 +1,85 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> + *
> + * 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_DPI_H
> +#define _MTK_DPI_H
> +
> +#include <linux/types.h>
> +#include <drm/drm_crtc.h>
> +
> +#include "mtk_drm_ddp_comp.h"
> +
> +struct device;
> +struct clk;
> +
> +enum mtk_dpi_out_bit_num {
> +       MTK_DPI_OUT_BIT_NUM_8BITS,
> +       MTK_DPI_OUT_BIT_NUM_10BITS,
> +       MTK_DPI_OUT_BIT_NUM_12BITS,
> +       MTK_DPI_OUT_BIT_NUM_16BITS
> +};
> +
> +enum mtk_dpi_out_yc_map {
> +       MTK_DPI_OUT_YC_MAP_RGB,
> +       MTK_DPI_OUT_YC_MAP_CYCY,
> +       MTK_DPI_OUT_YC_MAP_YCYC,
> +       MTK_DPI_OUT_YC_MAP_CY,
> +       MTK_DPI_OUT_YC_MAP_YC
> +};
> +
> +enum mtk_dpi_out_channel_swap {
> +       MTK_DPI_OUT_CHANNEL_SWAP_RGB,
> +       MTK_DPI_OUT_CHANNEL_SWAP_GBR,
> +       MTK_DPI_OUT_CHANNEL_SWAP_BRG,
> +       MTK_DPI_OUT_CHANNEL_SWAP_RBG,
> +       MTK_DPI_OUT_CHANNEL_SWAP_GRB,
> +       MTK_DPI_OUT_CHANNEL_SWAP_BGR
> +};
> +
> +enum mtk_dpi_out_color_format {
> +       MTK_DPI_COLOR_FORMAT_RGB,
> +       MTK_DPI_COLOR_FORMAT_RGB_FULL,
> +       MTK_DPI_COLOR_FORMAT_YCBCR_444,
> +       MTK_DPI_COLOR_FORMAT_YCBCR_422,
> +       MTK_DPI_COLOR_FORMAT_XV_YCC,
> +       MTK_DPI_COLOR_FORMAT_YCBCR_444_FULL,
> +       MTK_DPI_COLOR_FORMAT_YCBCR_422_FULL
> +};
> +
> +struct mtk_dpi {
> +       struct mtk_ddp_comp ddp_comp;
> +       struct drm_encoder encoder;
> +       void __iomem *regs;
> +       struct device *dev;
> +       struct clk *engine_clk;
> +       struct clk *pixel_clk;
> +       struct clk *tvd_clk;
> +       int irq;
> +       struct drm_display_mode mode;
> +       enum mtk_dpi_out_color_format color_format;
> +       enum mtk_dpi_out_yc_map yc_map;
> +       enum mtk_dpi_out_bit_num bit_num;
> +       enum mtk_dpi_out_channel_swap channel_swap;
> +       bool power_sta;
> +       u8 power_ctl;
> +};
> +
> +static inline struct mtk_dpi *mtk_dpi_from_encoder(struct drm_encoder *e)
> +{
> +       return container_of(e, struct mtk_dpi, encoder);
> +}
> +
> +int mtk_dpi_set_display_mode(struct mtk_dpi *dpi,
> +                            struct drm_display_mode *mode);
> +
> +#endif /* _MTK_DPI_H */
> diff --git a/drivers/gpu/drm/mediatek/mtk_dpi_regs.h b/drivers/gpu/drm/mediatek/mtk_dpi_regs.h
> new file mode 100644
> index 0000000..4b6ad47
> --- /dev/null
> +++ b/drivers/gpu/drm/mediatek/mtk_dpi_regs.h
> @@ -0,0 +1,228 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Jie Qiu <jie.qiu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> + *
> + * 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_DPI_REGS_H
> +#define __MTK_DPI_REGS_H
> +
> +#define DPI_EN                 0x00
> +#define EN                             BIT(0)
> +
> +#define DPI_RET                        0x04
> +#define RST                            BIT(0)
> +
> +#define DPI_INTEN              0x08
> +#define INT_VSYNC_EN                   BIT(0)
> +#define INT_VDE_EN                     BIT(1)
> +#define INT_UNDERFLOW_EN               BIT(2)
> +
> +#define DPI_INTSTA             0x0C
> +#define INT_VSYNC_STA                  BIT(0)
> +#define INT_VDE_STA                    BIT(1)
> +#define INT_UNDERFLOW_STA              BIT(2)
> +
> +#define DPI_CON                        0x10
> +#define BG_ENABLE                      BIT(0)
> +#define IN_RB_SWAP                     BIT(1)
> +#define INTL_EN                                BIT(2)
> +#define TDFP_EN                                BIT(3)
> +#define CLPF_EN                                BIT(4)
> +#define YUV422_EN                      BIT(5)
> +#define CSC_ENABLE                     BIT(6)
> +#define R601_SEL                       BIT(7)
> +#define EMBSYNC_EN                     BIT(8)
> +#define VS_LODD_EN                     BIT(16)
> +#define VS_LEVEN_EN                    BIT(17)
> +#define VS_RODD_EN                     BIT(18)
> +#define VS_REVEN                       BIT(19)
> +#define FAKE_DE_LODD                   BIT(20)
> +#define FAKE_DE_LEVEN                  BIT(21)
> +#define FAKE_DE_RODD                   BIT(22)
> +#define FAKE_DE_REVEN                  BIT(23)
> +
> +#define DPI_OUTPUT_SETTING     0x14
> +#define CH_SWAP                                0
> +#define CH_SWAP_MASK                   (0x7 << 0)
> +#define SWAP_RGB                       0x00
> +#define SWAP_GBR                       0x01
> +#define SWAP_BRG                       0x02
> +#define SWAP_RBG                       0x03
> +#define SWAP_GRB                       0x04
> +#define SWAP_BGR                       0x05
> +#define BIT_SWAP                       BIT(3)
> +#define B_MASK                         BIT(4)
> +#define G_MASK                         BIT(5)
> +#define R_MASK                         BIT(6)
> +#define DE_MASK                                BIT(8)
> +#define HS_MASK                                BIT(9)
> +#define VS_MASK                                BIT(10)
> +#define DE_POL                         BIT(12)
> +#define HSYNC_POL                      BIT(13)
> +#define VSYNC_POL                      BIT(14)
> +#define CK_POL                         BIT(15)
> +#define OEN_OFF                                BIT(16)
> +#define EDGE_SEL                       BIT(17)
> +#define OUT_BIT                                18
> +#define OUT_BIT_MASK                   (0x3 << 18)
> +#define OUT_BIT_8                      0x00
> +#define OUT_BIT_10                     0x01
> +#define OUT_BIT_12                     0x02
> +#define OUT_BIT_16                     0x03
> +#define YC_MAP                         20
> +#define YC_MAP_MASK                    (0x7 << 20)
> +#define YC_MAP_RGB                     0x00
> +#define YC_MAP_CYCY                    0x04
> +#define YC_MAP_YCYC                    0x05
> +#define YC_MAP_CY                      0x06
> +#define YC_MAP_YC                      0x07
> +
> +#define DPI_SIZE               0x18
> +#define HSIZE                          0
> +#define HSIZE_MASK                     (0x1FFF << 0)
> +#define VSIZE                          16
> +#define VSIZE_MASK                     (0x1FFF << 16)
> +
> +#define DPI_DDR_SETTING                0x1C
> +#define DDR_EN                         BIT(0)
> +#define DDDR_SEL                       BIT(1)
> +#define DDR_4PHASE                     BIT(2)
> +#define DDR_WIDTH                      (0x3 << 4)
> +#define DDR_PAD_MODE                   (0x1 << 8)
> +
> +#define DPI_TGEN_HWIDTH                0x20
> +#define HPW                            0
> +#define HPW_MASK                       (0xFFF << 0)
> +
> +#define DPI_TGEN_HPORCH                0x24
> +#define HBP                            0
> +#define HBP_MASK                       (0xFFF << 0)
> +#define HFP                            16
> +#define HFP_MASK                       (0xFFF << 16)
> +
> +#define DPI_TGEN_VWIDTH                0x28
> +#define DPI_TGEN_VPORCH                0x2C
> +
> +#define VSYNC_WIDTH_SHIFT              0
> +#define VSYNC_WIDTH_MASK               (0xFFF << 0)
> +#define VSYNC_HALF_LINE_SHIFT          16
> +#define VSYNC_HALF_LINE_MASK           BIT(16)
> +#define VSYNC_BACK_PORCH_SHIFT         0
> +#define VSYNC_BACK_PORCH_MASK          (0xFFF << 0)
> +#define VSYNC_FRONT_PORCH_SHIFT                16
> +#define VSYNC_FRONT_PORCH_MASK         (0xFFF << 16)
> +
> +#define DPI_BG_HCNTL           0x30
> +#define BG_RIGHT                       (0x1FFF << 0)
> +#define BG_LEFT                                (0x1FFF << 16)
> +
> +#define DPI_BG_VCNTL           0x34
> +#define BG_BOT                         (0x1FFF << 0)
> +#define BG_TOP                         (0x1FFF << 16)
> +
> +#define DPI_BG_COLOR           0x38
> +#define BG_B                           (0xF << 0)
> +#define BG_G                           (0xF << 8)
> +#define BG_R                           (0xF << 16)
> +
> +#define DPI_FIFO_CTL           0x3C
> +#define FIFO_VALID_SET                 (0x1F << 0)
> +#define FIFO_RST_SEL                   (0x1 << 8)
> +
> +#define DPI_STATUS             0x40
> +#define VCOUNTER                       (0x1FFF << 0)
> +#define DPI_BUSY                       BIT(16)
> +#define OUTEN                          BIT(17)
> +#define FIELD                          BIT(20)
> +#define TDLR                           BIT(21)
> +
> +#define DPI_TMODE              0x44
> +#define DPI_OEN_ON                     BIT(0)
> +
> +#define DPI_CHECKSUM           0x48
> +#define DPI_CHECKSUM_MASK              (0xFFFFFF << 0)
> +#define DPI_CHECKSUM_READY             BIT(30)
> +#define DPI_CHECKSUM_EN                        BIT(31)
> +
> +#define DPI_DUMMY              0x50
> +#define DPI_DUMMY_MASK                 (0xFFFFFFFF << 0)
> +
> +#define DPI_TGEN_VWIDTH_LEVEN  0x68
> +#define DPI_TGEN_VPORCH_LEVEN  0x6C
> +#define DPI_TGEN_VWIDTH_RODD   0x70
> +#define DPI_TGEN_VPORCH_RODD   0x74
> +#define DPI_TGEN_VWIDTH_REVEN  0x78
> +#define DPI_TGEN_VPORCH_REVEN  0x7C
> +
> +#define DPI_ESAV_VTIMING_LODD  0x80
> +#define ESAV_VOFST_LODD                        (0xFFF << 0)
> +#define ESAV_VWID_LODD                 (0xFFF << 16)
> +
> +#define DPI_ESAV_VTIMING_LEVEN 0x84
> +#define ESAV_VOFST_LEVEN               (0xFFF << 0)
> +#define ESAV_VWID_LEVEN                        (0xFFF << 16)
> +
> +#define DPI_ESAV_VTIMING_RODD  0x88
> +#define ESAV_VOFST_RODD                        (0xFFF << 0)
> +#define ESAV_VWID_RODD                 (0xFFF << 16)
> +
> +#define DPI_ESAV_VTIMING_REVEN 0x8C
> +#define ESAV_VOFST_REVEN               (0xFFF << 0)
> +#define ESAV_VWID_REVEN                        (0xFFF << 16)
> +
> +#define DPI_ESAV_FTIMING       0x90
> +#define ESAV_FOFST_ODD                 (0xFFF << 0)
> +#define ESAV_FOFST_EVEN                        (0xFFF << 16)
> +
> +#define DPI_CLPF_SETTING       0x94
> +#define CLPF_TYPE                      (0x3 << 0)
> +#define ROUND_EN                       BIT(4)
> +
> +#define DPI_Y_LIMIT            0x98
> +#define Y_LIMINT_BOT                   0
> +#define Y_LIMINT_BOT_MASK              (0xFFF << 0)
> +#define Y_LIMINT_TOP                   16
> +#define Y_LIMINT_TOP_MASK              (0xFFF << 16)
> +
> +#define DPI_C_LIMIT            0x9C
> +#define C_LIMIT_BOT                    0
> +#define C_LIMIT_BOT_MASK               (0xFFF << 0)
> +#define C_LIMIT_TOP                    16
> +#define C_LIMIT_TOP_MASK               (0xFFF << 16)
> +
> +#define DPI_YUV422_SETTING     0xA0
> +#define UV_SWAP                                BIT(0)
> +#define CR_DELSEL                      BIT(4)
> +#define CB_DELSEL                      BIT(5)
> +#define Y_DELSEL                       BIT(6)
> +#define DE_DELSEL                      BIT(7)
> +
> +#define DPI_EMBSYNC_SETTING    0xA4
> +#define EMBSYNC_R_CR_EN                        BIT(0)
> +#define EMPSYNC_G_Y_EN                 BIT(1)
> +#define EMPSYNC_B_CB_EN                        BIT(2)
> +#define ESAV_F_INV                     BIT(4)
> +#define ESAV_V_INV                     BIT(5)
> +#define ESAV_H_INV                     BIT(6)
> +#define ESAV_CODE_MAN                  BIT(8)
> +#define VS_OUT_SEL                     (0x7 << 12)
> +
> +#define DPI_ESAV_CODE_SET0     0xA8
> +#define ESAV_CODE0                     (0xFFF << 0)
> +#define ESAV_CODE1                     (0xFFF << 16)
> +
> +#define DPI_ESAV_CODE_SET1     0xAC
> +#define ESAV_CODE2                     (0xFFF << 0)
> +#define ESAV_CODE3_MSB                 BIT(16)
> +
> +#define DPI_H_FRE_CON          0xE0
> +#define H_FRE_2N                       BIT(25)
> +#endif /* __MTK_DPI_REGS_H */
> diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> index 4fcc0e0..a69958c 100644
> --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> @@ -553,6 +553,7 @@ static struct platform_driver * const mtk_drm_drivers[] = {
>         &mtk_disp_rdma_driver,
>         &mtk_dsi_driver,
>         &mtk_mipi_tx_driver,
> +       &mtk_dpi_driver,
>  };
>
>  static int __init mtk_drm_init(void)
> diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
> index 161a362..e0fff2c 100644
> --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.h
> +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h
> @@ -52,5 +52,6 @@ extern struct platform_driver mtk_disp_ovl_driver;
>  extern struct platform_driver mtk_disp_rdma_driver;
>  extern struct platform_driver mtk_dsi_driver;
>  extern struct platform_driver mtk_mipi_tx_driver;
> +extern struct platform_driver mtk_dpi_driver;
>
>  #endif /* MTK_DRM_DRV_H */
> --
> 2.7.0
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v13 04/14] drm/mediatek: Add DPI sub driver
  2016-03-09 14:23     ` Daniel Kurtz
@ 2016-03-15 11:45       ` Philipp Zabel
  0 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-15 11:45 UTC (permalink / raw)
  To: Daniel Kurtz
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, dri-devel,
	Jie Qiu, Cawa Cheng, YT Shen, Yingjoe Chen,
	open list:OPEN FIRMWARE AND...,
	Jitao Shi, Sasha Hauer, Pawel Moll, Ian Campbell, Rob Herring,
	moderated list:ARM/Mediatek SoC support, Matthias Brugger,
	Paul Bolle, Emil Velikov, Tomasz Figa, Kumar Gala

Hi Daniel,

Am Mittwoch, den 09.03.2016, 22:23 +0800 schrieb Daniel Kurtz:
> Hi Philipp, Jie,
> 
> Some small comments.
> Nothing that can't be fixed after merging if you prefer.
>
> On Tue, Mar 8, 2016 at 9:27 PM, Philipp Zabel <p.zabel@pengutronix.de> wrote:
[...]
> > +static int mtk_dpi_power_on(struct mtk_dpi *dpi, enum mtk_dpi_power_ctl pctl)
> > +{
> > +       int ret;
> > +
> > +       dpi->power_ctl |= pctl;
> > +
> > +       if (!(dpi->power_ctl & DPI_POWER_START) &&
> > +           !((dpi->power_ctl & DPI_POWER_ENABLE) &&
> > +             ((dpi->power_ctl & DPI_POWER_RESUME))))
> 
> There one too many () on the POWER_RESUME clause.

I'll try to remove the suspend ops and this with them.

[...]
> > +static SIMPLE_DEV_PM_OPS(mtk_dpi_pm_ops, mtk_dpi_suspend, mtk_dpi_resume);
> 
> Like hdmi & dsi, I think we can remove these PM routines.
> And maybe then we can remove the "DPI_POWER_RESUME" logic (which I
> don't grok yet)?

Right, also we probably should use drm_atomic_helper_suspend/resume in
mtk_drm_sys_suspend/resume.

[...]
> > diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.h b/drivers/gpu/drm/mediatek/mtk_dpi.h
> > new file mode 100644
> > index 0000000..4fa4114
> > --- /dev/null
> > +++ b/drivers/gpu/drm/mediatek/mtk_dpi.h
> 
> All of this can be moved to mtk_dpi.c.

Ok, I'll merge this into mtk_dpi.c.

regards
Philipp

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v13 03/14] drm/mediatek: Add DSI sub driver
  2016-03-09 14:07     ` Daniel Kurtz
@ 2016-03-15 11:49       ` Philipp Zabel
  2016-03-15 12:06         ` Daniel Kurtz
  0 siblings, 1 reply; 22+ messages in thread
From: Philipp Zabel @ 2016-03-15 11:49 UTC (permalink / raw)
  To: Daniel Kurtz
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, dri-devel,
	Jie Qiu, Cawa Cheng, YT Shen, Yingjoe Chen,
	open list:OPEN FIRMWARE AND...,
	Jitao Shi, Sasha Hauer, Pawel Moll, Ian Campbell, Rob Herring,
	moderated list:ARM/Mediatek SoC support, Matthias Brugger,
	Paul Bolle, Emil Velikov, Tomasz Figa, Kumar Gala

Hi Daniel,

Am Mittwoch, den 09.03.2016, 22:07 +0800 schrieb Daniel Kurtz:
> Hi Philipp, CK,
> 
> Some small comments.
> Nothing that couldn't be addressed after merging, if you prefer.
> 
> On Tue, Mar 8, 2016 at 9:27 PM, Philipp Zabel <p.zabel@pengutronix.de> wrote:
[...]
> > +static int mtk_dsi_poweron(struct mtk_dsi *dsi)
> > +{
> > +       struct device *dev = dsi->dev;
> > +       int ret;
> > +
> > +       if (++dsi->refcount != 1)
> > +               return 0;
> 
> What is the point of this refcount?
> I believe dsi->enabled already ensures poweron/poweroff calls are paired.

mtk_dsi_poweron() is called from both mtk_dsi_encoder_enable() and
mtk_dsi_ddp_start() and enables just enough of the DSI to provide power
and the pixel clock. The reason is that the DSI also provides the pixel
clock to the rest of the pipeline elements.

dsi->enabled only tracks the whole DSI being active after the call of
mtk_dsi_encoder_enable().

[...]
> > +static int mtk_dsi_create_conn_enc(struct drm_device *drm, struct mtk_dsi *dsi)
> > +{
> > +       int ret;
> > +
> > +       ret = drm_encoder_init(drm, &dsi->encoder, &mtk_dsi_encoder_funcs,
> > +                              DRM_MODE_ENCODER_DSI, NULL);
> > +       if (ret) {
> > +               DRM_ERROR("Failed to encoder init to drm\n");
> > +               return ret;
> > +       }
> > +       drm_encoder_helper_add(&dsi->encoder, &mtk_dsi_encoder_helper_funcs);
> > +
> > +       /*
> > +        * Currently display data paths are statically assigned to a crtc each.
> > +        * crtc 0 is OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0
> > +        */
> > +       dsi->encoder.possible_crtcs = 1;
> > +
> > +       /* Pre-empt DP connector creation if there's a bridge */
> > +       ret = mtk_drm_attach_bridge(dsi->bridge, &dsi->encoder);
> > +       if (!ret)
> > +               return 0;
> 
> nit: the above valid early termination of this function here is a bit unusual.
> It might be more clear if the "connector init" part below was split out into its
> own helper function.

Good point, will do that.

[...]
> > +static int mtk_dsi_remove(struct platform_device *pdev)
> > +{
> > +       struct mtk_dsi *dsi = platform_get_drvdata(pdev);
> > +
> > +       mtk_output_dsi_disable(dsi);
> 
> This one looks unmatched.

It is, indeed we should let the drm core disable the encoder before the
driver is removed.

[...]
> > +static int mtk_dsi_resume(struct device *dev)
> > +{
> > +       struct mtk_dsi *dsi;
> > +
> > +       dsi = dev_get_drvdata(dev);
> > +
> > +       mtk_output_dsi_enable(dsi);
> > +       DRM_DEBUG_DRIVER("dsi resume success!\n");
> > +
> > +       return 0;
> > +}
> > +#endif
> > +static SIMPLE_DEV_PM_OPS(mtk_dsi_pm_ops, mtk_dsi_suspend, mtk_dsi_resume);
> 
> I don't think we want PM ops.
> The MTK DRM driver disables/enables encoders during suspend/resume.

Yes, and that will also allow to merge mtk_output_dsi_disable() into
mtk_dsi_encoder_disable(), too.

[...]
> > +static unsigned long mtk_mipi_tx_pll_recalc_rate(struct clk_hw *hw,
> > +                                                unsigned long parent_rate)
> > +{
> > +       struct mtk_mipi_tx *mipi_tx = container_of(hw, struct mtk_mipi_tx,
> > +                                                  pll_hw);
> 
> An inline function / macro would help here.

Ok.

[...]
> > +static void mtk_mipi_tx_power_off_signal(struct phy *phy)
> > +{
> > +       struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
> > +
> > +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_PAD_TIE_LOW_EN,
> > +                        RG_DSI_PAD_TIE_LOW_EN);
> 
> As mentioned in the HDMI review, _set_bits() / _clr_bits() is more readable than
> _mask() if we are just setting/clearing groups of bits.

I don't think
	mtk_mipi_tx_set_bits(mipi_tx, MIPITX_DSI_TOP_CON,
	                     RG_DSI_PAD_TIE_LOW_EN);
is that much easier to read. How about calling the function
mtk_mipi_tx_update_bits instead?

regards
Philipp

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v13 03/14] drm/mediatek: Add DSI sub driver
  2016-03-15 11:49       ` Philipp Zabel
@ 2016-03-15 12:06         ` Daniel Kurtz
  0 siblings, 0 replies; 22+ messages in thread
From: Daniel Kurtz @ 2016-03-15 12:06 UTC (permalink / raw)
  To: Philipp Zabel
  Cc: Mark Rutland, Stephen Boyd, Michael Turquette, dri-devel,
	Jie Qiu, Cawa Cheng, YT Shen, Yingjoe Chen,
	open list:OPEN FIRMWARE AND...,
	Jitao Shi, Sasha Hauer, Pawel Moll, Ian Campbell, Rob Herring,
	moderated list:ARM/Mediatek SoC support, Matthias Brugger,
	Paul Bolle, Emil Velikov, Tomasz Figa, Kumar Gala

On Tue, Mar 15, 2016 at 7:49 PM, Philipp Zabel <p.zabel@pengutronix.de> wrote:
>
> Hi Daniel,
>
> Am Mittwoch, den 09.03.2016, 22:07 +0800 schrieb Daniel Kurtz:
> > Hi Philipp, CK,
> >
> > Some small comments.
> > Nothing that couldn't be addressed after merging, if you prefer.
> >
> > On Tue, Mar 8, 2016 at 9:27 PM, Philipp Zabel <p.zabel@pengutronix.de> wrote:
> [...]
> > > +static int mtk_dsi_poweron(struct mtk_dsi *dsi)
> > > +{
> > > +       struct device *dev = dsi->dev;
> > > +       int ret;
> > > +
> > > +       if (++dsi->refcount != 1)
> > > +               return 0;
> >
> > What is the point of this refcount?
> > I believe dsi->enabled already ensures poweron/poweroff calls are paired.
>
> mtk_dsi_poweron() is called from both mtk_dsi_encoder_enable() and
> mtk_dsi_ddp_start() and enables just enough of the DSI to provide power
> and the pixel clock. The reason is that the DSI also provides the pixel
> clock to the rest of the pipeline elements.
>
> dsi->enabled only tracks the whole DSI being active after the call of
> mtk_dsi_encoder_enable().
>
> [...]
> > > +static int mtk_dsi_create_conn_enc(struct drm_device *drm, struct mtk_dsi *dsi)
> > > +{
> > > +       int ret;
> > > +
> > > +       ret = drm_encoder_init(drm, &dsi->encoder, &mtk_dsi_encoder_funcs,
> > > +                              DRM_MODE_ENCODER_DSI, NULL);
> > > +       if (ret) {
> > > +               DRM_ERROR("Failed to encoder init to drm\n");
> > > +               return ret;
> > > +       }
> > > +       drm_encoder_helper_add(&dsi->encoder, &mtk_dsi_encoder_helper_funcs);
> > > +
> > > +       /*
> > > +        * Currently display data paths are statically assigned to a crtc each.
> > > +        * crtc 0 is OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0
> > > +        */
> > > +       dsi->encoder.possible_crtcs = 1;
> > > +
> > > +       /* Pre-empt DP connector creation if there's a bridge */
> > > +       ret = mtk_drm_attach_bridge(dsi->bridge, &dsi->encoder);
> > > +       if (!ret)
> > > +               return 0;
> >
> > nit: the above valid early termination of this function here is a bit unusual.
> > It might be more clear if the "connector init" part below was split out into its
> > own helper function.
>
> Good point, will do that.
>
> [...]
> > > +static int mtk_dsi_remove(struct platform_device *pdev)
> > > +{
> > > +       struct mtk_dsi *dsi = platform_get_drvdata(pdev);
> > > +
> > > +       mtk_output_dsi_disable(dsi);
> >
> > This one looks unmatched.
>
> It is, indeed we should let the drm core disable the encoder before the
> driver is removed.
>
> [...]
> > > +static int mtk_dsi_resume(struct device *dev)
> > > +{
> > > +       struct mtk_dsi *dsi;
> > > +
> > > +       dsi = dev_get_drvdata(dev);
> > > +
> > > +       mtk_output_dsi_enable(dsi);
> > > +       DRM_DEBUG_DRIVER("dsi resume success!\n");
> > > +
> > > +       return 0;
> > > +}
> > > +#endif
> > > +static SIMPLE_DEV_PM_OPS(mtk_dsi_pm_ops, mtk_dsi_suspend, mtk_dsi_resume);
> >
> > I don't think we want PM ops.
> > The MTK DRM driver disables/enables encoders during suspend/resume.
>
> Yes, and that will also allow to merge mtk_output_dsi_disable() into
> mtk_dsi_encoder_disable(), too.
>
> [...]
> > > +static unsigned long mtk_mipi_tx_pll_recalc_rate(struct clk_hw *hw,
> > > +                                                unsigned long parent_rate)
> > > +{
> > > +       struct mtk_mipi_tx *mipi_tx = container_of(hw, struct mtk_mipi_tx,
> > > +                                                  pll_hw);
> >
> > An inline function / macro would help here.
>
> Ok.
>
> [...]
> > > +static void mtk_mipi_tx_power_off_signal(struct phy *phy)
> > > +{
> > > +       struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
> > > +
> > > +       mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_PAD_TIE_LOW_EN,
> > > +                        RG_DSI_PAD_TIE_LOW_EN);
> >
> > As mentioned in the HDMI review, _set_bits() / _clr_bits() is more readable than
> > _mask() if we are just setting/clearing groups of bits.
>
> I don't think
>         mtk_mipi_tx_set_bits(mipi_tx, MIPITX_DSI_TOP_CON,
>                              RG_DSI_PAD_TIE_LOW_EN);
> is that much easier to read. How about calling the function
> mtk_mipi_tx_update_bits instead?

Actually, the important part here was to remove the redundant 'value'
parameter, and instead have dedicated clear / set functions that
operate on just the mask.



>
> regards
> Philipp
>
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v13 06/14] drm/mediatek: Add HDMI support
       [not found]     ` <CAGS+omD6_hNMVYCnmsHheyxpGZXDf7H+XVHT5C4wVCKDvgpQ7w-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2016-03-21 17:45       ` Philipp Zabel
  0 siblings, 0 replies; 22+ messages in thread
From: Philipp Zabel @ 2016-03-21 17:45 UTC (permalink / raw)
  To: Daniel Kurtz, Jie Qiu
  Cc: dri-devel, Rob Herring, Daniel Vetter, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Dave Airlie, Matthias Brugger,
	Cawa Cheng, CK Hu, YT Shen, Jitao Shi, Paul Bolle, Daniel Stone,
	Tomasz Figa, Yingjoe Chen, Michael Turquette, Stephen Boyd,
	Emil Velikov, open list:OPEN FIRMWARE AND...,
	moderated list:ARM/Mediatek SoC support

Hi Daniel, Jie,

Am Mittwoch, den 09.03.2016, 21:52 +0800 schrieb Daniel Kurtz:
> Hi Philipp & Jie,
> 
> Sorry I only now had a chance to dig deeper and review the HDMI driver.

I wish you had had a chance to do this earlier, But better now than
after the merge. I'll split the HDMI patches from the others in the next
round.

> Lots of comments inline below...
>
> On Tue, Mar 8, 2016 at 9:27 PM, Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> wrote:
[...]
> > diff --git a/drivers/gpu/drm/mediatek/mtk_cec.c b/drivers/gpu/drm/mediatek/mtk_cec.c
> > new file mode 100644
> > index 0000000..cba3647
> > --- /dev/null
> > +++ b/drivers/gpu/drm/mediatek/mtk_cec.c
> > +void mtk_cec_set_hpd_event(struct device *dev,
> > +                          void (*hpd_event)(bool hpd, struct device *dev),
> > +                          struct device *hdmi_dev)
> > +{
> > +       struct mtk_cec *cec = dev_get_drvdata(dev);
> > +
> > +       cec->hdmi_dev = hdmi_dev;
> > +       cec->hpd_event = hpd_event;
> 
> Lock this so to prevent race with the irq?

Yes.

[...]
> > +int mtk_cec_irq(struct device *dev)
> 
> AFAICT, this function is never used.

Correct, since the IRQ number is not exported to the sound drivers
anymore, I can drop it now.

[...]
> > +static void mtk_cec_htplg_irq_enable(struct mtk_cec *cec)
> > +{
> > +       mtk_cec_mask(cec, CEC_CKGEN, 0, PDN);
> > +       mtk_cec_mask(cec, CEC_CKGEN, CEC_32K_PDN, CEC_32K_PDN);
> > +       mtk_cec_mask(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR,
> > +                    HDMI_PORD_INT_32K_CLR);
> > +       mtk_cec_mask(cec, RX_GEN_WD, RX_INT_32K_CLR, RX_INT_32K_CLR);
> > +       mtk_cec_mask(cec, RX_GEN_WD, HDMI_HTPLG_INT_32K_CLR,
> > +                    HDMI_HTPLG_INT_32K_CLR);
> > +       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR);
> > +       mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_CLR);
> > +       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_CLR);
> > +       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_EN);
> > +       mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_EN);
> > +       mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_EN);
> 
> This is a bit wasteful.  Can you just clear all of these bits in a single write?
> (this applies to this entire file).

I think so. If there are no problems, I'll combine them into a single
update per register.

> > +
> > +       mtk_cec_mask(cec, RX_EVENT, HDMI_PORD_INT_EN, HDMI_PORD_INT_EN);
> > +       mtk_cec_mask(cec, RX_EVENT, HDMI_HTPLG_INT_EN, HDMI_HTPLG_INT_EN);
> > +}
> > +
> > +static void mtk_cec_htplg_irq_disable(struct mtk_cec *cec)
> > +{
> 
> Why does irq_enable do so much more work than irq_disable?

It also clears the interrupt status and sets the clock. I'll move the
initialization out of this function.

> > +       mtk_cec_mask(cec, RX_EVENT, 0, HDMI_PORD_INT_EN);
> > +       mtk_cec_mask(cec, RX_EVENT, 0, HDMI_HTPLG_INT_EN);
> > +}
> > +
> > +static void mtk_cec_clear_htplg_irq(struct mtk_cec *cec)
> > +{
> > +       mtk_cec_mask(cec, TR_CONFIG, CLEAR_CEC_IRQ, CLEAR_CEC_IRQ);
> > +       mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_HTPLG_INT_CLR,
> > +                    HDMI_HTPLG_INT_CLR);
> > +       mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_PORD_INT_CLR,
> > +                    HDMI_PORD_INT_CLR);
> > +       mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_FULL_INT_CLR,
> > +                    HDMI_FULL_INT_CLR);
> > +       mtk_cec_mask(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR,
> > +                    HDMI_PORD_INT_32K_CLR);
> > +       mtk_cec_mask(cec, RX_GEN_WD, RX_INT_32K_CLR, RX_INT_32K_CLR);
> > +       mtk_cec_mask(cec, RX_GEN_WD, HDMI_HTPLG_INT_32K_CLR,
> > +                    HDMI_HTPLG_INT_32K_CLR);
> > +       udelay(5);
> 
> Do you really need this delay in the middle of the isr handler?

I can turn it into an usleep_range(5, 10). Whether the delay is needed
at all or how long it really has to be, I don't know.

[...]
> > +static irqreturn_t mtk_cec_htplg_isr_thread(int irq, void *arg)
> > +{
> > +       struct device *dev = arg;
> > +       struct mtk_cec *cec = dev_get_drvdata(dev);
> > +       bool hpd;
> > +
> > +       mtk_cec_clear_htplg_irq(cec);
> > +       hpd = mtk_cec_hpd_high(dev);
> > +
> > +       if (cec->hpd != hpd) {
> > +               dev_info(dev, "hotplug event!,cur hpd = %d, hpd = %d\n",
> > +                        cec->hpd, hpd);
> 
> dev_dbg if anything

Ok.

[...]
> > 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..ff661e0
> > --- /dev/null
> > +++ b/drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c
[...]
> > +static void mtk_hdmi_bridge_disable(struct drm_bridge *bridge)
> > +{
> > +       struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
> > +
> > +       phy_power_off(hdmi->phy);
> > +       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]);
> > +       clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]);
> 
> As far as I can tell, __drm_helper_disable_unused_functions() doesn't
> check if crtc/encoder/bridge are disabled before calling the
> ->*en/disable*() callbacks.
>
> So, these clk_disable_unprepare() may be called with the HDMI already disabled,
> trigerring their WARN_ON().
> 
> So, perhaps we also need to track enabled/disabled state separately here in
> the driver.

I'll add that.

[...]
> > +       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > +       hdmi->regs = devm_ioremap_resource(dev, mem);
> > +       if (IS_ERR(hdmi->regs)) {
> > +               dev_err(dev, "Failed to ioremap hdmi_shell: %ld\n",
> > +                       PTR_ERR(hdmi->regs));
> 
> What is hdmi_shell?

I don't know, I assumed it's just the name of the HDMI control register
space.

> In any case, I don't think you need to print anything here.

I'll drop the printk.

[...]
> > +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);
> 
> audio_pdev is not set in this patch.
> Is there more audio stuff that should be removed from this patch?

I suppose I could also remove the hdmi_audio_param struct and simplify
mtk_hdmi_aud_set_input() a bit.

> > +       platform_set_drvdata(pdev, NULL);
> 
> I don't think this is necessary.

Right.

[...]
> > +static int mtk_hdmi_resume(struct device *dev)
> > +{
> > +       struct mtk_hdmi *hdmi = dev_get_drvdata(dev);
> > +       int ret = 0;
> > +
> > +       ret = mtk_hdmi_clk_enable_audio(hdmi);
> > +       if (ret) {
> > +               dev_err(dev, "hdmi resume failed!\n");
> > +               return ret;
> > +       }
> > +
> > +       mtk_hdmi_power_on(hdmi);
> > +       mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode);
> > +       phy_power_on(hdmi->phy);
> > +       dev_dbg(dev, "hdmi resume success!\n");
> > +       return 0;
> > +}
> > +#endif
> > +static SIMPLE_DEV_PM_OPS(mtk_hdmi_pm_ops,
> > +                        mtk_hdmi_suspend, mtk_hdmi_resume);
> 
> I do not think these suspend/resume ops are needed.
> The MTK DRM driver turn off connectors at suspend, and re-enables them
> at resume.

I have to move the audio clock enabling into the bridge enable/disable
to drop these completely, not sure yet if that will have any side
effects.

[...]
> > diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c
> > new file mode 100644
> > index 0000000..30ec7b5
> > --- /dev/null
> > +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c
> 
> Hmm... what is the value in splitting this out into its own separate mtk_hdmi.c?

If nobody minds, I'll happily merge mtk_drm_hdmi_drv.c, mtk_hdmi.c and
mtk_hdmi_hw.c. The downside is that the file is now >1.8k lines, but I
think the amount of newly static functions is worth it.

[...]
> > +static int mtk_hdmi_aud_set_input(struct mtk_hdmi *hdmi)
> > +{
[...]
> > +       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);
> 
> These three all change the same register.  Combine them into a single helper
> function that just writes GRL_AUDIO_CFG once.

Ok.

[...]
> > +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); */
> 
> why is this commented out?

Not yet implemented at this point. I'll remove it.

> > +                       mtk_hdmi_hw_aud_set_mclk(
> > +                       hdmi,
> > +                       hdmi->aud_param.aud_mclk);
> 
> indentation is funny here

Will fix, and the following similar issues, too.

> > +                       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);
> 
> These clauses all seem to do the same thing; only the mclk parameter is
> different.  Can you refactor them into a helper function?

Yes, I'll have to integrate a few changes from the
"drm/mediatek: hdmi: Add audio interface to the hdmi-codec driver"
patch here.

[...]
> > +int mtk_hdmi_hpd_high(struct mtk_hdmi *hdmi)
> > +{
> > +       return hdmi->cec_dev ? mtk_cec_hpd_high(hdmi->cec_dev) : false;
> 
> I don't think we would get this far if cec_dev was NULL.

You are right, the HDMI device defers probing until it can find the CEC
device. I'll drop mtk_hdmi_hpd_high and call mtk_cec_hpd_high directly.

> > +int mtk_hdmi_output_init(struct mtk_hdmi *hdmi)
> > +{
> > +       struct hdmi_audio_param *aud_param = &hdmi->aud_param;
> > +
> > +       if (hdmi->init)
> > +               return -EINVAL;
> 
> This check is not needed; this function is only called once, during probe.
> In fact, I don't think the "->init" field is needed at all.

I agree. mtk_hdmi_output_init is called in probe before drm_bridge_add.
mtk_hdmi_output_set_display_mode, which this is supposed to protect, is
only called from the bridge_enable callback. Another indication that the
code is split over too many files.

[...]
> > +int mtk_hdmi_output_set_display_mode(struct mtk_hdmi *hdmi,
> > +                                    struct drm_display_mode *mode)
> > +{
> > +       int ret;
> > +
> > +       if (!hdmi->init) {
> 
> Is this possible?

No. I'll remove it.

> > +               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);
> > +       if (ret) {
> > +               dev_err(hdmi->dev, "Failed to set vpll: %d\n", ret);
> 
> cleanup on error?

The only things that happen before are vid_black/aud_mute and PHY power
off. I assume neither can reasonably be reverted if we can't even set
the PLL correctly?

> > +               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");
> 
> what?  No.  The "product" should refer to the MTK HDMI block.

I need a little help here. "mediatek", "On-chip HDMI"?
The Intel driver sets "intel", "Integrated gfx", for example.

> > diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.h b/drivers/gpu/drm/mediatek/mtk_hdmi.h
> > new file mode 100644
> > index 0000000..9403915
> > --- /dev/null
> > +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.h
[...]
> > +struct mtk_hdmi {
> > +       struct drm_bridge bridge;
> > +       struct drm_connector conn;
> > +       struct device *dev;
> > +       struct phy *phy;
> > +       struct device *cec_dev;
> > +       struct i2c_adapter *ddc_adpt;
> > +       struct clk *clk[MTK_HDMI_CLK_COUNT];
> > +#if defined(CONFIG_DEBUG_FS)
> > +       struct dentry *debugfs;
> > +#endif
> 
> Remove all of the debugfs stuff from this patch, since it isn't implemented.

Ok.

[...]
> > 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
[...]
> > +struct mtk_hdmi_i2c {
> 
> Throughout this driver, I think we should:
> 
> s/mtk_hdmi_i2c/mtk_hdmi_ddc

I'm fine with that.

[...]
> > +static void ddcm_trigger_mode(struct mtk_hdmi_i2c *i2c, int mode)
> > +{
[...]
> > +       while (sif_bit_is_set(i2c, DDC_DDCMCTL1, DDCM_TRI)) {
> > +               timeout -= 2;
> > +               udelay(2);
> > +               if (timeout <= 0)
> > +                       break;
> > +       }
> 
> Use iopoll?

I'll replace the loop with readl_poll_timeout().

[...]
> > +static int mtk_hdmi_i2c_probe(struct platform_device *pdev)
> > +{
[...]
> > +       i2c->clk = devm_clk_get(dev, "ddc-i2c");
> > +       if (IS_ERR(i2c->clk)) {
> > +               dev_err(dev, "get ddc_clk failed : %p ,\n", i2c->clk);
> 
> nit, no space before ':'

Ok.

[...]
> > +       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");
> 
> nit: don't really need to print anything here.

Ok.

[...]
> > +       strlcpy(i2c->adap.name, "mediatek-hdmi-i2c", sizeof(i2c->adap.name));
> > +       i2c->adap.owner = THIS_MODULE;
> 
> i2c->adap.class = I2C_CLASS_DDC;

Ok.

> > +       i2c->adap.algo = &mtk_hdmi_i2c_algorithm;
> > +       i2c->adap.retries = 3;
> 
> why set this?

Jie, is there a known issue that made it necessary to enable the
automatic retries?

[...]
> > +       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);
> 
> remove these debugging lines.

Ok.

[...]
> > +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);
> 
> To match probe order, call i2c_del_adapter() first.

Ok.

[...]
> > 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..99c7ffc
> > --- /dev/null
> > +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c
> 
> What is the value in having this as a separate mtk_hdmi_hw.c file?
> If these functions were in mtk_hdmi.c, they could all be static, and
> the compiler
> could inline them away.

Yes, I'd prefer to merge all three into mtk_hdmi.c.

[...]
> > +#include "mtk_hdmi_hw.h"
> > +#include "mtk_hdmi_regs.h"
> > +#include "mtk_hdmi.h"
> 
> I think these usually go after system includes.

Ok.

[...]
> > +#define NCTS_BYTES          0x07
> 
> move above the functions

Ok.

[...]
> > +       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:
> 
> Just fall through if none of the above?

These are the only four currently defined. I'll change frame_type to
enum hdmi_infoframe_type and drop the default label.

[...]
> > +void mtk_hdmi_hw_config_sys(struct mtk_hdmi *hdmi)
> > +{
> > +       regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20,
> > +                          HDMI_OUT_FIFO_EN | MHL_MODE_ON, 0);
> > +       mdelay(2);
> 
> Can this be msleep instead of mdelay?
> It is a bit rude to hog the CPU for 2 msec.

Yes this is ultimately called by .bridge_enable.
Same for the others two mdelay()s.

[...]
> > +void mtk_hdmi_hw_aud_set_bit_num(struct mtk_hdmi *hdmi,
> > +                                enum hdmi_audio_sample_size bit_num)
> > +{
> > +       u32 val = 0;
> 
> no 0 init

I'll use a switch statement below instead.

> > +
> > +       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(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;
> 
> no 0 init

Ok.

> > +
> > +       val = mtk_hdmi_read(hdmi, GRL_CFG0);
> > +       val &= ~0x33;
> 
> #define this mask

Ok.

[...]
> > +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;
> 
> better:
>   u8 sw[3], uv;

Yes, I'll try to make this function a bit more readable.

> > +
> > +       if (channel_count == 2) {
> > +               val_1 = 0x04;
> > +               val_2 = 0x50;
> 
> Some #defines with meaningful names would be nice here.

I'll add a few defines. Also I notice that the ch_switch matrix
configuration is always the same.

[...]
> > +void mtk_hdmi_hw_aud_set_input_type(struct mtk_hdmi *hdmi,
> > +                                   enum hdmi_aud_input_type input_type)
> > +{
> > +       u32 val = 0;
> 
> no need to 0 init

Ok.

[...]
> > +void mtk_hdmi_hw_aud_set_channel_status(struct mtk_hdmi *hdmi,
> > +                                       u8 *l_chan_status, u8 *r_chan_status,
> > +                                       enum hdmi_audio_sample_frequency
> > +                                       aud_hdmi_fs)
> > +{
> > +       u8 l_status[5];
> > +       u8 r_status[5];
> > +       u8 val = 0;
> 
> no need to 0 init

Ok.

[...]
> > +       val = l_chan_status[4];
> > +       val |= ((~(l_status[3] & 0x0f)) << 4);
> > +       l_status[4] = val;
> > +       r_status[4] = val;
> > +
> > +       val = l_status[0];
> 
> nit: You don't need to bounce through val here.
> You can just write the *_status[n] value directly.

You are right, I have already fixed this in the later audio patches.
I'll reorganize this a bit.

[...]
> > +void mtk_hdmi_hw_aud_src_reenable(struct mtk_hdmi *hdmi)
> > +{
> > +       u32 val;
> > +
> > +       val = mtk_hdmi_read(hdmi, GRL_MIX_CTRL);
> > +       if (val & MIX_CTRL_SRC_EN) {
> > +               val &= ~MIX_CTRL_SRC_EN;
> > +               mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
> > +               usleep_range(255, 512);
> 
> Those are some very precise values for a range...

Peculiar. If there are no objections, I'll change them to 250-500.

> > +               val |= MIX_CTRL_SRC_EN;
> > +               mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val);
> > +       }
> > +}
> > +
> > +void mtk_hdmi_hw_aud_src_off(struct mtk_hdmi *hdmi)
> 
> Perhaps *_disable() would be more consistent.

Ok. Although the use of disable/reenable here seems to be inverted
compared to the common enable/disable pattern.

[...]
> > +void mtk_hdmi_hw_aud_aclk_inv_enable(struct mtk_hdmi *hdmi, bool enable)
> 
> nit: I prefer explicit _enable() and _disable() functions w/out the
> 'enable' parameter.

This function doesn't enable/disable a clock, it justs sets the ACLK_INV
bit, which supposedly determines whether the inverse audio clock is used
to latch data when downsampling 192k to 48k in I2S. I'll rename this to
mtk_hdmi_hw_aud_set_aclk_inv(). Or I could drop this function when
merging hdmi_hw.c into hdmi.c and use something like
	mtk_hdmi_clear_bits(hdmi, GRL_CFG2, CFG2_ACLK_INV)
directly.

[...]
> > +static unsigned int hdmi_expected_cts(unsigned int audio_sample_rate,
> > +                                     unsigned int tmds_clock, unsigned int n)
> > +{
> > +       return DIV_ROUND_CLOSEST_ULL((u64)hdmi_mode_clock_to_hz(tmds_clock) * n,
> > +                                    128 * audio_sample_rate);
> > +}
> 
> Doug Anderson may have some opinions about how N & CTS are computed.

I intend to use Arnaud's N/CTS helpers instead, when they are merged.

[...]
> > +static void do_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, unsigned int n,
> > +                                   unsigned int cts)
> > +{
> > +       unsigned char val[NCTS_BYTES];
> > +       int i;
> > +
> > +       mtk_hdmi_write(hdmi, GRL_NCTS, 0);
> > +       mtk_hdmi_write(hdmi, GRL_NCTS, 0);
> > +       mtk_hdmi_write(hdmi, GRL_NCTS, 0);
> > +       memset(val, 0, sizeof(val));
> 
> not necessary, since you fill in all 7 bytes anyway.

Ok.

> > +
> > +       val[0] = (cts >> 24) & 0xff;
> > +       val[1] = (cts >> 16) & 0xff;
> > +       val[2] = (cts >> 8) & 0xff;
> > +       val[3] = cts & 0xff;
> > +
> > +       val[4] = (n >> 16) & 0xff;
> > +       val[5] = (n >> 8) & 0xff;
> > +       val[6] = n & 0xff;
> 
> all of these "& 0xff" are not needed, since val is an unsigned char array.

Even so, this makes it clear what happens. I expect the compiler to
optimize them away if possible.

> > +
> > +       for (i = 0; i < NCTS_BYTES; i++)
> > +               mtk_hdmi_write(hdmi, GRL_NCTS, val[i]);
> 
> What an interesting design.  We write all 10 bytes to the same register address?

That is what I am told.

> In this case, why bother with val at all?
> Just directly call mtk_hdmi_write() for each of the bytes above.

Yes, that would be better.

> > diff --git a/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c b/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c
> > new file mode 100644
> > index 0000000..5d9f07f
> > --- /dev/null
> > +++ b/drivers/gpu/drm/mediatek/mtk_mt8173_hdmi_phy.c
[...]
> > +static void mtk_hdmi_pll_unprepare(struct clk_hw *hw)
> > +{
> > +       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
> > +
> > +       dev_dbg(hdmi_phy->dev, "prepare\n");
> 
> nit: "unprepare" (or just '"%s\n", __func__)', here and everywhere.

Ok.

> > +static int mtk_hdmi_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> > +                                unsigned long parent_rate)
> > +{
> > +       struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
> > +       unsigned int pre_div;
> > +       unsigned int div;
> > +
> > +       dev_dbg(hdmi_phy->dev, "set rate : %lu, parent: %lu\n", rate,
> 
> nit, no space before the first ':'.

Ok.

> > +static void mtk_hdmi_phy_enable_tmds(struct mtk_hdmi_phy *hdmi_phy)
> > +{
> > +       mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, RG_HDMITX_SER_EN,
> > +                         RG_HDMITX_SER_EN);
> 
> nit: lots of these calls might be more readable (and easier to maintain &
> review) if we used two helper functions:
> 
> mtk_hdmi_phy_set_bits(struct mtk_hdmi_phy *hdmi_phy, u32 offset, u32 mask);
> mtk_hdmi_phy_clr_bits(struct mtk_hdmi_phy *hdmi_phy, u32 offset, u32 mask);
> 
> and
> 
> mtk_hdmi_set_bits()
> mtk_hdmi_clr_bits()

It seems I'm too used to regmap_update_bits. I don't find separate
set/clear functions easier to read at all, and I stumble over the clr
abbreviation. I'll change to _set_bits and _clear_bits functions and
hope that I am in the minority.

[...]
> > +static struct phy_ops mtk_hdmi_phy_ops = {
> 
> static const

Ok. Thanks for the review!

regards
Philipp

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2016-03-21 17:45 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-03-08 13:27 [PATCH v13 00/14] MT8173 DRM support Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 01/14] dt-bindings: drm/mediatek: Add Mediatek display subsystem dts binding Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 02/14] drm/mediatek: Add DRM Driver for Mediatek SoC MT8173 Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 03/14] drm/mediatek: Add DSI sub driver Philipp Zabel
     [not found]   ` <1457443649-12133-4-git-send-email-p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
2016-03-09 14:07     ` Daniel Kurtz
2016-03-15 11:49       ` Philipp Zabel
2016-03-15 12:06         ` Daniel Kurtz
2016-03-08 13:27 ` [PATCH v13 04/14] drm/mediatek: Add DPI " Philipp Zabel
     [not found]   ` <1457443649-12133-5-git-send-email-p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
2016-03-09 14:23     ` Daniel Kurtz
2016-03-15 11:45       ` Philipp Zabel
     [not found] ` <1457443649-12133-1-git-send-email-p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
2016-03-08 13:27   ` [PATCH v13 05/14] dt-bindings: drm/mediatek: Add Mediatek HDMI dts binding Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 06/14] drm/mediatek: Add HDMI support Philipp Zabel
2016-03-09 13:52   ` Daniel Kurtz
     [not found]     ` <CAGS+omD6_hNMVYCnmsHheyxpGZXDf7H+XVHT5C4wVCKDvgpQ7w-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2016-03-21 17:45       ` Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 07/14] drm/mediatek: enable hdmi output control bit Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 08/14] arm64: dts: mt8173: Add display subsystem related nodes Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 09/14] arm64: dts: mt8173: Add HDMI " Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 10/14] clk: mediatek: make dpi0_sel propagate rate changes Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 11/14] clk: mediatek: Add hdmi_ref HDMI PHY PLL reference clock output Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 12/14] dt-bindings: hdmi-connector: add DDC I2C bus phandle documentation Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 13/14] clk: mediatek: remove hdmitx_dig_cts from TOP clocks Philipp Zabel
2016-03-08 13:27 ` [PATCH v13 14/14] arm64: dts: mt8173-evb: enable HDMI output Philipp Zabel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).