From 7c9bee96b281696f69ac1b45c423156919a1c3a6 Mon Sep 17 00:00:00 2001
From: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Date: Sat, 18 Nov 2023 01:22:02 +0800
Subject: [PATCH 1/3] meson8/meson8b/meson8m2: Support HDMI

The following codes are come from https://github.com/xdarklight/linux/commits/meson-mx-integration-5.18-20220516.

Special thank to Martin Blumenstingl.

---
 .../bindings/display/amlogic,meson-vpu.yaml   |   16 +
 .../phy/amlogic,meson-cvbs-dac-phy.yaml       |   81 +
 arch/arm/boot/dts/amlogic/meson.dtsi          |   13 +
 arch/arm/boot/dts/amlogic/meson8.dtsi         |  168 +-
 arch/arm/boot/dts/amlogic/meson8b.dtsi        |  171 +-
 arch/arm/boot/dts/amlogic/meson8m2.dtsi       |    4 +
 drivers/gpu/drm/meson/Kconfig                 |    9 +
 drivers/gpu/drm/meson/Makefile                |    1 +
 drivers/gpu/drm/meson/meson_drv.c             |  313 +++-
 drivers/gpu/drm/meson/meson_drv.h             |   49 +-
 drivers/gpu/drm/meson/meson_encoder_cvbs.c    |   61 +-
 drivers/gpu/drm/meson/meson_encoder_hdmi.c    |   69 +-
 drivers/gpu/drm/meson/meson_plane.c           |   37 +-
 drivers/gpu/drm/meson/meson_transwitch_hdmi.c | 1579 +++++++++++++++++
 drivers/gpu/drm/meson/meson_transwitch_hdmi.h |  536 ++++++
 drivers/gpu/drm/meson/meson_vclk.c            |  146 ++
 drivers/gpu/drm/meson/meson_venc.c            |   44 +-
 drivers/gpu/drm/meson/meson_viu.c             |   18 +-
 drivers/phy/amlogic/Kconfig                   |   10 +
 drivers/phy/amlogic/Makefile                  |    1 +
 drivers/phy/amlogic/phy-meson-cvbs-dac.c      |  375 ++++
 21 files changed, 3576 insertions(+), 125 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/phy/amlogic,meson-cvbs-dac-phy.yaml
 create mode 100644 drivers/gpu/drm/meson/meson_transwitch_hdmi.c
 create mode 100644 drivers/gpu/drm/meson/meson_transwitch_hdmi.h
 create mode 100644 drivers/phy/amlogic/phy-meson-cvbs-dac.c

diff --git a/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml b/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml
index cb0a90f0..96c32747 100644
--- a/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml
+++ b/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml
@@ -66,8 +66,12 @@ properties:
           - const: amlogic,meson-gx-vpu
       - enum:
           - amlogic,meson-g12a-vpu # G12A (S905X2, S905Y2, S905D2)
+          - amlogic,meson8-vpu
+          - amlogic,meson8b-vpu
+          - amlogic,meson8m2-vpu
 
   reg:
+    minItems: 1
     maxItems: 2
 
   reg-names:
@@ -82,6 +86,15 @@ properties:
     description: should point to a canvas provider node
     $ref: /schemas/types.yaml#/definitions/phandle
 
+  phys:
+    maxItems: 1
+    description:
+      PHY specifier for the CVBS DAC
+
+  phy-names:
+    items:
+      - const: cvbs-dac
+
   power-domains:
     maxItems: 1
     description: phandle to the associated power domain
@@ -130,6 +143,9 @@ examples:
         #size-cells = <0>;
         amlogic,canvas = <&canvas>;
 
+        phys = <&cvbs_dac_phy>;
+        phy-names = "cvbs-dac";
+
         /* CVBS VDAC output port */
         port@0 {
             reg = <0>;
diff --git a/Documentation/devicetree/bindings/phy/amlogic,meson-cvbs-dac-phy.yaml b/Documentation/devicetree/bindings/phy/amlogic,meson-cvbs-dac-phy.yaml
new file mode 100644
index 00000000..d73cb12c
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/amlogic,meson-cvbs-dac-phy.yaml
@@ -0,0 +1,81 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/phy/amlogic,meson-cvbs-dac-phy.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Amlogic Meson Composite Video Baseband Signal DAC
+
+maintainers:
+  - Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+
+description: |+
+  The CVBS DAC node should be the child of a syscon node with the
+  required property:
+
+  compatible = "amlogic,meson-hhi-sysctrl", "simple-mfd", "syscon"
+
+  Refer to the bindings described in
+  Documentation/devicetree/bindings/mfd/syscon.yaml
+
+properties:
+  $nodename:
+    pattern: "^video-dac@[0-9a-f]+$"
+
+  compatible:
+    oneOf:
+      - items:
+          - enum:
+              - amlogic,meson8-cvbs-dac
+              - amlogic,meson-gxbb-cvbs-dac
+              - amlogic,meson-gxl-cvbs-dac
+              - amlogic,meson-g12a-cvbs-dac
+          - const: amlogic,meson-cvbs-dac
+      - const: amlogic,meson-cvbs-dac
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    minItems: 1
+
+  nvmem-cells:
+    minItems: 1
+
+  nvmem-cell-names:
+    items:
+      - const: cvbs_trimming
+
+  "#phy-cells":
+    const: 0
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - "#phy-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    video-dac@2f4 {
+      compatible = "amlogic,meson8-cvbs-dac", "amlogic,meson-cvbs-dac";
+      reg = <0x2f4 0x8>;
+
+      #phy-cells = <0>;
+
+      clocks = <&vdac_clock>;
+
+      nvmem-cells = <&cvbs_trimming>;
+      nvmem-cell-names = "cvbs_trimming";
+    };
+  - |
+    video-dac@2ec {
+      compatible = "amlogic,meson-g12a-cvbs-dac", "amlogic,meson-cvbs-dac";
+      reg = <0x2ec 0x8>;
+
+      #phy-cells = <0>;
+
+      clocks = <&vdac_clock>;
+    };
diff --git a/arch/arm/boot/dts/amlogic/meson.dtsi b/arch/arm/boot/dts/amlogic/meson.dtsi
index 8e3860d5..9a56cdf7 100644
--- a/arch/arm/boot/dts/amlogic/meson.dtsi
+++ b/arch/arm/boot/dts/amlogic/meson.dtsi
@@ -35,6 +35,19 @@ hhi: system-controller@4000 {
 					     "simple-mfd",
 					     "syscon";
 				reg = <0x4000 0x400>;
+				#address-cells = <1>;
+				#size-cells = <1>;
+				ranges = <0x0 0x4000 0x400>;
+
+
+				cvbs_dac: video-dac@2f4 {
+					compatible = "amlogic,meson-cvbs-dac";
+					reg = <0x2f4 0x8>;
+
+					#phy-cells = <0>;
+
+					status = "disabled";
+				};
 			};
 
 			aiu: audio-controller@5400 {
diff --git a/arch/arm/boot/dts/amlogic/meson8.dtsi b/arch/arm/boot/dts/amlogic/meson8.dtsi
index 59932fbf..6c27d520 100644
--- a/arch/arm/boot/dts/amlogic/meson8.dtsi
+++ b/arch/arm/boot/dts/amlogic/meson8.dtsi
@@ -314,6 +314,113 @@ mali: gpu@c0000 {
 			operating-points-v2 = <&gpu_opp_table>;
 			#cooling-cells = <2>; /* min followed by max */
 		};
+
+		hdmi_tx: hdmi-tx@42000 {
+			compatible = "amlogic,meson8-hdmi-tx";
+			reg = <0x42000 0xc>;
+			interrupts = <GIC_SPI 57 IRQ_TYPE_EDGE_RISING>;
+			phys = <&hdmi_tx_phy>;
+			phy-names = "hdmi";
+			clocks = <&clkc CLKID_HDMI_PCLK>,
+				 <&clkc CLKID_HDMI_SYS>;
+			clock-names = "pclk", "sys";
+
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			#sound-dai-cells = <1>;
+			sound-name-prefix = "HDMITX";
+
+			status = "disabled";
+
+			/* VPU VENC Input */
+			hdmi_tx_venc_port: port@0 {
+				reg = <0>;
+
+				hdmi_tx_in: endpoint {
+					remote-endpoint = <&hdmi_tx_out>;
+				};
+			};
+
+			/* TMDS Output */
+			hdmi_tx_tmds_port: port@1 {
+				reg = <1>;
+			};
+		};
+
+		vpu: vpu@100000 {
+			compatible = "amlogic,meson8-vpu";
+
+			reg = <0x100000 0x10000>;
+			reg-names = "vpu";
+
+			interrupts = <GIC_SPI 3 IRQ_TYPE_EDGE_RISING>;
+
+			amlogic,canvas = <&canvas>;
+
+			/*
+			 * The VCLK{,2}_IN path always needs to derived from
+			 * the CLKID_VID_PLL_FINAL_DIV so other clocks like
+			 * MPLL1 are not used (MPLL1 is reserved for audio
+			 * purposes).
+			 */
+			assigned-clocks = <&clkc CLKID_VCLK_IN_SEL>,
+					  <&clkc CLKID_VCLK2_IN_SEL>;
+			assigned-clock-parents = <&clkc CLKID_VID_PLL_FINAL_DIV>,
+						 <&clkc CLKID_VID_PLL_FINAL_DIV>;
+
+			clocks = <&clkc CLKID_VPU_INTR>,
+				 <&clkc CLKID_HDMI_INTR_SYNC>,
+				 <&clkc CLKID_GCLK_VENCI_INT>,
+				 <&clkc CLKID_HDMI_PLL_HDMI_OUT>,
+				 <&clkc CLKID_HDMI_TX_PIXEL>,
+				 <&clkc CLKID_CTS_ENCP>,
+				 <&clkc CLKID_CTS_ENCI>,
+				 <&clkc CLKID_CTS_ENCT>,
+				 <&clkc CLKID_CTS_ENCL>,
+				 <&clkc CLKID_CTS_VDAC0>;
+			clock-names = "vpu_intr",
+				      "hdmi_intr_sync",
+				      "venci_int",
+				      "tmds",
+				      "hdmi_tx_pixel",
+				      "cts_encp",
+				      "cts_enci",
+				      "cts_enct",
+				      "cts_encl",
+				      "cts_vdac0";
+
+			resets = <&clkc CLKC_RESET_VID_DIVIDER_CNTL_RESET_N_PRE>,
+				 <&clkc CLKC_RESET_VID_DIVIDER_CNTL_RESET_N_POST>,
+				 <&clkc CLKC_RESET_VID_DIVIDER_CNTL_SOFT_RESET_PRE>,
+				 <&clkc CLKC_RESET_VID_DIVIDER_CNTL_SOFT_RESET_POST>;
+			reset-names = "vid_pll_pre",
+				      "vid_pll_post",
+				      "vid_pll_soft_pre",
+				      "vid_pll_soft_post";
+
+			phys = <&cvbs_dac>;
+			phy-names = "cvbs-dac";
+
+			power-domains = <&pwrc PWRC_MESON8_VPU_ID>;
+
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			/* CVBS VDAC output port */
+			cvbs_vdac_port: port@0 {
+				reg = <0>;
+			};
+
+			/* HDMI-TX output port */
+			hdmi_tx_port: port@1 {
+				reg = <1>;
+
+				hdmi_tx_out: endpoint {
+					remote-endpoint = <&hdmi_tx_in>;
+				};
+			};
+		};
 	};
 }; /* end of / */
 
@@ -363,6 +470,14 @@ gpio_ao: ao-bank@14 {
 			gpio-ranges = <&pinctrl_aobus 0 0 16>;
 		};
 
+		hdmi_cec_ao_pins: hdmi-cec-ao {
+			mux {
+				groups = "hdmi_cec_ao";
+				function = "hdmi_cec_ao";
+				bias-pull-up;
+			};
+		};
+
 		i2s_am_clk_pins: i2s-am-clk-out {
 			mux {
 				groups = "i2s_am_clk_out_ao";
@@ -427,6 +542,15 @@ mux {
 			};
 		};
 	};
