build/patch/kernel/archive/meson-6.1/generic-0001-m8-m8b-m8m2-Support-HDMI.patch

4407 lines
137 KiB
Diff

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/meson.dtsi | 13 +
arch/arm/boot/dts/meson8.dtsi | 168 +-
arch/arm/boot/dts/meson8b.dtsi | 171 +-
arch/arm/boot/dts/meson8m2.dtsi | 4 +
drivers/gpu/drm/meson/Kconfig | 9 +
drivers/gpu/drm/meson/Makefile | 1 +
drivers/gpu/drm/meson/meson_drv.c | 315 +++-
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, 3577 insertions(+), 126 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 6655a93b187..bbc58c8bdfc 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
@@ -125,6 +138,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 00000000000..d73cb12c0d9
--- /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/meson.dtsi b/arch/arm/boot/dts/meson.dtsi
index 8e3860d5d91..9a56cdf776a 100644
--- a/arch/arm/boot/dts/meson.dtsi
+++ b/arch/arm/boot/dts/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/meson8.dtsi b/arch/arm/boot/dts/meson8.dtsi
index 0f8bac8bac8..646bb8b102b 100644
--- a/arch/arm/boot/dts/meson8.dtsi
+++ b/arch/arm/boot/dts/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",
@@ -584,6 +724,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>;
@@ -593,6 +744,10 @@ temperature_calib: calib@1f4 {
/* only the upper two bytes are relevant */
reg = <0x1f4 0x4>;
};
+
+ cvbs_trimming: calib@1f8 {
+ reg = <0x1f8 0x2>;
+ };
};
&ethmac {
@@ -608,16 +763,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>;
@@ -625,6 +782,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/meson8b.dtsi b/arch/arm/boot/dts/meson8b.dtsi
index cf9c04a61ba..20acfc607f0 100644
--- a/arch/arm/boot/dts/meson8b.dtsi
+++ b/arch/arm/boot/dts/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/meson8m2.dtsi b/arch/arm/boot/dts/meson8m2.dtsi
index 6725dd9fd82..fcb2ad97609 100644
--- a/arch/arm/boot/dts/meson8m2.dtsi
+++ b/arch/arm/boot/dts/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 823909da87d..ba9f1bc8408 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"
@@ -17,3 +18,11 @@ config DRM_MESON_DW_HDMI
default y if DRM_MESON
select DRM_DW_HDMI
imply DRM_DW_HDMI_I2S_AUDIO
+
+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 3afa31bdc95..817a5270aee 100644
--- a/drivers/gpu/drm/meson/Makefile
+++ b/drivers/gpu/drm/meson/Makefile
@@ -6,3 +6,4 @@ meson-drm-y += meson_encoder_hdmi.o
obj-$(CONFIG_DRM_MESON) += meson-drm.o
obj-$(CONFIG_DRM_MESON_DW_HDMI) += meson_dw_hdmi.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 3b24a924b7b..091c670ab0e 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>
@@ -132,30 +133,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_generic_setup(priv->drm, preferred_bpp);
}
struct meson_drm_soc_attr {
@@ -164,13 +282,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 */ }
}
},
@@ -211,67 +345,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) {
@@ -287,11 +477,11 @@ static int meson_drv_bind_master(struct device *dev, bool has_components)
*/
ret = drm_aperture_remove_framebuffers(false, &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;
@@ -306,7 +496,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 */
@@ -319,7 +509,7 @@ static int meson_drv_bind_master(struct device *dev, bool has_components)
ret = component_bind_all(drm->dev, drm);
if (ret) {
dev_err(drm->dev, "Couldn't bind all components\n");
- goto exit_afbcd;
+ goto exit_cvbs_dac_phy;
}
}
@@ -353,7 +543,7 @@ static int meson_drv_bind_master(struct device *dev, bool has_components)
if (ret)
goto uninstall_irq;
- drm_fbdev_generic_setup(drm, 32);
+ meson_fbdev_setup(priv);
return 0;
@@ -362,6 +552,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);
@@ -398,6 +592,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 = {
@@ -412,6 +610,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);
}
@@ -422,6 +622,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);
@@ -504,6 +705,18 @@ static int meson_drv_remove(struct platform_device *pdev)
return 0;
}
+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,
};
@@ -523,6 +736,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 c62ee358456..fe0a8f8762f 100644
--- a/drivers/gpu/drm/meson/meson_drv.h
+++ b/drivers/gpu/drm/meson/meson_drv.h
@@ -7,22 +7,29 @@
#ifndef __MESON_DRV_H
#define __MESON_DRV_H
+#include <linux/clk.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_device.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 3f73b211fa8..833f701fe27 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 53231bfdf7e..f950c557d5f 100644
--- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c
+++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c
@@ -188,13 +188,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);
@@ -215,7 +215,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)
@@ -227,17 +234,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");
@@ -260,7 +272,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,
@@ -274,13 +290,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),
@@ -432,8 +462,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 815dfe30492..27e39577218 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 00000000000..e88bdba7c16
--- /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 00000000000..14929475c0c
--- /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 2a82119eb58..a2c1bf1aed7 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 3c55ed00335..009882bda7b 100644
--- a/drivers/gpu/drm/meson/meson_venc.c
+++ b/drivers/gpu/drm/meson/meson_venc.c
@@ -60,10 +60,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 = {
@@ -1749,31 +1745,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 cd399b0b718..bdfa342c4ca 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 ce7ba3eb2a8..671435b605f 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 91e3b9790c0..f6c38f7386a 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 00000000000..96549e63bc1
--- /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