+
+	cec_AO: cec@100 {
+		compatible = "amlogic,meson-gx-ao-cec"; // FIXME
+		reg = <0x100 0x14>;
+		interrupts = <GIC_SPI 151 IRQ_TYPE_EDGE_RISING>;
+		// TODO: 32768HZ clock
+		hdmi-phandle = <&hdmi_tx>;
+		status = "disabled";
+	};
 };
 
 &ao_arc_rproc {
@@ -479,6 +603,22 @@ gpio: banks@80b0 {
 			gpio-ranges = <&pinctrl_cbus 0 0 120>;
 		};
 
+		hdmi_hpd_pins: hdmi-hpd {
+			mux {
+				groups = "hdmi_hpd";
+				function = "hdmi";
+				bias-disable;
+			};
+		};
+
+		hdmi_i2c_pins: hdmi-i2c {
+			mux {
+				groups = "hdmi_sda", "hdmi_scl";
+				function = "hdmi";
+				bias-disable;
+			};
+		};
+
 		sd_a_pins: sd-a {
 			mux {
 				groups = "sd_d0_a", "sd_d1_a", "sd_d2_a",
@@ -601,6 +741,17 @@ smp-sram@1ff80 {
 	};
 };
 
+&cvbs_dac {
+	compatible = "amlogic,meson8-cvbs-dac", "amlogic,meson-cvbs-dac";
+
+	clocks = <&clkc CLKID_CTS_VDAC0>;
+
+	nvmem-cells = <&cvbs_trimming>;
+	nvmem-cell-names = "cvbs_trimming";
+
+	status = "okay";
+};
+
 &efuse {
 	compatible = "amlogic,meson8-efuse";
 	clocks = <&clkc CLKID_EFUSE>;
@@ -610,6 +761,10 @@ temperature_calib: calib@1f4 {
 		/* only the upper two bytes are relevant */
 		reg = <0x1f4 0x4>;
 	};
+
+	cvbs_trimming: calib@1f8 {
+		reg = <0x1f8 0x2>;
+	};
 };
 
 &ethmac {
@@ -625,16 +780,18 @@ &gpio_intc {
 };
 
 &hhi {
-	clkc: clock-controller {
+	clkc: clock-controller@0 {
 		compatible = "amlogic,meson8-clkc";
+		reg = <0x0 0x39c>;
 		clocks = <&xtal>, <&ddr_clkc DDR_CLKID_DDR_PLL>;
 		clock-names = "xtal", "ddr_pll";
 		#clock-cells = <1>;
 		#reset-cells = <1>;
 	};
 
-	pwrc: power-controller {
+	pwrc: power-controller@100 {
 		compatible = "amlogic,meson8-pwrc";
+		reg = <0x100 0x10>;
 		#power-domain-cells = <1>;
 		amlogic,ao-sysctrl = <&pmu>;
 		clocks = <&clkc CLKID_VPU>;
@@ -642,6 +799,13 @@ pwrc: power-controller {
 		assigned-clocks = <&clkc CLKID_VPU>;
 		assigned-clock-rates = <364285714>;
 	};
+
+	hdmi_tx_phy: hdmi-phy@3a0 {
+		compatible = "amlogic,meson8-hdmi-tx-phy";
+		clocks = <&clkc CLKID_HDMI_PLL_HDMI_OUT>;
+		reg = <0x3a0 0xc>;
+		#phy-cells = <0>;
+	};
 };
 
 &hwrng {
diff --git a/arch/arm/boot/dts/amlogic/meson8b.dtsi b/arch/arm/boot/dts/amlogic/meson8b.dtsi
index 5198f517..da9216ee 100644
--- a/arch/arm/boot/dts/amlogic/meson8b.dtsi
+++ b/arch/arm/boot/dts/amlogic/meson8b.dtsi
@@ -276,6 +276,116 @@ mali: gpu@c0000 {
 			operating-points-v2 = <&gpu_opp_table>;
 			#cooling-cells = <2>; /* min followed by max */
 		};
+
+		hdmi_tx: hdmi-tx@42000 {
+			compatible = "amlogic,meson8b-hdmi-tx";
+			reg = <0x42000 0xc>;
+			interrupts = <GIC_SPI 57 IRQ_TYPE_EDGE_RISING>;
+			phys = <&hdmi_tx_phy>;
+			phy-names = "hdmi";
+			clocks = <&clkc CLKID_HDMI_PCLK>,
+				 <&clkc CLKID_HDMI_SYS>;
+			clock-names = "pclk", "sys";
+
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			#sound-dai-cells = <1>;
+			sound-name-prefix = "HDMITX";
+
+			status = "disabled";
+
+			/* VPU VENC Input */
+			hdmi_tx_venc_port: port@0 {
+				reg = <0>;
+
+				hdmi_tx_in: endpoint {
+					remote-endpoint = <&hdmi_tx_out>;
+				};
+			};
+
+			/* TMDS Output */
+			hdmi_tx_tmds_port: port@1 {
+				reg = <1>;
+			};
+		};
+
+		vpu: vpu@100000 {
+			compatible = "amlogic,meson8b-vpu";
+
+			reg = <0x100000 0x10000>;
+			reg-names = "vpu";
+
+			interrupts = <GIC_SPI 3 IRQ_TYPE_EDGE_RISING>;
+
+			amlogic,canvas = <&canvas>;
+
+			/*
+			 * The VCLK{,2}_IN path always needs to derived from
+			 * the CLKID_VID_PLL_FINAL_DIV so other clocks like
+			 * MPLL1 are not used (MPLL1 is reserved for audio
+			 * purposes).
+			 */
+			assigned-clocks = <&clkc CLKID_VCLK_IN_SEL>,
+					  <&clkc CLKID_VCLK2_IN_SEL>;
+			assigned-clock-parents = <&clkc CLKID_VID_PLL_FINAL_DIV>,
+						 <&clkc CLKID_VID_PLL_FINAL_DIV>;
+
+			clocks = <&clkc CLKID_VPU_INTR>,
+				 <&clkc CLKID_HDMI_INTR_SYNC>,
+				 <&clkc CLKID_GCLK_VENCI_INT>,
+				 <&clkc CLKID_HDMI_PLL_HDMI_OUT>,
+				 <&clkc CLKID_HDMI_TX_PIXEL>,
+				 <&clkc CLKID_CTS_ENCP>,
+				 <&clkc CLKID_CTS_ENCI>,
+				 <&clkc CLKID_CTS_ENCT>,
+				 <&clkc CLKID_CTS_ENCL>,
+				 <&clkc CLKID_CTS_VDAC0>;
+			clock-names = "vpu_intr",
+				      "hdmi_intr_sync",
+				      "venci_int",
+				      "tmds",
+				      "hdmi_tx_pixel",
+				      "cts_encp",
+				      "cts_enci",
+				      "cts_enct",
+				      "cts_encl",
+				      "cts_vdac0";
+
+			resets = <&clkc CLKC_RESET_VID_DIVIDER_CNTL_RESET_N_PRE>,
+				 <&clkc CLKC_RESET_VID_DIVIDER_CNTL_RESET_N_POST>,
+				 <&clkc CLKC_RESET_VID_DIVIDER_CNTL_SOFT_RESET_PRE>,
+				 <&clkc CLKC_RESET_VID_DIVIDER_CNTL_SOFT_RESET_POST>;
+			reset-names = "vid_pll_pre",
+				      "vid_pll_post",
+				      "vid_pll_soft_pre",
+				      "vid_pll_soft_post";
+
+			phys = <&cvbs_dac>;
+			phy-names = "cvbs-dac";
+
+			power-domains = <&pwrc PWRC_MESON8_VPU_ID>;
+
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			#sound-dai-cells = <0>;
+			sound-name-prefix = "HDMITX";
+
+			/* CVBS VDAC output port */
+			cvbs_vdac_port: port@0 {
+				reg = <0>;
+			};
+
+			/* HDMI-TX output port */
+			hdmi_tx_port: port@1 {
+				reg = <1>;
+
+				hdmi_tx_out: endpoint {
+					remote-endpoint = <&hdmi_tx_in>;
+				};
+			};
+		};
 	};
 }; /* end of / */
 
@@ -325,6 +435,14 @@ gpio_ao: ao-bank@14 {
 			gpio-ranges = <&pinctrl_aobus 0 0 16>;
 		};
 
+		hdmi_cec_ao_pins: hdmi-cec-ao {
+			mux {
+				groups = "hdmi_cec_1";
+				function = "hdmi_cec";
+				bias-pull-up;
+			};
+		};
+
 		i2s_am_clk_pins: i2s-am-clk-out {
 			mux {
 				groups = "i2s_am_clk_out";
@@ -381,6 +499,15 @@ mux {
 			};
 		};
 	};
+
+	cec_AO: cec@100 {
+		compatible = "amlogic,meson-gx-ao-cec"; // FIXME
+		reg = <0x100 0x14>;
+		interrupts = <GIC_SPI 151 IRQ_TYPE_EDGE_RISING>;
+		// TODO: 32768HZ clock
+		hdmi-phandle = <&hdmi_tx>;
+		status = "disabled";
+	};
 };
 
 &ao_arc_rproc {
@@ -471,6 +598,22 @@ mux {
 			};
 		};
 
+		hdmi_hpd_pins: hdmi-hpd {
+			mux {
+				groups = "hdmi_hpd";
+				function = "hdmi";
+				bias-disable;
+			};
+		};
+
+		hdmi_i2c_pins: hdmi-i2c {
+			mux {
+				groups = "hdmi_sda", "hdmi_scl";
+				function = "hdmi";
+				bias-disable;
+			};
+		};
+
 		i2c_a_pins: i2c-a {
 			mux {
 				groups = "i2c_sda_a", "i2c_sck_a";
@@ -547,6 +690,16 @@ smp-sram@1ff80 {
 	};
 };
 
+&cvbs_dac {
+	compatible = "amlogic,meson8-cvbs-dac", "amlogic,meson-cvbs-dac";
+
+	clocks = <&clkc CLKID_CTS_VDAC0>;
+
+	nvmem-cells = <&cvbs_trimming>;
+	nvmem-cell-names = "cvbs_trimming";
+
+	status = "okay";
+};
 
 &efuse {
 	compatible = "amlogic,meson8b-efuse";
@@ -557,6 +710,10 @@ temperature_calib: calib@1f4 {
 		/* only the upper two bytes are relevant */
 		reg = <0x1f4 0x4>;
 	};
+
+	cvbs_trimming: calib@1f8 {
+		reg = <0x1f8 0x2>;
+	};
 };
 
 &ethmac {
@@ -586,16 +743,18 @@ &gpio_intc {
 };
 
 &hhi {
-	clkc: clock-controller {
+	clkc: clock-controller@0 {
 		compatible = "amlogic,meson8b-clkc";
+		reg = <0x0 0x39c>;
 		clocks = <&xtal>, <&ddr_clkc DDR_CLKID_DDR_PLL>;
 		clock-names = "xtal", "ddr_pll";
 		#clock-cells = <1>;
 		#reset-cells = <1>;
 	};
 
-	pwrc: power-controller {
+	pwrc: power-controller@100 {
 		compatible = "amlogic,meson8b-pwrc";
+		reg = <0x100 0x10>;
 		#power-domain-cells = <1>;
 		amlogic,ao-sysctrl = <&pmu>;
 		resets = <&reset RESET_DBLK>,
@@ -617,6 +776,14 @@ pwrc: power-controller {
 		assigned-clocks = <&clkc CLKID_VPU>;
 		assigned-clock-rates = <182142857>;
 	};
+
+	hdmi_tx_phy: hdmi-phy@3a0 {
+		compatible = "amlogic,meson8b-hdmi-tx-phy",
+			     "amlogic,meson8-hdmi-tx-phy";
+		clocks = <&clkc CLKID_HDMI_PLL_HDMI_OUT>;
+		reg = <0x3a0 0xc>;
+		#phy-cells = <0>;
+	};
 };
 
 &hwrng {
diff --git a/arch/arm/boot/dts/amlogic/meson8m2.dtsi b/arch/arm/boot/dts/amlogic/meson8m2.dtsi
index 6725dd9f..fcb2ad97 100644
--- a/arch/arm/boot/dts/amlogic/meson8m2.dtsi
+++ b/arch/arm/boot/dts/amlogic/meson8m2.dtsi
@@ -96,6 +96,10 @@ &usb1_phy {
 	compatible = "amlogic,meson8m2-usb2-phy", "amlogic,meson-mx-usb2-phy";
 };
 
+&vpu {
+	compatible = "amlogic,meson8m2-vpu";
+};
+
 &wdt {
 	compatible = "amlogic,meson8m2-wdt", "amlogic,meson8b-wdt";
 };
diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig
index 615fdd0c..eff3e34b 100644
--- a/drivers/gpu/drm/meson/Kconfig
+++ b/drivers/gpu/drm/meson/Kconfig
@@ -10,6 +10,7 @@ config DRM_MESON
 	select REGMAP_MMIO
 	select MESON_CANVAS
 	select CEC_CORE if CEC_NOTIFIER
+	imply PHY_MESON_CVBS_DAC
 
 config DRM_MESON_DW_HDMI
 	tristate "HDMI Synopsys Controller support for Amlogic Meson Display"
@@ -24,3 +25,11 @@ config DRM_MESON_DW_MIPI_DSI
 	default y if DRM_MESON
 	select DRM_DW_MIPI_DSI
 	select GENERIC_PHY_MIPI_DPHY
+
+config DRM_MESON_TRANSWITCH_HDMI
+	tristate "Amlogic Meson8/8b/8m2 TranSwitch HDMI 1.4 Controller support"
+	depends on ARM || COMPILE_TEST
+	depends on DRM_MESON
+	default y if DRM_MESON
+	select REGMAP_MMIO
+	select SND_SOC_HDMI_CODEC if SND_SOC
diff --git a/drivers/gpu/drm/meson/Makefile b/drivers/gpu/drm/meson/Makefile
index 43071bdb..c44cb6c5 100644
--- a/drivers/gpu/drm/meson/Makefile
+++ b/drivers/gpu/drm/meson/Makefile
@@ -7,3 +7,4 @@ meson-drm-y += meson_encoder_hdmi.o meson_encoder_dsi.o
 obj-$(CONFIG_DRM_MESON) += meson-drm.o
 obj-$(CONFIG_DRM_MESON_DW_HDMI) += meson_dw_hdmi.o
 obj-$(CONFIG_DRM_MESON_DW_MIPI_DSI) += meson_dw_mipi_dsi.o
+obj-$(CONFIG_DRM_MESON_TRANSWITCH_HDMI) += meson_transwitch_hdmi.o
diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c
index cb674966..e8134e4c 100644
--- a/drivers/gpu/drm/meson/meson_drv.c
+++ b/drivers/gpu/drm/meson/meson_drv.c
@@ -12,6 +12,7 @@
 #include <linux/module.h>
 #include <linux/of_graph.h>
 #include <linux/sys_soc.h>
+#include <linux/phy/phy.h>
 #include <linux/platform_device.h>
 #include <linux/soc/amlogic/meson-canvas.h>
 
@@ -133,30 +134,147 @@ static struct regmap_config meson_regmap_config = {
 	.max_register   = 0x1000,
 };
 
+static int meson_cvbs_dac_phy_init(struct meson_drm *priv)
+{
+	struct platform_device *pdev;
+	const char *platform_id_name;
+
+	priv->cvbs_dac = devm_phy_optional_get(priv->dev, "cvbs-dac");
+	if (IS_ERR(priv->cvbs_dac))
+		return dev_err_probe(priv->dev, PTR_ERR(priv->cvbs_dac),
+				     "Failed to get the 'cvbs-dac' PHY\n");
+	else if (priv->cvbs_dac)
+		return 0;
+
+	switch (priv->compat) {
+	case VPU_COMPATIBLE_GXBB:
+		platform_id_name = "meson-gxbb-cvbs-dac";
+		break;
+	case VPU_COMPATIBLE_GXL:
+	case VPU_COMPATIBLE_GXM:
+		platform_id_name = "meson-gxl-cvbs-dac";
+		break;
+	case VPU_COMPATIBLE_G12A:
+		platform_id_name = "meson-g12a-cvbs-dac";
+		break;
+	default:
+		return dev_err_probe(priv->dev, -EINVAL,
+				     "No CVBS DAC platform ID found\n");
+	}
+
+	pdev = platform_device_register_data(priv->dev, platform_id_name,
+					     PLATFORM_DEVID_AUTO, NULL, 0);
+	if (IS_ERR(pdev))
+		return dev_err_probe(priv->dev, PTR_ERR(pdev),
+				     "Failed to register fallback CVBS DAC PHY platform device\n");
+
+	priv->cvbs_dac = platform_get_drvdata(pdev);
+	if (IS_ERR(priv->cvbs_dac)) {
+		platform_device_unregister(pdev);
+		return dev_err_probe(priv->dev, PTR_ERR(priv->cvbs_dac),
+				     "Failed to get the 'cvbs-dac' PHY from it's platform device\n");
+	}
+
+	dev_warn(priv->dev, "Using fallback for old .dtbs without CVBS DAC\n");
+
+	priv->cvbs_dac_pdev = pdev;
+
+	return 0;
+}
+
+static void meson_cvbs_dac_phy_exit(struct meson_drm *priv)
+{
+	platform_device_unregister(priv->cvbs_dac_pdev);
+}
+
 static void meson_vpu_init(struct meson_drm *priv)
 {
-	u32 value;
+	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) {
+		writel(0x0, priv->io_base + _REG(VPU_MEM_PD_REG0));
+		writel(0x0, priv->io_base + _REG(VPU_MEM_PD_REG1));
+	} else {
+		u32 value;
+
+		/*
+		* Slave dc0 and dc5 connected to master port 1.
+		* By default other slaves are connected to master port 0.
+		*/
+		value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1) |
+			VPU_RDARB_SLAVE_TO_MASTER_PORT(5, 1);
+		writel_relaxed(value,
+			       priv->io_base + _REG(VPU_RDARB_MODE_L1C1));
+
+		/* Slave dc0 connected to master port 1 */
+		value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1);
+		writel_relaxed(value,
+			       priv->io_base + _REG(VPU_RDARB_MODE_L1C2));
+
+		/* Slave dc4 and dc7 connected to master port 1 */
+		value = VPU_RDARB_SLAVE_TO_MASTER_PORT(4, 1) |
+			VPU_RDARB_SLAVE_TO_MASTER_PORT(7, 1);
+		writel_relaxed(value,
+			       priv->io_base + _REG(VPU_RDARB_MODE_L2C1));
+
+		/* Slave dc1 connected to master port 1 */
+		value = VPU_RDARB_SLAVE_TO_MASTER_PORT(1, 1);
+		writel_relaxed(value,
+			       priv->io_base + _REG(VPU_WRARB_MODE_L2C1));
+	}
+}
+
+static int meson_video_clock_init(struct meson_drm *priv)
+{
+	int ret;
+
+	ret = clk_bulk_prepare(VPU_VID_CLK_NUM, priv->vid_clks);
+	if (ret)
+		return dev_err_probe(priv->dev, ret,
+				     "Failed to prepare the video clocks\n");
+
+	ret = clk_bulk_prepare(priv->num_intr_clks, priv->intr_clks);
+	if (ret)
+		return dev_err_probe(priv->dev, ret,
+				     "Failed to prepare the interrupt clocks\n");
+
+	return 0;
+}
+
+static void meson_video_clock_exit(struct meson_drm *priv)
+{
+	if (priv->clk_dac_enabled)
+		clk_disable(priv->clk_dac);
+
+	if (priv->clk_venc_enabled)
+		clk_disable(priv->clk_venc);
+
+	clk_bulk_unprepare(priv->num_intr_clks, priv->intr_clks);
+	clk_bulk_unprepare(VPU_VID_CLK_NUM, priv->vid_clks);
+}
+
+static void meson_fbdev_setup(struct meson_drm *priv)
+{
+	unsigned int preferred_bpp;
 
 	/*
-	 * Slave dc0 and dc5 connected to master port 1.
-	 * By default other slaves are connected to master port 0.
+	 * All SoC generations before GXBB don't have a way to configure the
+	 * alpha value for DRM_FORMAT_XRGB8888 and DRM_FORMAT_XBGR8888. These
+	 * formats have an X component instead of an alpha component. On
+	 * Meson8/8b/8m2 there is no way to configure the alpha value to use
+	 * instead of the X component. This results in the fact that the
+	 * formats with X component are only supported on GXBB and newer. Use
+	 * 24 bits per pixel and therefore DRM_FORMAT_RGB888 to get a
+	 * working framebuffer console.
 	 */
-	value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1) |
-		VPU_RDARB_SLAVE_TO_MASTER_PORT(5, 1);
-	writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C1));
-
-	/* Slave dc0 connected to master port 1 */
-	value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1);
-	writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C2));
-
-	/* Slave dc4 and dc7 connected to master port 1 */
-	value = VPU_RDARB_SLAVE_TO_MASTER_PORT(4, 1) |
-		VPU_RDARB_SLAVE_TO_MASTER_PORT(7, 1);
-	writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L2C1));
-
-	/* Slave dc1 connected to master port 1 */
-	value = VPU_RDARB_SLAVE_TO_MASTER_PORT(1, 1);
-	writel_relaxed(value, priv->io_base + _REG(VPU_WRARB_MODE_L2C1));
+	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2))
+		preferred_bpp = 24;
+	else
+		preferred_bpp = 32;
+
+	drm_fbdev_dma_setup(priv->drm, preferred_bpp);
 }
 
 struct meson_drm_soc_attr {
@@ -165,13 +283,29 @@ struct meson_drm_soc_attr {
 };
 
 static const struct meson_drm_soc_attr meson_drm_soc_attrs[] = {
-	/* S805X/S805Y HDMI PLL won't lock for HDMI PHY freq > 1,65GHz */
+	/* The maximum frequency of HDMI PHY on Meson8 and Meson8m2 is ~3GHz */
+	{
+		.limits = {
+			.max_hdmi_phy_freq = 2976000,
+		},
+		.attrs = (const struct soc_device_attribute []) {
+			{ .soc_id = "Meson8 (S802)", },
+			{ .soc_id = "Meson8m2 (S812)", },
+			{ /* sentinel */ },
+		}
+	},
+	/*
+	 * GXL S805X/S805Y HDMI PLL won't lock for HDMI PHY freq > 1,65GHz.
+	 * Meson8b (S805) only supports "1200p@60 max resolution" according to
+	 * the public datasheet.
+	 */
 	{
 		.limits = {
 			.max_hdmi_phy_freq = 1650000,
 		},
 		.attrs = (const struct soc_device_attribute []) {
 			{ .soc_id = "GXL (S805*)", },
+			{ .soc_id = "Meson8b (S805)", },
 			{ /* sentinel */ }
 		}
 	},
@@ -212,67 +346,123 @@ static int meson_drv_bind_master(struct device *dev, bool has_components)
 	priv->compat = match->compat;
 	priv->afbcd.ops = match->afbcd_ops;
 
+	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) {
+		priv->vid_pll_resets[VPU_RESET_VID_PLL_PRE].id = "vid_pll_pre";
+		priv->vid_pll_resets[VPU_RESET_VID_PLL_POST].id = "vid_pll_post";
+		priv->vid_pll_resets[VPU_RESET_VID_PLL_SOFT_PRE].id = "vid_pll_soft_pre";
+		priv->vid_pll_resets[VPU_RESET_VID_PLL_SOFT_POST].id = "vid_pll_soft_post";
+
+		ret = devm_reset_control_bulk_get_exclusive(dev,
+							    VPU_RESET_VID_PLL_NUM,
+							    priv->vid_pll_resets);
+		if (ret)
+			goto free_drm;
+
+		priv->intr_clks[0].id = "vpu_intr";
+		priv->intr_clks[1].id = "hdmi_intr_sync";
+		priv->intr_clks[2].id = "venci_int";
+		priv->num_intr_clks = 3;
+
+		ret = devm_clk_bulk_get(dev, priv->num_intr_clks,
+					priv->intr_clks);
+		if (ret)
+			goto free_drm;
+
+		priv->vid_clks[VPU_VID_CLK_TMDS].id = "tmds";
+		priv->vid_clks[VPU_VID_CLK_HDMI_TX_PIXEL].id = "hdmi_tx_pixel";
+		priv->vid_clks[VPU_VID_CLK_CTS_ENCP].id = "cts_encp";
+		priv->vid_clks[VPU_VID_CLK_CTS_ENCI].id = "cts_enci";
+		priv->vid_clks[VPU_VID_CLK_CTS_ENCT].id = "cts_enct";
+		priv->vid_clks[VPU_VID_CLK_CTS_ENCL].id = "cts_encl";
+		priv->vid_clks[VPU_VID_CLK_CTS_VDAC0].id = "cts_vdac0";
+
+		ret = devm_clk_bulk_get(dev, VPU_VID_CLK_NUM, priv->vid_clks);
+		if (ret)
+			goto free_drm;
+	} else {
+		priv->intr_clks[0].id = "vpu_intr";
+		priv->num_intr_clks = 1;
+
+		ret = devm_clk_bulk_get_optional(dev, priv->num_intr_clks,
+						 priv->intr_clks);
+		if (ret)
+			goto free_drm;
+	}
+
+	ret = meson_video_clock_init(priv);
+	if (ret)
+		goto free_drm;
+
 	regs = devm_platform_ioremap_resource_byname(pdev, "vpu");
 	if (IS_ERR(regs)) {
 		ret = PTR_ERR(regs);
-		goto free_drm;
+		goto video_clock_exit;
 	}
 
 	priv->io_base = regs;
 
+	/*
+	 * The HHI resource is optional because it contains the clocks and CVBS
+	 * encoder registers. These are managed by separate drivers though.
+	 */
 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hhi");
-	if (!res) {
-		ret = -EINVAL;
-		goto free_drm;
-	}
-	/* Simply ioremap since it may be a shared register zone */
-	regs = devm_ioremap(dev, res->start, resource_size(res));
-	if (!regs) {
-		ret = -EADDRNOTAVAIL;
-		goto free_drm;
-	}
+	if (res) {
+		/* Simply ioremap since it may be a shared register zone */
+		regs = devm_ioremap(dev, res->start, resource_size(res));
+		if (!regs) {
+			ret = -EADDRNOTAVAIL;
+			goto video_clock_exit;
+		}
 
-	priv->hhi = devm_regmap_init_mmio(dev, regs,
-					  &meson_regmap_config);
-	if (IS_ERR(priv->hhi)) {
-		dev_err(&pdev->dev, "Couldn't create the HHI regmap\n");
-		ret = PTR_ERR(priv->hhi);
-		goto free_drm;
+		priv->hhi = devm_regmap_init_mmio(dev, regs,
+						  &meson_regmap_config);
+		if (IS_ERR(priv->hhi)) {
+			dev_err(&pdev->dev,
+				"Couldn't create the HHI regmap\n");
+			ret = PTR_ERR(priv->hhi);
+			goto video_clock_exit;
+		}
 	}
 
 	priv->canvas = meson_canvas_get(dev);
 	if (IS_ERR(priv->canvas)) {
 		ret = PTR_ERR(priv->canvas);
-		goto free_drm;
+		goto video_clock_exit;
 	}
 
 	ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1);
 	if (ret)
-		goto free_drm;
+		goto video_clock_exit;
 	ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_0);
 	if (ret) {
 		meson_canvas_free(priv->canvas, priv->canvas_id_osd1);
-		goto free_drm;
+		goto video_clock_exit;
 	}
 	ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_1);
 	if (ret) {
 		meson_canvas_free(priv->canvas, priv->canvas_id_osd1);
 		meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0);
-		goto free_drm;
+		goto video_clock_exit;
 	}
 	ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_2);
 	if (ret) {
 		meson_canvas_free(priv->canvas, priv->canvas_id_osd1);
 		meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0);
 		meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1);
-		goto free_drm;
+		goto video_clock_exit;
 	}
 
+	ret = meson_cvbs_dac_phy_init(priv);
+	if (ret)
+		goto free_drm;
+
 	priv->vsync_irq = platform_get_irq(pdev, 0);
 
 	ret = drm_vblank_init(drm, 1);
 	if (ret)
-		goto free_drm;
+		goto exit_cvbs_dac_phy;
 
 	/* Assign limits per soc revision/package */
 	for (i = 0 ; i < ARRAY_SIZE(meson_drm_soc_attrs) ; ++i) {
@@ -288,11 +478,11 @@ static int meson_drv_bind_master(struct device *dev, bool has_components)
 	 */
 	ret = drm_aperture_remove_framebuffers(&meson_driver);
 	if (ret)
-		goto free_drm;
+		goto exit_cvbs_dac_phy;
 
 	ret = drmm_mode_config_init(drm);
 	if (ret)
-		goto free_drm;
+		goto exit_cvbs_dac_phy;
 	drm->mode_config.max_width = 3840;
 	drm->mode_config.max_height = 2160;
 	drm->mode_config.funcs = &meson_mode_config_funcs;
@@ -307,7 +497,7 @@ static int meson_drv_bind_master(struct device *dev, bool has_components)
 	if (priv->afbcd.ops) {
 		ret = priv->afbcd.ops->init(priv);
 		if (ret)
-			goto free_drm;
+			goto exit_cvbs_dac_phy;
 	}
 
 	/* Encoder Initialization */
@@ -362,7 +552,7 @@ static int meson_drv_bind_master(struct device *dev, bool has_components)
 	if (ret)
 		goto uninstall_irq;
 
-	drm_fbdev_dma_setup(drm, 32);
+	meson_fbdev_setup(priv);
 
 	return 0;
 
@@ -371,6 +561,10 @@ static int meson_drv_bind_master(struct device *dev, bool has_components)
 exit_afbcd:
 	if (priv->afbcd.ops)
 		priv->afbcd.ops->exit(priv);
+exit_cvbs_dac_phy:
+	meson_cvbs_dac_phy_exit(priv);
+video_clock_exit:
+	meson_video_clock_exit(priv);
 free_drm:
 	drm_dev_put(drm);
 
@@ -415,6 +609,10 @@ static void meson_drv_unbind(struct device *dev)
 
 	if (priv->afbcd.ops)
 		priv->afbcd.ops->exit(priv);
+
+	meson_cvbs_dac_phy_exit(priv);
+
+	meson_video_clock_exit(priv);
 }
 
 static const struct component_master_ops meson_drv_master_ops = {
@@ -429,6 +627,8 @@ static int __maybe_unused meson_drv_pm_suspend(struct device *dev)
 	if (!priv)
 		return 0;
 
+	// TODO: video clock suspend
+
 	return drm_mode_config_helper_suspend(priv->drm);
 }
 
@@ -439,6 +639,7 @@ static int __maybe_unused meson_drv_pm_resume(struct device *dev)
 	if (!priv)
 		return 0;
 
+	meson_video_clock_init(priv);
 	meson_vpu_init(priv);
 	meson_venc_init(priv);
 	meson_vpp_init(priv);
@@ -521,6 +722,18 @@ static void meson_drv_remove(struct platform_device *pdev)
 	component_master_del(&pdev->dev, &meson_drv_master_ops);
 }
 
+static struct meson_drm_match_data meson_drm_m8_data = {
+	.compat = VPU_COMPATIBLE_M8,
+};
+
+static struct meson_drm_match_data meson_drm_m8b_data = {
+	.compat = VPU_COMPATIBLE_M8B,
+};
+
+static struct meson_drm_match_data meson_drm_m8m2_data = {
+	.compat = VPU_COMPATIBLE_M8M2,
+};
+
 static struct meson_drm_match_data meson_drm_gxbb_data = {
 	.compat = VPU_COMPATIBLE_GXBB,
 };
@@ -540,6 +753,12 @@ static struct meson_drm_match_data meson_drm_g12a_data = {
 };
 
 static const struct of_device_id dt_match[] = {
+	{ .compatible = "amlogic,meson8-vpu",
+	  .data       = (void *)&meson_drm_m8_data },
+	{ .compatible = "amlogic,meson8b-vpu",
+	  .data       = (void *)&meson_drm_m8b_data },
+	{ .compatible = "amlogic,meson8m2-vpu",
+	  .data       = (void *)&meson_drm_m8m2_data },
 	{ .compatible = "amlogic,meson-gxbb-vpu",
 	  .data       = (void *)&meson_drm_gxbb_data },
 	{ .compatible = "amlogic,meson-gxl-vpu",
diff --git a/drivers/gpu/drm/meson/meson_drv.h b/drivers/gpu/drm/meson/meson_drv.h
index 3f9345c1..59f80fcc 100644
--- a/drivers/gpu/drm/meson/meson_drv.h
+++ b/drivers/gpu/drm/meson/meson_drv.h
@@ -7,21 +7,28 @@
 #ifndef __MESON_DRV_H
 #define __MESON_DRV_H
 
+#include <linux/clk.h>
 #include <linux/device.h>
 #include <linux/of.h>
 #include <linux/regmap.h>
+#include <linux/reset.h>
 
 struct drm_crtc;
 struct drm_device;
 struct drm_plane;
 struct meson_drm;
 struct meson_afbcd_ops;
+struct phy;
+struct platform_device;
 
 enum vpu_compatible {
-	VPU_COMPATIBLE_GXBB = 0,
-	VPU_COMPATIBLE_GXL  = 1,
-	VPU_COMPATIBLE_GXM  = 2,
-	VPU_COMPATIBLE_G12A = 3,
+	VPU_COMPATIBLE_M8 = 0,
+	VPU_COMPATIBLE_M8B = 1,
+	VPU_COMPATIBLE_M8M2 = 2,
+	VPU_COMPATIBLE_GXBB = 3,
+	VPU_COMPATIBLE_GXL  = 4,
+	VPU_COMPATIBLE_GXM  = 5,
+	VPU_COMPATIBLE_G12A = 6,
 };
 
 enum {
@@ -40,6 +47,25 @@ struct meson_drm_soc_limits {
 	unsigned int max_hdmi_phy_freq;
 };
 
+enum vpu_bulk_clk_id {
+	VPU_VID_CLK_TMDS = 0,
+	VPU_VID_CLK_HDMI_TX_PIXEL,
+	VPU_VID_CLK_CTS_ENCP,
+	VPU_VID_CLK_CTS_ENCI,
+	VPU_VID_CLK_CTS_ENCT,
+	VPU_VID_CLK_CTS_ENCL,
+	VPU_VID_CLK_CTS_VDAC0,
+	VPU_VID_CLK_NUM
+};
+
+enum vpu_bulk_vid_pll_reset_id {
+	VPU_RESET_VID_PLL_PRE = 0,
+	VPU_RESET_VID_PLL_POST,
+	VPU_RESET_VID_PLL_SOFT_PRE,
+	VPU_RESET_VID_PLL_SOFT_POST,
+	VPU_RESET_VID_PLL_NUM
+};
+
 struct meson_drm {
 	struct device *dev;
 	enum vpu_compatible compat;
@@ -61,6 +87,21 @@ struct meson_drm {
 
 	const struct meson_drm_soc_limits *limits;
 
+	struct phy *cvbs_dac;
+	bool cvbs_dac_enabled;
+	struct platform_device *cvbs_dac_pdev;
+
+	struct clk_bulk_data intr_clks[3];
+	unsigned int num_intr_clks;
+	bool intr_clks_enabled;
+	struct clk_bulk_data vid_clks[VPU_VID_CLK_NUM];
+	bool vid_clk_rate_exclusive[VPU_VID_CLK_NUM];
+	struct clk *clk_venc;
+	bool clk_venc_enabled;
+	struct clk *clk_dac;
+	bool clk_dac_enabled;
+	struct reset_control_bulk_data vid_pll_resets[VPU_RESET_VID_PLL_NUM];
+
 	/* Components Data */
 	struct {
 		bool osd1_enabled;
diff --git a/drivers/gpu/drm/meson/meson_encoder_cvbs.c b/drivers/gpu/drm/meson/meson_encoder_cvbs.c
index 3f73b211..833f701f 100644
--- a/drivers/gpu/drm/meson/meson_encoder_cvbs.c
+++ b/drivers/gpu/drm/meson/meson_encoder_cvbs.c
@@ -11,6 +11,7 @@
 
 #include <linux/export.h>
 #include <linux/of_graph.h>
+#include <linux/phy/phy.h>
 
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
@@ -24,12 +25,6 @@
 #include "meson_vclk.h"
 #include "meson_encoder_cvbs.h"
 
-/* HHI VDAC Registers */
-#define HHI_VDAC_CNTL0		0x2F4 /* 0xbd offset in data sheet */
-#define HHI_VDAC_CNTL0_G12A	0x2EC /* 0xbd offset in data sheet */
-#define HHI_VDAC_CNTL1		0x2F8 /* 0xbe offset in data sheet */
-#define HHI_VDAC_CNTL1_G12A	0x2F0 /* 0xbe offset in data sheet */
-
 struct meson_encoder_cvbs {
 	struct drm_encoder	encoder;
 	struct drm_bridge	bridge;
@@ -87,11 +82,28 @@ static int meson_encoder_cvbs_attach(struct drm_bridge *bridge,
 {
 	struct meson_encoder_cvbs *meson_encoder_cvbs =
 					bridge_to_meson_encoder_cvbs(bridge);
+	int ret;
+
+	ret = phy_init(meson_encoder_cvbs->priv->cvbs_dac);
+	if (ret)
+		return ret;
 
 	return drm_bridge_attach(bridge->encoder, meson_encoder_cvbs->next_bridge,
 				 &meson_encoder_cvbs->bridge, flags);
 }
 
+static void meson_encoder_cvbs_detach(struct drm_bridge *bridge)
+{
+	struct meson_encoder_cvbs *meson_encoder_cvbs =
+					bridge_to_meson_encoder_cvbs(bridge);
+	int ret;
+
+	ret = phy_exit(meson_encoder_cvbs->priv->cvbs_dac);
+	if (ret)
+		dev_err(meson_encoder_cvbs->priv->dev,
+			"Failed to exit the CVBS DAC\n");
+}
+
 static int meson_encoder_cvbs_get_modes(struct drm_bridge *bridge,
 					struct drm_connector *connector)
 {
@@ -148,6 +160,7 @@ static void meson_encoder_cvbs_atomic_enable(struct drm_bridge *bridge,
 	struct drm_connector_state *conn_state;
 	struct drm_crtc_state *crtc_state;
 	struct drm_connector *connector;
+	int ret;
 
 	connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
 	if (WARN_ON(!connector))
@@ -177,16 +190,13 @@ static void meson_encoder_cvbs_atomic_enable(struct drm_bridge *bridge,
 	writel_bits_relaxed(VENC_VDAC_SEL_ATV_DMD, 0,
 			    priv->io_base + _REG(VENC_VDAC_DACSEL0));
 
-	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) {
-		regmap_write(priv->hhi, HHI_VDAC_CNTL0, 1);
-		regmap_write(priv->hhi, HHI_VDAC_CNTL1, 0);
-	} else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
-		 meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL)) {
-		regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0xf0001);
-		regmap_write(priv->hhi, HHI_VDAC_CNTL1, 0);
-	} else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) {
-		regmap_write(priv->hhi, HHI_VDAC_CNTL0_G12A, 0x906001);
-		regmap_write(priv->hhi, HHI_VDAC_CNTL1_G12A, 0);
+	if (!priv->cvbs_dac_enabled) {
+		ret = phy_power_on(priv->cvbs_dac);
+		if (ret)
+			dev_err(priv->dev,
+				"Failed to power on the CVBS DAC\n");
+		else
+			priv->cvbs_dac_enabled = true;
 	}
 }
 
@@ -196,19 +206,22 @@ static void meson_encoder_cvbs_atomic_disable(struct drm_bridge *bridge,
 	struct meson_encoder_cvbs *meson_encoder_cvbs =
 					bridge_to_meson_encoder_cvbs(bridge);
 	struct meson_drm *priv = meson_encoder_cvbs->priv;
+	int ret;
 
-	/* Disable CVBS VDAC */
-	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) {
-		regmap_write(priv->hhi, HHI_VDAC_CNTL0_G12A, 0);
-		regmap_write(priv->hhi, HHI_VDAC_CNTL1_G12A, 0);
-	} else {
-		regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0);
-		regmap_write(priv->hhi, HHI_VDAC_CNTL1, 8);
-	}
+	if (!priv->cvbs_dac_enabled)
+		return;
+
+	ret = phy_power_off(priv->cvbs_dac);
+	if (ret)
+		dev_err(priv->dev,
+			"Failed to power off the CVBS DAC\n");
+	else
+		priv->cvbs_dac_enabled = false;
 }
 
 static const struct drm_bridge_funcs meson_encoder_cvbs_bridge_funcs = {
 	.attach = meson_encoder_cvbs_attach,
+	.detach = meson_encoder_cvbs_detach,
 	.mode_valid = meson_encoder_cvbs_mode_valid,
 	.get_modes = meson_encoder_cvbs_get_modes,
 	.atomic_enable = meson_encoder_cvbs_atomic_enable,
diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c
index 25ea7655..a3e6f86e 100644
--- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c
+++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c
@@ -190,13 +190,13 @@ static void meson_encoder_hdmi_atomic_enable(struct drm_bridge *bridge,
 {
 	struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge);
 	struct drm_atomic_state *state = bridge_state->base.state;
-	unsigned int ycrcb_map = VPU_HDMI_OUTPUT_CBYCR;
 	struct meson_drm *priv = encoder_hdmi->priv;
 	struct drm_connector_state *conn_state;
 	const struct drm_display_mode *mode;
 	struct drm_crtc_state *crtc_state;
 	struct drm_connector *connector;
 	bool yuv420_mode = false;
+	unsigned int ycrcb_map;
 	int vic;
 
 	connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
@@ -217,7 +217,14 @@ static void meson_encoder_hdmi_atomic_enable(struct drm_bridge *bridge,
 
 	dev_dbg(priv->dev, "\"%s\" vic %d\n", mode->name, vic);
 
-	if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) {
+	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) {
+		if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_RGB888_1X24)
+			ycrcb_map = VPU_HDMI_OUTPUT_YCBCR;
+		else
+			ycrcb_map = VPU_HDMI_OUTPUT_CRYCB;
+	} else if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) {
 		ycrcb_map = VPU_HDMI_OUTPUT_CRYCB;
 		yuv420_mode = true;
 	} else if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYVY8_1X16)
@@ -229,17 +236,22 @@ static void meson_encoder_hdmi_atomic_enable(struct drm_bridge *bridge,
 	/* VCLK Set clock */
 	meson_encoder_hdmi_set_vclk(encoder_hdmi, mode);
 
-	if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24)
-		/* Setup YUV420 to HDMI-TX, no 10bit diphering */
-		writel_relaxed(2 | (2 << 2),
-			       priv->io_base + _REG(VPU_HDMI_FMT_CTRL));
-	else if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYVY8_1X16)
-		/* Setup YUV422 to HDMI-TX, no 10bit diphering */
-		writel_relaxed(1 | (2 << 2),
-				priv->io_base + _REG(VPU_HDMI_FMT_CTRL));
-	else
-		/* Setup YUV444 to HDMI-TX, no 10bit diphering */
-		writel_relaxed(0, priv->io_base + _REG(VPU_HDMI_FMT_CTRL));
+	if (!meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) &&
+	    !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) &&
+	    !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) {
+		if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24)
+			/* Setup YUV420 to HDMI-TX, no 10bit diphering */
+			writel_relaxed(2 | (2 << 2),
+				       priv->io_base + _REG(VPU_HDMI_FMT_CTRL));
+		else if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYVY8_1X16)
+			/* Setup YUV422 to HDMI-TX, no 10bit diphering */
+			writel_relaxed(1 | (2 << 2),
+					priv->io_base + _REG(VPU_HDMI_FMT_CTRL));
+		else
+			/* Setup YUV444 to HDMI-TX, no 10bit diphering */
+			writel_relaxed(0,
+				       priv->io_base + _REG(VPU_HDMI_FMT_CTRL));
+	}
 
 	dev_dbg(priv->dev, "%s\n", priv->venc.hdmi_use_enci ? "VENCI" : "VENCP");
 
@@ -262,7 +274,11 @@ static void meson_encoder_hdmi_atomic_disable(struct drm_bridge *bridge,
 	writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN));
 }
 
-static const u32 meson_encoder_hdmi_out_bus_fmts[] = {
+static const u32 meson8_encoder_hdmi_out_bus_fmts[] = {
+	MEDIA_BUS_FMT_YUV8_1X24,
+};
+
+static const u32 meson_gx_encoder_hdmi_out_bus_fmts[] = {
 	MEDIA_BUS_FMT_YUV8_1X24,
 	MEDIA_BUS_FMT_UYVY8_1X16,
 	MEDIA_BUS_FMT_UYYVYY8_0_5X24,
@@ -276,13 +292,27 @@ meson_encoder_hdmi_get_inp_bus_fmts(struct drm_bridge *bridge,
 					u32 output_fmt,
 					unsigned int *num_input_fmts)
 {
+	struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge);
+	struct meson_drm *priv = encoder_hdmi->priv;
+	unsigned int num_out_bus_fmts;
+	const u32 *out_bus_fmts;
 	u32 *input_fmts = NULL;
 	int i;
 
+	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) {
+		num_out_bus_fmts = ARRAY_SIZE(meson8_encoder_hdmi_out_bus_fmts);
+		out_bus_fmts = meson8_encoder_hdmi_out_bus_fmts;
+	} else {
+		num_out_bus_fmts = ARRAY_SIZE(meson_gx_encoder_hdmi_out_bus_fmts);
+		out_bus_fmts = meson_gx_encoder_hdmi_out_bus_fmts;
+	}
+
 	*num_input_fmts = 0;
 
-	for (i = 0 ; i < ARRAY_SIZE(meson_encoder_hdmi_out_bus_fmts) ; ++i) {
-		if (output_fmt == meson_encoder_hdmi_out_bus_fmts[i]) {
+	for (i = 0 ; i < num_out_bus_fmts ; ++i) {
+		if (output_fmt == out_bus_fmts[i]) {
 			*num_input_fmts = 1;
 			input_fmts = kcalloc(*num_input_fmts,
 					     sizeof(*input_fmts),
@@ -436,8 +466,11 @@ int meson_encoder_hdmi_init(struct meson_drm *priv)
 
 	drm_connector_attach_max_bpc_property(meson_encoder_hdmi->connector, 8, 8);
 
-	/* Handle this here until handled by drm_bridge_connector_init() */
-	meson_encoder_hdmi->connector->ycbcr_420_allowed = true;
+	if (!meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) &&
+	    !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) &&
+	    !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2))
+		/* Handle this here until handled by drm_bridge_connector_init() */
+		meson_encoder_hdmi->connector->ycbcr_420_allowed = true;
 
 	pdev = of_find_device_by_node(remote);
 	of_node_put(remote);
diff --git a/drivers/gpu/drm/meson/meson_plane.c b/drivers/gpu/drm/meson/meson_plane.c
index 815dfe30..27e39577 100644
--- a/drivers/gpu/drm/meson/meson_plane.c
+++ b/drivers/gpu/drm/meson/meson_plane.c
@@ -200,8 +200,11 @@ static void meson_plane_atomic_update(struct drm_plane *plane,
 			priv->viu.osd1_ctrl_stat2 &= ~OSD_DPATH_MALI_AFBCD;
 	}
 
-	/* On GXBB, Use the old non-HDR RGB2YUV converter */
-	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB))
+	/* On GXBB and earlier, Use the old non-HDR RGB2YUV converter */
+	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB))
 		priv->viu.osd1_blk0_cfg[0] |= OSD_OUTPUT_COLOR_RGB;
 
 	if (priv->viu.osd1_afbcd &&
@@ -471,7 +474,20 @@ static const struct drm_plane_funcs meson_plane_funcs = {
 	.format_mod_supported   = meson_plane_format_mod_supported,
 };
 
-static const uint32_t supported_drm_formats[] = {
+/*
+ * X components (for example in DRM_FORMAT_XRGB8888 and DRM_FORMAT_XBGR8888)
+ * are not supported because these older SoC's are lacking the OSD_REPLACE_EN
+ * bit to replace the X alpha component with a static value, leaving the alpha
+ * component in an undefined state.
+ */
+static const uint32_t supported_drm_formats_m8[] = {
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_ABGR8888,
+	DRM_FORMAT_RGB888,
+	DRM_FORMAT_RGB565,
+};
+
+static const uint32_t supported_drm_formats_gx[] = {
 	DRM_FORMAT_ARGB8888,
 	DRM_FORMAT_ABGR8888,
 	DRM_FORMAT_XRGB8888,
@@ -533,6 +549,8 @@ int meson_plane_create(struct meson_drm *priv)
 {
 	struct meson_plane *meson_plane;
 	struct drm_plane *plane;
+	unsigned int num_drm_formats;
+	const uint32_t *drm_formats;
 	const uint64_t *format_modifiers = format_modifiers_default;
 
 	meson_plane = devm_kzalloc(priv->drm->dev, sizeof(*meson_plane),
@@ -548,10 +566,19 @@ int meson_plane_create(struct meson_drm *priv)
 	else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A))
 		format_modifiers = format_modifiers_afbc_g12a;
 
+	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) {
+		drm_formats = supported_drm_formats_m8;
+		num_drm_formats = ARRAY_SIZE(supported_drm_formats_m8);
+	} else {
+		drm_formats = supported_drm_formats_gx;
+		num_drm_formats = ARRAY_SIZE(supported_drm_formats_gx);
+	}
+
 	drm_universal_plane_init(priv->drm, plane, 0xFF,
 				 &meson_plane_funcs,
-				 supported_drm_formats,
-				 ARRAY_SIZE(supported_drm_formats),
+				 drm_formats, num_drm_formats,
 				 format_modifiers,
 				 DRM_PLANE_TYPE_PRIMARY, "meson_primary_plane");
 
diff --git a/drivers/gpu/drm/meson/meson_transwitch_hdmi.c b/drivers/gpu/drm/meson/meson_transwitch_hdmi.c
new file mode 100644
index 00000000..e88bdba7
--- /dev/null
+++ b/drivers/gpu/drm/meson/meson_transwitch_hdmi.c
@@ -0,0 +1,1579 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * All registers and magic values are taken from Amlogic's GPL kernel sources:
+ *   Copyright (C) 2010 Amlogic, Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <drm/display/drm_hdmi_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_device.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_print.h>
+
+#include <sound/hdmi-codec.h>
+
+#include <uapi/linux/media-bus-format.h>
+
+#include "meson_transwitch_hdmi.h"
+
+#define HDMI_ADDR_PORT					0x0
+#define HDMI_DATA_PORT					0x4
+#define HDMI_CTRL_PORT					0x8
+	#define HDMI_CTRL_PORT_APB3_ERR_EN		BIT(15)
+
+struct meson_txc_hdmi {
+	struct device			*dev;
+
+	struct regmap			*regmap;
+
+	struct clk			*pclk;
+	struct clk			*sys_clk;
+
+	struct phy			*phy;
+	bool				phy_is_on;
+
+	struct mutex			codec_mutex;
+	enum drm_connector_status	last_connector_status;
+	hdmi_codec_plugged_cb		codec_plugged_cb;
+	struct device			*codec_dev;
+
+	struct platform_device		*hdmi_codec_pdev;
+
+	struct drm_connector		*current_connector;
+
+	struct drm_bridge		bridge;
+	struct drm_bridge		*next_bridge;
+
+	bool				sink_is_hdmi;
+};
+
+#define bridge_to_meson_txc_hdmi(x) container_of(x, struct meson_txc_hdmi, bridge)
+
+static const struct regmap_range meson_txc_hdmi_regmap_ranges[] = {
+	regmap_reg_range(0x0000, 0x07ff),
+	regmap_reg_range(0x8000, 0x800c),
+};
+
+static const struct regmap_access_table meson_txc_hdmi_regmap_access = {
+	.yes_ranges = meson_txc_hdmi_regmap_ranges,
+	.n_yes_ranges = ARRAY_SIZE(meson_txc_hdmi_regmap_ranges),
+};
+
+static int meson_txc_hdmi_reg_read(void *context, unsigned int addr,
+				   unsigned int *data)
+{
+	void __iomem *base = context;
+
+	writel(addr, base + HDMI_ADDR_PORT);
+	writel(addr, base + HDMI_ADDR_PORT);
+
+	*data = readl(base + HDMI_DATA_PORT);
+
+	return 0;
+}
+
+static int meson_txc_hdmi_reg_write(void *context, unsigned int addr,
+				    unsigned int data)
+{
+	void __iomem *base = context;
+
+	writel(addr, base + HDMI_ADDR_PORT);
+	writel(addr, base + HDMI_ADDR_PORT);
+
+	writel(data, base + HDMI_DATA_PORT);
+
+	return 0;
+}
+
+static const struct regmap_config meson_txc_hdmi_regmap_config = {
+	.reg_bits = 16,
+	.val_bits = 16,
+	.reg_stride = 1,
+	.reg_read = meson_txc_hdmi_reg_read,
+	.reg_write = meson_txc_hdmi_reg_write,
+	.rd_table = &meson_txc_hdmi_regmap_access,
+	.wr_table = &meson_txc_hdmi_regmap_access,
+	.max_register = HDMI_OTHER_RX_PACKET_INTR_CLR,
+	.fast_io = true,
+};
+
+static void meson_txc_hdmi_write_infoframe(struct regmap *regmap,
+					   unsigned int tx_pkt_reg, u8 *buf,
+					   unsigned int len, bool enable)
+{
+	unsigned int i;
+
+	/* Write the data bytes by starting at register offset 1 */
+	for (i = HDMI_INFOFRAME_HEADER_SIZE; i < len; i++)
+		regmap_write(regmap,
+			     tx_pkt_reg + i - HDMI_INFOFRAME_HEADER_SIZE + 1,
+			     buf[i]);
+
+	/* Zero all remaining data bytes */
+	for (; i < 0x1c; i++)
+		regmap_write(regmap, tx_pkt_reg + i, 0x00);
+
+	/* Write the header (which we skipped above) */
+	regmap_write(regmap, tx_pkt_reg + 0x00, buf[3]);
+	regmap_write(regmap, tx_pkt_reg + 0x1c, buf[0]);
+	regmap_write(regmap, tx_pkt_reg + 0x1d, buf[1]);
+	regmap_write(regmap, tx_pkt_reg + 0x1e, buf[2]);
+
+	regmap_write(regmap, tx_pkt_reg + 0x1f, enable ? 0xff : 0x00);
+}
+
+static void meson_txc_hdmi_disable_infoframe(struct meson_txc_hdmi *priv,
+					     unsigned int tx_pkt_reg)
+{
+	u8 buf[HDMI_INFOFRAME_HEADER_SIZE] = { 0 };
+
+	meson_txc_hdmi_write_infoframe(priv->regmap, tx_pkt_reg, buf,
+				       HDMI_INFOFRAME_HEADER_SIZE, false);
+}
+
+static void meson_txc_hdmi_sys5_reset_assert(struct meson_txc_hdmi *priv)
+{
+	/* A comment in the vendor driver says: bit5,6 is converted */
+	regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2,
+		     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH3_RST_IN |
+		     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH0_RST_IN);
+	usleep_range(10, 20);
+
+	regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2,
+		     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN |
+		     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN);
+	usleep_range(10, 20);
+
+	regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1,
+		     TX_SYS5_TX_SOFT_RESET_1_TX_PIXEL_RSTN |
+		     TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN |
+		     TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_MASTER_RSTN |
+		     TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_RESAMPLE_RSTN |
+		     TX_SYS5_TX_SOFT_RESET_1_TX_I2S_RESET_RSTN |
+		     TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH2 |
+		     TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH1 |
+		     TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH0);
+	usleep_range(10, 20);
+}
+
+static void meson_txc_hdmi_sys5_reset_deassert(struct meson_txc_hdmi *priv)
+{
+	/* Release the resets except tmds_clk */
+	regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1,
+		     TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN);
+	usleep_range(10, 20);
+
+	/* Release the tmds_clk reset as well */
+	regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, 0x0);
+	usleep_range(10, 20);
+
+	regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2,
+		     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN |
+		     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN |
+		     TX_SYS5_TX_SOFT_RESET_2_HDMI_SR_RST);
+	usleep_range(10, 20);
+
+	regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2,
+		     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN |
+		     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN);
+	usleep_range(10, 20);
+}
+
+static void meson_txc_hdmi_config_hdcp_registers(struct meson_txc_hdmi *priv)
+{
+	regmap_write(priv->regmap, TX_HDCP_CONFIG0,
+		     FIELD_PREP(TX_HDCP_CONFIG0_ROM_ENCRYPT_OFF, 0x3));
+	regmap_write(priv->regmap, TX_HDCP_MEM_CONFIG, 0x0);
+	regmap_write(priv->regmap, TX_HDCP_ENCRYPT_BYTE, 0x0);
+
+	regmap_write(priv->regmap, TX_HDCP_MODE, TX_HDCP_MODE_CLEAR_AVMUTE);
+
+	regmap_write(priv->regmap, TX_HDCP_MODE, TX_HDCP_MODE_ESS_CONFIG);
+}
+
+static u8 meson_txc_hdmi_bus_fmt_to_color_depth(unsigned int bus_format)
+{
+	switch (bus_format) {
+	case MEDIA_BUS_FMT_RGB888_1X24:
+	case MEDIA_BUS_FMT_YUV8_1X24:
+	case MEDIA_BUS_FMT_UYVY8_1X16:
+		/* 8 bit */
+		return 0x0;
+
+	case MEDIA_BUS_FMT_RGB101010_1X30:
+	case MEDIA_BUS_FMT_YUV10_1X30:
+	case MEDIA_BUS_FMT_UYVY10_1X20:
+		/* 10 bit */
+		return 0x1;
+
+	case MEDIA_BUS_FMT_RGB121212_1X36:
+	case MEDIA_BUS_FMT_YUV12_1X36:
+	case MEDIA_BUS_FMT_UYVY12_1X24:
+		/* 12 bit */
+		return 0x2;
+
+	case MEDIA_BUS_FMT_RGB161616_1X48:
+	case MEDIA_BUS_FMT_YUV16_1X48:
+		/* 16 bit */
+		return 0x3;
+
+	default:
+		/* unknown, default to 8 bit */
+		return 0x0;
+	}
+}
+
+static u8 meson_txc_hdmi_bus_fmt_to_color_format(unsigned int bus_format)
+{
+	switch (bus_format) {
+	case MEDIA_BUS_FMT_YUV8_1X24:
+	case MEDIA_BUS_FMT_YUV10_1X30:
+	case MEDIA_BUS_FMT_YUV12_1X36:
+	case MEDIA_BUS_FMT_YUV16_1X48:
+		/* Documented as YCbCr444 */
+		return 0x1;
+
+	case MEDIA_BUS_FMT_UYVY8_1X16:
+	case MEDIA_BUS_FMT_UYVY10_1X20:
+	case MEDIA_BUS_FMT_UYVY12_1X24:
+		/* Documented as YCbCr422 */
+		return 0x3;
+
+	case MEDIA_BUS_FMT_RGB888_1X24:
+	case MEDIA_BUS_FMT_RGB101010_1X30:
+	case MEDIA_BUS_FMT_RGB121212_1X36:
+	case MEDIA_BUS_FMT_RGB161616_1X48:
+	default:
+		/* Documented as RGB444 */
+		return 0x0;
+	}
+}
+
+static void meson_txc_hdmi_config_color_space(struct meson_txc_hdmi *priv,
+					      unsigned int input_bus_format,
+					      unsigned int output_bus_format,
+					      enum hdmi_quantization_range quant_range,
+					      enum hdmi_colorimetry colorimetry)
+{
+	unsigned int regval;
+
+	regmap_write(priv->regmap, TX_VIDEO_DTV_MODE,
+		     FIELD_PREP(TX_VIDEO_DTV_MODE_COLOR_DEPTH,
+				meson_txc_hdmi_bus_fmt_to_color_depth(output_bus_format)));
+
+	regmap_write(priv->regmap, TX_VIDEO_DTV_OPTION_L,
+		     FIELD_PREP(TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_FORMAT,
+				meson_txc_hdmi_bus_fmt_to_color_format(output_bus_format)) |
+		     FIELD_PREP(TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_FORMAT,
+				meson_txc_hdmi_bus_fmt_to_color_format(input_bus_format)) |
+		     FIELD_PREP(TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_DEPTH,
+				meson_txc_hdmi_bus_fmt_to_color_depth(output_bus_format)) |
+		     FIELD_PREP(TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_DEPTH,
+				meson_txc_hdmi_bus_fmt_to_color_depth(input_bus_format)));
+
+	if (quant_range == HDMI_QUANTIZATION_RANGE_LIMITED)
+		regval = FIELD_PREP(TX_VIDEO_DTV_OPTION_H_OUTPUT_COLOR_RANGE,
+				    TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_16_235) |
+			 FIELD_PREP(TX_VIDEO_DTV_OPTION_H_INPUT_COLOR_RANGE,
+				    TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_16_235);
+	else
+		regval = FIELD_PREP(TX_VIDEO_DTV_OPTION_H_OUTPUT_COLOR_RANGE,
+				    TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_0_255) |
+			 FIELD_PREP(TX_VIDEO_DTV_OPTION_H_INPUT_COLOR_RANGE,
+				    TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_0_255);
+
+	regmap_write(priv->regmap, TX_VIDEO_DTV_OPTION_H, regval);
+
+	if (colorimetry == HDMI_COLORIMETRY_ITU_601) {
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B0, 0x2f);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B1, 0x1d);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R0, 0x8b);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R1, 0x4c);
+
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB0, 0x18);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB1, 0x58);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR0, 0xd0);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR1, 0xb6);
+	} else {
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B0, 0x7b);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B1, 0x12);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R0, 0x6c);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R1, 0x36);
+
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB0, 0xf2);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB1, 0x2f);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR0, 0xd4);
+		regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR1, 0x77);
+	}
+}
+
+static void meson_txc_hdmi_config_serializer_clock(struct meson_txc_hdmi *priv,
+						   enum hdmi_colorimetry colorimetry)
+{
+	/* Serializer Internal clock setting */
+	if (colorimetry == HDMI_COLORIMETRY_ITU_601)
+		regmap_write(priv->regmap, TX_SYS1_PLL, 0x24);
+	else
+		regmap_write(priv->regmap, TX_SYS1_PLL, 0x22);
+
+#if 0
+	// TODO: not ported yet
+	if ((param->VIC==HDMI_1080p60)&&(param->color_depth==COLOR_30BIT)&&(hdmi_rd_reg(0x018)==0x22)) {
+		regmap_write(priv->regmap, TX_SYS1_PLL, 0x12);
+	}
+#endif
+}
+
+static void meson_txc_hdmi_reconfig_packet_setting(struct meson_txc_hdmi *priv,
+						   u8 cea_mode)
+{
+	u8 alloc_active2, alloc_eof1, alloc_sof1, alloc_sof2;
+
+	regmap_write(priv->regmap, TX_PACKET_CONTROL_1,
+		     FIELD_PREP(TX_PACKET_CONTROL_1_PACKET_START_LATENCY, 58));
+	regmap_write(priv->regmap, TX_PACKET_CONTROL_2,
+		     TX_PACKET_CONTROL_2_HORIZONTAL_GC_PACKET_TRANSPORT_EN);
+
+	switch (cea_mode) {
+	case 31:
+		/* 1920x1080p50 */
+		alloc_active2 = 0x12;
+		alloc_eof1 = 0x10;
+		alloc_sof1 = 0xb6;
+		alloc_sof2 = 0x11;
+		break;
+	case 93:
+		/* 3840x2160p24 */
+		alloc_active2 = 0x12;
+		alloc_eof1 = 0x47;
+		alloc_sof1 = 0xf8;
+		alloc_sof2 = 0x52;
+		break;
+	case 94:
+		/* 3840x2160p25 */
+		alloc_active2 = 0x12;
+		alloc_eof1 = 0x44;
+		alloc_sof1 = 0xda;
+		alloc_sof2 = 0x52;
+		break;
+	case 95:
+		/* 3840x2160p30 */
+		alloc_active2 = 0x0f;
+		alloc_eof1 = 0x3a;
+		alloc_sof1 = 0x60;
+		alloc_sof2 = 0x52;
+		break;
+	case 98:
+		/* 4096x2160p24 */
+		alloc_active2 = 0x12;
+		alloc_eof1 = 0x47;
+		alloc_sof1 = 0xf8;
+		alloc_sof2 = 0x52;
+		break;
+	default:
+		/* Disable the special packet settings only */
+		regmap_write(priv->regmap, TX_PACKET_ALLOC_ACTIVE_1, 0x00);
+		return;
+	}
+
+	/*
+	 * The vendor driver says: manually configure these register to get
+	 * stable video timings.
+	 */
+	regmap_write(priv->regmap, TX_PACKET_ALLOC_ACTIVE_1, 0x01);
+	regmap_write(priv->regmap, TX_PACKET_ALLOC_ACTIVE_2, alloc_active2);
+	regmap_write(priv->regmap, TX_PACKET_ALLOC_EOF_1, alloc_eof1);
+	regmap_write(priv->regmap, TX_PACKET_ALLOC_EOF_2, 0x12);
+	regmap_write(priv->regmap, TX_CORE_ALLOC_VSYNC_0, 0x01);
+	regmap_write(priv->regmap, TX_CORE_ALLOC_VSYNC_1, 0x00);
+	regmap_write(priv->regmap, TX_CORE_ALLOC_VSYNC_2, 0x0a);
+	regmap_write(priv->regmap, TX_PACKET_ALLOC_SOF_1, alloc_sof1);
+	regmap_write(priv->regmap, TX_PACKET_ALLOC_SOF_2, alloc_sof2);
+	regmap_update_bits(priv->regmap, TX_PACKET_CONTROL_1,
+			   TX_PACKET_CONTROL_1_FORCE_PACKET_TIMING,
+			   TX_PACKET_CONTROL_1_FORCE_PACKET_TIMING);
+}
+
+static void meson_txc_hdmi_set_avi_infoframe(struct meson_txc_hdmi *priv,
+					     struct drm_connector *conn,
+					     const struct drm_display_mode *mode,
+					     const struct drm_connector_state *conn_state,
+					     unsigned int output_bus_format,
+					     enum hdmi_quantization_range quant_range,
+					     enum hdmi_colorimetry colorimetry)
+{
+	u8 buf[HDMI_INFOFRAME_SIZE(AVI)], *video_code;
+	struct hdmi_avi_infoframe frame;
+	int ret;
+
+	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, conn, mode);
+	if (ret < 0) {
+		drm_err(priv->bridge.dev,
+			"Failed to setup AVI infoframe: %d\n", ret);
+		return;
+	}
+
+	switch (output_bus_format) {
+	case MEDIA_BUS_FMT_YUV8_1X24:
+	case MEDIA_BUS_FMT_YUV10_1X30:
+	case MEDIA_BUS_FMT_YUV12_1X36:
+	case MEDIA_BUS_FMT_YUV16_1X48:
+		frame.colorspace = HDMI_COLORSPACE_YUV444;
+		break;
+
+	case MEDIA_BUS_FMT_UYVY8_1X16:
+	case MEDIA_BUS_FMT_UYVY10_1X20:
+	case MEDIA_BUS_FMT_UYVY12_1X24:
+		frame.colorspace = HDMI_COLORSPACE_YUV422;
+		break;
+
+	case MEDIA_BUS_FMT_RGB888_1X24:
+	case MEDIA_BUS_FMT_RGB101010_1X30:
+	case MEDIA_BUS_FMT_RGB121212_1X36:
+	case MEDIA_BUS_FMT_RGB161616_1X48:
+	default:
+		frame.colorspace = HDMI_COLORSPACE_RGB;
+		break;
+	}
+
+	drm_hdmi_avi_infoframe_colorimetry(&frame, conn_state);
+	drm_hdmi_avi_infoframe_quant_range(&frame, conn, mode, quant_range);
+	drm_hdmi_avi_infoframe_bars(&frame, conn_state);
+
+	ret = hdmi_avi_infoframe_pack(&frame, buf, sizeof(buf));
+	if (ret < 0) {
+		drm_err(priv->bridge.dev,
+			"Failed to pack AVI infoframe: %d\n", ret);
+		return;
+	}
+
+	video_code = &buf[HDMI_INFOFRAME_HEADER_SIZE + 3];
+	if (*video_code > 108) {
+		regmap_write(priv->regmap, TX_PKT_REG_EXCEPT0_BASE_ADDR,
+			     *video_code);
+		*video_code = 0x00;
+	} else {
+		regmap_write(priv->regmap, TX_PKT_REG_EXCEPT0_BASE_ADDR,
+			     0x00);
+	}
+
+	meson_txc_hdmi_write_infoframe(priv->regmap,
+				       TX_PKT_REG_AVI_INFO_BASE_ADDR, buf,
+				       sizeof(buf), true);
+}
+
+static void meson_txc_hdmi_set_vendor_infoframe(struct meson_txc_hdmi *priv,
+						struct drm_connector *conn,
+						const struct drm_display_mode *mode)
+{
+	u8 buf[HDMI_INFOFRAME_HEADER_SIZE + 6];
+	struct hdmi_vendor_infoframe frame;
+	int ret;
+
+	ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame, conn, mode);
+	if (ret) {
+		drm_dbg(priv->bridge.dev,
+			"Failed to setup vendor infoframe: %d\n", ret);
+		return;
+	}
+
+	ret = hdmi_vendor_infoframe_pack(&frame, buf, sizeof(buf));
+	if (ret < 0) {
+		drm_err(priv->bridge.dev,
+			"Failed to pack vendor infoframe: %d\n", ret);
+		return;
+	}
+
+	meson_txc_hdmi_write_infoframe(priv->regmap,
+				       TX_PKT_REG_VEND_INFO_BASE_ADDR, buf,
+				       sizeof(buf), true);
+}
+
+static void meson_txc_hdmi_set_spd_infoframe(struct meson_txc_hdmi *priv)
+{
+	u8 buf[HDMI_INFOFRAME_SIZE(SPD)];
+	struct hdmi_spd_infoframe frame;
+	int ret;
+
+	ret = hdmi_spd_infoframe_init(&frame, "Amlogic", "Meson TXC HDMI");
+	if (ret < 0) {
+		drm_err(priv->bridge.dev,
+			"Failed to setup SPD infoframe: %d\n", ret);
+		return;
+	}
+
+	ret = hdmi_spd_infoframe_pack(&frame, buf, sizeof(buf));
+	if (ret < 0) {
+		drm_err(priv->bridge.dev,
+			"Failed to pack SDP infoframe: %d\n", ret);
+		return;
+	}
+
+	meson_txc_hdmi_write_infoframe(priv->regmap,
+				       TX_PKT_REG_SPD_INFO_BASE_ADDR, buf,
+				       sizeof(buf), true);
+}
+
+static void meson_txc_hdmi_handle_plugged_change(struct meson_txc_hdmi *priv)
+{
+	bool plugged;
+
+	plugged = priv->last_connector_status == connector_status_connected;
+
+	if (priv->codec_dev && priv->codec_plugged_cb)
+		priv->codec_plugged_cb(priv->codec_dev, plugged);
+}
+
+static int meson_txc_hdmi_bridge_attach(struct drm_bridge *bridge,
+					enum drm_bridge_attach_flags flags)
+{
+	struct meson_txc_hdmi *priv = bridge->driver_private;
+
+	if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
+		drm_err(bridge->dev,
+			"DRM_BRIDGE_ATTACH_NO_CONNECTOR flag is not set but needed\n");
+		return -EINVAL;
+	}
+
+	return drm_bridge_attach(bridge->encoder, priv->next_bridge, bridge,
+				 flags);
+}
+
+/* Can return a maximum of 11 possible output formats for a mode/connector */
+#define MAX_OUTPUT_SEL_FORMATS	11
+
+static u32 *
+meson_txc_hdmi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
+						 struct drm_bridge_state *bridge_state,
+						 struct drm_crtc_state *crtc_state,
+						 struct drm_connector_state *conn_state,
+						 unsigned int *num_output_fmts)
+{
+	struct drm_connector *conn = conn_state->connector;
+	struct drm_display_info *info = &conn->display_info;
+	u8 max_bpc = conn_state->max_requested_bpc;
+	unsigned int i = 0;
+	u32 *output_fmts;
+
+	*num_output_fmts = 0;
+
+	output_fmts = kcalloc(MAX_OUTPUT_SEL_FORMATS, sizeof(*output_fmts),
+			      GFP_KERNEL);
+	if (!output_fmts)
+		return NULL;
+
+	/* If we are the only bridge, avoid negotiating with ourselves */
+	if (list_is_singular(&bridge->encoder->bridge_chain)) {
+		*num_output_fmts = 1;
+		output_fmts[0] = MEDIA_BUS_FMT_FIXED;
+
+		return output_fmts;
+	}
+
+	/*
+	 * Order bus formats from 16bit to 8bit and from YUV422 to RGB
+	 * if supported. In any case the default RGB888 format is added
+	 */
+
+	if (max_bpc >= 16 && info->bpc == 16) {
+		if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444)
+			output_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48;
+
+		output_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48;
+	}
+
+	if (max_bpc >= 12 && info->bpc >= 12) {
+		if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422)
+			output_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
+
+		if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444)
+			output_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
+
+		output_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
+	}
+
+	if (max_bpc >= 10 && info->bpc >= 10) {
+		if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422)
+			output_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
+
+		if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444)
+			output_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
+
+		output_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
+	}
+
+	if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422)
+		output_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
+
+	if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444)
+		output_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
+
+	/* Default 8bit RGB fallback */
+	output_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
+
+	*num_output_fmts = i;
+
+	return output_fmts;
+}
+
+/* Can return a maximum of 3 possible input formats for an output format */
+#define MAX_INPUT_SEL_FORMATS	3
+
+static u32 *
+meson_txc_hdmi_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
+						struct drm_bridge_state *bridge_state,
+						struct drm_crtc_state *crtc_state,
+						struct drm_connector_state *conn_state,
+						u32 output_fmt,
+						unsigned int *num_input_fmts)
+{
+	u32 *input_fmts;
+	unsigned int i = 0;
+
+	*num_input_fmts = 0;
+
+	input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
+			     GFP_KERNEL);
+	if (!input_fmts)
+		return NULL;
+
+	switch (output_fmt) {
+	/* If MEDIA_BUS_FMT_FIXED is tested, return default bus format */
+	case MEDIA_BUS_FMT_FIXED:
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
+		break;
+
+	/* 8bit */
+	case MEDIA_BUS_FMT_RGB888_1X24:
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
+		input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
+		break;
+	case MEDIA_BUS_FMT_YUV8_1X24:
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
+		input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
+		break;
+	case MEDIA_BUS_FMT_UYVY8_1X16:
+		input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
+		break;
+
+	/* 10bit */
+	case MEDIA_BUS_FMT_RGB101010_1X30:
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
+		input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
+		break;
+	case MEDIA_BUS_FMT_YUV10_1X30:
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
+		input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
+		break;
+	case MEDIA_BUS_FMT_UYVY10_1X20:
+		input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
+		break;
+
+	/* 12bit */
+	case MEDIA_BUS_FMT_RGB121212_1X36:
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
+		input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
+		break;
+	case MEDIA_BUS_FMT_YUV12_1X36:
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
+		input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
+		break;
+	case MEDIA_BUS_FMT_UYVY12_1X24:
+		input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
+		break;
+
+	/* 16bit */
+	case MEDIA_BUS_FMT_RGB161616_1X48:
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48;
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48;
+		break;
+	case MEDIA_BUS_FMT_YUV16_1X48:
+		input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48;
+		input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48;
+		break;
+	}
+
+	*num_input_fmts = i;
+
+	if (*num_input_fmts == 0) {
+		kfree(input_fmts);
+		input_fmts = NULL;
+	}
+
+	return input_fmts;
+}
+
+static void meson_txc_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
+						struct drm_bridge_state *old_bridge_state)
+{
+	struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge);
+	struct drm_atomic_state *state = old_bridge_state->base.state;
+	enum hdmi_quantization_range quant_range;
+	struct drm_connector_state *conn_state;
+	struct drm_bridge_state *bridge_state;
+	const struct drm_display_mode *mode;
+	enum hdmi_colorimetry colorimetry;
+	struct drm_crtc_state *crtc_state;
+	struct drm_connector *connector;
+	unsigned int i;
+	u8 cea_mode;
+
+	bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
+
+	connector = drm_atomic_get_new_connector_for_encoder(state,
+							     bridge->encoder);
+	if (WARN_ON(!connector))
+		return;
+
+	conn_state = drm_atomic_get_new_connector_state(state, connector);
+	if (WARN_ON(!conn_state))
+		return;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
+	if (WARN_ON(!crtc_state))
+		return;
+
+	priv->current_connector = connector;
+
+	mode = &crtc_state->adjusted_mode;
+
+	cea_mode = drm_match_cea_mode(mode);
+
+	if (priv->sink_is_hdmi) {
+		quant_range = drm_default_rgb_quant_range(mode);
+
+		switch (cea_mode) {
+		case 2 ... 3:
+		case 6 ... 7:
+		case 17 ... 18:
+		case 21 ... 22:
+			colorimetry = HDMI_COLORIMETRY_ITU_601;
+			break;
+
+		default:
+			colorimetry = HDMI_COLORIMETRY_ITU_709;
+			break;
+		}
+
+		meson_txc_hdmi_set_avi_infoframe(priv, connector, mode,
+						 conn_state,
+						 bridge_state->output_bus_cfg.format,
+						 quant_range, colorimetry);
+		meson_txc_hdmi_set_vendor_infoframe(priv, connector, mode);
+		meson_txc_hdmi_set_spd_infoframe(priv);
+	} else {
+		quant_range = HDMI_QUANTIZATION_RANGE_FULL;
+		colorimetry = HDMI_COLORIMETRY_NONE;
+	}
+
+	meson_txc_hdmi_sys5_reset_assert(priv);
+
+	meson_txc_hdmi_config_hdcp_registers(priv);
+
+	if (cea_mode == 39)
+		regmap_write(priv->regmap, TX_VIDEO_DTV_TIMING, 0x0);
+	else
+		regmap_write(priv->regmap, TX_VIDEO_DTV_TIMING,
+			     TX_VIDEO_DTV_TIMING_DISABLE_VIC39_CORRECTION);
+
+	regmap_write(priv->regmap, TX_CORE_DATA_CAPTURE_2,
+		     TX_CORE_DATA_CAPTURE_2_INTERNAL_PACKET_ENABLE);
+	regmap_write(priv->regmap, TX_CORE_DATA_MONITOR_1,
+		     TX_CORE_DATA_MONITOR_1_LANE0 |
+		     FIELD_PREP(TX_CORE_DATA_MONITOR_1_SELECT_LANE0, 0x7));
+	regmap_write(priv->regmap, TX_CORE_DATA_MONITOR_2,
+		     FIELD_PREP(TX_CORE_DATA_MONITOR_2_MONITOR_SELECT, 0x2));
+
+	if (priv->sink_is_hdmi)
+		regmap_write(priv->regmap, TX_TMDS_MODE,
+			     TX_TMDS_MODE_FORCED_HDMI |
+			     TX_TMDS_MODE_HDMI_CONFIG);
+	else
+		regmap_write(priv->regmap, TX_TMDS_MODE,
+			     TX_TMDS_MODE_FORCED_HDMI);
+
+	regmap_write(priv->regmap, TX_SYS4_CONNECT_SEL_1, 0x0);
+
+	/*
+	 * Set tmds_clk pattern to be "0000011111" before being sent to AFE
+	 * clock channel.
+	 */
+	regmap_write(priv->regmap, TX_SYS4_CK_INV_VIDEO,
+		     TX_SYS4_CK_INV_VIDEO_TMDS_CLK_PATTERN);
+
+	regmap_write(priv->regmap, TX_SYS5_FIFO_CONFIG,
+		     TX_SYS5_FIFO_CONFIG_CLK_CHANNEL3_OUTPUT_ENABLE |
+		     TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL2_ENABLE |
+		     TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL1_ENABLE |
+		     TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL0_ENABLE);
+
+	meson_txc_hdmi_config_color_space(priv,
+					  bridge_state->input_bus_cfg.format,
+					  bridge_state->output_bus_cfg.format,
+					  quant_range, colorimetry);
+
+	meson_txc_hdmi_sys5_reset_deassert(priv);
+
+	meson_txc_hdmi_config_serializer_clock(priv, colorimetry);
+	meson_txc_hdmi_reconfig_packet_setting(priv, cea_mode);
+
+	/* all resets need to be applied twice */
+	for (i = 0; i < 2; i++) {
+		regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1,
+			     TX_SYS5_TX_SOFT_RESET_1_TX_PIXEL_RSTN |
+			     TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN |
+			     TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_MASTER_RSTN |
+			     TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_RESAMPLE_RSTN |
+			     TX_SYS5_TX_SOFT_RESET_1_TX_I2S_RESET_RSTN |
+			     TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH2 |
+			     TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH1 |
+			     TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH0);
+		regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2,
+			     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH3_RST_IN |
+			     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN |
+			     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN |
+			     TX_SYS5_TX_SOFT_RESET_2_HDMI_CH0_RST_IN |
+			     TX_SYS5_TX_SOFT_RESET_2_HDMI_SR_RST |
+			     TX_SYS5_TX_SOFT_RESET_2_TX_DDC_HDCP_RSTN |
+			     TX_SYS5_TX_SOFT_RESET_2_TX_DDC_EDID_RSTN |
+			     TX_SYS5_TX_SOFT_RESET_2_TX_DIG_RESET_N_CH3);
+		usleep_range(5000, 10000);
+		regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, 0x00);
+		regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2, 0x00);
+		usleep_range(5000, 10000);
+	}
+
+	if (!priv->phy_is_on) {
+		int ret;
+
+		ret = phy_power_on(priv->phy);
+		if (ret)
+			drm_err(bridge->dev, "Failed to turn on PHY\n");
+		else
+			priv->phy_is_on = true;
+	}
+}
+
+static void meson_txc_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
+						 struct drm_bridge_state *old_bridge_state)
+{
+	struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge);
+
+	priv->current_connector = NULL;
+
+	if (priv->phy_is_on) {
+		int ret;
+
+		ret = phy_power_off(priv->phy);
+		if (ret)
+			drm_err(bridge->dev, "Failed to turn off PHY\n");
+		else
+			priv->phy_is_on = false;
+	}
+
+	meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_AUDIO_INFO_BASE_ADDR);
+	meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_AVI_INFO_BASE_ADDR);
+	meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_EXCEPT0_BASE_ADDR);
+	meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_VEND_INFO_BASE_ADDR);
+}
+
+static enum drm_mode_status
+meson_txc_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
+				 const struct drm_display_info *info,
+				 const struct drm_display_mode *mode)
+{
+	return MODE_OK;
+}
+
+static enum drm_connector_status meson_txc_hdmi_bridge_detect(struct drm_bridge *bridge)
+{
+	struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge);
+	enum drm_connector_status status;
+	unsigned int val;
+
+	regmap_read(priv->regmap, TX_HDCP_ST_EDID_STATUS, &val);
+	if (val & TX_HDCP_ST_EDID_STATUS_HPD_STATUS)
+		status = connector_status_connected;
+	else
+		status = connector_status_disconnected;
+
+	mutex_lock(&priv->codec_mutex);
+	if (priv->last_connector_status != status) {
+		priv->last_connector_status = status;
+		meson_txc_hdmi_handle_plugged_change(priv);
+	}
+	mutex_unlock(&priv->codec_mutex);
+
+	return status;
+}
+
+static int meson_txc_hdmi_get_edid_block(void *data, u8 *buf, unsigned int block,
+					 size_t len)
+{
+	unsigned int i, regval, start = block * EDID_LENGTH;
+	struct meson_txc_hdmi *priv = data;
+	int ret;
+
+	/* Start the DDC transaction */
+	regmap_update_bits(priv->regmap, TX_HDCP_EDID_CONFIG,
+			   TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG, 0);
+	regmap_update_bits(priv->regmap, TX_HDCP_EDID_CONFIG,
+			   TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG,
+			   TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG);
+
+	ret = regmap_read_poll_timeout(priv->regmap,
+				       TX_HDCP_ST_EDID_STATUS,
+				       regval,
+				       (regval & TX_HDCP_ST_EDID_STATUS_EDID_DATA_READY),
+				       1000, 200000);
+
+	regmap_update_bits(priv->regmap, TX_HDCP_EDID_CONFIG,
+			   TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG, 0);
+
+	if (ret)
+		return ret;
+
+	for (i = 0; i < len; i++) {
+		regmap_read(priv->regmap, TX_RX_EDID_OFFSET + start + i,
+			    &regval);
+		buf[i] = regval;
+	}
+
+	return 0;
+}
+
+static struct edid *meson_txc_hdmi_bridge_get_edid(struct drm_bridge *bridge,
+						   struct drm_connector *connector)
+{
+	struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge);
+	struct edid *edid;
+
+	edid = drm_do_get_edid(connector, meson_txc_hdmi_get_edid_block, priv);
+	if (!edid) {
+		drm_dbg(priv->bridge.dev, "Failed to get EDID\n");
+		return NULL;
+	}
+
+	priv->sink_is_hdmi = drm_detect_hdmi_monitor(edid);
+
+	return edid;
+}
+
+static const struct drm_bridge_funcs meson_txc_hdmi_bridge_funcs = {
+	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+	.atomic_reset = drm_atomic_helper_bridge_reset,
+	.attach = meson_txc_hdmi_bridge_attach,
+	.atomic_get_output_bus_fmts = meson_txc_hdmi_bridge_atomic_get_output_bus_fmts,
+	.atomic_get_input_bus_fmts = meson_txc_hdmi_bridge_atomic_get_input_bus_fmts,
+	.atomic_enable = meson_txc_hdmi_bridge_atomic_enable,
+	.atomic_disable = meson_txc_hdmi_bridge_atomic_disable,
+	.mode_valid = meson_txc_hdmi_bridge_mode_valid,
+	.detect = meson_txc_hdmi_bridge_detect,
+	.get_edid = meson_txc_hdmi_bridge_get_edid,
+};
+
+static int meson_txc_hdmi_parse_dt(struct meson_txc_hdmi *priv)
+{
+	struct device_node *endpoint, *remote;
+
+	endpoint = of_graph_get_endpoint_by_regs(priv->dev->of_node, 1, -1);
+	if (!endpoint) {
+		dev_err(priv->dev, "Missing endpoint in port@1\n");
+		return -ENODEV;
+	}
+
+	remote = of_graph_get_remote_port_parent(endpoint);
+	of_node_put(endpoint);
+	if (!remote) {
+		dev_err(priv->dev, "Endpoint in port@1 unconnected\n");
+		return -ENODEV;
+	}
+
+	if (!of_device_is_available(remote)) {
+		dev_err(priv->dev, "port@1 remote device is disabled\n");
+		of_node_put(remote);
+		return -ENODEV;
+	}
+
+	priv->next_bridge = of_drm_find_bridge(remote);
+	of_node_put(remote);
+	if (!priv->next_bridge)
+		return -EPROBE_DEFER;
+
+	return 0;
+}
+
+static int meson_txc_hdmi_hw_init(struct meson_txc_hdmi *priv)
+{
+	unsigned long ddc_i2c_bus_clk_hz = 500 * 1000;
+	unsigned long sys_clk_hz = 24 * 1000 * 1000;
+	int ret;
+
+	ret = phy_init(priv->phy);
+	if (ret) {
+		dev_err(priv->dev, "Failed to initialize the PHY: %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_set_rate(priv->sys_clk, sys_clk_hz);
+	if (ret) {
+		dev_err(priv->dev, "Failed to set HDMI system clock to 24MHz\n");
+		goto err_phy_exit;
+	}
+
+	ret = clk_prepare_enable(priv->sys_clk);
+	if (ret) {
+		dev_err(priv->dev, "Failed to enable the sys clk\n");
+		goto err_phy_exit;
+	}
+
+	regmap_update_bits(priv->regmap, HDMI_OTHER_CTRL1,
+			   HDMI_OTHER_CTRL1_POWER_ON,
+			   HDMI_OTHER_CTRL1_POWER_ON);
+
+	regmap_write(priv->regmap, TX_HDMI_PHY_CONFIG0,
+		     TX_HDMI_PHY_CONFIG0_HDMI_COMMON_B7_B0);
+
+	regmap_write(priv->regmap, TX_HDCP_MODE, 0x40);
+
+	/*
+	 * The vendor driver comments that this is a setting for "Band-gap and
+	 * main-bias". 0x1d = power-up, 0x00 = power-down.
+	 */
+	regmap_write(priv->regmap, TX_SYS1_AFE_TEST, 0x1d);
+
+	meson_txc_hdmi_config_serializer_clock(priv, HDMI_COLORIMETRY_NONE);
+
+	/*
+	 * The vendor driver has a comment with the following information for
+	 * the magic value:
+	 * bit[2:0]=011: CK channel output TMDS CLOCK
+	 * bit[2:0]=101, ck channel output PHYCLCK
+	 */
+	regmap_write(priv->regmap, TX_SYS1_AFE_CONNECT, 0xfb);
+
+	/* Termination resistor calib value */
+	regmap_write(priv->regmap, TX_CORE_CALIB_VALUE, 0x0f);
+
+	/* HPD glitch filter */
+	regmap_write(priv->regmap, TX_HDCP_HPD_FILTER_L, 0xa0);
+	regmap_write(priv->regmap, TX_HDCP_HPD_FILTER_H, 0xa0);
+
+	/* Disable MEM power-down */
+	regmap_write(priv->regmap, TX_MEM_PD_REG0, 0x0);
+
+	regmap_write(priv->regmap, TX_HDCP_CONFIG3,
+		     FIELD_PREP(TX_HDCP_CONFIG3_DDC_I2C_BUS_CLOCK_TIME_DIVIDER,
+				(sys_clk_hz / ddc_i2c_bus_clk_hz) - 1));
+
+	/* Enable software controlled DDC transaction */
+	regmap_write(priv->regmap, TX_HDCP_EDID_CONFIG,
+		     TX_HDCP_EDID_CONFIG_FORCED_MEM_COPY_DONE |
+		     TX_HDCP_EDID_CONFIG_MEM_COPY_DONE_CONFIG);
+	regmap_write(priv->regmap, TX_CORE_EDID_CONFIG_MORE,
+		     TX_CORE_EDID_CONFIG_MORE_SYS_TRIGGER_CONFIG_SEMI_MANU);
+
+	/* mask (= disable) all interrupts */
+	regmap_write(priv->regmap, HDMI_OTHER_INTR_MASKN, 0x0);
+
+	/* clear any pending interrupt */
+	regmap_write(priv->regmap, HDMI_OTHER_INTR_STAT_CLR,
+		     HDMI_OTHER_INTR_STAT_CLR_EDID_RISING |
+		     HDMI_OTHER_INTR_STAT_CLR_HPD_FALLING |
+		     HDMI_OTHER_INTR_STAT_CLR_HPD_RISING);
+
+	return 0;
+
+err_phy_exit:
+	phy_exit(priv->phy);
+	return 0;
+}
+
+static void meson_txc_hdmi_hw_exit(struct meson_txc_hdmi *priv)
+{
+	int ret;
+
+	/* mask (= disable) all interrupts */
+	regmap_write(priv->regmap, HDMI_OTHER_INTR_MASKN,
+		     HDMI_OTHER_INTR_MASKN_TX_EDID_INT_RISE |
+		     HDMI_OTHER_INTR_MASKN_TX_HPD_INT_FALL |
+		     HDMI_OTHER_INTR_MASKN_TX_HPD_INT_RISE);
+
+	regmap_update_bits(priv->regmap, HDMI_OTHER_CTRL1,
+			   HDMI_OTHER_CTRL1_POWER_ON, 0);
+
+	clk_disable_unprepare(priv->sys_clk);
+
+	ret = phy_exit(priv->phy);
+	if (ret)
+		dev_err(priv->dev, "Failed to exit the PHY: %d\n", ret);
+}
+
+static u32 meson_txc_hdmi_hdmi_codec_calc_audio_n(struct hdmi_codec_params *hparms)
+{
+	u32 audio_n;
+
+	if ((hparms->sample_rate % 44100) == 0)
+		audio_n = (128 * hparms->sample_rate) / 900;
+	else
+		audio_n = (128 * hparms->sample_rate) / 1000;
+
+	if (hparms->cea.coding_type == HDMI_AUDIO_CODING_TYPE_EAC3 ||
+	    hparms->cea.coding_type == HDMI_AUDIO_CODING_TYPE_DTS_HD)
+		audio_n *= 4;
+
+	return audio_n;
+}
+
+static u8 meson_txc_hdmi_hdmi_codec_coding_type(struct hdmi_codec_params *hparms)
+{
+	switch (hparms->cea.coding_type) {
+	case HDMI_AUDIO_CODING_TYPE_MLP:
+		return TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_HBR_AUDIO_PACKET;
+	case HDMI_AUDIO_CODING_TYPE_DSD:
+		return TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_ONE_BIT_AUDIO;
+	case HDMI_AUDIO_CODING_TYPE_DST:
+		return TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_DST_AUDIO_PACKET;
+	default:
+		return TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_AUDIO_SAMPLE_PACKET;
+	}
+}
+
+static int meson_txc_hdmi_hdmi_codec_hw_params(struct device *dev, void *data,
+					       struct hdmi_codec_daifmt *fmt,
+					       struct hdmi_codec_params *hparms)
+{
+	u8 buf[HDMI_INFOFRAME_SIZE(AUDIO)];
+	struct meson_txc_hdmi *priv = data;
+	u16 audio_tx_format;
+	u32 audio_n;
+	int len, i;
+
+	if (hparms->cea.coding_type == HDMI_AUDIO_CODING_TYPE_MLP) {
+		/*
+		 * TODO: fixed CTS is not supported yet, it needs special
+		 * TX_SYS1_ACR_N_* settings
+		 */
+		return -EINVAL;
+	}
+
+	switch (hparms->sample_width) {
+	case 16:
+		audio_tx_format = FIELD_PREP(TX_AUDIO_FORMAT_BIT_WIDTH_MASK,
+					     TX_AUDIO_FORMAT_BIT_WIDTH_16);
+		break;
+
+	case 20:
+		audio_tx_format = FIELD_PREP(TX_AUDIO_FORMAT_BIT_WIDTH_MASK,
+					     TX_AUDIO_FORMAT_BIT_WIDTH_20);
+		break;
+
+	case 24:
+		audio_tx_format = FIELD_PREP(TX_AUDIO_FORMAT_BIT_WIDTH_MASK,
+					     TX_AUDIO_FORMAT_BIT_WIDTH_24);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt->fmt) {
+	case HDMI_I2S:
+		regmap_update_bits(priv->regmap, HDMI_OTHER_CTRL1,
+				   HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON,
+				   HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON);
+
+		audio_tx_format |= TX_AUDIO_FORMAT_SPDIF_OR_I2S |
+				   TX_AUDIO_FORMAT_I2S_ONE_BIT_OR_I2S |
+				   FIELD_PREP(TX_AUDIO_FORMAT_I2S_FORMAT, 0x2);
+
+		if (hparms->channels > 2)
+			audio_tx_format |= TX_AUDIO_FORMAT_I2S_2_OR_8_CH;
+
+		regmap_write(priv->regmap, TX_AUDIO_FORMAT,
+			     audio_tx_format);
+
+		regmap_write(priv->regmap, TX_AUDIO_I2S, TX_AUDIO_I2S_ENABLE);
+		regmap_write(priv->regmap, TX_AUDIO_SPDIF, 0x0);
+		break;
+
+	case HDMI_SPDIF:
+		regmap_update_bits(priv->regmap, HDMI_OTHER_CTRL1,
+				   HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON, 0x0);
+
+		if (hparms->cea.coding_type == HDMI_AUDIO_CODING_TYPE_STREAM)
+			audio_tx_format |= TX_AUDIO_FORMAT_SPDIF_CHANNEL_STATUS_FROM_DATA_OR_REG;
+
+		regmap_write(priv->regmap, TX_AUDIO_FORMAT,
+			     audio_tx_format);
+
+		regmap_write(priv->regmap, TX_AUDIO_I2S, 0x0);
+		regmap_write(priv->regmap, TX_AUDIO_SPDIF, TX_AUDIO_SPDIF_ENABLE);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (hparms->channels > 2)
+		regmap_write(priv->regmap, TX_AUDIO_HEADER,
+			     TX_AUDIO_HEADER_AUDIO_SAMPLE_PACKET_HEADER_LAYOUT1);
+	else
+		regmap_write(priv->regmap, TX_AUDIO_HEADER, 0x0);
+
+	regmap_write(priv->regmap, TX_AUDIO_SAMPLE,
+		     FIELD_PREP(TX_AUDIO_SAMPLE_CHANNEL_VALID,
+				BIT(hparms->channels) - 1));
+
+	audio_n = meson_txc_hdmi_hdmi_codec_calc_audio_n(hparms);
+
+	regmap_write(priv->regmap, TX_SYS1_ACR_N_0,
+		     FIELD_PREP(TX_SYS1_ACR_N_0_N_BYTE0,
+				(audio_n >> 0) & 0xff));
+	regmap_write(priv->regmap, TX_SYS1_ACR_N_1,
+		     FIELD_PREP(TX_SYS1_ACR_N_1_N_BYTE1,
+				(audio_n >> 8) & 0xff));
+	regmap_update_bits(priv->regmap, TX_SYS1_ACR_N_2,
+			   TX_SYS1_ACR_N_2_N_UPPER_NIBBLE,
+			   FIELD_PREP(TX_SYS1_ACR_N_2_N_UPPER_NIBBLE,
+				      (audio_n >> 16) & 0xf));
+
+	regmap_write(priv->regmap, TX_SYS0_ACR_CTS_0, 0x0);
+	regmap_write(priv->regmap, TX_SYS0_ACR_CTS_1, 0x0);
+	regmap_write(priv->regmap, TX_SYS0_ACR_CTS_2,
+		     TX_SYS0_ACR_CTS_2_FORCE_ARC_STABLE);
+
+	regmap_write(priv->regmap, TX_AUDIO_CONTROL,
+		     TX_AUDIO_CONTROL_AUTO_AUDIO_FIFO_CLEAR |
+		     FIELD_PREP(TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_MASK,
+				meson_txc_hdmi_hdmi_codec_coding_type(hparms)) |
+		     TX_AUDIO_CONTROL_AUDIO_SAMPLE_PACKET_FLAT);
+
+	len = hdmi_audio_infoframe_pack(&hparms->cea, buf, sizeof(buf));
+	if (len < 0)
+		return len;
+
+	meson_txc_hdmi_write_infoframe(priv->regmap,
+				       TX_PKT_REG_AUDIO_INFO_BASE_ADDR,
+				       buf, len, true);
+
+	for (i = 0; i < ARRAY_SIZE(hparms->iec.status); i++) {
+		unsigned char sub1, sub2;
+
+		sub1 = sub2 = hparms->iec.status[i];
+
+		if (i == 2) {
+			sub1 |= FIELD_PREP(IEC958_AES2_CON_CHANNEL, 1);
+			sub2 |= FIELD_PREP(IEC958_AES2_CON_CHANNEL, 2);
+		}
+
+		regmap_write(priv->regmap, TX_IEC60958_SUB1_OFFSET + i, sub1);
+		regmap_write(priv->regmap, TX_IEC60958_SUB2_OFFSET + i, sub2);
+	}
+
+	return 0;
+}
+
+static int meson_txc_hdmi_hdmi_codec_audio_startup(struct device *dev,
+						   void *data)
+{
+	struct meson_txc_hdmi *priv = data;
+
+	regmap_update_bits(priv->regmap, TX_PACKET_CONTROL_2,
+			   TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE, 0x0);
+
+	/* reset audio master and sample */
+	regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1,
+		     TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_RESAMPLE_RSTN |
+		     TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_MASTER_RSTN);
+	regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, 0x0);
+
+	regmap_write(priv->regmap, TX_AUDIO_CONTROL_MORE,
+		     TX_AUDIO_CONTROL_MORE_ENABLE);
+
+	regmap_write(priv->regmap, TX_AUDIO_FIFO,
+		     FIELD_PREP(TX_AUDIO_FIFO_FIFO_DEPTH_MASK,
+				TX_AUDIO_FIFO_FIFO_DEPTH_512) |
+		     FIELD_PREP(TX_AUDIO_FIFO_CRITICAL_THRESHOLD_MASK,
+			        TX_AUDIO_FIFO_CRITICAL_THRESHOLD_DEPTH_DIV16) |
+		     FIELD_PREP(TX_AUDIO_FIFO_NORMAL_THRESHOLD_MASK,
+			        TX_AUDIO_FIFO_NORMAL_THRESHOLD_DEPTH_DIV8));
+
+	regmap_write(priv->regmap, TX_AUDIO_LIPSYNC, 0x0);
+
+	regmap_write(priv->regmap, TX_SYS1_ACR_N_2,
+		     FIELD_PREP(TX_SYS1_ACR_N_2_N_MEAS_TOLERANCE, 0x3));
+
+	return 0;
+}
+
+static void meson_txc_hdmi_hdmi_codec_audio_shutdown(struct device *dev,
+						     void *data)
+{
+	struct meson_txc_hdmi *priv = data;
+
+	meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_AUDIO_INFO_BASE_ADDR);
+
+	regmap_write(priv->regmap, TX_AUDIO_CONTROL_MORE, 0x0);
+	regmap_update_bits(priv->regmap, HDMI_OTHER_CTRL1,
+			   HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON, 0x0);
+
+	regmap_update_bits(priv->regmap, TX_PACKET_CONTROL_2,
+			   TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE,
+			   TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE);
+}
+
+static int meson_txc_hdmi_hdmi_codec_mute_stream(struct device *dev,
+						 void *data,
+						 bool enable, int direction)
+{
+	struct meson_txc_hdmi *priv = data;
+
+	regmap_write(priv->regmap, TX_AUDIO_PACK,
+		     enable ? 0 : TX_AUDIO_PACK_AUDIO_SAMPLE_PACKETS_ENABLE);
+
+	return 0;
+}
+
+static int meson_txc_hdmi_hdmi_codec_get_eld(struct device *dev, void *data,
+					     uint8_t *buf, size_t len)
+{
+	struct meson_txc_hdmi *priv = data;
+
+	if (priv->current_connector)
+		memcpy(buf, priv->current_connector->eld,
+		       min_t(size_t, MAX_ELD_BYTES, len));
+	else
+		memset(buf, 0, len);
+
+	return 0;
+}
+
+static int meson_txc_hdmi_hdmi_codec_get_dai_id(struct snd_soc_component *component,
+						struct device_node *endpoint)
+{
+	struct of_endpoint of_ep;
+	int ret;
+
+	ret = of_graph_parse_endpoint(endpoint, &of_ep);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * HDMI sound should be located as reg = <2>
+	 * Then, it is sound port 0
+	 */
+	if (of_ep.port == 2)
+		return 0;
+
+	return -EINVAL;
+}
+
+static int meson_txc_hdmi_hdmi_codec_hook_plugged_cb(struct device *dev,
+						     void *data,
+						     hdmi_codec_plugged_cb fn,
+						     struct device *codec_dev)
+{
+	struct meson_txc_hdmi *priv = data;
+
+	mutex_lock(&priv->codec_mutex);
+	priv->codec_plugged_cb = fn;
+	priv->codec_dev = codec_dev;
+	meson_txc_hdmi_handle_plugged_change(priv);
+	mutex_unlock(&priv->codec_mutex);
+
+	return 0;
+}
+
+static struct hdmi_codec_ops meson_txc_hdmi_hdmi_codec_ops = {
+	.hw_params		= meson_txc_hdmi_hdmi_codec_hw_params,
+	.audio_startup		= meson_txc_hdmi_hdmi_codec_audio_startup,
+	.audio_shutdown		= meson_txc_hdmi_hdmi_codec_audio_shutdown,
+	.mute_stream		= meson_txc_hdmi_hdmi_codec_mute_stream,
+	.get_eld		= meson_txc_hdmi_hdmi_codec_get_eld,
+	.get_dai_id		= meson_txc_hdmi_hdmi_codec_get_dai_id,
+	.hook_plugged_cb	= meson_txc_hdmi_hdmi_codec_hook_plugged_cb,
+};
+
+static int meson_txc_hdmi_hdmi_codec_init(struct meson_txc_hdmi *priv)
+{
+	struct hdmi_codec_pdata pdata = {
+		.ops			= &meson_txc_hdmi_hdmi_codec_ops,
+		.i2s			= 1,
+		.spdif			= 1,
+		.max_i2s_channels	= 8,
+		.data			= priv,
+	};
+
+	priv->hdmi_codec_pdev = platform_device_register_data(priv->dev,
+							      HDMI_CODEC_DRV_NAME,
+							      PLATFORM_DEVID_AUTO,
+							      &pdata, sizeof(pdata));
+	return PTR_ERR_OR_ZERO(priv->hdmi_codec_pdev);
+}
+
+static int meson_txc_hdmi_bind(struct device *dev, struct device *master,
+			       void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct meson_txc_hdmi *priv;
+	void __iomem *base;
+	u32 regval;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->dev = dev;
+
+	mutex_init(&priv->codec_mutex);
+
+	dev_set_drvdata(dev, priv);
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	priv->regmap = devm_regmap_init(dev, NULL, base,
+					&meson_txc_hdmi_regmap_config);
+	if (IS_ERR(priv->regmap))
+		return PTR_ERR(priv->regmap);
+
+	priv->pclk = devm_clk_get(dev, "pclk");
+	if (IS_ERR(priv->pclk)) {
+		ret = PTR_ERR(priv->pclk);
+		return dev_err_probe(dev, ret, "Failed to get the pclk\n");
+	}
+
+	priv->sys_clk = devm_clk_get(dev, "sys");
+	if (IS_ERR(priv->sys_clk)) {
+		ret = PTR_ERR(priv->sys_clk);
+		return dev_err_probe(dev, ret,
+				     "Failed to get the sys clock\n");
+	}
+
+	priv->phy = devm_phy_get(dev, "hdmi");
+	if (IS_ERR(priv->phy)) {
+		ret = PTR_ERR(priv->phy);
+		return dev_err_probe(dev, ret, "Failed to get the HDMI PHY\n");
+	}
+
+	ret = meson_txc_hdmi_parse_dt(priv);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(priv->pclk);
+	if (ret) {
+		dev_err_probe(dev, ret, "Failed to enable the pclk\n");
+		return ret;
+	}
+
+	regval = readl(base + HDMI_CTRL_PORT);
+	regval |= HDMI_CTRL_PORT_APB3_ERR_EN;
+	writel(regval, base + HDMI_CTRL_PORT);
+
+	ret = meson_txc_hdmi_hw_init(priv);
+	if (ret)
+		goto err_disable_clk;
+
+	ret = meson_txc_hdmi_hdmi_codec_init(priv);
+	if (ret)
+		goto err_hw_exit;
+
+	priv->bridge.driver_private = priv;
+	priv->bridge.funcs = &meson_txc_hdmi_bridge_funcs;
+	priv->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID;
+	priv->bridge.of_node = dev->of_node;
+	priv->bridge.interlace_allowed = true;
+
+	drm_bridge_add(&priv->bridge);
+
+	return 0;
+
+err_hw_exit:
+	meson_txc_hdmi_hw_exit(priv);
+err_disable_clk:
+	clk_disable_unprepare(priv->pclk);
+	return ret;
+}
+
+static void meson_txc_hdmi_unbind(struct device *dev, struct device *master,
+				  void *data)
+{
+	struct meson_txc_hdmi *priv = dev_get_drvdata(dev);
+
+	platform_device_unregister(priv->hdmi_codec_pdev);
+
+	drm_bridge_remove(&priv->bridge);
+
+	meson_txc_hdmi_hw_exit(priv);
+
+	clk_disable_unprepare(priv->pclk);
+}
+
+static const struct component_ops meson_txc_hdmi_component_ops = {
+	.bind = meson_txc_hdmi_bind,
+	.unbind = meson_txc_hdmi_unbind,
+};
+
+static int meson_txc_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &meson_txc_hdmi_component_ops);
+}
+
+static int meson_txc_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &meson_txc_hdmi_component_ops);
+
+	return 0;
+}
+
+static const struct of_device_id meson_txc_hdmi_of_table[] = {
+	{ .compatible = "amlogic,meson8-hdmi-tx" },
+	{ .compatible = "amlogic,meson8b-hdmi-tx" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, meson_txc_hdmi_of_table);
+
+static struct platform_driver meson_txc_hdmi_platform_driver = {
+	.probe		= meson_txc_hdmi_probe,
+	.remove		= meson_txc_hdmi_remove,
+	.driver		= {
+		.name		= "meson-transwitch-hdmi",
+		.of_match_table	= meson_txc_hdmi_of_table,
+	},
+};
+module_platform_driver(meson_txc_hdmi_platform_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Amlogic Meson8 and Meson8b TranSwitch HDMI 1.4 TX driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/meson/meson_transwitch_hdmi.h b/drivers/gpu/drm/meson/meson_transwitch_hdmi.h
new file mode 100644
index 00000000..14929475
--- /dev/null
+++ b/drivers/gpu/drm/meson/meson_transwitch_hdmi.h
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * All registers and magic values are taken from Amlogic's GPL kernel sources:
+ *   Copyright (C) 2010 Amlogic, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+
+#ifndef __MESON_TRANSWITCH_HDMI_H__
+#define __MESON_TRANSWITCH_HDMI_H__
+
+/* HDMI TX register */
+
+// System config 0
+#define TX_SYS0_AFE_SIGNAL						0x0000
+#define TX_SYS0_AFE_LOOP						0x0001
+#define TX_SYS0_ACR_CTS_0						0x0002
+	#define TX_SYS0_ACR_CTS_0_AUDIO_CTS_BYTE0		GENMASK(7, 0)
+#define TX_SYS0_ACR_CTS_1						0x0003
+	#define TX_SYS0_ACR_CTS_1_AUDIO_CTS_BYTE1		GENMASK(7, 0)
+#define TX_SYS0_ACR_CTS_2						0x0004
+	#define TX_SYS0_ACR_CTS_2_FORCE_ARC_STABLE			BIT(5)
+#define TX_SYS0_BIST_CONTROL						0x0005
+	#define TX_SYS0_BIST_CONTROL_AFE_BIST_ENABLE			BIT(7)
+	#define TX_SYS0_BIST_CONTROL_TMDS_SHIFT_PATTERN_SELECT		BIT(6)
+	#define TX_SYS0_BIST_CONTROL_TMDS_PRBS_PATTERN_SELECT	GENMASK(5, 4)
+	#define TX_SYS0_BIST_CONTROL_TMDS_REPEAT_BIST_PATTERN	GENMASK(2, 0)
+
+#define TX_SYS0_BIST_DATA_0						0x0006
+#define TX_SYS0_BIST_DATA_1						0x0007
+#define TX_SYS0_BIST_DATA_2						0x0008
+#define TX_SYS0_BIST_DATA_3						0x0009
+#define TX_SYS0_BIST_DATA_4						0x000A
+#define TX_SYS0_BIST_DATA_5						0x000B
+#define TX_SYS0_BIST_DATA_6						0x000C
+#define TX_SYS0_BIST_DATA_7						0x000D
+#define TX_SYS0_BIST_DATA_8						0x000E
+#define TX_SYS0_BIST_DATA_9						0x000F
+
+// system config 1
+#define TX_HDMI_PHY_CONFIG0						0x0010
+	#define TX_HDMI_PHY_CONFIG0_HDMI_COMMON_B7_B0		GENMASK(7, 0)
+#define TX_HDMI_PHY_CONFIG1						0x0010
+	#define TX_HDMI_PHY_CONFIG1_HDMI_COMMON_B11_B8		GENMASK(3, 0)
+	#define TX_HDMI_PHY_CONFIG1_HDMI_CTL_REG_B3_B0		GENMASK(7, 4)
+#define TX_HDMI_PHY_CONFIG2						0x0012
+    #define TX_HDMI_PHY_CONFIG_HDMI_CTL_REG_B11_B4		GENMASK(7, 0)
+#define TX_HDMI_PHY_CONFIG3						0x0013
+	#define TX_HDMI_PHY_CONFIG3_HDMI_L2H_CTL		GENMASK(3, 0)
+	#define TX_HDMI_PHY_CONFIG3_HDMI_MDR_PU			GENMASK(7, 4)
+#define TX_HDMI_PHY_CONFIG4						0x0014
+	#define TX_HDMI_PHY_CONFIG4_HDMI_LF_PD				BIT(0)
+	#define TX_HDMI_PHY_CONFIG4_HDMI_PHY_CLK_EN			BIT(1)
+	#define TX_HDMI_PHY_CONFIG4_HDMI_MODE			GENMASK(3, 2)
+	#define TX_HDMI_PHY_CONFIG4_HDMI_MODE_NORMAL			0x0
+	#define TX_HDMI_PHY_CONFIG4_HDMI_MODE_CLK_CH3_EQUAL_CH0		0x1
+	#define TX_HDMI_PHY_CONFIG4_HDMI_MODE_ALTERNATE_HIGH_LOW	0x2
+	#define TX_HDMI_PHY_CONFIG4_HDMI_MODE_ALTERNATE_LOW_HIGH	0x3
+	#define TX_HDMI_PHY_CONFIG4_HDMI_PREM_CTL		GENMASK(7, 4)
+#define TX_HDMI_PHY_CONFIG5						0x0015
+	#define TX_HDMI_PHY_CONFIG5_HDMI_VCM_CTL		GENMASK(7, 5)
+	#define TX_HDMI_PHY_CONFIG5_HDMI_PREFCTL		GENMASK(2, 0)
+#define TX_HDMI_PHY_CONFIG6						0x0016
+	#define TX_HDMI_PHY_CONFIG6_HDMI_RTERM_CTL		GENMASK(3, 0)
+	#define TX_HDMI_PHY_CONFIG6_HDMI_SWING_CTL		GENMASK(7, 4)
+#define TX_SYS1_AFE_TEST						0x0017
+#define TX_SYS1_PLL							0x0018
+#define TX_SYS1_TUNE							0x0019
+#define TX_SYS1_AFE_CONNECT						0x001A
+
+#define TX_SYS1_ACR_N_0							0x001C
+	#define TX_SYS1_ACR_N_0_N_BYTE0				GENMASK(7, 0)
+#define TX_SYS1_ACR_N_1							0x001D
+	#define TX_SYS1_ACR_N_1_N_BYTE1				GENMASK(7, 0)
+#define TX_SYS1_ACR_N_2							0x001E
+	#define TX_SYS1_ACR_N_2_N_MEAS_TOLERANCE		GENMASK(7, 4)
+	#define TX_SYS1_ACR_N_2_N_UPPER_NIBBLE			GENMASK(3, 0)
+#define TX_SYS1_PRBS_DATA						0x001F
+	#define TX_SYS1_PRBS_DATA_PRBS_MODE			GENMASK(1, 0)
+	#define TX_SYS1_PRBS_DATA_PRBS_MODE_11				0x0
+	#define TX_SYS1_PRBS_DATA_PRBS_MODE_15				0x1
+	#define TX_SYS1_PRBS_DATA_PRBS_MODE_7				0x2
+	#define TX_SYS1_PRBS_DATA_PRBS_MODE_31				0x3
+
+// HDCP CONFIG
+#define TX_HDCP_ECC_CONFIG						0x0024
+#define TX_HDCP_CRC_CONFIG						0x0025
+#define TX_HDCP_EDID_CONFIG						0x0026
+	#define TX_HDCP_EDID_CONFIG_FORCED_SYS_TRIGGER			BIT(7)
+	#define TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG			BIT(6)
+	#define TX_HDCP_EDID_CONFIG_MEM_ACC_SEQ_MODE			BIT(5)
+	#define TX_HDCP_EDID_CONFIG_MEM_ACC_SEQ_START			BIT(4)
+	#define TX_HDCP_EDID_CONFIG_FORCED_MEM_COPY_DONE		BIT(3)
+	#define TX_HDCP_EDID_CONFIG_MEM_COPY_DONE_CONFIG		BIT(2)
+	#define TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG_SEMI_MANU	BIT(1)
+
+#define TX_HDCP_MEM_CONFIG						0x0027
+	#define TX_HDCP_MEM_CONFIG_READ_DECRYPT				BIT(3)
+
+#define TX_HDCP_HPD_FILTER_L						0x0028
+#define TX_HDCP_HPD_FILTER_H						0x0029
+#define TX_HDCP_ENCRYPT_BYTE						0x002A
+#define TX_HDCP_CONFIG0							0x002B
+	#define TX_HDCP_CONFIG0_ROM_ENCRYPT_OFF			GENMASK(4, 3)
+
+#define TX_HDCP_CONFIG1							0x002C
+#define TX_HDCP_CONFIG2							0x002D
+#define TX_HDCP_CONFIG3							0x002E
+	#define TX_HDCP_CONFIG3_DDC_I2C_BUS_CLOCK_TIME_DIVIDER	GENMASK(7, 0)
+
+#define TX_HDCP_MODE							0x002F
+	#define TX_HDCP_MODE_CP_DESIRED					BIT(7)
+	#define TX_HDCP_MODE_ESS_CONFIG					BIT(6)
+	#define TX_HDCP_MODE_SET_AVMUTE					BIT(5)
+	#define TX_HDCP_MODE_CLEAR_AVMUTE				BIT(4)
+	#define TX_HDCP_MODE_HDCP_1_1					BIT(3)
+	#define TX_HDCP_MODE_VSYNC_HSYNC_FORCED_POLARITY_SELECT		BIT(2)
+	#define TX_HDCP_MODE_FORCED_VSYNC_POLARITY			BIT(1)
+	#define TX_HDCP_MODE_FORCED_HSYNC_POLARITY			BIT(0)
+
+// Video config, part 1
+#define TX_VIDEO_ACTIVE_PIXELS_0					0x0030
+#define TX_VIDEO_ACTIVE_PIXELS_1					0x0031
+#define TX_VIDEO_FRONT_PIXELS						0x0032
+#define TX_VIDEO_HSYNC_PIXELS						0x0033
+#define TX_VIDEO_BACK_PIXELS						0x0034
+#define TX_VIDEO_ACTIVE_LINES_0						0x0035
+#define TX_VIDEO_ACTIVE_LINES_1						0x0036
+#define TX_VIDEO_EOF_LINES						0x0037
+#define TX_VIDEO_VSYNC_LINES						0x0038
+#define TX_VIDEO_SOF_LINES						0x0039
+#define TX_VIDEO_DTV_TIMING						0x003A
+	#define TX_VIDEO_DTV_TIMING_FORCE_DTV_TIMING_AUTO		BIT(7)
+	#define TX_VIDEO_DTV_TIMING_FORCE_VIDEO_SCAN			BIT(6)
+	#define TX_VIDEO_DTV_TIMING_FORCE_VIDEO_FIELD			BIT(5)
+	#define TX_VIDEO_DTV_TIMING_DISABLE_VIC39_CORRECTION		BIT(4)
+
+#define TX_VIDEO_DTV_MODE						0x003B
+	#define TX_VIDEO_DTV_MODE_FORCED_DEFAULT_PHASE			BIT(7)
+	#define TX_VIDEO_DTV_MODE_COLOR_DEPTH			GENMASK(1, 0)
+
+#define TX_VIDEO_DTV_FORMAT0						0x003C
+#define TX_VIDEO_DTV_FORMAT1						0x003D
+#define TX_VIDEO_PIXEL_PACK						0x003F
+// video config, part 2
+#define TX_VIDEO_CSC_COEFF_B0						0x0040
+#define TX_VIDEO_CSC_COEFF_B1						0x0041
+#define TX_VIDEO_CSC_COEFF_R0						0x0042
+#define TX_VIDEO_CSC_COEFF_R1						0x0043
+#define TX_VIDEO_CSC_COEFF_CB0						0x0044
+#define TX_VIDEO_CSC_COEFF_CB1						0x0045
+#define TX_VIDEO_CSC_COEFF_CR0						0x0046
+#define TX_VIDEO_CSC_COEFF_CR1						0x0047
+#define TX_VIDEO_DTV_OPTION_L						0x0048
+	#define TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_FORMAT	GENMASK(7, 6)
+	#define TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_FORMAT	GENMASK(5, 4)
+	#define TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_DEPTH	GENMASK(3, 2)
+	#define TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_DEPTH		GENMASK(1, 0)
+
+#define TX_VIDEO_DTV_OPTION_H						0x0049
+	#define TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_16_235		0x0
+	#define TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_16_240		0x1
+	#define TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_1_254			0x2
+	#define TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_0_255			0x3
+	#define TX_VIDEO_DTV_OPTION_H_OUTPUT_COLOR_RANGE	GENMASK(3, 2)
+	#define TX_VIDEO_DTV_OPTION_H_INPUT_COLOR_RANGE		GENMASK(1, 0)
+
+#define TX_VIDEO_DTV_FILTER						0x004A
+#define TX_VIDEO_DTV_DITHER						0x004B
+#define TX_VIDEO_DTV_DEDITHER						0x004C
+#define TX_VIDEO_PROC_CONFIG0						0x004E
+#define TX_VIDEO_PROC_CONFIG1						0x004F
+
+// Audio config
+#define TX_AUDIO_FORMAT							0x0058
+	#define TX_AUDIO_FORMAT_SPDIF_OR_I2S				BIT(7)
+	#define TX_AUDIO_FORMAT_I2S_2_OR_8_CH				BIT(6)
+	#define TX_AUDIO_FORMAT_I2S_FORMAT			GENMASK(5, 4)
+	#define TX_AUDIO_FORMAT_BIT_WIDTH_MASK			GENMASK(3, 2)
+	#define TX_AUDIO_FORMAT_BIT_WIDTH_16				0x1
+	#define TX_AUDIO_FORMAT_BIT_WIDTH_20				0x2
+	#define TX_AUDIO_FORMAT_BIT_WIDTH_24				0x3
+	#define TX_AUDIO_FORMAT_WS_POLARITY				BIT(1)
+	#define TX_AUDIO_FORMAT_I2S_ONE_BIT_OR_I2S			BIT(0)
+	#define TX_AUDIO_FORMAT_SPDIF_CHANNEL_STATUS_FROM_DATA_OR_REG	BIT(0)
+
+#define TX_AUDIO_SPDIF							0x0059
+	#define TX_AUDIO_SPDIF_ENABLE					BIT(0)
+#define TX_AUDIO_I2S							0x005A
+	#define TX_AUDIO_I2S_ENABLE					BIT(0)
+#define TX_AUDIO_FIFO							0x005B
+	#define TX_AUDIO_FIFO_FIFO_DEPTH_MASK			GENMASK(7, 4)
+	#define TX_AUDIO_FIFO_FIFO_DEPTH_512				0x4
+	#define TX_AUDIO_FIFO_CRITICAL_THRESHOLD_MASK		GENMASK(3, 2)
+	#define TX_AUDIO_FIFO_CRITICAL_THRESHOLD_DEPTH_DIV16		0x2
+	#define TX_AUDIO_FIFO_NORMAL_THRESHOLD_MASK		GENMASK(1, 0)
+	#define TX_AUDIO_FIFO_NORMAL_THRESHOLD_DEPTH_DIV8		0x1
+#define TX_AUDIO_LIPSYNC						0x005C
+	#define TX_AUDIO_LIPSYNC_NORMALIZED_LIPSYNC_PARAM	GENMASK(7, 0)
+#define TX_AUDIO_CONTROL						0x005D
+	#define TX_AUDIO_CONTROL_FORCED_AUDIO_FIFO_CLEAR		BIT(7)
+	#define TX_AUDIO_CONTROL_AUTO_AUDIO_FIFO_CLEAR			BIT(6)
+	#define TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_MASK		GENMASK(5, 4)
+	#define TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_AUDIO_SAMPLE_PACKET	0x0
+	#define TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_ONE_BIT_AUDIO	0x1
+	#define TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_HBR_AUDIO_PACKET	0x2
+	#define TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_DST_AUDIO_PACKET	0x3
+	#define TX_AUDIO_CONTROL_AUDIO_SAMPLE_PACKET_VALID		BIT(2)
+	#define TX_AUDIO_CONTROL_AUDIO_SAMPLE_PACKET_USER		BIT(1)
+	#define TX_AUDIO_CONTROL_AUDIO_SAMPLE_PACKET_FLAT		BIT(0)
+#define TX_AUDIO_HEADER							0x005E
+	#define TX_AUDIO_HEADER_AUDIO_SAMPLE_PACKET_HEADER_LAYOUT1	BIT(7)
+	#define TX_AUDIO_HEADER_SET_NORMAL_DOUBLE_IN_DST_PACKET_HEADER	BIT(6)
+#define TX_AUDIO_SAMPLE							0x005F
+	#define TX_AUDIO_SAMPLE_CHANNEL_VALID			GENMASK(7, 0)
+#define TX_AUDIO_VALID							0x0060
+#define TX_AUDIO_USER							0x0061
+#define TX_AUDIO_PACK							0x0062
+	#define TX_AUDIO_PACK_AUDIO_SAMPLE_PACKETS_ENABLE		BIT(0)
+#define TX_AUDIO_CONTROL_MORE						0x0064
+	#define TX_AUDIO_CONTROL_MORE_ENABLE				BIT(0)
+
+// tmds config
+#define TX_TMDS_MODE							0x0068
+	#define TX_TMDS_MODE_FORCED_HDMI				BIT(7)
+	#define TX_TMDS_MODE_HDMI_CONFIG				BIT(6)
+	#define TX_TMDS_MODE_BIT_SWAP					BIT(3)
+	#define TX_TMDS_MODE_CHANNEL_SWAP			GENMASK(2, 0)
+
+#define TX_TMDS_CONFIG0							0x006C
+#define TX_TMDS_CONFIG1							0x006D
+
+// packet config
+#define TX_PACKET_ALLOC_ACTIVE_1					0x0078
+#define TX_PACKET_ALLOC_ACTIVE_2					0x0079
+#define TX_PACKET_ALLOC_EOF_1						0x007A
+#define TX_PACKET_ALLOC_EOF_2						0x007B
+#define TX_PACKET_ALLOC_SOF_1						0x007C
+#define TX_PACKET_ALLOC_SOF_2						0x007D
+#define TX_PACKET_CONTROL_1						0x007E
+	#define TX_PACKET_CONTROL_1_FORCE_PACKET_TIMING			BIT(7)
+	#define TX_PACKET_CONTROL_1_PACKET_ALLOC_MODE			BIT(6)
+	#define TX_PACKET_CONTROL_1_PACKET_START_LATENCY	GENMASK(5, 0)
+
+#define TX_PACKET_CONTROL_2						0x007F
+	#define TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE		BIT(3)
+	#define TX_PACKET_CONTROL_2_HORIZONTAL_GC_PACKET_TRANSPORT_EN	BIT(1)
+
+#define TX_CORE_EDID_CONFIG_MORE					0x0080
+	#define TX_CORE_EDID_CONFIG_MORE_KEEP_EDID_ERROR		BIT(1)
+	#define TX_CORE_EDID_CONFIG_MORE_SYS_TRIGGER_CONFIG_SEMI_MANU	BIT(0)
+
+#define TX_CORE_ALLOC_VSYNC_0						0x0081
+#define TX_CORE_ALLOC_VSYNC_1						0x0082
+#define TX_CORE_ALLOC_VSYNC_2						0x0083
+#define TX_MEM_PD_REG0							0x0084
+
+// core config
+#define TX_CORE_DATA_CAPTURE_1						0x00F0
+#define TX_CORE_DATA_CAPTURE_2						0x00F1
+	#define TX_CORE_DATA_CAPTURE_2_AUDIO_SOURCE_SELECT	GENMASK(7, 6)
+	#define TX_CORE_DATA_CAPTURE_2_EXTERNAL_PACKET_ENABLE		BIT(5)
+	#define TX_CORE_DATA_CAPTURE_2_INTERNAL_PACKET_ENABLE		BIT(4)
+	#define TX_CORE_DATA_CAPTURE_2_AFE_FIFO_SRC_LANE1	GENMASK(3, 2)
+	#define TX_CORE_DATA_CAPTURE_2_AFE_FIFO_SRC_LANE0	GENMASK(1, 0)
+
+#define TX_CORE_DATA_MONITOR_1						0x00F2
+	#define TX_CORE_DATA_MONITOR_1_LANE1				BIT(7)
+	#define TX_CORE_DATA_MONITOR_1_SELECT_LANE1		GENMASK(6, 4)
+	#define TX_CORE_DATA_MONITOR_1_LANE0				BIT(3)
+	#define TX_CORE_DATA_MONITOR_1_SELECT_LANE0		GENMASK(2, 0)
+
+#define TX_CORE_DATA_MONITOR_2						0x00F3
+	#define TX_CORE_DATA_MONITOR_2_MONITOR_SELECT		GENMASK(2, 0)
+
+#define TX_CORE_CALIB_MODE						0x00F4
+#define TX_CORE_CALIB_SAMPLE_DELAY					0x00F5
+#define TX_CORE_CALIB_VALUE_AUTO					0x00F6
+#define TX_CORE_CALIB_VALUE						0x00F7
+
+// system config 4
+#define TX_SYS4_TX_CKI_DDR						0x00A0
+#define TX_SYS4_TX_CKO_DDR						0x00A1
+#define TX_SYS4_RX_CKI_DDR						0x00A2
+#define TX_SYS4_RX_CKO_DDR						0x00A3
+#define TX_SYS4_CONNECT_SEL_0						0x00A4
+#define TX_SYS4_CONNECT_SEL_1						0x00A5
+	#define TX_SYS4_CONNECT_SEL_1_TX_CONNECT_SEL_UPPER_CHANNEL_DATA	BIT(6)
+
+#define TX_SYS4_CONNECT_SEL_2						0x00A6
+#define TX_SYS4_CONNECT_SEL_3						0x00A7
+#define TX_SYS4_CK_INV_VIDEO						0x00A8
+	#define TX_SYS4_CK_INV_VIDEO_TMDS_CLK_PATTERN			BIT(4)
+#define TX_SYS4_CK_INV_AUDIO						0x00A9
+#define TX_SYS4_CK_INV_AFE						0x00AA
+#define TX_SYS4_CK_INV_CH01						0x00AB
+#define TX_SYS4_CK_INV_CH2						0x00AC
+#define TX_SYS4_CK_CEC							0x00AD
+#define TX_SYS4_CK_SOURCE_1						0x00AE
+#define TX_SYS4_CK_SOURCE_2						0x00AF
+
+#define TX_IEC60958_SUB1_OFFSET						0x00B0
+#define TX_IEC60958_SUB2_OFFSET						0x00C8
+
+// system config 5
+#define TX_SYS5_TX_SOFT_RESET_1						0x00E0
+	#define TX_SYS5_TX_SOFT_RESET_1_TX_PIXEL_RSTN			BIT(7)
+	#define TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN			BIT(6)
+	#define TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_MASTER_RSTN		BIT(5)
+	#define TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_RESAMPLE_RSTN		BIT(4)
+	#define TX_SYS5_TX_SOFT_RESET_1_TX_I2S_RESET_RSTN		BIT(3)
+	#define TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH2		BIT(2)
+	#define TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH1		BIT(1)
+	#define TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH0		BIT(0)
+
+#define TX_SYS5_TX_SOFT_RESET_2						0x00E1
+	#define TX_SYS5_TX_SOFT_RESET_2_HDMI_CH3_RST_IN			BIT(7)
+	#define TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN			BIT(6)
+	#define TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN			BIT(5)
+	#define TX_SYS5_TX_SOFT_RESET_2_HDMI_CH0_RST_IN			BIT(4)
+	#define TX_SYS5_TX_SOFT_RESET_2_HDMI_SR_RST			BIT(3)
+	#define TX_SYS5_TX_SOFT_RESET_2_TX_DDC_HDCP_RSTN		BIT(2)
+	#define TX_SYS5_TX_SOFT_RESET_2_TX_DDC_EDID_RSTN		BIT(1)
+	#define TX_SYS5_TX_SOFT_RESET_2_TX_DIG_RESET_N_CH3		BIT(0)
+
+#define TX_SYS5_RX_SOFT_RESET_1						0x00E2
+#define TX_SYS5_RX_SOFT_RESET_2						0x00E3
+#define TX_SYS5_RX_SOFT_RESET_3						0x00E4
+#define TX_SYS5_SSTL_BIDIR_IN						0x00E5
+#define TX_SYS5_SSTL_IN							0x00E6
+#define TX_SYS5_SSTL_DIFF_IN						0x00E7
+#define TX_SYS5_FIFO_CONFIG						0x00E8
+	#define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL2_BYPASS		BIT(6)
+	#define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL1_BYPASS		BIT(5)
+	#define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL0_BYPASS		BIT(4)
+	#define TX_SYS5_FIFO_CONFIG_CLK_CHANNEL3_OUTPUT_ENABLE		BIT(3)
+	#define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL2_ENABLE		BIT(2)
+	#define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL1_ENABLE		BIT(1)
+	#define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL0_ENABLE		BIT(0)
+
+#define TX_SYS5_FIFO_SAMP01_CFG						0x00E9
+#define TX_SYS5_FIFO_SAMP23_CFG						0x00EA
+#define TX_SYS5_CONNECT_FIFO_CFG					0x00EB
+#define TX_SYS5_IO_CALIB_CONTROL					0x00EC
+#define TX_SYS5_SSTL_BIDIR_OUT						0x00ED
+#define TX_SYS5_SSTL_OUT						0x00EE
+#define TX_SYS5_SSTL_DIFF_OUT						0x00EF
+
+// HDCP shadow register
+#define TX_HDCP_SHW_BKSV_0						0x0100
+#define TX_HDCP_SHW_BKSV_1						0x0101
+#define TX_HDCP_SHW_BKSV_2						0x0102
+#define TX_HDCP_SHW_BKSV_3						0x0103
+#define TX_HDCP_SHW_BKSV_4						0x0104
+#define TX_HDCP_SHW_RI1_0						0x0108
+#define TX_HDCP_SHW_RI1_1						0x0109
+#define TX_HDCP_SHW_PJ1							0x010A
+#define TX_HDCP_SHW_AKSV_0						0x0110
+#define TX_HDCP_SHW_AKSV_1						0x0111
+#define TX_HDCP_SHW_AKSV_2						0x0112
+#define TX_HDCP_SHW_AKSV_3						0x0113
+#define TX_HDCP_SHW_AKSV_4						0x0114
+#define TX_HDCP_SHW_AINFO						0x0115
+#define TX_HDCP_SHW_AN_0						0x0118
+#define TX_HDCP_SHW_AN_1						0x0119
+#define TX_HDCP_SHW_AN_2						0x011A
+#define TX_HDCP_SHW_AN_3						0x011B
+#define TX_HDCP_SHW_AN_4						0x011C
+#define TX_HDCP_SHW_AN_5						0x011D
+#define TX_HDCP_SHW_AN_6						0x011E
+#define TX_HDCP_SHW_AN_7						0x011F
+#define TX_HDCP_SHW_V1_H0_0						0x0120
+#define TX_HDCP_SHW_V1_H0_1						0x0121
+#define TX_HDCP_SHW_V1_H0_2						0x0122
+#define TX_HDCP_SHW_V1_H0_3						0x0123
+#define TX_HDCP_SHW_V1_H1_0						0x0124
+#define TX_HDCP_SHW_V1_H1_1						0x0125
+#define TX_HDCP_SHW_V1_H1_2						0x0126
+#define TX_HDCP_SHW_V1_H1_3						0x0127
+#define TX_HDCP_SHW_V1_H2_0						0x0128
+#define TX_HDCP_SHW_V1_H2_1						0x0129
+#define TX_HDCP_SHW_V1_H2_2						0x012A
+#define TX_HDCP_SHW_V1_H2_3						0x012B
+#define TX_HDCP_SHW_V1_H3_0						0x012C
+#define TX_HDCP_SHW_V1_H3_1						0x012D
+#define TX_HDCP_SHW_V1_H3_2						0x012E
+#define TX_HDCP_SHW_V1_H3_3						0x012F
+#define TX_HDCP_SHW_V1_H4_0						0x0130
+#define TX_HDCP_SHW_V1_H4_1						0x0131
+#define TX_HDCP_SHW_V1_H4_2						0x0132
+#define TX_HDCP_SHW_V1_H4_3						0x0133
+#define TX_HDCP_SHW_BCAPS						0x0140
+#define TX_HDCP_SHW_BSTATUS_0						0x0141
+#define TX_HDCP_SHW_BSTATUS_1						0x0142
+#define TX_HDCP_SHW_KSV_FIFO						0x0143
+
+// system status 0
+#define TX_SYSST0_CONNECT_FIFO						0x0180
+#define TX_SYSST0_PLL_MONITOR						0x0181
+#define TX_SYSST0_AFE_FIFO						0x0182
+#define TX_SYSST0_ROM_STATUS						0x018F
+
+// hdcp status
+#define TX_HDCP_ST_AUTHENTICATION					0x0190
+#define TX_HDCP_ST_FRAME_COUNT						0x0191
+#define TX_HDCP_ST_STATUS_0						0x0192
+#define TX_HDCP_ST_STATUS_1						0x0193
+#define TX_HDCP_ST_STATUS_2						0x0194
+#define TX_HDCP_ST_STATUS_3						0x0195
+#define TX_HDCP_ST_EDID_STATUS						0x0196
+	#define TX_HDCP_ST_EDID_STATUS_SYSTEM_STATUS		GENMASK(7, 6)
+	#define TX_HDCP_ST_EDID_STATUS_SYSTEM_STATUS_NO_SINK_ATTACHED	0x0
+	#define TX_HDCP_ST_EDID_STATUS_SYSTEM_STATUS_READING_EDID	0x1
+	#define TX_HDCP_ST_EDID_STATUS_SYSTEM_STATUS_DVI_MODE		0x2
+	#define TX_HDCP_ST_EDID_STATUS_SYSTEM_STATUS_HDMI_MODE		0x3
+	#define TX_HDCP_ST_EDID_STATUS_EDID_DATA_READY			BIT(4)
+	#define TX_HDCP_ST_EDID_STATUS_HPD_STATUS			BIT(1)
+
+#define TX_HDCP_ST_MEM_STATUS						0x0197
+#define TX_HDCP_ST_ST_MODE						0x019F
+
+// video status
+#define TX_VIDEO_ST_ACTIVE_PIXELS_1					0x01A0
+#define TX_VIDEO_ST_ACTIVE_PIXELS_2					0x01A1
+#define TX_VIDEO_ST_FRONT_PIXELS					0x01A2
+#define TX_VIDEO_ST_HSYNC_PIXELS					0x01A3
+#define TX_VIDEO_ST_BACK_PIXELS						0x01A4
+#define TX_VIDEO_ST_ACTIVE_LINES_1					0x01A5
+#define TX_VIDEO_ST_ACTIVE_LINES_2					0x01A6
+#define TX_VIDEO_ST_EOF_LINES						0x01A7
+#define TX_VIDEO_ST_VSYNC_LINES						0x01A8
+#define TX_VIDEO_ST_SOF_LINES						0x01A9
+#define TX_VIDEO_ST_DTV_TIMING						0x01AA
+#define TX_VIDEO_ST_DTV_MODE						0x01AB
+// audio status
+#define TX_VIDEO_ST_AUDIO_STATUS					0x01AC
+#define TX_AFE_STATUS_0							0x01AE
+#define TX_AFE_STATUS_1							0x01AF
+
+#define TX_IEC60958_ST_SUB1_OFFSET					0x01B0
+#define TX_IEC60958_ST_SUB2_OFFSET					0x01C8
+
+// system status 1
+#define TX_SYSST1_CALIB_BIT_RESULT_0					0x01E0
+#define TX_SYSST1_CALIB_BIT_RESULT_1					0x01E1
+//HDMI_STATUS_OUT[7:0]
+#define TX_HDMI_PHY_READBACK_0						0x01E2
+//HDMI_COMP_OUT[4]
+//HDMI_STATUS_OUT[11:8]
+#define TX_HDMI_PHY_READBACK_1						0x01E3
+#define TX_SYSST1_CALIB_BIT_RESULT_4					0x01E4
+#define TX_SYSST1_CALIB_BIT_RESULT_5					0x01E5
+#define TX_SYSST1_CALIB_BIT_RESULT_6					0x01E6
+#define TX_SYSST1_CALIB_BIT_RESULT_7					0x01E7
+#define TX_SYSST1_CALIB_BUS_RESULT_0					0x01E8
+#define TX_SYSST1_CALIB_BUS_RESULT_1					0x01E9
+#define TX_SYSST1_CALIB_BUS_RESULT_2					0x01EA
+#define TX_SYSST1_CALIB_BUS_RESULT_3					0x01EB
+#define TX_SYSST1_CALIB_BUS_RESULT_4					0x01EC
+#define TX_SYSST1_CALIB_BUS_RESULT_5					0x01ED
+#define TX_SYSST1_CALIB_BUS_RESULT_6					0x01EE
+#define TX_SYSST1_CALIB_BUS_RESULT_7					0x01EF
+
+// Packet status
+#define TX_PACKET_ST_REQUEST_STATUS_1					0x01F0
+#define TX_PACKET_ST_REQUEST_STATUS_2					0x01F1
+#define TX_PACKET_ST_REQUEST_MISSED_1					0x01F2
+#define TX_PACKET_ST_REQUEST_MISSED_2					0x01F3
+#define TX_PACKET_ST_ENCODE_STATUS_0					0x01F4
+#define TX_PACKET_ST_ENCODE_STATUS_1					0x01F5
+#define TX_PACKET_ST_ENCODE_STATUS_2					0x01F6
+#define TX_PACKET_ST_TIMER_STATUS					0x01F7
+
+// tmds status
+#define TX_TMDS_ST_CLOCK_METER_1					0x01F8
+#define TX_TMDS_ST_CLOCK_METER_2					0x01F9
+#define TX_TMDS_ST_CLOCK_METER_3					0x01FA
+#define TX_TMDS_ST_TMDS_STATUS_1					0x01FC
+#define TX_TMDS_ST_TMDS_STATUS_2					0x01FD
+#define TX_TMDS_ST_TMDS_STATUS_3					0x01FE
+#define TX_TMDS_ST_TMDS_STATUS_4					0x01FF
+
+// Packet register
+#define TX_PKT_REG_SPD_INFO_BASE_ADDR					0x0200
+#define TX_PKT_REG_VEND_INFO_BASE_ADDR					0x0220
+#define TX_PKT_REG_MPEG_INFO_BASE_ADDR					0x0240
+#define TX_PKT_REG_AVI_INFO_BASE_ADDR					0x0260
+#define TX_PKT_REG_AUDIO_INFO_BASE_ADDR					0x0280
+#define TX_PKT_REG_ACP_INFO_BASE_ADDR					0x02A0
+#define TX_PKT_REG_ISRC1_BASE_ADDR					0x02C0
+#define TX_PKT_REG_ISRC2_BASE_ADDR					0x02E0
+#define TX_PKT_REG_EXCEPT0_BASE_ADDR					0x0300
+#define TX_PKT_REG_EXCEPT1_BASE_ADDR					0x0320
+#define TX_PKT_REG_EXCEPT2_BASE_ADDR					0x0340
+#define TX_PKT_REG_EXCEPT3_BASE_ADDR					0x0360
+#define TX_PKT_REG_EXCEPT4_BASE_ADDR					0x0380
+#define TX_PKT_REG_GAMUT_P0_BASE_ADDR					0x03A0
+#define TX_PKT_REG_GAMUT_P1_1_BASE_ADDR					0x03C0
+#define TX_PKT_REG_GAMUT_P1_2_BASE_ADDR					0x03E0
+
+#define TX_RX_EDID_OFFSET						0x0600
+
+/* HDMI OTHER registers */
+
+#define HDMI_OTHER_CTRL0						0x8000
+#define HDMI_OTHER_CTRL1						0x8001
+	#define HDMI_OTHER_CTRL1_POWER_ON				BIT(15)
+	#define HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON			BIT(13)
+
+#define HDMI_OTHER_STATUS0						0x8002
+#define HDMI_OTHER_CTRL2						0x8003
+#define HDMI_OTHER_INTR_MASKN						0x8004
+	#define HDMI_OTHER_INTR_MASKN_TX_EDID_INT_RISE			BIT(2)
+	#define HDMI_OTHER_INTR_MASKN_TX_HPD_INT_FALL			BIT(1)
+	#define HDMI_OTHER_INTR_MASKN_TX_HPD_INT_RISE			BIT(0)
+
+#define HDMI_OTHER_INTR_STAT						0x8005
+	#define HDMI_OTHER_INTR_STAT_EDID_RISING			BIT(2)
+	#define HDMI_OTHER_INTR_STAT_HPD_FALLING			BIT(1)
+	#define HDMI_OTHER_INTR_STAT_HPD_RISING				BIT(0)
+
+#define HDMI_OTHER_INTR_STAT_CLR					0x8006
+	#define HDMI_OTHER_INTR_STAT_CLR_EDID_RISING			BIT(2)
+	#define HDMI_OTHER_INTR_STAT_CLR_HPD_FALLING			BIT(1)
+	#define HDMI_OTHER_INTR_STAT_CLR_HPD_RISING			BIT(0)
+
+#define HDMI_OTHER_AVI_INTR_MASKN0					0x8008
+#define HDMI_OTHER_AVI_INTR_MASKN1					0x8009
+#define HDMI_OTHER_RX_AINFO_INTR_MASKN0					0x800a
+#define HDMI_OTHER_RX_AINFO_INTR_MASKN1					0x800b
+#define HDMI_OTHER_RX_PACKET_INTR_CLR					0x800c
+
+#endif /* __MESON_TRANSWITCH_HDMI_H__ */
diff --git a/drivers/gpu/drm/meson/meson_vclk.c b/drivers/gpu/drm/meson/meson_vclk.c
index 2a82119e..a2c1bf1a 100644
--- a/drivers/gpu/drm/meson/meson_vclk.c
+++ b/drivers/gpu/drm/meson/meson_vclk.c
@@ -732,6 +732,11 @@ meson_vclk_dmt_supported_freq(struct meson_drm *priv, unsigned int freq)
 			return MODE_CLOCK_HIGH;
 	}
 
+	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2))
+		return MODE_OK;
+
 	if (meson_hdmi_pll_find_params(priv, freq, &m, &frac, &od))
 		return MODE_OK;
 
@@ -784,6 +789,11 @@ meson_vclk_vic_supported_freq(struct meson_drm *priv, unsigned int phy_freq,
 			return MODE_CLOCK_HIGH;
 	}
 
+	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2))
+		return MODE_OK;
+
 	for (i = 0 ; params[i].pixel_freq ; ++i) {
 		DRM_DEBUG_DRIVER("i = %d pixel_freq = %d alt = %d\n",
 				 i, params[i].pixel_freq,
@@ -1024,6 +1034,128 @@ static void meson_vclk_set(struct meson_drm *priv, unsigned int pll_base_freq,
 	regmap_update_bits(priv->hhi, HHI_VID_CLK_CNTL, VCLK_EN, VCLK_EN);
 }
 
+static int meson_vclk_set_rate_exclusive(struct meson_drm *priv,
+					 enum vpu_bulk_clk_id clk_id,
+					 unsigned int rate_khz)
+{
+	struct clk *clk = priv->vid_clks[clk_id].clk;
+	int ret;
+
+	ret = clk_set_rate_exclusive(clk, rate_khz * 1000UL);
+	if (ret)
+		return ret;
+
+	priv->vid_clk_rate_exclusive[clk_id] = true;
+
+	return 0;
+}
+
+static void meson_vclk_disable_ccf(struct meson_drm *priv)
+{
+	unsigned int i;
+
+	/* allow all clocks to be changed in _enable again */
+	for (i = 0; i < VPU_VID_CLK_NUM; i++) {
+		if (!priv->vid_clk_rate_exclusive[i])
+			continue;
+
+		clk_rate_exclusive_put(priv->vid_clks[i].clk);
+		priv->vid_clk_rate_exclusive[i] = false;
+	}
+
+	if (priv->clk_dac_enabled) {
+		clk_disable(priv->clk_dac);
+		priv->clk_dac_enabled = false;
+	}
+
+	if (priv->clk_venc_enabled) {
+		clk_disable(priv->clk_venc);
+		priv->clk_venc_enabled = false;
+	}
+}
+
+static int meson_vclk_enable_ccf(struct meson_drm *priv, unsigned int target,
+				 bool hdmi_use_enci, unsigned int phy_freq,
+				 unsigned int dac_freq, unsigned int venc_freq)
+{
+	enum vpu_bulk_clk_id venc_clk_id, dac_clk_id;
+	int ret;
+
+	if (target == MESON_VCLK_TARGET_CVBS || hdmi_use_enci)
+		venc_clk_id = VPU_VID_CLK_CTS_ENCI;
+	else
+		venc_clk_id = VPU_VID_CLK_CTS_ENCP;
+
+	if (target == MESON_VCLK_TARGET_CVBS)
+		dac_clk_id = VPU_VID_CLK_CTS_VDAC0;
+	else
+		dac_clk_id = VPU_VID_CLK_HDMI_TX_PIXEL;
+
+	/*
+	 * The TMDS clock also updates the PLL. Protect the PLL rate so all
+	 * following clocks are derived from the PLL setting which matches the
+	 * TMDS clock.
+	 */
+	ret = meson_vclk_set_rate_exclusive(priv, VPU_VID_CLK_TMDS, phy_freq);
+	if (ret) {
+		dev_err(priv->dev, "Failed to set TMDS clock to %ukHz: %d\n",
+			phy_freq, ret);
+		goto out_enable_clocks;
+	}
+
+	/*
+	 * The DAC clock may be derived from a parent of the VENC clock so we
+	 * must protect the VENC clock from changing it's rate. This works
+	 * because the DAC freq can be divided by the VENC clock.
+	 */
+	ret = meson_vclk_set_rate_exclusive(priv, venc_clk_id, venc_freq);
+	if (ret) {
+		dev_warn(priv->dev,
+			 "Failed to set VENC clock to %ukHz while TMDS clock is %ukHz: %d\n",
+			 venc_freq, phy_freq, ret);
+		goto out_enable_clocks;
+	}
+
+	priv->clk_venc = priv->vid_clks[venc_clk_id].clk;
+
+	/*
+	 * after changing any of the VID_PLL_* clocks (which can happen when
+	 * update the VENC clock rate) we need to assert and then de-assert the
+	 * VID_DIVIDER_CNTL_* reset lines.
+	 */
+	reset_control_bulk_assert(VPU_RESET_VID_PLL_NUM, priv->vid_pll_resets);
+	reset_control_bulk_deassert(VPU_RESET_VID_PLL_NUM, priv->vid_pll_resets);
+
+	ret = meson_vclk_set_rate_exclusive(priv, dac_clk_id, dac_freq);
+	if (ret) {
+		dev_warn(priv->dev,
+			 "Failed to set pixel clock to %ukHz while TMDS clock is %ukHz: %d\n",
+			 dac_freq, phy_freq, ret);
+		goto out_enable_clocks;
+	}
+
+	priv->clk_dac = priv->vid_clks[dac_clk_id].clk;
+
+out_enable_clocks:
+	ret = clk_enable(priv->clk_venc);
+	if (ret)
+		dev_err(priv->dev,
+			"Failed to re-enable the VENC clock at %ukHz: %d\n",
+			venc_freq, ret);
+	else
+		priv->clk_venc_enabled = true;
+
+	ret = clk_enable(priv->clk_dac);
+	if (ret)
+		dev_err(priv->dev,
+			"Failed to re-enable the pixel clock at %ukHz: %d\n",
+			dac_freq, ret);
+	else
+		priv->clk_dac_enabled = true;
+
+	return ret;
+}
+
 void meson_vclk_setup(struct meson_drm *priv, unsigned int target,
 		      unsigned int phy_freq, unsigned int vclk_freq,
 		      unsigned int venc_freq, unsigned int dac_freq,
@@ -1034,6 +1166,20 @@ void meson_vclk_setup(struct meson_drm *priv, unsigned int target,
 	unsigned int hdmi_tx_div;
 	unsigned int venc_div;
 
+	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
+	    meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) {
+		/* CVBS video clocks are generated off a 1296MHz base clock */
+		if (target == MESON_VCLK_TARGET_CVBS)
+			phy_freq = 1296000;
+
+		dev_err(priv->dev, "%s(target: %u, phy: %u, dac: %u, venc: %u, hdmi_use_enci: %u)\n", __func__, target, phy_freq, dac_freq, venc_freq, hdmi_use_enci);
+		meson_vclk_disable_ccf(priv);
+		meson_vclk_enable_ccf(priv, target, hdmi_use_enci, phy_freq,
+				      dac_freq, venc_freq);
+		return;
+	}
+
 	if (target == MESON_VCLK_TARGET_CVBS) {
 		meson_venci_cvbs_clock_config(priv);
 		return;
diff --git a/drivers/gpu/drm/meson/meson_venc.c b/drivers/gpu/drm/meson/meson_venc.c
index 3bf0d6e4..d834359c 100644
--- a/drivers/gpu/drm/meson/meson_venc.c
+++ b/drivers/gpu/drm/meson/meson_venc.c
@@ -62,10 +62,6 @@
 
 /* HHI Registers */
 #define HHI_GCLK_MPEG2		0x148 /* 0x52 offset in data sheet */
-#define HHI_VDAC_CNTL0		0x2F4 /* 0xbd offset in data sheet */
-#define HHI_VDAC_CNTL0_G12A	0x2EC /* 0xbb offset in data sheet */
-#define HHI_VDAC_CNTL1		0x2F8 /* 0xbe offset in data sheet */
-#define HHI_VDAC_CNTL1_G12A	0x2F0 /* 0xbc offset in data sheet */
 #define HHI_HDMI_PHY_CNTL0	0x3a0 /* 0xe8 offset in data sheet */
 
 struct meson_cvbs_enci_mode meson_cvbs_enci_pal = {
@@ -1957,31 +1953,47 @@ void meson_venc_enable_vsync(struct meson_drm *priv)
 		writel_relaxed(VENC_INTCTRL_ENCI_LNRST_INT_EN,
 			       priv->io_base + _REG(VENC_INTCTRL));
 	}
-	regmap_update_bits(priv->hhi, HHI_GCLK_MPEG2, BIT(25), BIT(25));
+
+	if (priv->intr_clks[0].clk) {
+		if (!priv->intr_clks_enabled) {
+			int ret;
+
+			ret = clk_bulk_enable(priv->num_intr_clks,
+					      priv->intr_clks);
+			if (ret)
+				dev_err(priv->dev,
+					"Failed to enable the interrupt clocks\n");
+			else
+				priv->intr_clks_enabled = true;
+		}
+	} else {
+		regmap_update_bits(priv->hhi, HHI_GCLK_MPEG2, BIT(25), BIT(25));
+	}
 }
 
 void meson_venc_disable_vsync(struct meson_drm *priv)
 {
-	regmap_update_bits(priv->hhi, HHI_GCLK_MPEG2, BIT(25), 0);
+	if (priv->intr_clks[0].clk) {
+		if (priv->intr_clks_enabled) {
+			clk_bulk_disable(priv->num_intr_clks,
+					 priv->intr_clks);
+			priv->intr_clks_enabled = false;
+		}
+	} else {
+		regmap_update_bits(priv->hhi, HHI_GCLK_MPEG2, BIT(25), 0);
+	}
+
 	writel_relaxed(0, priv->io_base + _REG(VENC_INTCTRL));
 }
 
 void meson_venc_init(struct meson_drm *priv)
 {
-	/* Disable CVBS VDAC */
-	if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) {
-		regmap_write(priv->hhi, HHI_VDAC_CNTL0_G12A, 0);
-		regmap_write(priv->hhi, HHI_VDAC_CNTL1_G12A, 8);
-	} else {
-		regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0);
-		regmap_write(priv->hhi, HHI_VDAC_CNTL1, 8);
-	}
-
 	/* Power Down Dacs */
 	writel_relaxed(0xff, priv->io_base + _REG(VENC_VDAC_SETTING));
 
 	/* Disable HDMI PHY */
-	regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0);
+	if (priv->hhi)
+		regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0);
 
 	/* Disable HDMI */
 	writel_bits_relaxed(VPU_HDMI_ENCI_DATA_TO_HDMI |
diff --git a/drivers/gpu/drm/meson/meson_viu.c b/drivers/gpu/drm/meson/meson_viu.c
index cd399b0b..bdfa342c 100644
--- a/drivers/gpu/drm/meson/meson_viu.c
+++ b/drivers/gpu/drm/meson/meson_viu.c
@@ -448,13 +448,17 @@ void meson_viu_init(struct meson_drm *priv)
 	writel_relaxed(reg, priv->io_base + _REG(VIU_OSD1_FIFO_CTRL_STAT));
 	writel_relaxed(reg, priv->io_base + _REG(VIU_OSD2_FIFO_CTRL_STAT));
 
-	/* Set OSD alpha replace value */
-	writel_bits_relaxed(0xff << OSD_REPLACE_SHIFT,
-			    0xff << OSD_REPLACE_SHIFT,
-			    priv->io_base + _REG(VIU_OSD1_CTRL_STAT2));
-	writel_bits_relaxed(0xff << OSD_REPLACE_SHIFT,
-			    0xff << OSD_REPLACE_SHIFT,
-			    priv->io_base + _REG(VIU_OSD2_CTRL_STAT2));
+	if (!meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) &&
+	    !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) &&
+	    !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) {
+		/* Set OSD alpha replace value */
+		writel_bits_relaxed(0xff << OSD_REPLACE_SHIFT,
+				    0xff << OSD_REPLACE_SHIFT,
+				    priv->io_base + _REG(VIU_OSD1_CTRL_STAT2));
+		writel_bits_relaxed(0xff << OSD_REPLACE_SHIFT,
+				    0xff << OSD_REPLACE_SHIFT,
+				    priv->io_base + _REG(VIU_OSD2_CTRL_STAT2));
+	}
 
 	/* Disable VD1 AFBC */
 	/* di_mif0_en=0 mif0_to_vpp_en=0 di_mad_en=0 and afbc vd1 set=0*/
diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig
index ce7ba3eb..671435b6 100644
--- a/drivers/phy/amlogic/Kconfig
+++ b/drivers/phy/amlogic/Kconfig
@@ -25,6 +25,16 @@ config PHY_MESON8B_USB2
 	  Meson8b and GXBB SoCs.
 	  If unsure, say N.
 
+config PHY_MESON_CVBS_DAC
+	tristate "Amlogic Meson CVBS DAC PHY driver"
+	depends on ARCH_MESON || COMPILE_TEST
+	depends on OF
+	select MFD_SYSCON
+	help
+	  Enable this to support the CVBS DAC (PHY) found in Amlogic
+	  Meson SoCs.
+	  If unsure, say N.
+
 config PHY_MESON_GXL_USB2
 	tristate "Meson GXL and GXM USB2 PHY drivers"
 	default ARCH_MESON
diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile
index 91e3b979..f6c38f73 100644
--- a/drivers/phy/amlogic/Makefile
+++ b/drivers/phy/amlogic/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_PHY_MESON8_HDMI_TX)		+= phy-meson8-hdmi-tx.o
 obj-$(CONFIG_PHY_MESON8B_USB2)			+= phy-meson8b-usb2.o
+obj-$(CONFIG_PHY_MESON_CVBS_DAC)		+= phy-meson-cvbs-dac.o
 obj-$(CONFIG_PHY_MESON_GXL_USB2)		+= phy-meson-gxl-usb2.o
 obj-$(CONFIG_PHY_MESON_G12A_USB2)		+= phy-meson-g12a-usb2.o
 obj-$(CONFIG_PHY_MESON_G12A_USB3_PCIE)		+= phy-meson-g12a-usb3-pcie.o
diff --git a/drivers/phy/amlogic/phy-meson-cvbs-dac.c b/drivers/phy/amlogic/phy-meson-cvbs-dac.c
new file mode 100644
index 00000000..96549e63
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-cvbs-dac.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 BayLibre, SAS
+ * Copyright (C) 2021 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/property.h>
+#include <linux/mfd/syscon.h>
+#include <linux/nvmem-consumer.h>
+
+#define HHI_VDAC_CNTL0_MESON8			0x2F4 /* 0xbd offset in data sheet */
+#define HHI_VDAC_CNTL1_MESON8			0x2F8 /* 0xbe offset in data sheet */
+
+#define HHI_VDAC_CNTL0_G12A			0x2EC /* 0xbd offset in data sheet */
+#define HHI_VDAC_CNTL1_G12A			0x2F0 /* 0xbe offset in data sheet */
+
+enum phy_meson_cvbs_dac_reg {
+	MESON_CDAC_CTRL_RESV1,
+	MESON_CDAC_CTRL_RESV2,
+	MESON_CDAC_VREF_ADJ,
+	MESON_CDAC_RL_ADJ,
+	MESON_CDAC_CLK_PHASE_SEL,
+	MESON_CDAC_DRIVER_ADJ,
+	MESON_CDAC_EXT_VREF_EN,
+	MESON_CDAC_BIAS_C,
+	MESON_VDAC_CNTL0_RESERVED,
+	MESON_CDAC_GSW,
+	MESON_CDAC_PWD,
+	MESON_VDAC_CNTL1_RESERVED,
+	MESON_CVBS_DAC_NUM_REGS
+};
+
+struct phy_meson_cvbs_dac_data {
+	const struct reg_field	*reg_fields;
+	u8			cdac_ctrl_resv2_enable_val;
+	u8			cdac_vref_adj_enable_val;
+	u8			cdac_rl_adj_enable_val;
+	bool			disable_ignore_cdac_pwd;
+	bool			needs_cvbs_trimming_nvmem_cell;
+};
+
+struct phy_meson_cvbs_dac_priv {
+	struct regmap_field			*regs[MESON_CVBS_DAC_NUM_REGS];
+	const struct phy_meson_cvbs_dac_data	*data;
+	u8					cdac_gsw_enable_val;
+};
+
+static const struct reg_field phy_meson8_cvbs_dac_reg_fields[] = {
+	[MESON_CDAC_CTRL_RESV1] =	REG_FIELD(HHI_VDAC_CNTL0_MESON8, 0, 7),
+	[MESON_CDAC_CTRL_RESV2] =	REG_FIELD(HHI_VDAC_CNTL0_MESON8, 8, 15),
+	[MESON_CDAC_VREF_ADJ] =		REG_FIELD(HHI_VDAC_CNTL0_MESON8, 16, 20),
+	[MESON_CDAC_RL_ADJ] =		REG_FIELD(HHI_VDAC_CNTL0_MESON8, 21, 23),
+	[MESON_CDAC_CLK_PHASE_SEL] =	REG_FIELD(HHI_VDAC_CNTL0_MESON8, 24, 24),
+	[MESON_CDAC_DRIVER_ADJ] =	REG_FIELD(HHI_VDAC_CNTL0_MESON8, 25, 25),
+	[MESON_CDAC_EXT_VREF_EN] =	REG_FIELD(HHI_VDAC_CNTL0_MESON8, 26, 26),
+	[MESON_CDAC_BIAS_C] =		REG_FIELD(HHI_VDAC_CNTL0_MESON8, 27, 27),
+	[MESON_VDAC_CNTL0_RESERVED] =	REG_FIELD(HHI_VDAC_CNTL0_MESON8, 28, 31),
+	[MESON_CDAC_GSW] =		REG_FIELD(HHI_VDAC_CNTL1_MESON8, 0, 2),
+	[MESON_CDAC_PWD] =		REG_FIELD(HHI_VDAC_CNTL1_MESON8, 3, 3),
+	[MESON_VDAC_CNTL1_RESERVED] =	REG_FIELD(HHI_VDAC_CNTL1_MESON8, 4, 31),
+};
+
+static const struct reg_field phy_meson_g12a_cvbs_dac_reg_fields[] = {
+	[MESON_CDAC_CTRL_RESV1] =	REG_FIELD(HHI_VDAC_CNTL0_G12A, 0, 7),
+	[MESON_CDAC_CTRL_RESV2] =	REG_FIELD(HHI_VDAC_CNTL0_G12A, 8, 15),
+	[MESON_CDAC_VREF_ADJ] =		REG_FIELD(HHI_VDAC_CNTL0_G12A, 16, 20),
+	[MESON_CDAC_RL_ADJ] =		REG_FIELD(HHI_VDAC_CNTL0_G12A, 21, 23),
+	[MESON_CDAC_CLK_PHASE_SEL] =	REG_FIELD(HHI_VDAC_CNTL0_G12A, 24, 24),
+	[MESON_CDAC_DRIVER_ADJ] =	REG_FIELD(HHI_VDAC_CNTL0_G12A, 25, 25),
+	[MESON_CDAC_EXT_VREF_EN] =	REG_FIELD(HHI_VDAC_CNTL0_G12A, 26, 26),
+	[MESON_CDAC_BIAS_C] =		REG_FIELD(HHI_VDAC_CNTL0_G12A, 27, 27),
+	[MESON_VDAC_CNTL0_RESERVED] =	REG_FIELD(HHI_VDAC_CNTL0_G12A, 28, 31),
+	[MESON_CDAC_GSW] =		REG_FIELD(HHI_VDAC_CNTL1_G12A, 0, 2),
+	[MESON_CDAC_PWD] =		REG_FIELD(HHI_VDAC_CNTL1_G12A, 3, 3),
+	[MESON_VDAC_CNTL1_RESERVED] =	REG_FIELD(HHI_VDAC_CNTL1_G12A, 4, 31),
+};
+
+static const struct phy_meson_cvbs_dac_data phy_meson8_cvbs_dac_data = {
+	.reg_fields			= phy_meson8_cvbs_dac_reg_fields,
+	.cdac_ctrl_resv2_enable_val	= 0x0,
+	.cdac_vref_adj_enable_val	= 0x0,
+	.cdac_rl_adj_enable_val		= 0x0,
+	.disable_ignore_cdac_pwd	= false,
+	.needs_cvbs_trimming_nvmem_cell	= true,
+};
+
+static const struct phy_meson_cvbs_dac_data phy_meson_gxbb_cvbs_dac_data = {
+	.reg_fields			= phy_meson8_cvbs_dac_reg_fields,
+	.cdac_ctrl_resv2_enable_val	= 0x0,
+	.cdac_vref_adj_enable_val	= 0x0,
+	.cdac_rl_adj_enable_val		= 0x0,
+	.disable_ignore_cdac_pwd	= false,
+	.needs_cvbs_trimming_nvmem_cell	= false,
+};
+
+static const struct phy_meson_cvbs_dac_data phy_meson_gxl_cvbs_dac_data = {
+	.reg_fields			= phy_meson8_cvbs_dac_reg_fields,
+	.cdac_ctrl_resv2_enable_val	= 0x0,
+	.cdac_vref_adj_enable_val	= 0xf,
+	.cdac_rl_adj_enable_val		= 0x0,
+	.disable_ignore_cdac_pwd	= false,
+	.needs_cvbs_trimming_nvmem_cell	= false,
+};
+
+static const struct phy_meson_cvbs_dac_data phy_meson_g12a_cvbs_dac_data = {
+	.reg_fields			= phy_meson_g12a_cvbs_dac_reg_fields,
+	.cdac_ctrl_resv2_enable_val	= 0x60,
+	.cdac_vref_adj_enable_val	= 0x10,
+	.cdac_rl_adj_enable_val		= 0x4,
+	.disable_ignore_cdac_pwd	= true,
+	.needs_cvbs_trimming_nvmem_cell	= false,
+};
+
+static int phy_meson_cvbs_dac_power_on(struct phy *phy)
+{
+	struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy);
+
+	regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV1], 0x1);
+	regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV2],
+			   priv->data->cdac_ctrl_resv2_enable_val);
+	regmap_field_write(priv->regs[MESON_CDAC_VREF_ADJ],
+			   priv->data->cdac_vref_adj_enable_val);
+	regmap_field_write(priv->regs[MESON_CDAC_RL_ADJ],
+			   priv->data->cdac_rl_adj_enable_val);
+	regmap_field_write(priv->regs[MESON_CDAC_GSW],
+			   priv->cdac_gsw_enable_val);
+	regmap_field_write(priv->regs[MESON_CDAC_PWD], 0x0);
+
+	return 0;
+}
+
+static int phy_meson_cvbs_dac_power_off(struct phy *phy)
+{
+	struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy);
+
+	regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV1], 0x0);
+	regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV2], 0x0);
+	regmap_field_write(priv->regs[MESON_CDAC_VREF_ADJ], 0x0);
+	regmap_field_write(priv->regs[MESON_CDAC_RL_ADJ], 0x0);
+	regmap_field_write(priv->regs[MESON_CDAC_GSW], 0x0);
+
+	if (priv->data->disable_ignore_cdac_pwd)
+		regmap_field_write(priv->regs[MESON_CDAC_PWD], 0x0);
+	else
+		regmap_field_write(priv->regs[MESON_CDAC_PWD], 0x1);
+
+	return 0;
+}
+
+static int phy_meson_cvbs_dac_init(struct phy *phy)
+{
+	struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy);
+
+	regmap_field_write(priv->regs[MESON_CDAC_CLK_PHASE_SEL], 0x0);
+	regmap_field_write(priv->regs[MESON_CDAC_DRIVER_ADJ], 0x0);
+	regmap_field_write(priv->regs[MESON_CDAC_EXT_VREF_EN], 0x0);
+	regmap_field_write(priv->regs[MESON_CDAC_BIAS_C], 0x0);
+	regmap_field_write(priv->regs[MESON_VDAC_CNTL0_RESERVED], 0x0);
+	regmap_field_write(priv->regs[MESON_VDAC_CNTL1_RESERVED], 0x0);
+
+	return phy_meson_cvbs_dac_power_off(phy);
+}
+
+static const struct phy_ops phy_meson_cvbs_dac_ops = {
+	.init		= phy_meson_cvbs_dac_init,
+	.power_on	= phy_meson_cvbs_dac_power_on,
+	.power_off	= phy_meson_cvbs_dac_power_off,
+	.owner		= THIS_MODULE,
+};
+
+static u8 phy_meson_cvbs_trimming_version(u8 trimming1)
+{
+	if ((trimming1 & 0xf0) == 0xa0)
+		return 5;
+	else if ((trimming1 & 0xf0) == 0x40)
+		return 2;
+	else if ((trimming1 & 0xc0) == 0x80)
+		return 1;
+	else if ((trimming1 & 0xc0) == 0x00)
+		return 0;
+	else
+		return 0xff;
+}
+
+static int phy_meson_cvbs_read_trimming(struct device *dev,
+					struct phy_meson_cvbs_dac_priv *priv)
+{
+	struct nvmem_cell *cell;
+	u8 *trimming;
+	size_t len;
+
+	cell = devm_nvmem_cell_get(dev, "cvbs_trimming");
+	if (IS_ERR(cell))
+		return dev_err_probe(dev, PTR_ERR(cell),
+				     "Failed to get the 'cvbs_trimming' nvmem-cell\n");
+
+	trimming = nvmem_cell_read(cell, &len);
+	if (IS_ERR(trimming)) {
+		/*
+		 * TrustZone firmware may block access to the CVBS trimming
+		 * data stored in the eFuse. On those devices the trimming data
+		 * is stored in the u-boot environment. However, the known
+		 * examples of trimming data in the u-boot environment are all
+		 * zero.
+		 */
+		dev_dbg(dev,
+			"Failed to read the 'cvbs_trimming' nvmem-cell - falling back to a default value\n");
+		priv->cdac_gsw_enable_val = 0x0;
+		return 0;
+	}
+
+	if (len != 2) {
+		kfree(trimming);
+		return dev_err_probe(dev, -EINVAL,
+				     "Read the 'cvbs_trimming' nvmem-cell with invalid length\n");
+	}
+
+	switch (phy_meson_cvbs_trimming_version(trimming[1])) {
+	case 1:
+	case 2:
+	case 5:
+		priv->cdac_gsw_enable_val = trimming[0] & 0x7;
+		break;
+	default:
+		priv->cdac_gsw_enable_val = 0x0;
+		break;
+	}
+
+	kfree(trimming);
+
+	return 0;
+}
+
+static int phy_meson_cvbs_dac_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct phy_meson_cvbs_dac_priv *priv;
+	struct phy_provider *phy_provider;
+	struct device *dev = &pdev->dev;
+	struct regmap *hhi;
+	struct phy *phy;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	if (np) {
+		priv->data = device_get_match_data(dev);
+		if (!priv->data)
+			return dev_err_probe(dev, -EINVAL,
+					     "Could not find the OF match data\n");
+
+		hhi = syscon_node_to_regmap(np->parent);
+		if (IS_ERR(hhi))
+			return dev_err_probe(dev, PTR_ERR(hhi),
+					     "Failed to get the parent syscon\n");
+	} else {
+		const struct platform_device_id *pdev_id;
+
+		pdev_id = platform_get_device_id(pdev);
+		if (!pdev_id)
+			return dev_err_probe(dev, -EINVAL,
+					     "Failed to find platform device ID\n");
+
+		priv->data = (void *)pdev_id->driver_data;
+		if (!priv->data)
+			return dev_err_probe(dev, -EINVAL,
+					     "Could not find the platform driver data\n");
+
+		hhi = syscon_regmap_lookup_by_compatible("amlogic,meson-gx-hhi-sysctrl");
+		if (IS_ERR(hhi))
+			return dev_err_probe(dev, PTR_ERR(hhi),
+					     "Failed to get the \"amlogic,meson-gx-hhi-sysctrl\" syscon\n");
+	}
+
+	if (priv->data->needs_cvbs_trimming_nvmem_cell) {
+		ret = phy_meson_cvbs_read_trimming(dev, priv);
+		if (ret)
+			return ret;
+	}
+
+	ret = devm_regmap_field_bulk_alloc(dev, hhi, priv->regs,
+					   priv->data->reg_fields,
+					   MESON_CVBS_DAC_NUM_REGS);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to create regmap fields\n");
+
+	phy = devm_phy_create(dev, np, &phy_meson_cvbs_dac_ops);
+	if (IS_ERR(phy))
+		return PTR_ERR(phy);
+
+	phy_set_drvdata(phy, priv);
+
+	if (np) {
+		phy_provider = devm_of_phy_provider_register(dev,
+							     of_phy_simple_xlate);
+		ret = PTR_ERR_OR_ZERO(phy_provider);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "Failed to register PHY provider\n");
+	}
+
+	platform_set_drvdata(pdev, phy);
+
+	return 0;
+}
+
+static const struct of_device_id phy_meson_cvbs_dac_of_match[] = {
+	{
+		.compatible = "amlogic,meson8-cvbs-dac",
+		.data = &phy_meson8_cvbs_dac_data,
+	},
+	{
+		.compatible = "amlogic,meson-gxbb-cvbs-dac",
+		.data = &phy_meson_gxbb_cvbs_dac_data,
+	},
+	{
+		.compatible = "amlogic,meson-gxl-cvbs-dac",
+		.data = &phy_meson_gxl_cvbs_dac_data,
+	},
+	{
+		.compatible = "amlogic,meson-g12a-cvbs-dac",
+		.data = &phy_meson_g12a_cvbs_dac_data,
+	},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, phy_meson_cvbs_dac_of_match);
+
+/*
+ * The platform_device_id table is used for backwards compatibility with old
+ * .dtbs which don't have a CVBS DAC node (where the VPU DRM driver registers
+ * this as a platform device. Support for additional SoCs should only be added
+ * to the of_device_id table above.
+ */
+static const struct platform_device_id phy_meson_cvbs_dac_device_ids[] = {
+	{
+		.name = "meson-gxbb-cvbs-dac",
+		.driver_data = (kernel_ulong_t)&phy_meson_gxbb_cvbs_dac_data,
+	},
+	{
+		.name = "meson-gxl-cvbs-dac",
+		.driver_data = (kernel_ulong_t)&phy_meson_gxl_cvbs_dac_data,
+	},
+	{
+		.name = "meson-g12a-cvbs-dac",
+		.driver_data = (kernel_ulong_t)&phy_meson_g12a_cvbs_dac_data,
+	},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, phy_meson_cvbs_dac_device_ids);
+
+static struct platform_driver phy_meson_cvbs_dac_driver = {
+	.driver = {
+		.name		= "phy-meson-cvbs-dac",
+		.of_match_table	= phy_meson_cvbs_dac_of_match,
+	},
+	.id_table = phy_meson_cvbs_dac_device_ids,
+	.probe = phy_meson_cvbs_dac_probe,
+};
+module_platform_driver(phy_meson_cvbs_dac_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Amlogic Meson CVBS DAC driver");
+MODULE_LICENSE("GPL v2");
-- 
2.34.1

