diff --git a/config/kernel/linux-meson-current.config b/config/kernel/linux-meson-current.config index 5717a80ecf..a0cdd4e819 100644 --- a/config/kernel/linux-meson-current.config +++ b/config/kernel/linux-meson-current.config @@ -5710,7 +5710,7 @@ CONFIG_DRM_I2C_SIL164=m # ARM devices # # CONFIG_DRM_HDLCD is not set -# CONFIG_DRM_MALI_DISPLAY is not set +CONFIG_DRM_MALI_DISPLAY=y # CONFIG_DRM_KOMEDA is not set # end of ARM devices @@ -5857,6 +5857,7 @@ CONFIG_DRM_ETNAVIV_THERMAL=y # CONFIG_DRM_MXSFB is not set CONFIG_DRM_MESON=y CONFIG_DRM_MESON_DW_HDMI=y +CONFIG_DRM_MESON_TRANSWITCH_HDMI=y # CONFIG_DRM_ARCPGU is not set # CONFIG_DRM_BOCHS is not set # CONFIG_DRM_CIRRUS_QEMU is not set @@ -5872,7 +5873,7 @@ CONFIG_TINYDRM_ILI9486=m # CONFIG_TINYDRM_ST7735R is not set # CONFIG_DRM_PL111 is not set # CONFIG_DRM_TVE200 is not set -# CONFIG_DRM_LIMA is not set +CONFIG_DRM_LIMA=y # CONFIG_DRM_PANFROST is not set # CONFIG_DRM_MCDE is not set CONFIG_DRM_TIDSS=m @@ -8205,7 +8206,9 @@ CONFIG_RESET_MESON_AUDIO_ARB=m CONFIG_GENERIC_PHY=y CONFIG_GENERIC_PHY_MIPI_DPHY=y # CONFIG_PHY_CAN_TRANSCEIVER is not set +CONFIG_PHY_MESON8_HDMI_TX=y CONFIG_PHY_MESON8B_USB2=y +CONFIG_PHY_MESON_CVBS_DAC=y CONFIG_PHY_MESON_GXL_USB2=y CONFIG_PHY_MESON_G12A_USB2=y CONFIG_PHY_MESON_G12A_USB3_PCIE=y diff --git a/config/kernel/linux-meson-legacy.config b/config/kernel/linux-meson-legacy.config index 8dc64c288e..797b920fe4 100644 --- a/config/kernel/linux-meson-legacy.config +++ b/config/kernel/linux-meson-legacy.config @@ -5400,7 +5400,7 @@ CONFIG_DRM_I2C_SIL164=m # ARM devices # # CONFIG_DRM_HDLCD is not set -# CONFIG_DRM_MALI_DISPLAY is not set +CONFIG_DRM_MALI_DISPLAY=y # CONFIG_DRM_KOMEDA is not set # end of ARM devices @@ -5522,6 +5522,7 @@ CONFIG_DRM_DW_HDMI=y # CONFIG_DRM_DW_HDMI_AHB_AUDIO is not set # CONFIG_DRM_DW_HDMI_I2S_AUDIO is not set # CONFIG_DRM_DW_HDMI_CEC is not set +CONFIG_DRM_TRANSWITCH_TXC_48352=y # end of Display Interface Bridges # CONFIG_DRM_STI is not set @@ -5531,6 +5532,7 @@ CONFIG_DRM_ETNAVIV_THERMAL=y # CONFIG_DRM_MXSFB is not set CONFIG_DRM_MESON=y CONFIG_DRM_MESON_DW_HDMI=y +CONFIG_DRM_MESON_MX_HDMI=y # CONFIG_DRM_CIRRUS_QEMU is not set # CONFIG_DRM_GM12U320 is not set CONFIG_TINYDRM_HX8357D=m @@ -5543,7 +5545,7 @@ CONFIG_TINYDRM_ILI9341=m # CONFIG_TINYDRM_ST7735R is not set # CONFIG_DRM_PL111 is not set # CONFIG_DRM_TVE200 is not set -# CONFIG_DRM_LIMA is not set +CONFIG_DRM_LIMA=y # CONFIG_DRM_PANFROST is not set # CONFIG_DRM_MCDE is not set # CONFIG_DRM_TIDSS is not set @@ -7773,6 +7775,7 @@ CONFIG_RESET_MESON_AUDIO_ARB=m # CONFIG_GENERIC_PHY=y CONFIG_GENERIC_PHY_MIPI_DPHY=y +CONFIG_PHY_MESON8_HDMI_TX=y CONFIG_PHY_MESON8B_USB2=y CONFIG_PHY_MESON_GXL_USB2=y CONFIG_PHY_MESON_G12A_USB2=y diff --git a/patch/kernel/archive/meson-5.10/board_odroidc1/dts-Enable-HDMI.patch b/patch/kernel/archive/meson-5.10/board_odroidc1/dts-Enable-HDMI.patch new file mode 100644 index 0000000000..136765cd44 --- /dev/null +++ b/patch/kernel/archive/meson-5.10/board_odroidc1/dts-Enable-HDMI.patch @@ -0,0 +1,57 @@ +From e59c976f9dea2d6e010dd1a615565117a31f047a Mon Sep 17 00:00:00 2001 +From: Martin Blumenstingl +Date: Fri, 20 Mar 2020 15:17:51 +0100 +Subject: [PATCH 1/1] ARM: dts: meson8b: odroid-c1: enable HDMI for the + Odroid-C1 - WiP + +WiP + +Signed-off-by: Martin Blumenstingl +--- + arch/arm/boot/dts/meson8b-odroidc1.dts | 23 +++++++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/arch/arm/boot/dts/meson8b-odroidc1.dts b/arch/arm/boot/dts/meson8b-odroidc1.dts +index 386dd44daba..af49a50a69a 100644 +--- a/arch/arm/boot/dts/meson8b-odroidc1.dts ++++ b/arch/arm/boot/dts/meson8b-odroidc1.dts +@@ -32,6 +32,17 @@ emmc_pwrseq: emmc-pwrseq { + reset-gpios = <&gpio BOOT_9 GPIO_ACTIVE_LOW>; + }; + ++ hdmi-connector { ++ compatible = "hdmi-connector"; ++ type = "a"; ++ ++ port { ++ hdmi_connector_in: endpoint { ++ remote-endpoint = <&hdmi_tx_tmds_out>; ++ }; ++ }; ++ }; ++ + leds { + compatible = "gpio-leds"; + blue { +@@ -298,6 +309,18 @@ usb-hub { + }; + }; + ++&hdmi_tx { ++ status = "okay"; ++ pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; ++ pinctrl-names = "default"; ++}; ++ ++&hdmi_tx_tmds_port { ++ hdmi_tx_tmds_out: endpoint { ++ remote-endpoint = <&hdmi_connector_in>; ++ }; ++}; ++ + &ir_receiver { + status = "okay"; + pinctrl-0 = <&ir_recv_pins>; +-- +2.25.1 + diff --git a/patch/kernel/archive/meson-5.10/board_onecloud/add-dts.patch b/patch/kernel/archive/meson-5.10/board_onecloud/0001-add-dts.patch similarity index 100% rename from patch/kernel/archive/meson-5.10/board_onecloud/add-dts.patch rename to patch/kernel/archive/meson-5.10/board_onecloud/0001-add-dts.patch diff --git a/patch/kernel/archive/meson-5.10/board_onecloud/0002-dts-Support-HDMI.patch b/patch/kernel/archive/meson-5.10/board_onecloud/0002-dts-Support-HDMI.patch new file mode 100644 index 0000000000..b901f6dd5a --- /dev/null +++ b/patch/kernel/archive/meson-5.10/board_onecloud/0002-dts-Support-HDMI.patch @@ -0,0 +1,92 @@ +dts: Support HDMI + +--- + arch/arm/boot/dts/meson8b-onecloud.dts | 58 ++++++++++++++++++++++++++ + 1 file changed, 58 insertions(+) + +diff --git a/arch/arm/boot/dts/meson8b-onecloud.dts b/arch/arm/boot/dts/meson8b-onecloud.dts +index 050b2e65348..69fff33c496 100644 +--- a/arch/arm/boot/dts/meson8b-onecloud.dts ++++ b/arch/arm/boot/dts/meson8b-onecloud.dts +@@ -32,6 +32,48 @@ emmc_pwrseq: emmc-pwrseq { + reset-gpios = <&gpio BOOT_9 GPIO_ACTIVE_LOW>; + }; + ++ hdmi-connector { ++ compatible = "hdmi-connector"; ++ type = "a"; ++ ++ port { ++ hdmi_connector_in: endpoint { ++ remote-endpoint = <&hdmi_tx_tmds_out>; ++ }; ++ }; ++ }; ++ ++ sound { ++ compatible = "amlogic,gx-sound-card"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-rates = <294912000>, ++ <270950400>; ++ ++ dai-link-0 { ++ sound-dai = <&aiu AIU_CPU CPU_I2S_FIFO>; ++ }; ++ ++ dai-link-1 { ++ sound-dai = <&aiu AIU_CPU CPU_I2S_ENCODER>; ++ dai-format = "i2s"; ++ mclk-fs = <256>; ++ ++ codec-0 { ++ sound-dai = <&aiu AIU_HDMI CTRL_I2S>; ++ }; ++ }; ++ ++ dai-link-2 { ++ sound-dai = <&aiu AIU_HDMI CTRL_OUT>; ++ ++ codec-0 { ++ sound-dai = <&hdmi_tx 0>; ++ }; ++ }; ++ }; ++ + button { + // compatible = "gpio-keys-polled"; + // poll-interval = <100>; +@@ -142,6 +184,10 @@ vcc_core: regulator-vcc-core { + }; + }; + ++&aiu { ++ status = "okay"; ++}; ++ + &cpu0 { + cpu-supply = <&vcc_core>; + }; +@@ -150,6 +196,18 @@ &mali { + mali-supply = <&vcc_core>; + }; + ++&hdmi_tx { ++ status = "okay"; ++ pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; ++ pinctrl-names = "default"; ++}; ++ ++&hdmi_tx_tmds_port { ++ hdmi_tx_tmds_out: endpoint { ++ remote-endpoint = <&hdmi_connector_in>; ++ }; ++}; ++ + &gpio { + gpio-line-names = + // 0-3 Bank GPIOX 0-3 +-- +2.25.1 + diff --git a/patch/kernel/archive/meson-5.10/m8-m8b-m8m2-Support-HDMI.patch b/patch/kernel/archive/meson-5.10/m8-m8b-m8m2-Support-HDMI.patch new file mode 100644 index 0000000000..1624edd69f --- /dev/null +++ b/patch/kernel/archive/meson-5.10/m8-m8b-m8m2-Support-HDMI.patch @@ -0,0 +1,5177 @@ +meson8/meson8b/meson8m2: Support HDMI + +The following codes are come from https://github.com/xdarklight/linux/commits/meson-mx-integration-5.11-20210124. + +Special thank. + +--- + arch/arm/boot/dts/meson.dtsi | 11 + + arch/arm/boot/dts/meson8.dtsi | 166 +- + arch/arm/boot/dts/meson8b.dtsi | 163 ++ + arch/arm/boot/dts/meson8m2.dtsi | 4 + + drivers/clk/meson/meson8b.c | 177 +- + drivers/clk/meson/meson8b.h | 22 +- + drivers/gpu/drm/bridge/Kconfig | 2 + + drivers/gpu/drm/bridge/Makefile | 1 + + drivers/gpu/drm/bridge/transwitch/Kconfig | 8 + + drivers/gpu/drm/bridge/transwitch/Makefile | 2 + + .../drm/bridge/transwitch/txccq-txc-48352.c | 1598 +++++++++++++++++ + .../drm/bridge/transwitch/txccq-txc-48352.h | 537 ++++++ + drivers/gpu/drm/meson/Kconfig | 7 + + drivers/gpu/drm/meson/Makefile | 1 + + drivers/gpu/drm/meson/meson_drv.c | 316 +++- + drivers/gpu/drm/meson/meson_drv.h | 41 +- + drivers/gpu/drm/meson/meson_dw_hdmi.c | 79 +- + drivers/gpu/drm/meson/meson_mx_hdmi.c | 432 +++++ + drivers/gpu/drm/meson/meson_osd_afbcd.c | 41 +- + drivers/gpu/drm/meson/meson_osd_afbcd.h | 1 - + drivers/gpu/drm/meson/meson_plane.c | 33 +- + drivers/gpu/drm/meson/meson_registers.h | 5 - + drivers/gpu/drm/meson/meson_vclk.c | 100 +- + drivers/gpu/drm/meson/meson_venc.c | 64 +- + drivers/gpu/drm/meson/meson_venc.h | 4 + + drivers/gpu/drm/meson/meson_venc_cvbs.c | 17 +- + drivers/gpu/drm/meson/meson_viu.c | 45 +- + drivers/phy/amlogic/Kconfig | 11 + + drivers/phy/amlogic/Makefile | 1 + + drivers/phy/amlogic/phy-meson8-hdmi-tx.c | 150 ++ + include/drm/bridge/txccq_txc_48352.h | 16 + + include/dt-bindings/clock/meson8b-clkc.h | 7 + + 32 files changed, 3803 insertions(+), 259 deletions(-) + create mode 100644 drivers/gpu/drm/bridge/transwitch/Kconfig + create mode 100644 drivers/gpu/drm/bridge/transwitch/Makefile + create mode 100644 drivers/gpu/drm/bridge/transwitch/txccq-txc-48352.c + create mode 100644 drivers/gpu/drm/bridge/transwitch/txccq-txc-48352.h + create mode 100644 drivers/gpu/drm/meson/meson_mx_hdmi.c + create mode 100644 drivers/phy/amlogic/phy-meson8-hdmi-tx.c + create mode 100644 include/drm/bridge/txccq_txc_48352.h + +diff --git a/arch/arm/boot/dts/meson.dtsi b/arch/arm/boot/dts/meson.dtsi +index c928ae312..6fa8c89e1 100644 +--- a/arch/arm/boot/dts/meson.dtsi ++++ b/arch/arm/boot/dts/meson.dtsi +@@ -5,6 +5,7 @@ + + #include + #include ++#include + + / { + #address-cells = <1>; +@@ -31,6 +32,16 @@ hhi: system-controller@4000 { + reg = <0x4000 0x400>; + }; + ++ aiu: audio-controller@5400 { ++ compatible = "amlogic,aiu"; ++ #sound-dai-cells = <2>; ++ reg = <0x5400 0x2ac>; ++ interrupts = , ++ ; ++ interrupt-names = "i2s", "spdif"; ++ status = "disabled"; ++ }; ++ + assist: assist@7c00 { + compatible = "amlogic,meson-mx-assist", "syscon"; + reg = <0x7c00 0x200>; +diff --git a/arch/arm/boot/dts/meson8.dtsi b/arch/arm/boot/dts/meson8.dtsi +index 08533116a..b1f6a3406 100644 +--- a/arch/arm/boot/dts/meson8.dtsi ++++ b/arch/arm/boot/dts/meson8.dtsi +@@ -255,14 +255,131 @@ mali: gpu@c0000 { + clocks = <&clkc CLKID_CLK81>, <&clkc CLKID_MALI>; + clock-names = "bus", "core"; + +- assigned-clocks = <&clkc CLKID_MALI>; +- assigned-clock-rates = <318750000>; +- + operating-points-v2 = <&gpu_opp_table>; + }; ++ ++ hdmi_tx: hdmi-tx@42000 { ++ compatible = "amlogic,meson8-hdmi-tx"; ++ reg = <0x42000 0xc>; ++ interrupts = ; ++ 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 = <0>; ++ 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 = ; ++ ++ amlogic,canvas = <&canvas>; ++ amlogic,hhi-sysctrl = <&hhi>; ++ ++ 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"; ++ ++ power-domains = <&pwrc PWRC_MESON8_VPU_ID>; ++ ++ nvmem-cells = <&cvbs_trimming>; ++ nvmem-cell-names = "cvbs_trimming"; ++ ++ #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 / */ + ++&aiu { ++ compatible = "amlogic,aiu-meson8", "amlogic,aiu"; ++ clocks = <&clkc CLKID_AIU_GLUE>, ++ <&clkc CLKID_I2S_OUT>, ++ <&clkc CLKID_AOCLK_GATE>, ++ <&clkc CLKID_CTS_AMCLK>, ++ <&clkc CLKID_MIXER_IFACE>, ++ <&clkc CLKID_IEC958>, ++ <&clkc CLKID_IEC958_GATE>, ++ <&clkc CLKID_CTS_MCLK_I958>, ++ <&clkc CLKID_CTS_I958>; ++ clock-names = "pclk", ++ "i2s_pclk", ++ "i2s_aoclk", ++ "i2s_mclk", ++ "i2s_mixer", ++ "spdif_pclk", ++ "spdif_aoclk", ++ "spdif_mclk", ++ "spdif_mclk_sel"; ++ resets = <&reset RESET_AIU>; ++}; ++ + &aobus { + pmu: pmu@e0 { + compatible = "amlogic,meson8-pmu", "syscon"; +@@ -286,6 +403,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; ++ }; ++ }; ++ + uart_ao_a_pins: uart_ao_a { + mux { + groups = "uart_tx_ao_a", "uart_rx_ao_a"; +@@ -318,6 +443,15 @@ mux { + }; + }; + }; ++ ++ cec_AO: cec@100 { ++ compatible = "amlogic,meson-gx-ao-cec"; // FIXME ++ reg = <0x100 0x14>; ++ interrupts = ; ++ // TODO: 32768HZ clock ++ hdmi-phandle = <&hdmi_tx>; ++ status = "disabled"; ++ }; + }; + + &cbus { +@@ -362,6 +496,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", +@@ -462,6 +612,10 @@ temperature_calib: calib@1f4 { + /* only the upper two bytes are relevant */ + reg = <0x1f4 0x4>; + }; ++ ++ cvbs_trimming: calib@1f8 { ++ reg = <0x1f8 0x2>; ++ }; + }; + + ðmac { +@@ -485,6 +639,12 @@ clkc: clock-controller { + #reset-cells = <1>; + }; + ++ hdmi_tx_phy: phy { ++ compatible = "amlogic,meson8-hdmi-tx-phy"; ++ clocks = <&clkc CLKID_HDMI_PLL_HDMI_OUT>; ++ #phy-cells = <0>; ++ }; ++ + pwrc: power-controller { + compatible = "amlogic,meson8-pwrc"; + #power-domain-cells = <1>; +diff --git a/arch/arm/boot/dts/meson8b.dtsi b/arch/arm/boot/dts/meson8b.dtsi +index f6eb7c803..9fc8823b2 100644 +--- a/arch/arm/boot/dts/meson8b.dtsi ++++ b/arch/arm/boot/dts/meson8b.dtsi +@@ -222,9 +222,129 @@ mali: gpu@c0000 { + clock-names = "bus", "core"; + operating-points-v2 = <&gpu_opp_table>; + }; ++ ++ hdmi_tx: hdmi-tx@42000 { ++ compatible = "amlogic,meson8b-hdmi-tx"; ++ reg = <0x42000 0xc>; ++ interrupts = ; ++ phys = <&hdmi_tx_phy>; ++ phy-names = "hdmi"; ++ clocks = <&clkc CLKID_HDMI_PCLK>, ++ <&clkc CLKID_HDMI_SYS>; ++ clock-names = "pclk", "sys"; ++ sound-name-prefix = "HDMITX"; ++ #sound-dai-cells = <0>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ 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 = ; ++ ++ amlogic,canvas = <&canvas>; ++ amlogic,hhi-sysctrl = <&hhi>; ++ ++ 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"; ++ ++ power-domains = <&pwrc PWRC_MESON8_VPU_ID>; ++ ++ nvmem-cells = <&cvbs_trimming>; ++ nvmem-cell-names = "cvbs_trimming"; ++ ++ #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 / */ + ++&aiu { ++ compatible = "amlogic,aiu-meson8b", "amlogic,aiu"; ++ clocks = <&clkc CLKID_AIU_GLUE>, ++ <&clkc CLKID_I2S_OUT>, ++ <&clkc CLKID_AOCLK_GATE>, ++ <&clkc CLKID_CTS_AMCLK>, ++ <&clkc CLKID_MIXER_IFACE>, ++ <&clkc CLKID_IEC958>, ++ <&clkc CLKID_IEC958_GATE>, ++ <&clkc CLKID_CTS_MCLK_I958>, ++ <&clkc CLKID_CTS_I958>; ++ clock-names = "pclk", ++ "i2s_pclk", ++ "i2s_aoclk", ++ "i2s_mclk", ++ "i2s_mixer", ++ "spdif_pclk", ++ "spdif_aoclk", ++ "spdif_mclk", ++ "spdif_mclk_sel"; ++ resets = <&reset RESET_AIU>; ++}; ++ + &aobus { + pmu: pmu@e0 { + compatible = "amlogic,meson8b-pmu", "syscon"; +@@ -248,6 +368,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; ++ }; ++ }; ++ + uart_ao_a_pins: uart_ao_a { + mux { + groups = "uart_tx_ao_a", "uart_rx_ao_a"; +@@ -264,6 +392,15 @@ mux { + }; + }; + }; ++ ++ cec_AO: cec@100 { ++ compatible = "amlogic,meson-gx-ao-cec"; // FIXME ++ reg = <0x100 0x14>; ++ interrupts = ; ++ // TODO: 32768HZ clock ++ hdmi-phandle = <&hdmi_tx>; ++ status = "disabled"; ++ }; + }; + + &cbus { +@@ -346,6 +483,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"; +@@ -426,6 +579,10 @@ temperature_calib: calib@1f4 { + /* only the upper two bytes are relevant */ + reg = <0x1f4 0x4>; + }; ++ ++ cvbs_trimming: calib@1f8 { ++ reg = <0x1f8 0x2>; ++ }; + }; + + ðmac { +@@ -463,6 +620,12 @@ clkc: clock-controller { + #reset-cells = <1>; + }; + ++ hdmi_tx_phy: phy { ++ compatible = "amlogic,meson8b-hdmi-tx-phy"; ++ clocks = <&clkc CLKID_HDMI_PLL_HDMI_OUT>; ++ #phy-cells = <0>; ++ }; ++ + pwrc: power-controller { + compatible = "amlogic,meson8b-pwrc"; + #power-domain-cells = <1>; +diff --git a/arch/arm/boot/dts/meson8m2.dtsi b/arch/arm/boot/dts/meson8m2.dtsi +index 6725dd9fd..fcb2ad976 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/clk/meson/meson8b.c b/drivers/clk/meson/meson8b.c +index 862f0756b..98aaf028f 100644 +--- a/drivers/clk/meson/meson8b.c ++++ b/drivers/clk/meson/meson8b.c +@@ -127,6 +127,36 @@ static struct clk_regmap meson8b_fixed_pll = { + }, + }; + ++static const struct reg_sequence meson8b_hdmi_pll_init_regs[] = { ++ { .reg = HHI_VID_PLL_CNTL2, .def = 0x59c88000 }, ++ { .reg = HHI_VID_PLL_CNTL3, .def = 0xca49b022 }, ++ { .reg = HHI_VID_PLL_CNTL4, .def = 0x0023b100 }, ++ { .reg = HHI_VID_PLL_CNTL5, .def = 0x00016385 }, ++ { .reg = HHI_VID2_PLL_CNTL2, .def = 0x0430a800 }, ++}; ++ ++static const struct pll_params_table hdmi_pll_params_table[] = { ++ PLL_PARAMS(34, 1), ++ PLL_PARAMS(40, 1), ++ PLL_PARAMS(42, 1), ++ PLL_PARAMS(44, 1), ++ PLL_PARAMS(45, 1), ++ PLL_PARAMS(49, 1), ++ PLL_PARAMS(52, 1), ++ PLL_PARAMS(54, 1), ++ PLL_PARAMS(56, 1), ++ PLL_PARAMS(59, 1), ++ PLL_PARAMS(60, 1), ++ PLL_PARAMS(61, 1), ++ PLL_PARAMS(62, 1), ++ PLL_PARAMS(64, 1), ++ PLL_PARAMS(66, 1), ++ PLL_PARAMS(68, 1), ++ PLL_PARAMS(71, 1), ++ PLL_PARAMS(82, 1), ++ { /* sentinel */ } ++}; ++ + static struct clk_regmap meson8b_hdmi_pll_dco = { + .data = &(struct meson_clk_pll_data){ + .en = { +@@ -159,11 +189,14 @@ static struct clk_regmap meson8b_hdmi_pll_dco = { + .shift = 29, + .width = 1, + }, ++ .table = hdmi_pll_params_table, ++ .init_regs = meson8b_hdmi_pll_init_regs, ++ .init_count = ARRAY_SIZE(meson8b_hdmi_pll_init_regs), + }, + .hw.init = &(struct clk_init_data){ + /* sometimes also called "HPLL" or "HPLL PLL" */ + .name = "hdmi_pll_dco", +- .ops = &meson_clk_pll_ro_ops, ++ .ops = &meson_clk_pll_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "xtal", + .name = "xtal", +@@ -182,7 +215,7 @@ static struct clk_regmap meson8b_hdmi_pll_lvds_out = { + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmi_pll_lvds_out", +- .ops = &clk_regmap_divider_ro_ops, ++ .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_hdmi_pll_dco.hw + }, +@@ -200,7 +233,7 @@ static struct clk_regmap meson8b_hdmi_pll_hdmi_out = { + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmi_pll_hdmi_out", +- .ops = &clk_regmap_divider_ro_ops, ++ .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_hdmi_pll_dco.hw + }, +@@ -1054,6 +1087,23 @@ static struct clk_regmap meson8b_l2_dram_clk_gate = { + }, + }; + ++/* also called LVDS_CLK_EN */ ++static struct clk_regmap meson8b_vid_pll_lvds_en = { ++ .data = &(struct clk_regmap_gate_data){ ++ .offset = HHI_VID_DIVIDER_CNTL, ++ .bit_idx = 11, ++ }, ++ .hw.init = &(struct clk_init_data){ ++ .name = "vid_pll_lvds_en", ++ .ops = &clk_regmap_gate_ops, ++ .parent_hws = (const struct clk_hw *[]) { ++ &meson8b_hdmi_pll_lvds_out.hw ++ }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++}; ++ + static struct clk_regmap meson8b_vid_pll_in_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HHI_VID_DIVIDER_CNTL, +@@ -1062,7 +1112,7 @@ static struct clk_regmap meson8b_vid_pll_in_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_in_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + /* + * TODO: depending on the SoC there is also a second parent: + * Meson8: unknown +@@ -1070,7 +1120,7 @@ static struct clk_regmap meson8b_vid_pll_in_sel = { + * Meson8m2: vid2_pll + */ + .parent_hws = (const struct clk_hw *[]) { +- &meson8b_hdmi_pll_lvds_out.hw ++ &meson8b_vid_pll_lvds_en.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, +@@ -1084,7 +1134,7 @@ static struct clk_regmap meson8b_vid_pll_in_en = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_in_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vid_pll_in_sel.hw + }, +@@ -1093,15 +1143,23 @@ static struct clk_regmap meson8b_vid_pll_in_en = { + }, + }; + ++static const struct clk_div_table vid_pll_pre_div_table[] = { ++ { .val = 0, .div = 1 }, ++ { .val = 4, .div = 5 }, ++ { .val = 5, .div = 6 }, ++ { /* sentinel */ } ++}; ++ + static struct clk_regmap meson8b_vid_pll_pre_div = { + .data = &(struct clk_regmap_div_data){ + .offset = HHI_VID_DIVIDER_CNTL, + .shift = 4, + .width = 3, ++ .table = vid_pll_pre_div_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_pre_div", +- .ops = &clk_regmap_divider_ro_ops, ++ .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vid_pll_in_en.hw + }, +@@ -1110,15 +1168,22 @@ static struct clk_regmap meson8b_vid_pll_pre_div = { + }, + }; + ++static const struct clk_div_table vid_pll_post_div_table[] = { ++ { .val = 0, .div = 1 }, ++ { .val = 1, .div = 2 }, ++ { /* sentinel */ } ++}; ++ + static struct clk_regmap meson8b_vid_pll_post_div = { + .data = &(struct clk_regmap_div_data){ + .offset = HHI_VID_DIVIDER_CNTL, + .shift = 12, + .width = 3, ++ .table = vid_pll_post_div_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_post_div", +- .ops = &clk_regmap_divider_ro_ops, ++ .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vid_pll_pre_div.hw + }, +@@ -1135,7 +1200,7 @@ static struct clk_regmap meson8b_vid_pll = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + /* TODO: parent 0x2 is vid_pll_pre_div_mult7_div2 */ + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vid_pll_pre_div.hw, +@@ -1146,15 +1211,23 @@ static struct clk_regmap meson8b_vid_pll = { + }, + }; + ++static const struct clk_div_table meson8b_vid_pll_final_div_table[] = { ++ { .val = 0, .div = 1 }, ++ { .val = 1, .div = 2 }, ++ { .val = 3, .div = 4 }, ++ { /* sentinel */ } ++}; ++ + static struct clk_regmap meson8b_vid_pll_final_div = { + .data = &(struct clk_regmap_div_data){ + .offset = HHI_VID_CLK_DIV, + .shift = 0, + .width = 8, ++ .table = meson8b_vid_pll_final_div_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_final_div", +- .ops = &clk_regmap_divider_ro_ops, ++ .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vid_pll.hw + }, +@@ -1163,6 +1236,10 @@ static struct clk_regmap meson8b_vid_pll_final_div = { + }, + }; + ++/* ++ * parent 0x6 is meson8b_mpll1 but we don't use it here because it's reserved ++ * for the audio outputs. ++ */ + static const struct clk_hw *meson8b_vclk_mux_parent_hws[] = { + &meson8b_vid_pll_final_div.hw, + &meson8b_fclk_div4.hw, +@@ -1170,7 +1247,6 @@ static const struct clk_hw *meson8b_vclk_mux_parent_hws[] = { + &meson8b_fclk_div5.hw, + &meson8b_vid_pll_final_div.hw, + &meson8b_fclk_div7.hw, +- &meson8b_mpll1.hw, + }; + + static struct clk_regmap meson8b_vclk_in_sel = { +@@ -1181,7 +1257,7 @@ static struct clk_regmap meson8b_vclk_in_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_in_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1195,7 +1271,7 @@ static struct clk_regmap meson8b_vclk_in_en = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_in_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_in_sel.hw + }, +@@ -1211,7 +1287,7 @@ static struct clk_regmap meson8b_vclk_en = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_in_en.hw + }, +@@ -1227,7 +1303,7 @@ static struct clk_regmap meson8b_vclk_div1_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div1_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_en.hw + }, +@@ -1257,7 +1333,7 @@ static struct clk_regmap meson8b_vclk_div2_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div2_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_div2_div.hw + }, +@@ -1287,7 +1363,7 @@ static struct clk_regmap meson8b_vclk_div4_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div4_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_div4_div.hw + }, +@@ -1317,7 +1393,7 @@ static struct clk_regmap meson8b_vclk_div6_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div6_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_div6_div.hw + }, +@@ -1347,7 +1423,7 @@ static struct clk_regmap meson8b_vclk_div12_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div12_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_div12_div.hw + }, +@@ -1364,7 +1440,7 @@ static struct clk_regmap meson8b_vclk2_in_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_in_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1378,7 +1454,7 @@ static struct clk_regmap meson8b_vclk2_clk_in_en = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_in_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_in_sel.hw + }, +@@ -1394,7 +1470,7 @@ static struct clk_regmap meson8b_vclk2_clk_en = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_clk_in_en.hw + }, +@@ -1410,7 +1486,7 @@ static struct clk_regmap meson8b_vclk2_div1_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div1_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_clk_en.hw + }, +@@ -1440,7 +1516,7 @@ static struct clk_regmap meson8b_vclk2_div2_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div2_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_div2_div.hw + }, +@@ -1470,7 +1546,7 @@ static struct clk_regmap meson8b_vclk2_div4_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div4_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_div4_div.hw + }, +@@ -1500,7 +1576,7 @@ static struct clk_regmap meson8b_vclk2_div6_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div6_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_div6_div.hw + }, +@@ -1530,7 +1606,7 @@ static struct clk_regmap meson8b_vclk2_div12_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div12_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_div12_div.hw + }, +@@ -1555,7 +1631,7 @@ static struct clk_regmap meson8b_cts_enct_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_enct_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1569,7 +1645,7 @@ static struct clk_regmap meson8b_cts_enct = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_enct", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_cts_enct_sel.hw + }, +@@ -1586,7 +1662,7 @@ static struct clk_regmap meson8b_cts_encp_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_encp_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1600,7 +1676,7 @@ static struct clk_regmap meson8b_cts_encp = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_encp", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_cts_encp_sel.hw + }, +@@ -1617,7 +1693,7 @@ static struct clk_regmap meson8b_cts_enci_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_enci_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1631,7 +1707,7 @@ static struct clk_regmap meson8b_cts_enci = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_enci", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_cts_enci_sel.hw + }, +@@ -1648,7 +1724,7 @@ static struct clk_regmap meson8b_hdmi_tx_pixel_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmi_tx_pixel_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1662,7 +1738,7 @@ static struct clk_regmap meson8b_hdmi_tx_pixel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmi_tx_pixel", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_hdmi_tx_pixel_sel.hw + }, +@@ -1687,7 +1763,7 @@ static struct clk_regmap meson8b_cts_encl_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_encl_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk2_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk2_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1701,7 +1777,7 @@ static struct clk_regmap meson8b_cts_encl = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_encl", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_cts_encl_sel.hw + }, +@@ -1718,7 +1794,7 @@ static struct clk_regmap meson8b_cts_vdac0_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_vdac0_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk2_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk2_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1732,7 +1808,7 @@ static struct clk_regmap meson8b_cts_vdac0 = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_vdac0", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_cts_vdac0_sel.hw + }, +@@ -2915,6 +2991,7 @@ static struct clk_hw_onecell_data meson8_hw_onecell_data = { + [CLKID_CTS_MCLK_I958_DIV] = &meson8b_cts_mclk_i958_div.hw, + [CLKID_CTS_MCLK_I958] = &meson8b_cts_mclk_i958.hw, + [CLKID_CTS_I958] = &meson8b_cts_i958.hw, ++ [CLKID_VID_PLL_LVDS_EN] = &meson8b_vid_pll_lvds_en.hw, + [CLK_NR_CLKS] = NULL, + }, + .num = CLK_NR_CLKS, +@@ -3133,6 +3210,7 @@ static struct clk_hw_onecell_data meson8b_hw_onecell_data = { + [CLKID_CTS_MCLK_I958_DIV] = &meson8b_cts_mclk_i958_div.hw, + [CLKID_CTS_MCLK_I958] = &meson8b_cts_mclk_i958.hw, + [CLKID_CTS_I958] = &meson8b_cts_i958.hw, ++ [CLKID_VID_PLL_LVDS_EN] = &meson8b_vid_pll_lvds_en.hw, + [CLK_NR_CLKS] = NULL, + }, + .num = CLK_NR_CLKS, +@@ -3353,6 +3431,7 @@ static struct clk_hw_onecell_data meson8m2_hw_onecell_data = { + [CLKID_CTS_MCLK_I958_DIV] = &meson8b_cts_mclk_i958_div.hw, + [CLKID_CTS_MCLK_I958] = &meson8b_cts_mclk_i958.hw, + [CLKID_CTS_I958] = &meson8b_cts_i958.hw, ++ [CLKID_VID_PLL_LVDS_EN] = &meson8b_vid_pll_lvds_en.hw, + [CLK_NR_CLKS] = NULL, + }, + .num = CLK_NR_CLKS, +@@ -3551,6 +3630,7 @@ static struct clk_regmap *const meson8b_clk_regmaps[] = { + &meson8b_cts_mclk_i958_div, + &meson8b_cts_mclk_i958, + &meson8b_cts_i958, ++ &meson8b_vid_pll_lvds_en, + }; + + static const struct meson8b_clk_reset_line { +@@ -3743,18 +3823,8 @@ static void __init meson8b_clkc_init_common(struct device_node *np, + + map = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(map)) { +- pr_info("failed to get HHI regmap - Trying obsolete regs\n"); +- +- /* Generic clocks, PLLs and some of the reset-bits */ +- clk_base = of_iomap(np, 1); +- if (!clk_base) { +- pr_err("%s: Unable to map clk base\n", __func__); +- return; +- } +- +- map = regmap_init_mmio(NULL, clk_base, &clkc_regmap_config); +- if (IS_ERR(map)) +- return; ++ pr_err("failed to get HHI regmap - Trying obsolete regs\n"); ++ return; + } + + rstc = kzalloc(sizeof(*rstc), GFP_KERNEL); +@@ -3813,6 +3883,11 @@ static void __init meson8b_clkc_init_common(struct device_node *np, + return; + } + ++ /* The HDMI PLL VCO is limited to 1.2G~3.0GHz */ ++ clk_hw_set_rate_range(clk_hw_onecell_data->hws[CLKID_HDMI_PLL_DCO], ++ 1200 * 1000UL * 1000UL, ++ 3000 * 1000UL * 1000UL); ++ + ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, + clk_hw_onecell_data); + if (ret) +diff --git a/drivers/clk/meson/meson8b.h b/drivers/clk/meson/meson8b.h +index b1a5074cf..8d1e9cb15 100644 +--- a/drivers/clk/meson/meson8b.h ++++ b/drivers/clk/meson/meson8b.h +@@ -51,6 +51,16 @@ + #define HHI_SYS_PLL_CNTL 0x300 /* 0xc0 offset in data sheet */ + #define HHI_VID_PLL_CNTL 0x320 /* 0xc8 offset in data sheet */ + #define HHI_VID_PLL_CNTL2 0x324 /* 0xc9 offset in data sheet */ ++#define HHI_VID_PLL_CNTL3 0x328 /* 0xca offset in data sheet */ ++#define HHI_VID_PLL_CNTL4 0x32c /* 0xcb offset in data sheet */ ++#define HHI_VID_PLL_CNTL5 0x330 /* 0xcc offset in data sheet */ ++#define HHI_VID_PLL_CNTL6 0x334 /* 0xcd offset in data sheet */ ++#define HHI_VID2_PLL_CNTL 0x380 /* 0xe0 offset in data sheet */ ++#define HHI_VID2_PLL_CNTL2 0x384 /* 0xe1 offset in data sheet */ ++#define HHI_VID2_PLL_CNTL3 0x388 /* 0xe2 offset in data sheet */ ++#define HHI_VID2_PLL_CNTL4 0x38c /* 0xe3 offset in data sheet */ ++#define HHI_VID2_PLL_CNTL5 0x390 /* 0xe4 offset in data sheet */ ++#define HHI_VID2_PLL_CNTL6 0x394 /* 0xe5 offset in data sheet */ + + /* + * MPLL register offeset taken from the S905 datasheet. Vendor kernel source +@@ -107,8 +117,7 @@ + #define CLKID_PERIPH_SEL 125 + #define CLKID_AXI_SEL 127 + #define CLKID_L2_DRAM_SEL 129 +-#define CLKID_HDMI_PLL_LVDS_OUT 131 +-#define CLKID_HDMI_PLL_HDMI_OUT 132 ++#define CLKID_HDMI_PLL_LVDS_OUT 131 + #define CLKID_VID_PLL_IN_SEL 133 + #define CLKID_VID_PLL_IN_EN 134 + #define CLKID_VID_PLL_PRE_DIV 135 +@@ -137,17 +146,11 @@ + #define CLKID_VCLK2_DIV12_DIV 158 + #define CLKID_VCLK2_DIV12 159 + #define CLKID_CTS_ENCT_SEL 160 +-#define CLKID_CTS_ENCT 161 + #define CLKID_CTS_ENCP_SEL 162 +-#define CLKID_CTS_ENCP 163 + #define CLKID_CTS_ENCI_SEL 164 +-#define CLKID_CTS_ENCI 165 + #define CLKID_HDMI_TX_PIXEL_SEL 166 +-#define CLKID_HDMI_TX_PIXEL 167 + #define CLKID_CTS_ENCL_SEL 168 +-#define CLKID_CTS_ENCL 169 + #define CLKID_CTS_VDAC0_SEL 170 +-#define CLKID_CTS_VDAC0 171 + #define CLKID_HDMI_SYS_SEL 172 + #define CLKID_HDMI_SYS_DIV 173 + #define CLKID_MALI_0_SEL 175 +@@ -182,8 +185,9 @@ + #define CLKID_CTS_MCLK_I958_DIV 211 + #define CLKID_VCLK_EN 214 + #define CLKID_VCLK2_EN 215 ++#define CLKID_VID_PLL_LVDS_EN 216 + +-#define CLK_NR_CLKS 216 ++#define CLK_NR_CLKS 222 + + /* + * include the CLKID and RESETID that have +diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig +index 4e82647a6..708d82d61 100644 +--- a/drivers/gpu/drm/bridge/Kconfig ++++ b/drivers/gpu/drm/bridge/Kconfig +@@ -248,4 +248,6 @@ source "drivers/gpu/drm/bridge/cadence/Kconfig" + + source "drivers/gpu/drm/bridge/synopsys/Kconfig" + ++source "drivers/gpu/drm/bridge/transwitch/Kconfig" ++ + endmenu +diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile +index 2b3aff104..6f3bb787c 100644 +--- a/drivers/gpu/drm/bridge/Makefile ++++ b/drivers/gpu/drm/bridge/Makefile +@@ -27,3 +27,4 @@ obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi.o + obj-y += analogix/ + obj-y += cadence/ + obj-y += synopsys/ ++obj-y += transwitch/ +diff --git a/drivers/gpu/drm/bridge/transwitch/Kconfig b/drivers/gpu/drm/bridge/transwitch/Kconfig +new file mode 100644 +index 000000000..272084b0b +--- /dev/null ++++ b/drivers/gpu/drm/bridge/transwitch/Kconfig +@@ -0,0 +1,8 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++config DRM_TRANSWITCH_TXC_48352 ++ tristate ++ depends on DRM ++ depends on REGMAP ++ depends on OF ++ select CEC_CORE if CEC_NOTIFIER ++ select SND_SOC_HDMI_CODEC if SND_SOC +diff --git a/drivers/gpu/drm/bridge/transwitch/Makefile b/drivers/gpu/drm/bridge/transwitch/Makefile +new file mode 100644 +index 000000000..ef98e79a5 +--- /dev/null ++++ b/drivers/gpu/drm/bridge/transwitch/Makefile +@@ -0,0 +1,2 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++obj-$(CONFIG_DRM_TRANSWITCH_TXC_48352) += txccq-txc-48352.o +diff --git a/drivers/gpu/drm/bridge/transwitch/txccq-txc-48352.c b/drivers/gpu/drm/bridge/transwitch/txccq-txc-48352.c +new file mode 100644 +index 000000000..b2593a014 +--- /dev/null ++++ b/drivers/gpu/drm/bridge/transwitch/txccq-txc-48352.c +@@ -0,0 +1,1598 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2020 Martin Blumenstingl ++ * ++ * All registers and magic values are taken from Amlogic's GPL kernel sources: ++ * Copyright (C) 2010 Amlogic, Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#include ++#include ++ ++#include "txccq-txc-48352.h" ++ ++struct txc_48352 { ++ struct device *dev; ++ int irq; ++ struct regmap *tx_regmap; ++ struct regmap *other_regmap; ++ struct clk *sys_clk; ++ struct phy *phy; ++ bool phy_is_on; ++ ++ struct mutex cec_notifier_mutex; ++ struct cec_notifier *cec_notifier; ++ ++ struct mutex codec_mutex; ++ hdmi_codec_plugged_cb codec_plugged_cb; ++ struct device *codec_dev; ++ ++ struct platform_device *hdmi_codec_pdev; ++ ++ struct drm_connector connector; ++ struct drm_bridge bridge; ++ ++ unsigned int intr_stat; ++ ++ int cea_mode; ++ enum hdmi_colorimetry colorimetry; ++ unsigned int input_bus_format; ++ unsigned int output_bus_format; ++ bool limited_rgb_quant_range; ++ bool sink_is_hdmi; ++ bool sink_has_audio; ++}; ++ ++#define to_txc_48352(x) container_of(x, struct txc_48352, x) ++ ++static void txc_48352_write_infoframe(struct regmap *regmap, ++ unsigned int tx_pkt_reg, u8 *buf, ++ unsigned int len, bool enable) ++{ ++ unsigned int i; ++ ++ /* write the payload starting register offset 1 and skip the header */ ++ for (i = HDMI_INFOFRAME_HEADER_SIZE; i < len; i++) ++ regmap_write(regmap, ++ tx_pkt_reg + i - HDMI_INFOFRAME_HEADER_SIZE + 1, ++ buf[i]); ++ ++ /* zero the remaining payload bytes */ ++ for (; i < 0x1c; i++) ++ regmap_write(regmap, tx_pkt_reg + i, 0x00); ++ ++ /* write the header */ ++ 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 txc_48352_disable_infoframe(struct txc_48352 *priv, ++ unsigned int tx_pkt_reg) ++{ ++ u8 buf[HDMI_INFOFRAME_HEADER_SIZE] = { 0 }; ++ ++ txc_48352_write_infoframe(priv->tx_regmap, tx_pkt_reg, buf, ++ HDMI_INFOFRAME_HEADER_SIZE, false); ++} ++ ++static void txc_48352_sys5_reset_assert(struct txc_48352 *priv) ++{ ++ /* A comment in the vendor driver says: bit5,6 is converted */ ++ regmap_write(priv->tx_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->tx_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->tx_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 txc_48352_sys5_reset_deassert(struct txc_48352 *priv) ++{ ++ /* Release the resets except tmds_clk */ ++ regmap_write(priv->tx_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->tx_regmap, TX_SYS5_TX_SOFT_RESET_1, 0x0); ++ usleep_range(10, 20); ++ ++ regmap_write(priv->tx_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->tx_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 txc_48352_config_hdcp_registers(struct txc_48352 *priv) ++{ ++ regmap_write(priv->tx_regmap, TX_HDCP_CONFIG0, ++ FIELD_PREP(TX_HDCP_CONFIG0_ROM_ENCRYPT_OFF, 0x3)); ++ regmap_write(priv->tx_regmap, TX_HDCP_MEM_CONFIG, 0x0); ++ regmap_write(priv->tx_regmap, TX_HDCP_ENCRYPT_BYTE, 0x0); ++ ++ regmap_write(priv->tx_regmap, TX_HDCP_MODE, TX_HDCP_MODE_CLEAR_AVMUTE); ++ ++ regmap_write(priv->tx_regmap, TX_HDCP_MODE, TX_HDCP_MODE_ESS_CONFIG); ++} ++ ++static u8 txc_48352_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 enum hdmi_colorspace ++txc_48352_bus_fmt_hdmi_colorspace(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: ++ return HDMI_COLORSPACE_YUV444; ++ ++ case MEDIA_BUS_FMT_UYVY8_1X16: ++ case MEDIA_BUS_FMT_UYVY10_1X20: ++ case MEDIA_BUS_FMT_UYVY12_1X24: ++ return HDMI_COLORSPACE_YUV422; ++ ++ 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: ++ return HDMI_COLORSPACE_RGB; ++ ++ } ++} ++ ++static u8 txc_48352_bus_fmt_to_color_format(unsigned int bus_format) ++{ ++ switch (txc_48352_bus_fmt_hdmi_colorspace(bus_format)) { ++ case HDMI_COLORSPACE_YUV422: ++ /* Documented as YCbCr422 */ ++ return 0x3; ++ ++ case HDMI_COLORSPACE_YUV444: ++ /* Documented as YCbCr444 */ ++ return 0x1; ++ ++ case HDMI_COLORSPACE_RGB: ++ default: ++ /* Documented as RGB444 */ ++ return 0x0; ++ } ++} ++ ++static void txc_48352_config_color_space(struct txc_48352 *priv) ++{ ++ unsigned int regval; ++ ++ regmap_write(priv->tx_regmap, TX_VIDEO_DTV_MODE, ++ FIELD_PREP(TX_VIDEO_DTV_MODE_COLOR_DEPTH, ++ txc_48352_bus_fmt_to_color_depth(priv->output_bus_format))); ++ ++ regmap_write(priv->tx_regmap, TX_VIDEO_DTV_OPTION_L, ++ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_FORMAT, ++ txc_48352_bus_fmt_to_color_format(priv->output_bus_format)) | ++ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_FORMAT, ++ txc_48352_bus_fmt_to_color_format(priv->input_bus_format)) | ++ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_DEPTH, ++ txc_48352_bus_fmt_to_color_depth(priv->output_bus_format)) | ++ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_DEPTH, ++ txc_48352_bus_fmt_to_color_depth(priv->input_bus_format))); ++ ++ if (priv->limited_rgb_quant_range) ++ 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->tx_regmap, TX_VIDEO_DTV_OPTION_H, regval); ++ ++ if (priv->colorimetry == HDMI_COLORIMETRY_ITU_601) { ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_B0, 0x2f); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_B1, 0x1d); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_R0, 0x8b); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_R1, 0x4c); ++ ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_CB0, 0x18); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_CB1, 0x58); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_CR0, 0xd0); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_CR1, 0xb6); ++ } else { ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_B0, 0x7b); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_B1, 0x12); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_R0, 0x6c); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_R1, 0x36); ++ ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_CB0, 0xf2); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_CB1, 0x2f); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_CR0, 0xd4); ++ regmap_write(priv->tx_regmap, TX_VIDEO_CSC_COEFF_CR1, 0x77); ++ } ++} ++ ++/* ++ * FIXME - questions for CDNS team: ++ * - what is the name of the 0x0018 register? ++ * - what do BIT(1), BIT(2), BIT(4) and BIT(5) mean? ++ * - if it's not clear from the names of these bits: when to set each of them? ++ * (below code depends on the HDMI_COLORIMETRY and one special case also on ++ * the color depth and a special HDMI VIC) ++ */ ++static void txc_48352_config_serializer_clock(struct txc_48352 *priv) ++{ ++ /* Serializer Internal clock setting */ ++ if (priv->colorimetry == HDMI_COLORIMETRY_ITU_601) ++ regmap_write(priv->tx_regmap, 0x0018, 0x24); ++ else ++ regmap_write(priv->tx_regmap, 0x0018, 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->tx_regmap, 0x0018,0x12); ++ } ++#endif ++} ++ ++static void txc_48352_reconfig_packet_setting(struct txc_48352 *priv) ++{ ++ u8 alloc_active2, alloc_eof1, alloc_sof1, alloc_sof2; ++ ++ regmap_write(priv->tx_regmap, TX_PACKET_CONTROL_1, ++ FIELD_PREP(TX_PACKET_CONTROL_1_PACKET_START_LATENCY, 58)); ++ regmap_write(priv->tx_regmap, TX_PACKET_CONTROL_2, ++ TX_PACKET_CONTROL_2_HORIZONTAL_GC_PACKET_TRANSPORT_EN); ++ ++ switch (priv->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->tx_regmap, TX_PACKET_ALLOC_ACTIVE_1, 0x00); ++ return; ++ } ++ ++ /* ++ * The vendor driver says: manually configure these register to get ++ * stable video timings. ++ */ ++ regmap_write(priv->tx_regmap, TX_PACKET_ALLOC_ACTIVE_1, 0x01); ++ regmap_write(priv->tx_regmap, TX_PACKET_ALLOC_ACTIVE_2, alloc_active2); ++ regmap_write(priv->tx_regmap, TX_PACKET_ALLOC_EOF_1, alloc_eof1); ++ regmap_write(priv->tx_regmap, TX_PACKET_ALLOC_EOF_2, 0x12); ++ regmap_write(priv->tx_regmap, TX_CORE_ALLOC_VSYNC_0, 0x01); ++ regmap_write(priv->tx_regmap, TX_CORE_ALLOC_VSYNC_1, 0x00); ++ regmap_write(priv->tx_regmap, TX_CORE_ALLOC_VSYNC_2, 0x0a); ++ regmap_write(priv->tx_regmap, TX_PACKET_ALLOC_SOF_1, alloc_sof1); ++ regmap_write(priv->tx_regmap, TX_PACKET_ALLOC_SOF_2, alloc_sof2); ++ regmap_update_bits(priv->tx_regmap, TX_PACKET_CONTROL_1, ++ TX_PACKET_CONTROL_1_FORCE_PACKET_TIMING, ++ TX_PACKET_CONTROL_1_FORCE_PACKET_TIMING); ++} ++ ++static void txc_48352_set_avi_infoframe(struct txc_48352 *priv, ++ const struct drm_display_mode *mode) ++{ ++ u8 buf[HDMI_INFOFRAME_SIZE(AVI)], *video_code; ++ struct hdmi_avi_infoframe frame; ++ int ret; ++ ++ ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, ++ &priv->connector, mode); ++ if (ret < 0) { ++ drm_err(priv->bridge.dev, ++ "Failed to setup AVI infoframe: %d\n", ret); ++ return; ++ } ++ ++ /* fixed infoframe configuration not linked to the mode */ ++ frame.colorspace = ++ txc_48352_bus_fmt_hdmi_colorspace(priv->output_bus_format); ++ frame.colorimetry = priv->colorimetry; ++ ++ drm_hdmi_avi_infoframe_quant_range(&frame, ++ &priv->connector, mode, ++ priv->limited_rgb_quant_range ? ++ HDMI_QUANTIZATION_RANGE_LIMITED : ++ HDMI_QUANTIZATION_RANGE_FULL); ++ ++ 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 > 109) { ++ regmap_write(priv->tx_regmap, TX_PKT_REG_EXCEPT0_BASE_ADDR, ++ *video_code); ++ *video_code = 0x00; ++ } else { ++ regmap_write(priv->tx_regmap, TX_PKT_REG_EXCEPT0_BASE_ADDR, 0x00); ++ } ++ ++ txc_48352_write_infoframe(priv->tx_regmap, ++ TX_PKT_REG_AVI_INFO_BASE_ADDR, buf, ++ sizeof(buf), true); ++} ++ ++static void txc_48352_set_vendor_infoframe(struct txc_48352 *priv, ++ 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, ++ &priv->connector, ++ 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; ++ } ++ ++ txc_48352_write_infoframe(priv->tx_regmap, ++ TX_PKT_REG_VEND_INFO_BASE_ADDR, buf, ++ sizeof(buf), true); ++} ++ ++static void txc_48352_set_spd_infoframe(struct txc_48352 *priv) ++{ ++ u8 buf[HDMI_INFOFRAME_SIZE(SPD)]; ++ struct hdmi_spd_infoframe frame; ++ int ret; ++ ++ ret = hdmi_spd_infoframe_init(&frame, "TXCCQ", "TXC-48352"); ++ 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; ++ } ++ ++ txc_48352_write_infoframe(priv->tx_regmap, ++ TX_PKT_REG_SPD_INFO_BASE_ADDR, buf, ++ sizeof(buf), true); ++} ++ ++static void txc_48352_update_codec_status(struct txc_48352 *priv, ++ enum drm_connector_status status) ++{ ++ mutex_lock(&priv->codec_mutex); ++ if (priv->codec_dev && priv->codec_plugged_cb) ++ priv->codec_plugged_cb(priv->codec_dev, ++ status == connector_status_connected); ++ mutex_unlock(&priv->codec_mutex); ++} ++ ++static enum drm_connector_status txc_48352_detect(struct txc_48352 *priv) ++{ ++ enum drm_connector_status status; ++ unsigned int val; ++ ++ regmap_read(priv->tx_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; ++ ++ if (status == connector_status_disconnected) { ++ mutex_lock(&priv->cec_notifier_mutex); ++ cec_notifier_phys_addr_invalidate(priv->cec_notifier); ++ mutex_unlock(&priv->cec_notifier_mutex); ++ } ++ ++ txc_48352_update_codec_status(priv, status); ++ ++ return status; ++} ++ ++static int txc_48352_get_edid_block(void *data, u8 *buf, unsigned int block, ++ size_t len) ++{ ++ unsigned int i, regval, start = block * EDID_LENGTH; ++ struct txc_48352 *priv = data; ++ int ret; ++ ++ /* Start the DDC transaction */ ++ regmap_update_bits(priv->tx_regmap, TX_HDCP_EDID_CONFIG, ++ TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG, 0); ++ regmap_update_bits(priv->tx_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->tx_regmap, ++ TX_HDCP_ST_EDID_STATUS, ++ regval, ++ (regval & TX_HDCP_ST_EDID_STATUS_EDID_DATA_READY), ++ 1000, 200000); ++ ++ regmap_update_bits(priv->tx_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->tx_regmap, TX_RX_EDID_OFFSET + start + i, ++ ®val); ++ buf[i] = regval; ++ } ++ ++ return 0; ++} ++ ++static struct edid *txc_48352_get_edid(struct txc_48352 *priv, ++ struct drm_connector *connector) ++{ ++ struct edid *edid; ++ ++ edid = drm_do_get_edid(connector, txc_48352_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); ++ priv->sink_has_audio = drm_detect_monitor_audio(edid); ++ ++ return edid; ++} ++ ++static int txc_48352_connector_get_modes(struct drm_connector *connector) ++{ ++ struct txc_48352 *priv = to_txc_48352(connector); ++ struct edid *edid; ++ int ret; ++ ++ edid = txc_48352_get_edid(priv, connector); ++ if (!edid) ++ return 0; ++ ++ drm_connector_update_edid_property(connector, edid); ++ ++ mutex_lock(&priv->cec_notifier_mutex); ++ cec_notifier_set_phys_addr_from_edid(priv->cec_notifier, edid); ++ mutex_unlock(&priv->cec_notifier_mutex); ++ ++ ret = drm_add_edid_modes(connector, edid); ++ ++ kfree(edid); ++ ++ return ret; ++} ++ ++static struct drm_connector_helper_funcs txc_48352_connector_helper_funcs = { ++ .get_modes = txc_48352_connector_get_modes, ++}; ++ ++static enum drm_connector_status ++txc_48352_connector_detect(struct drm_connector *connector, bool force) ++{ ++ struct txc_48352 *priv = to_txc_48352(connector); ++ ++ return txc_48352_detect(priv); ++} ++ ++static const struct drm_connector_funcs txc_48352_connector_funcs = { ++ .fill_modes = drm_helper_probe_single_connector_modes, ++ .detect = txc_48352_connector_detect, ++ .destroy = drm_connector_cleanup, ++ .reset = drm_atomic_helper_connector_reset, ++ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, ++ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, ++}; ++ ++static int txc_48352_bridge_attach(struct drm_bridge *bridge, ++ enum drm_bridge_attach_flags flags) ++{ ++ struct txc_48352 *priv = bridge->driver_private; ++ struct drm_connector *connector = &priv->connector; ++ struct drm_encoder *encoder = bridge->encoder; ++ struct cec_connector_info conn_info; ++ struct cec_notifier *notifier; ++ int ret; ++ ++ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { ++ drm_err(priv->bridge.dev, "Skipping connector creation"); ++ return 0; ++ } ++ ++ connector->interlace_allowed = 1; ++ connector->polled = DRM_CONNECTOR_POLL_HPD; ++ ++ drm_connector_helper_add(connector, ++ &txc_48352_connector_helper_funcs); ++ ++ ret = drm_connector_init(bridge->dev, &priv->connector, ++ &txc_48352_connector_funcs, ++ DRM_MODE_CONNECTOR_HDMIA); ++ if (ret) ++ return ret; ++ ++ /* ++ * drm_connector_attach_max_bpc_property() requires the ++ * connector to have a state. ++ */ ++ drm_atomic_helper_connector_reset(connector); ++ ++ drm_connector_attach_max_bpc_property(connector, 8, 16); ++ ++ ret = drm_connector_attach_encoder(connector, encoder); ++ if (ret) ++ return ret; ++ ++ cec_fill_conn_info_from_drm(&conn_info, connector); ++ notifier = cec_notifier_conn_register(priv->dev, NULL, &conn_info); ++ if (!notifier) ++ return -ENOMEM; ++ ++ mutex_lock(&priv->cec_notifier_mutex); ++ priv->cec_notifier = notifier; ++ mutex_unlock(&priv->cec_notifier_mutex); ++ ++ return 0; ++} ++ ++static void txc_48352_bridge_detach(struct drm_bridge *bridge) ++{ ++ struct txc_48352 *priv = bridge->driver_private; ++ ++ mutex_lock(&priv->cec_notifier_mutex); ++ cec_notifier_conn_unregister(priv->cec_notifier); ++ priv->cec_notifier = NULL; ++ mutex_unlock(&priv->cec_notifier_mutex); ++} ++ ++static int txc_48352_bridge_atomic_check(struct drm_bridge *bridge, ++ struct drm_bridge_state *bridge_state, ++ struct drm_crtc_state *crtc_state, ++ struct drm_connector_state *conn_state) ++{ ++ struct txc_48352 *priv = bridge->driver_private; ++ ++ priv->output_bus_format = bridge_state->output_bus_cfg.format; ++ priv->input_bus_format = bridge_state->input_bus_cfg.format; ++ ++ drm_dbg(bridge->dev, "input format 0x%04x, output format 0x%04x\n", ++ priv->input_bus_format, priv->output_bus_format); ++ ++ return 0; ++} ++ ++/* Can return a maximum of 11 possible output formats for a mode/connector */ ++#define MAX_OUTPUT_SEL_FORMATS 11 ++ ++static u32 * ++txc_48352_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_YCRCB444) ++ 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_YCRCB422) ++ output_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; ++ ++ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) ++ 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_YCRCB422) ++ output_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; ++ ++ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) ++ output_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; ++ ++ output_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; ++ } ++ ++ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) ++ output_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; ++ ++ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) ++ 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 * ++txc_48352_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 txc_48352_bridge_enable(struct drm_bridge *bridge) ++{ ++ struct txc_48352 *priv = to_txc_48352(bridge); ++ unsigned int i; ++ ++ txc_48352_sys5_reset_assert(priv); ++ ++ txc_48352_config_hdcp_registers(priv); ++ ++ regmap_write(priv->tx_regmap, TX_AUDIO_CONTROL_MORE, 0x1); ++ ++ if (priv->cea_mode == 39) ++ regmap_write(priv->tx_regmap, TX_VIDEO_DTV_TIMING, 0x0); ++ else ++ regmap_write(priv->tx_regmap, TX_VIDEO_DTV_TIMING, ++ TX_VIDEO_DTV_TIMING_DISABLE_VIC39_CORRECTION); ++ ++ regmap_write(priv->tx_regmap, TX_CORE_DATA_CAPTURE_2, ++ TX_CORE_DATA_CAPTURE_2_INTERNAL_PACKET_ENABLE); ++ regmap_write(priv->tx_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->tx_regmap, TX_CORE_DATA_MONITOR_2, ++ FIELD_PREP(TX_CORE_DATA_MONITOR_2_MONITOR_SELECT, ++ 0x2)); ++ ++ if (priv->sink_is_hdmi) ++ regmap_write(priv->tx_regmap, TX_TMDS_MODE, ++ TX_TMDS_MODE_FORCED_HDMI | ++ TX_TMDS_MODE_HDMI_CONFIG); ++ else ++ regmap_write(priv->tx_regmap, TX_TMDS_MODE, ++ TX_TMDS_MODE_FORCED_HDMI); ++ ++ regmap_write(priv->tx_regmap, TX_SYS4_CONNECT_SEL_1, 0x0); ++ ++ /* ++ * Set tmds_clk pattern to be "0000011111" before being sent to AFE ++ * clock channel ++ */ ++ regmap_write(priv->tx_regmap, TX_SYS4_CK_INV_VIDEO, ++ TX_SYS4_CK_INV_VIDEO_TMDS_CLK_PATTERN); ++ ++ regmap_write(priv->tx_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); ++ ++ txc_48352_config_color_space(priv); ++ ++ txc_48352_sys5_reset_deassert(priv); ++ ++ txc_48352_config_serializer_clock(priv); ++ txc_48352_reconfig_packet_setting(priv); ++ ++ /* all resets need to be applied twice */ ++ for (i = 0; i < 2; i++) { ++ regmap_write(priv->tx_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->tx_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->tx_regmap, TX_SYS5_TX_SOFT_RESET_1, 0x00); ++ regmap_write(priv->tx_regmap, TX_SYS5_TX_SOFT_RESET_2, 0x00); ++ usleep_range(5000, 10000); ++ } ++ ++ if (!priv->phy_is_on) { ++ int 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 txc_48352_bridge_disable(struct drm_bridge *bridge) ++{ ++ struct txc_48352 *priv = to_txc_48352(bridge); ++ ++ if (priv->phy_is_on) { ++ int ret = phy_power_off(priv->phy); ++ if (ret) ++ drm_err(bridge->dev, "Failed to turn off PHY\n"); ++ else ++ priv->phy_is_on = false; ++ } ++ ++ txc_48352_disable_infoframe(priv, TX_PKT_REG_AUDIO_INFO_BASE_ADDR); ++ txc_48352_disable_infoframe(priv, TX_PKT_REG_AVI_INFO_BASE_ADDR); ++ txc_48352_disable_infoframe(priv, TX_PKT_REG_EXCEPT0_BASE_ADDR); ++ txc_48352_disable_infoframe(priv, TX_PKT_REG_VEND_INFO_BASE_ADDR); ++} ++ ++static void txc_48352_bridge_mode_set(struct drm_bridge *bridge, ++ const struct drm_display_mode *mode, ++ const struct drm_display_mode *adj) ++{ ++ struct txc_48352 *priv = to_txc_48352(bridge); ++ ++ if (priv->input_bus_format == MEDIA_BUS_FMT_FIXED) ++ priv->input_bus_format = MEDIA_BUS_FMT_RGB888_1X24; ++ ++ priv->cea_mode = drm_match_cea_mode(mode); ++ ++ if (priv->sink_is_hdmi) { ++ enum hdmi_quantization_range quant_range; ++ ++ quant_range = drm_default_rgb_quant_range(mode); ++ priv->limited_rgb_quant_range = ++ quant_range == HDMI_QUANTIZATION_RANGE_LIMITED; ++ ++ switch (priv->cea_mode) { ++ case 2 ... 3: ++ case 6 ... 7: ++ case 17 ... 18: ++ case 21 ... 22: ++ priv->colorimetry = HDMI_COLORIMETRY_ITU_601; ++ break; ++ ++ default: ++ priv->colorimetry = HDMI_COLORIMETRY_ITU_709; ++ break; ++ } ++ ++ txc_48352_set_avi_infoframe(priv, mode); ++ txc_48352_set_vendor_infoframe(priv, mode); ++ txc_48352_set_spd_infoframe(priv); ++ } else { ++ priv->limited_rgb_quant_range = false; ++ priv->colorimetry = HDMI_COLORIMETRY_NONE; ++ } ++} ++ ++static enum drm_mode_status ++txc_48352_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 txc_48352_bridge_detect(struct drm_bridge *bridge) ++{ ++ struct txc_48352 *priv = to_txc_48352(bridge); ++ ++ return txc_48352_detect(priv); ++} ++ ++static struct edid *txc_48352_bridge_get_edid(struct drm_bridge *bridge, ++ struct drm_connector *connector) ++{ ++ struct txc_48352 *priv = to_txc_48352(bridge); ++ ++ return txc_48352_get_edid(priv, connector); ++} ++ ++static const struct drm_bridge_funcs txc_48352_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 = txc_48352_bridge_attach, ++ .detach = txc_48352_bridge_detach, ++ .atomic_check = txc_48352_bridge_atomic_check, ++ .atomic_get_output_bus_fmts = txc_48352_bridge_atomic_get_output_bus_fmts, ++ .atomic_get_input_bus_fmts = txc_48352_bridge_atomic_get_input_bus_fmts, ++ .enable = txc_48352_bridge_enable, ++ .disable = txc_48352_bridge_disable, ++ .mode_set = txc_48352_bridge_mode_set, ++ .mode_valid = txc_48352_bridge_mode_valid, ++ .detect = txc_48352_bridge_detect, ++ .get_edid = txc_48352_bridge_get_edid, ++}; ++ ++static irqreturn_t txc_48352_irq_thread(int irq, void *dev_id) ++{ ++ enum drm_connector_status status; ++ struct txc_48352 *priv = dev_id; ++ ++ if (priv->intr_stat & (HDMI_OTHER_INTR_STAT_HPD_RISING | ++ HDMI_OTHER_INTR_STAT_HPD_FALLING)) { ++ drm_helper_hpd_irq_event(priv->bridge.dev); ++ ++ if (priv->intr_stat & HDMI_OTHER_INTR_STAT_HPD_RISING) ++ status = connector_status_connected; ++ else ++ status = connector_status_disconnected; ++ ++ drm_bridge_hpd_notify(&priv->bridge, status); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t txc_48352_irq_handler(int irq, void *dev_id) ++{ ++ struct txc_48352 *priv = dev_id; ++ irqreturn_t ret; ++ ++ regmap_read(priv->other_regmap, HDMI_OTHER_INTR_STAT, &priv->intr_stat); ++ if (!priv->intr_stat) ++ return IRQ_NONE; ++ ++ if (priv->intr_stat & (HDMI_OTHER_INTR_STAT_EDID_RISING | ++ HDMI_OTHER_INTR_STAT_HPD_FALLING | ++ HDMI_OTHER_INTR_STAT_HPD_RISING)) { ++ ret = IRQ_WAKE_THREAD; ++ } else { ++ dev_warn(priv->dev, "IRQ status has unknown bit set: 0x%04x\n", ++ priv->intr_stat); ++ ret = IRQ_HANDLED; ++ } ++ ++ regmap_write(priv->other_regmap, HDMI_OTHER_INTR_STAT_CLR, priv->intr_stat); ++ ++ return ret; ++} ++ ++static int txc_48352_init(struct txc_48352 *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->other_regmap, HDMI_OTHER_CTRL1, ++ HDMI_OTHER_CTRL1_POWER_ON, ++ HDMI_OTHER_CTRL1_POWER_ON); ++ ++ /* ++ * FIXME - questions for CDNS team: ++ * - what is the name of the 0x0010 register? ++ * - what do bit [7:0] stand for? ++ */ ++ regmap_write(priv->tx_regmap, 0x0010, 0xff); ++ ++ regmap_write(priv->tx_regmap, TX_HDCP_MODE, 0x40); ++ ++ /* ++ * FIXME - questions for CDNS team: ++ * - what is the name of the 0x0017 register? ++ * - is there a description for BIT(0), BIT(2), BIT(3) and BIT(4) or ++ * are 0x1d and 0x0 the only allowed values? ++ */ ++ /* Band-gap and main-bias. 0x1d = power-up, 0x00 = power-down */ ++ regmap_write(priv->tx_regmap, 0x0017, 0x1d); ++ ++ txc_48352_config_serializer_clock(priv); ++ ++ /* ++ * FIXME - questions for CDNS team: ++ * - what is the name of the 0x001a register? ++ * - is there a description for BIT(3), BIT(4), BIT(5), BIT(6) and ++ * BIT(7)? ++ */ ++ /* ++ * bit[2:0]=011: CK channel output TMDS CLOCK ++ * bit[2:0]=101, ck channel output PHYCLCK ++ */ ++ regmap_write(priv->tx_regmap, 0x001a, 0xfb); ++ ++ /* Termination resistor calib value */ ++ regmap_write(priv->tx_regmap, TX_CORE_CALIB_VALUE, 0x0f); ++ ++ /* HPD glitch filter */ ++ regmap_write(priv->tx_regmap, TX_HDCP_HPD_FILTER_L, 0xa0); ++ regmap_write(priv->tx_regmap, TX_HDCP_HPD_FILTER_H, 0xa0); ++ ++ /* Disable MEM power-down */ ++ regmap_write(priv->tx_regmap, TX_MEM_PD_REG0, 0); ++ ++ regmap_write(priv->tx_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->tx_regmap, TX_HDCP_EDID_CONFIG, ++ TX_HDCP_EDID_CONFIG_FORCED_MEM_COPY_DONE | ++ TX_HDCP_EDID_CONFIG_MEM_COPY_DONE_CONFIG); ++ regmap_write(priv->tx_regmap, TX_CORE_EDID_CONFIG_MORE, ++ TX_CORE_EDID_CONFIG_MORE_SYS_TRIGGER_CONFIG_SEMI_MANU); ++ ++ /* clear any pending interrupt */ ++ regmap_write(priv->other_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); ++ ++ /* unmask (= enable) all interrupts */ ++ regmap_write(priv->other_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); ++ ++ ret = devm_request_threaded_irq(priv->dev, priv->irq, ++ txc_48352_irq_handler, ++ txc_48352_irq_thread, 0, NULL, priv); ++ if (ret) { ++ dev_err(priv->dev, "Failed to request threaded irq: %d\n", ret); ++ goto err_clk_disable; ++ } ++ ++ return 0; ++ ++err_clk_disable: ++ clk_disable_unprepare(priv->sys_clk); ++err_phy_exit: ++ phy_exit(priv->phy); ++ return 0; ++} ++ ++static u32 txc_48352_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 txc_48352_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 txc_48352_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 txc_48352 *priv = data; ++ u8 audio_format_sample_width; ++ 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_format_sample_width = TX_AUDIO_FORMAT_BIT_WIDTH_16; ++ break; ++ ++ case 20: ++ audio_format_sample_width = TX_AUDIO_FORMAT_BIT_WIDTH_20; ++ break; ++ ++ case 24: ++ audio_format_sample_width = TX_AUDIO_FORMAT_BIT_WIDTH_24; ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ switch (fmt->fmt) { ++ case HDMI_I2S: ++ regmap_update_bits(priv->other_regmap, HDMI_OTHER_CTRL1, ++ HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON, ++ HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON); ++ ++ regmap_write(priv->tx_regmap, TX_AUDIO_FORMAT, ++ TX_AUDIO_FORMAT_SPDIF_OR_I2S | ++ FIELD_PREP(TX_AUDIO_FORMAT_I2S_FORMAT, 0x2) | ++ FIELD_PREP(TX_AUDIO_FORMAT_BIT_WIDTH_MASK, ++ audio_format_sample_width) | ++ TX_AUDIO_FORMAT_I2S_ONE_BIT_OR_I2S); ++ ++ if (hparms->channels > 2) ++ regmap_update_bits(priv->tx_regmap, TX_AUDIO_FORMAT, ++ TX_AUDIO_FORMAT_I2S_2_OR_8_CH, ++ TX_AUDIO_FORMAT_I2S_2_OR_8_CH); ++ ++ regmap_write(priv->tx_regmap, TX_AUDIO_I2S, TX_AUDIO_I2S_ENABLE); ++ regmap_write(priv->tx_regmap, TX_AUDIO_SPDIF, 0x0); ++ break; ++ ++ case HDMI_SPDIF: ++ regmap_update_bits(priv->other_regmap, HDMI_OTHER_CTRL1, ++ HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON, 0x0); ++ ++ if (hparms->cea.coding_type == HDMI_AUDIO_CODING_TYPE_PCM) ++ regmap_write(priv->tx_regmap, TX_AUDIO_FORMAT, ++ TX_AUDIO_FORMAT_SPDIF_CHANNEL_STATUS_FROM_DATA_OR_REG); ++ else ++ regmap_write(priv->tx_regmap, TX_AUDIO_FORMAT, 0x0); ++ ++ regmap_write(priv->tx_regmap, TX_AUDIO_I2S, 0x0); ++ regmap_write(priv->tx_regmap, TX_AUDIO_SPDIF, TX_AUDIO_SPDIF_ENABLE); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ if (hparms->channels > 2) ++ regmap_write(priv->tx_regmap, TX_AUDIO_HEADER, ++ TX_AUDIO_HEADER_AUDIO_SAMPLE_PACKET_HEADER_LAYOUT); ++ else ++ regmap_write(priv->tx_regmap, TX_AUDIO_HEADER, 0x0); ++ ++ regmap_write(priv->tx_regmap, TX_AUDIO_SAMPLE, ++ FIELD_PREP(TX_AUDIO_SAMPLE_CHANNEL_VALID, ++ BIT(hparms->channels) - 1)); ++ ++ audio_n = txc_48352_hdmi_codec_calc_audio_n(hparms); ++ ++ regmap_write(priv->tx_regmap, TX_SYS1_ACR_N_0, ++ FIELD_PREP(TX_SYS1_ACR_N_0_N_BYTE0, ++ (audio_n >> 0) & 0xff)); ++ regmap_write(priv->tx_regmap, TX_SYS1_ACR_N_1, ++ FIELD_PREP(TX_SYS1_ACR_N_1_N_BYTE1, ++ (audio_n >> 8) & 0xff)); ++ regmap_update_bits(priv->tx_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->tx_regmap, TX_SYS0_ACR_CTS_0, 0x0); ++ regmap_write(priv->tx_regmap, TX_SYS0_ACR_CTS_1, 0x0); ++ regmap_write(priv->tx_regmap, TX_SYS0_ACR_CTS_2, ++ TX_SYS0_ACR_CTS_2_FORCE_ARC_STABLE); ++ ++ regmap_write(priv->tx_regmap, TX_AUDIO_CONTROL, ++ TX_AUDIO_CONTROL_AUTO_AUDIO_FIFO_CLEAR | ++ FIELD_PREP(TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_MASK, ++ txc_48352_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; ++ ++ txc_48352_write_infoframe(priv->tx_regmap, ++ TX_PKT_REG_AUDIO_INFO_BASE_ADDR, ++ buf, len, true); ++ ++ for (i = 0; i < ARRAY_SIZE(hparms->iec.status); i++) { ++ if (i == 2) { ++ regmap_write(priv->tx_regmap, ++ TX_IEC60958_SUB1_OFFSET + i, ++ FIELD_PREP(IEC958_AES2_CON_SOURCE, ++ hparms->channels) | ++ FIELD_PREP(IEC958_AES2_CON_CHANNEL, 1)); ++ regmap_write(priv->tx_regmap, ++ TX_IEC60958_SUB2_OFFSET + i, ++ FIELD_PREP(IEC958_AES2_CON_SOURCE, ++ hparms->channels) | ++ FIELD_PREP(IEC958_AES2_CON_CHANNEL, 2)); ++ } else { ++ regmap_write(priv->tx_regmap, ++ TX_IEC60958_SUB1_OFFSET + i, ++ hparms->iec.status[i]); ++ regmap_write(priv->tx_regmap, ++ TX_IEC60958_SUB2_OFFSET + i, ++ hparms->iec.status[i]); ++ } ++ } ++ ++ return 0; ++} ++ ++static int txc_48352_hdmi_codec_audio_startup(struct device *dev, void *data) ++{ ++ struct txc_48352 *priv = data; ++ ++ regmap_update_bits(priv->tx_regmap, TX_PACKET_CONTROL_2, ++ TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE, 0x0); ++ ++ /* reset audio master and sample */ ++ regmap_write(priv->tx_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->tx_regmap, TX_SYS5_TX_SOFT_RESET_1, 0x0); ++ ++ regmap_write(priv->tx_regmap, TX_AUDIO_CONTROL_MORE, ++ TX_AUDIO_CONTROL_MORE_ENABLE); ++ ++ regmap_write(priv->tx_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->tx_regmap, TX_AUDIO_LIPSYNC, 0x0); ++ ++ regmap_write(priv->tx_regmap, TX_SYS1_ACR_N_2, ++ FIELD_PREP(TX_SYS1_ACR_N_2_N_MEAS_TOLERANCE, 0x3)); ++ ++ return 0; ++} ++ ++static void txc_48352_hdmi_codec_audio_shutdown(struct device *dev, void *data) ++{ ++ struct txc_48352 *priv = data; ++ ++ regmap_write(priv->tx_regmap, TX_AUDIO_CONTROL_MORE, 0x0); ++ regmap_update_bits(priv->other_regmap, HDMI_OTHER_CTRL1, ++ HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON, 0x0); ++ ++ regmap_update_bits(priv->tx_regmap, TX_PACKET_CONTROL_2, ++ TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE, ++ TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE); ++} ++ ++static int txc_48352_hdmi_codec_mute_stream(struct device *dev, void *data, ++ bool enable, int direction) ++{ ++ struct txc_48352 *priv = data; ++ ++ regmap_write(priv->tx_regmap, TX_AUDIO_PACK, ++ enable ? 0 : TX_AUDIO_PACK_AUDIO_SAMPLE_PACKETS_ENABLE); ++ ++ return 0; ++} ++ ++static int txc_48352_hdmi_codec_get_eld(struct device *dev, void *data, ++ uint8_t *buf, size_t len) ++{ ++ struct txc_48352 *priv = data; ++ ++ memcpy(buf, priv->connector.eld, min_t(size_t, MAX_ELD_BYTES, len)); ++ ++ return 0; ++} ++ ++static int txc_48352_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 txc_48352_hdmi_codec_hook_plugged_cb(struct device *dev, void *data, ++ hdmi_codec_plugged_cb fn, ++ struct device *codec_dev) ++{ ++ struct txc_48352 *priv = data; ++ ++ mutex_lock(&priv->codec_mutex); ++ priv->codec_plugged_cb = fn; ++ priv->codec_dev = codec_dev; ++ txc_48352_update_codec_status(priv, priv->connector.status); ++ mutex_unlock(&priv->codec_mutex); ++ ++ return 0; ++} ++ ++static struct hdmi_codec_ops txc_48352_hdmi_codec_ops = { ++ .hw_params = txc_48352_hdmi_codec_hw_params, ++ .audio_startup = txc_48352_hdmi_codec_audio_startup, ++ .audio_shutdown = txc_48352_hdmi_codec_audio_shutdown, ++ .mute_stream = txc_48352_hdmi_codec_mute_stream, ++ .get_eld = txc_48352_hdmi_codec_get_eld, ++ .get_dai_id = txc_48352_hdmi_codec_get_dai_id, ++ .hook_plugged_cb = txc_48352_hdmi_codec_hook_plugged_cb, ++}; ++ ++static int txc_48352_hdmi_codec_init(struct txc_48352 *priv) ++{ ++ struct hdmi_codec_pdata pdata = { ++ .ops = &txc_48352_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 void txc_48352_exit(struct txc_48352 *priv) ++{ ++ /* mask (= disable) all interrupts */ ++ regmap_write(priv->other_regmap, HDMI_OTHER_INTR_MASKN, 0); ++ ++ devm_free_irq(priv->dev, priv->irq, priv); ++ ++ regmap_update_bits(priv->other_regmap, HDMI_OTHER_CTRL1, ++ HDMI_OTHER_CTRL1_POWER_ON, 0); ++ ++ clk_disable_unprepare(priv->sys_clk); ++} ++ ++struct txc_48352 *txc_48352_bind(struct drm_encoder *encoder, ++ struct device *dev) ++{ ++ struct txc_48352 *priv; ++ int ret; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return ERR_PTR(-ENOMEM); ++ ++ priv->dev = dev; ++ ++ priv->input_bus_format = MEDIA_BUS_FMT_FIXED; ++ priv->output_bus_format = MEDIA_BUS_FMT_FIXED; ++ ++ mutex_init(&priv->cec_notifier_mutex); ++ mutex_init(&priv->codec_mutex); ++ ++ priv->irq = of_irq_get(dev->of_node, 0); ++ if (priv->irq < 0) ++ return ERR_PTR(priv->irq); ++ ++ priv->tx_regmap = dev_get_regmap(dev, "bridge"); ++ if (IS_ERR(priv->tx_regmap)) ++ return ERR_CAST(priv->tx_regmap); ++ ++ priv->other_regmap = dev_get_regmap(dev, "other"); ++ if (IS_ERR(priv->other_regmap)) ++ return ERR_CAST(priv->other_regmap); ++ ++ priv->sys_clk = devm_clk_get(dev, "sys"); ++ if (IS_ERR(priv->sys_clk)) { ++ dev_err(dev, "Failed to get the sys clock\n"); ++ return ERR_CAST(priv->sys_clk); ++ } ++ ++ priv->phy = devm_phy_get(priv->dev, "hdmi"); ++ if (IS_ERR(priv->phy)) { ++ ret = PTR_ERR(priv->phy); ++ dev_err(dev, "Failed to get the HDMI PHY: %d\n", ret); ++ return ERR_PTR(ret); ++ } ++ ++ ret = txc_48352_init(priv); ++ if (ret) ++ return ERR_PTR(ret); ++ ++ ret = txc_48352_hdmi_codec_init(priv); ++ if (ret) ++ return ERR_PTR(ret); ++ ++ priv->bridge.driver_private = priv; ++ priv->bridge.funcs = &txc_48352_bridge_funcs; ++ priv->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; ++ priv->bridge.of_node = dev->of_node; ++ ++ drm_bridge_add(&priv->bridge); ++ ++ return priv; ++} ++EXPORT_SYMBOL_GPL(txc_48352_bind); ++ ++void txc_48352_unbind(struct txc_48352 *priv) ++{ ++ platform_device_unregister(priv->hdmi_codec_pdev); ++ ++ drm_bridge_remove(&priv->bridge); ++ ++ txc_48352_exit(priv); ++} ++EXPORT_SYMBOL_GPL(txc_48352_unbind); ++ ++MODULE_AUTHOR("Martin Blumenstingl "); ++MODULE_DESCRIPTION("TranSwitch TXC-48352 HDMI 1.4 Transmitter IP core driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/gpu/drm/bridge/transwitch/txccq-txc-48352.h b/drivers/gpu/drm/bridge/transwitch/txccq-txc-48352.h +new file mode 100644 +index 000000000..8dcdb67fe +--- /dev/null ++++ b/drivers/gpu/drm/bridge/transwitch/txccq-txc-48352.h +@@ -0,0 +1,537 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2020 Martin Blumenstingl ++ * ++ * All registers and magic values are taken from Amlogic's GPL kernel sources: ++ * Copyright (C) 2010 Amlogic, Inc. ++ */ ++ ++#include ++#include ++ ++#ifndef __TXCCQ_TXC_84352_H__ ++#define __TXCCQ_TXC_84352_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_LAYOUT 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 /* __TXCCQ_TXC_84352_H__ */ +diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig +index 9f9281dd4..d4d4bb20e 100644 +--- a/drivers/gpu/drm/meson/Kconfig ++++ b/drivers/gpu/drm/meson/Kconfig +@@ -16,3 +16,10 @@ config DRM_MESON_DW_HDMI + default y if DRM_MESON + select DRM_DW_HDMI + imply DRM_DW_HDMI_I2S_AUDIO ++ ++config DRM_MESON_MX_HDMI ++ tristate "Amlogic Meson8/Meson8b HDMI Controller support" ++ depends on ARM || COMPILE_TEST ++ depends on DRM_MESON ++ default y if DRM_MESON ++ select DRM_TRANSWITCH_TXC_48352 +diff --git a/drivers/gpu/drm/meson/Makefile b/drivers/gpu/drm/meson/Makefile +index 28a519cdf..1ef803c0a 100644 +--- a/drivers/gpu/drm/meson/Makefile ++++ b/drivers/gpu/drm/meson/Makefile +@@ -5,3 +5,4 @@ meson-drm-y += meson_rdma.o meson_osd_afbcd.o + + obj-$(CONFIG_DRM_MESON) += meson-drm.o + obj-$(CONFIG_DRM_MESON_DW_HDMI) += meson_dw_hdmi.o ++obj-$(CONFIG_DRM_MESON_MX_HDMI) += meson_mx_hdmi.o +diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c +index 728fea509..c86517868 100644 +--- a/drivers/gpu/drm/meson/meson_drv.c ++++ b/drivers/gpu/drm/meson/meson_drv.c +@@ -9,7 +9,9 @@ + */ + + #include ++#include + #include ++#include + #include + #include + #include +@@ -90,7 +92,7 @@ static int meson_dumb_create(struct drm_file *file, struct drm_device *dev, + + DEFINE_DRM_GEM_CMA_FOPS(fops); + +-static struct drm_driver meson_driver = { ++static const struct drm_driver meson_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + + /* IRQ */ +@@ -132,28 +134,132 @@ static struct regmap_config meson_regmap_config = { + + 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)); ++ } ++} + +- /* +- * 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 i, ret; ++ ++ 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 0; ++ ++ for (i = 0; i < ARRAY_SIZE(priv->vid_pll_resets); i++) { ++ ret = reset_control_deassert(priv->vid_pll_resets[i]); ++ if (ret) ++ goto assert_resets; ++ } ++ ++ ret = clk_bulk_prepare_enable(VPU_VID_CLK_NUM, priv->vid_clks); ++ if (ret) ++ goto assert_resets; ++ ++ ret = clk_rate_exclusive_get(priv->vid_clks[VPU_VID_CLK_TMDS].clk); ++ if (ret) ++ goto disable_clks; ++ ++ ret = clk_bulk_prepare_enable(VPU_INTR_CLK_NUM, priv->intr_clks); ++ if (ret) ++ goto put_exclusive_clk; ++ ++ return 0; ++ ++put_exclusive_clk: ++ clk_rate_exclusive_put(priv->vid_clks[VPU_VID_CLK_TMDS].clk); ++assert_resets: ++ for (; i > 0; i++) ++ reset_control_assert(priv->vid_pll_resets[i - 1]); ++disable_clks: ++ clk_bulk_disable_unprepare(VPU_VID_CLK_NUM, priv->vid_clks); ++ ++ return ret; ++} ++ ++static void meson_video_clock_exit(struct meson_drm *priv) ++{ ++ unsigned 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)) ++ return; ++ ++ clk_bulk_disable_unprepare(VPU_INTR_CLK_NUM, priv->intr_clks); ++ ++ clk_rate_exclusive_put(priv->clk_venc); ++ clk_rate_exclusive_put(priv->vid_clks[VPU_VID_CLK_TMDS].clk); ++ ++ clk_bulk_disable_unprepare(VPU_VID_CLK_NUM, priv->vid_clks); ++ ++ for (i = 0; i < ARRAY_SIZE(priv->vid_pll_resets); i++) ++ reset_control_assert(priv->vid_pll_resets[i]); ++} ++ ++static int meson_cvbs_trimming_init(struct meson_drm *priv) ++{ ++ struct nvmem_cell *cell; ++ u8 *trimming; ++ size_t len; ++ ++ 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)) { ++ cell = devm_nvmem_cell_get(priv->dev, "cvbs_trimming"); ++ if (IS_ERR(cell)) ++ return PTR_ERR(cell); ++ ++ trimming = nvmem_cell_read(cell, &len); ++ if (IS_ERR(trimming)) ++ return PTR_ERR(trimming); ++ ++ if (len != 2) ++ return -EINVAL; ++ ++ if ((trimming[1] & 0xf0) == 0xa0 || ++ (trimming[1] & 0xf0) == 0x40 || ++ (trimming[1] & 0xc0) == 0x80) ++ priv->cvbs.cntl1 = trimming[0] & 0x7; ++ else ++ priv->cvbs.cntl1 = 0x0; ++ } else { ++ priv->cvbs.cntl1 = 0x0; ++ } ++ ++ return 0; + } + + static void meson_remove_framebuffers(void) +@@ -173,12 +279,44 @@ static void meson_remove_framebuffers(void) + kfree(ap); + } + ++static void meson_fbdev_setup(struct meson_drm *priv) ++{ ++ unsigned int preferred_bpp; ++ ++ /* ++ * All SoC generations before GXBB don't have a way to configure the ++ * alpha value for DRM_FORMAT_XRGB8888 and DRM_FORMAT_XBGR8888 with ++ * 32-bit but missing alpha ??? TODO: better explanation here. ++ * Use 24-bit to get a working framebuffer console. Applications that ++ * can do better (for example: kmscube) will switch to a better format ++ * like DRM_FORMAT_XRGB8888 while passing a sane alpha 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)) ++ preferred_bpp = 24; ++ else ++ preferred_bpp = 32; ++ ++ drm_fbdev_generic_setup(priv->drm, preferred_bpp); ++} ++ + struct meson_drm_soc_attr { + struct meson_drm_soc_limits limits; + const struct soc_device_attribute *attrs; + }; + + static const struct meson_drm_soc_attr meson_drm_soc_attrs[] = { ++ /* The maximum frequency of HDMI PLL on Meson8/8b/8m2 is ~3GHz */ ++ { ++ .limits = { ++ .max_hdmi_phy_freq = 2976000, ++ }, ++ .attrs = (const struct soc_device_attribute []) { ++ { .soc_id = "Meson8*", }, ++ { /* sentinel */ }, ++ } ++ }, + /* S805X/S805Y HDMI PLL won't lock for HDMI PHY freq > 1,65GHz */ + { + .limits = { +@@ -226,6 +364,51 @@ 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[0] = devm_reset_control_get_exclusive(dev, "vid_pll_pre"); ++ if (IS_ERR(priv->vid_pll_resets[0])) ++ return PTR_ERR(priv->vid_pll_resets[0]); ++ ++ priv->vid_pll_resets[1] = devm_reset_control_get_exclusive(dev, "vid_pll_post"); ++ if (IS_ERR(priv->vid_pll_resets[1])) ++ return PTR_ERR(priv->vid_pll_resets[1]); ++ ++ priv->vid_pll_resets[2] = devm_reset_control_get_exclusive(dev, "vid_pll_soft_pre"); ++ if (IS_ERR(priv->vid_pll_resets[2])) ++ return PTR_ERR(priv->vid_pll_resets[2]); ++ ++ priv->vid_pll_resets[3] = devm_reset_control_get_exclusive(dev, "vid_pll_soft_post"); ++ if (IS_ERR(priv->vid_pll_resets[3])) ++ return PTR_ERR(priv->vid_pll_resets[3]); ++ ++ priv->intr_clks[VPU_INTR_CLK_VPU].id = "vpu_intr"; ++ priv->intr_clks[VPU_INTR_CLK_HDMI_INTR_SYNC].id = "hdmi_intr_sync"; ++ priv->intr_clks[VPU_INTR_CLK_VENCI].id = "venci_int"; ++ ++ ret = devm_clk_bulk_get(dev, VPU_INTR_CLK_NUM, priv->intr_clks); ++ if (ret) ++ return ret; ++ ++ 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) ++ return ret; ++ ++ ret = meson_video_clock_init(priv); ++ if (ret) ++ goto free_drm; ++ // TODO: error handling below ++ } ++ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vpu"); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) { +@@ -235,24 +418,32 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + + priv->io_base = regs; + +- 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; +- } +- +- priv->hhi = devm_regmap_init_mmio(dev, regs, +- &meson_regmap_config); ++ priv->hhi = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, ++ "amlogic,hhi-sysctrl"); + if (IS_ERR(priv->hhi)) { +- dev_err(&pdev->dev, "Couldn't create the HHI regmap\n"); +- ret = PTR_ERR(priv->hhi); +- goto free_drm; ++ dev_dbg(dev, "Falling back to parsing the 'hhi' registers\n"); ++ ++ 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; ++ } ++ ++ 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->canvas = meson_canvas_get(dev); +@@ -283,6 +474,10 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + goto free_drm; + } + ++ ret = meson_cvbs_trimming_init(priv); ++ if (ret) ++ goto free_drm; ++ + priv->vsync_irq = platform_get_irq(pdev, 0); + + ret = drm_vblank_init(drm, 1); +@@ -360,7 +555,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; + +@@ -396,8 +591,12 @@ static void meson_drv_unbind(struct device *dev) + drm_irq_uninstall(drm); + drm_dev_put(drm); + +- if (priv->afbcd.ops) +- priv->afbcd.ops->exit(priv); ++ if (priv->afbcd.ops) { ++ priv->afbcd.ops->reset(priv); ++ meson_rdma_free(priv); ++ } ++ ++ meson_video_clock_exit(priv); + } + + static const struct component_master_ops meson_drv_master_ops = { +@@ -412,6 +611,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 +623,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); +@@ -480,17 +682,6 @@ static int meson_probe_remote(struct platform_device *pdev, + return count; + } + +-static void meson_drv_shutdown(struct platform_device *pdev) +-{ +- struct meson_drm *priv = dev_get_drvdata(&pdev->dev); +- +- if (!priv) +- return; +- +- drm_kms_helper_poll_fini(priv->drm); +- drm_atomic_helper_shutdown(priv->drm); +-} +- + static int meson_drv_probe(struct platform_device *pdev) + { + struct component_match *match = NULL; +@@ -525,6 +716,18 @@ static int meson_drv_probe(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, + }; +@@ -544,6 +747,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", +@@ -562,7 +771,6 @@ static const struct dev_pm_ops meson_drv_pm_ops = { + + static struct platform_driver meson_drm_platform_driver = { + .probe = meson_drv_probe, +- .shutdown = meson_drv_shutdown, + .driver = { + .name = "meson-drm", + .of_match_table = dt_match, +diff --git a/drivers/gpu/drm/meson/meson_drv.h b/drivers/gpu/drm/meson/meson_drv.h +index 177dac3ca..399516842 100644 +--- a/drivers/gpu/drm/meson/meson_drv.h ++++ b/drivers/gpu/drm/meson/meson_drv.h +@@ -7,10 +7,12 @@ + #ifndef __MESON_DRV_H + #define __MESON_DRV_H + ++#include + #include + #include + #include + #include ++#include + + struct drm_crtc; + struct drm_device; +@@ -19,10 +21,13 @@ struct meson_drm; + struct meson_afbcd_ops; + + 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, + }; + + struct meson_drm_match_data { +@@ -34,6 +39,24 @@ struct meson_drm_soc_limits { + unsigned int max_hdmi_phy_freq; + }; + ++enum vpu_bulk_intr_clk_id { ++ VPU_INTR_CLK_VPU = 0, ++ VPU_INTR_CLK_HDMI_INTR_SYNC, ++ VPU_INTR_CLK_VENCI, ++ VPU_INTR_CLK_NUM ++}; ++ ++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 ++}; ++ + struct meson_drm { + struct device *dev; + enum vpu_compatible compat; +@@ -54,6 +77,12 @@ struct meson_drm { + + const struct meson_drm_soc_limits *limits; + ++ struct clk_bulk_data intr_clks[VPU_INTR_CLK_NUM]; ++ struct clk_bulk_data vid_clks[VPU_VID_CLK_NUM]; ++ struct clk *clk_venc; ++ struct clk *clk_dac; ++ struct reset_control *vid_pll_resets[4]; ++ + /* Components Data */ + struct { + bool osd1_enabled; +@@ -167,6 +196,10 @@ struct meson_drm { + u64 modifier; + u32 format; + } afbcd; ++ ++ struct { ++ uint32_t cntl1; ++ } cvbs; + }; + + static inline int meson_vpu_is_compatible(struct meson_drm *priv, +diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c +index aad75a22d..c01aac9e4 100644 +--- a/drivers/gpu/drm/meson/meson_dw_hdmi.c ++++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c +@@ -115,12 +115,6 @@ + + static DEFINE_SPINLOCK(reg_lock); + +-enum meson_venc_source { +- MESON_VENC_SOURCE_NONE = 0, +- MESON_VENC_SOURCE_ENCI = 1, +- MESON_VENC_SOURCE_ENCP = 2, +-}; +- + struct meson_dw_hdmi; + + struct meson_dw_hdmi_data { +@@ -430,8 +424,6 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, + { + struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; + struct meson_drm *priv = dw_hdmi->priv; +- unsigned int wr_clk = +- readl_relaxed(priv->io_base + _REG(VPU_HDMI_SETTING)); + + DRM_DEBUG_DRIVER("\"%s\" div%d\n", mode->name, + mode->clock > 340000 ? 40 : 10); +@@ -504,35 +496,7 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, + meson_dw_hdmi_phy_reset(dw_hdmi); + meson_dw_hdmi_phy_reset(dw_hdmi); + +- /* Temporary Disable VENC video stream */ +- if (priv->venc.hdmi_use_enci) +- writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN)); +- else +- writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN)); +- +- /* Temporary Disable HDMI video stream to HDMI-TX */ +- writel_bits_relaxed(0x3, 0, +- priv->io_base + _REG(VPU_HDMI_SETTING)); +- writel_bits_relaxed(0xf << 8, 0, +- priv->io_base + _REG(VPU_HDMI_SETTING)); +- +- /* Re-Enable VENC video stream */ +- if (priv->venc.hdmi_use_enci) +- writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN)); +- else +- writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN)); +- +- /* Push back HDMI clock settings */ +- writel_bits_relaxed(0xf << 8, wr_clk & (0xf << 8), +- priv->io_base + _REG(VPU_HDMI_SETTING)); +- +- /* Enable and Select HDMI video source for HDMI-TX */ +- if (priv->venc.hdmi_use_enci) +- writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCI, +- priv->io_base + _REG(VPU_HDMI_SETTING)); +- else +- writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCP, +- priv->io_base + _REG(VPU_HDMI_SETTING)); ++ meson_venc_hdmi_bridge_reset(priv); + + return 0; + } +@@ -716,12 +680,12 @@ static const struct drm_encoder_funcs meson_venc_hdmi_encoder_funcs = { + }; + + static u32 * +-meson_venc_hdmi_encoder_get_inp_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) ++meson_dw_hdmi_encoder_get_inp_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 = NULL; + int i; +@@ -746,7 +710,7 @@ meson_venc_hdmi_encoder_get_inp_bus_fmts(struct drm_bridge *bridge, + return input_fmts; + } + +-static int meson_venc_hdmi_encoder_atomic_check(struct drm_bridge *bridge, ++static int meson_dw_hdmi_encoder_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +@@ -760,34 +724,27 @@ static int meson_venc_hdmi_encoder_atomic_check(struct drm_bridge *bridge, + return 0; + } + +-static void meson_venc_hdmi_encoder_disable(struct drm_bridge *bridge) ++static void meson_dw_hdmi_encoder_disable(struct drm_bridge *bridge) + { + struct meson_dw_hdmi *dw_hdmi = bridge_to_meson_dw_hdmi(bridge); + struct meson_drm *priv = dw_hdmi->priv; + + DRM_DEBUG_DRIVER("\n"); + +- writel_bits_relaxed(0x3, 0, +- priv->io_base + _REG(VPU_HDMI_SETTING)); +- +- writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN)); +- writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN)); ++ meson_venc_hdmi_encoder_disable(priv); + } + +-static void meson_venc_hdmi_encoder_enable(struct drm_bridge *bridge) ++static void meson_dw_hdmi_encoder_enable(struct drm_bridge *bridge) + { + struct meson_dw_hdmi *dw_hdmi = bridge_to_meson_dw_hdmi(bridge); + struct meson_drm *priv = dw_hdmi->priv; + + DRM_DEBUG_DRIVER("%s\n", priv->venc.hdmi_use_enci ? "VENCI" : "VENCP"); + +- if (priv->venc.hdmi_use_enci) +- writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN)); +- else +- writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN)); ++ meson_venc_hdmi_encoder_enable(priv); + } + +-static void meson_venc_hdmi_encoder_mode_set(struct drm_bridge *bridge, ++static void meson_dw_hdmi_encoder_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) + { +@@ -822,12 +779,12 @@ static void meson_venc_hdmi_encoder_mode_set(struct drm_bridge *bridge, + static const struct drm_bridge_funcs meson_venc_hdmi_encoder_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, +- .atomic_get_input_bus_fmts = meson_venc_hdmi_encoder_get_inp_bus_fmts, ++ .atomic_get_input_bus_fmts = meson_dw_hdmi_encoder_get_inp_bus_fmts, + .atomic_reset = drm_atomic_helper_bridge_reset, +- .atomic_check = meson_venc_hdmi_encoder_atomic_check, +- .enable = meson_venc_hdmi_encoder_enable, +- .disable = meson_venc_hdmi_encoder_disable, +- .mode_set = meson_venc_hdmi_encoder_mode_set, ++ .atomic_check = meson_dw_hdmi_encoder_atomic_check, ++ .enable = meson_dw_hdmi_encoder_enable, ++ .disable = meson_dw_hdmi_encoder_disable, ++ .mode_set = meson_dw_hdmi_encoder_mode_set, + }; + + /* DW HDMI Regmap */ +diff --git a/drivers/gpu/drm/meson/meson_mx_hdmi.c b/drivers/gpu/drm/meson/meson_mx_hdmi.c +new file mode 100644 +index 000000000..c2e7efa04 +--- /dev/null ++++ b/drivers/gpu/drm/meson/meson_mx_hdmi.c +@@ -0,0 +1,432 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2020 Martin Blumenstingl ++ * ++ * All registers and magic values are taken from Amlogic's GPL kernel sources: ++ * Copyright (C) 2010 Amlogic, Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "meson_drv.h" ++#include "meson_registers.h" ++#include "meson_vclk.h" ++#include "meson_venc.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_mx_hdmi { ++ struct device *dev; ++ struct drm_device *drm; ++ struct txc_48352 *txc_48352; ++ ++ struct clk *pclk; ++ ++ struct drm_encoder encoder; ++ struct drm_bridge bridge; ++ ++ unsigned int output_bus_format; ++}; ++ ++#define to_meson_mx_hdmi(x) container_of(x, struct meson_mx_hdmi, x) ++ ++static int meson_mx_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_mx_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_mx_hdmi_bus_regmap_config = { ++ .name = "bus", ++ .reg_bits = 16, ++ .val_bits = 16, ++ .reg_stride = 1, ++ .reg_read = meson_mx_hdmi_reg_read, ++ .reg_write = meson_mx_hdmi_reg_write, ++ .fast_io = true, ++}; ++ ++static int meson_mx_hdmi_child_regmap_read(void *context, unsigned int addr, ++ unsigned int *data) ++{ ++ struct regmap *bus_regmap = context; ++ ++ return regmap_read(bus_regmap, addr, data); ++} ++ ++static int meson_mx_hdmi_child_regmap_write(void *context, unsigned int addr, ++ unsigned int data) ++{ ++ struct regmap *bus_regmap = context; ++ ++ return regmap_write(bus_regmap, addr, data); ++} ++ ++static const struct regmap_config meson_mx_hdmi_bridge_regmap_config = { ++ .name = "bridge", ++ .reg_bits = 16, ++ .val_bits = 8, ++ .reg_stride = 1, ++ .reg_read = meson_mx_hdmi_child_regmap_read, ++ .reg_write = meson_mx_hdmi_child_regmap_write, ++ .max_register = 0x7ff, ++ .fast_io = true, ++}; ++ ++static const struct regmap_range meson_mx_hdmi_other_regmap_ranges[] = { ++ regmap_reg_range(0x8000, 0x800c), ++}; ++ ++static const struct regmap_access_table meson_mx_hdmi_other_regmap_access = { ++ .yes_ranges = meson_mx_hdmi_other_regmap_ranges, ++ .n_yes_ranges = ARRAY_SIZE(meson_mx_hdmi_other_regmap_ranges), ++}; ++ ++static const struct regmap_config meson_mx_hdmi_other_regmap_config = { ++ .name = "other", ++ .reg_bits = 16, ++ .val_bits = 16, ++ .reg_stride = 1, ++ .reg_read = meson_mx_hdmi_child_regmap_read, ++ .reg_write = meson_mx_hdmi_child_regmap_write, ++ .rd_table = &meson_mx_hdmi_other_regmap_access, ++ .wr_table = &meson_mx_hdmi_other_regmap_access, ++ .max_register = 0xffff, ++ .fast_io = true, ++}; ++ ++static const u32 meson_mx_hdmi_bridge_input_bus_fmts[] = { ++ /* ++ * TODO: meson_plane.c unconditionally enables the RGB2YUV converter ++ * (OSD_OUTPUT_COLOR_RGB). Thus the output format (from the VPU) is ++ * always YUV444, whereas the hardware can toggle at least between ++ * RGB888 and YUV444). ++ * Thus MEDIA_BUS_FMT_RGB888_1X24 is omitted here for now. ++ */ ++ MEDIA_BUS_FMT_YUV8_1X24, ++}; ++ ++static u32 * ++meson_mx_hdmi_bridge_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; ++ int i; ++ ++ *num_input_fmts = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(meson_mx_hdmi_bridge_input_bus_fmts) ; i++) { ++ if (output_fmt != meson_mx_hdmi_bridge_input_bus_fmts[i]) ++ continue; ++ ++ *num_input_fmts = 1; ++ input_fmts = kcalloc(*num_input_fmts, sizeof(*input_fmts), ++ GFP_KERNEL); ++ if (!input_fmts) ++ return NULL; ++ ++ input_fmts[0] = output_fmt; ++ ++ return input_fmts; ++ } ++ ++ return NULL; ++} ++ ++static int meson_mx_hdmi_bridge_atomic_check(struct drm_bridge *bridge, ++ struct drm_bridge_state *bridge_state, ++ struct drm_crtc_state *crtc_state, ++ struct drm_connector_state *conn_state) ++{ ++ struct meson_mx_hdmi *hdmi = to_meson_mx_hdmi(bridge); ++ ++ hdmi->output_bus_format = bridge_state->output_bus_cfg.format; ++ ++ return 0; ++} ++ ++static void meson_mx_hdmi_bridge_enable(struct drm_bridge *bridge) ++{ ++ struct meson_mx_hdmi *hdmi = to_meson_mx_hdmi(bridge); ++ struct meson_drm *priv = hdmi->drm->dev_private; ++ ++ meson_venc_hdmi_bridge_reset(priv); ++ ++ meson_venc_hdmi_encoder_enable(priv); ++} ++ ++static void meson_mx_hdmi_bridge_disable(struct drm_bridge *bridge) ++{ ++ struct meson_mx_hdmi *hdmi = to_meson_mx_hdmi(bridge); ++ struct meson_drm *priv = hdmi->drm->dev_private; ++ ++ meson_venc_hdmi_encoder_disable(priv); ++} ++ ++static void meson_mx_hdmi_bridge_mode_set(struct drm_bridge *bridge, ++ const struct drm_display_mode *mode, ++ const struct drm_display_mode *adj_mode) ++{ ++ unsigned int vclk_freq, venc_freq, hdmi_freq, phy_freq, ycrcb_map; ++ struct meson_mx_hdmi *hdmi = to_meson_mx_hdmi(bridge); ++ struct meson_drm *priv = hdmi->drm->dev_private; ++ int cea_mode = drm_match_cea_mode(mode); ++ ++ if (hdmi->output_bus_format == MEDIA_BUS_FMT_RGB888_1X24) ++ ycrcb_map = VPU_HDMI_OUTPUT_YCBCR; ++ else ++ ycrcb_map = VPU_HDMI_OUTPUT_CRYCB; ++ ++ /* VENC + VENC-DVI Mode setup */ ++ meson_venc_hdmi_mode_set(priv, cea_mode, ycrcb_map, false, mode); ++ ++ vclk_freq = mode->clock; ++ phy_freq = vclk_freq * 10; ++ ++ if (!cea_mode) { ++ meson_vclk_setup(priv, MESON_VCLK_TARGET_DMT, phy_freq, ++ vclk_freq, vclk_freq, vclk_freq, false); ++ } else { ++ if (mode->flags & DRM_MODE_FLAG_DBLCLK) ++ vclk_freq *= 2; ++ ++ venc_freq = vclk_freq; ++ hdmi_freq = vclk_freq; ++ ++ if (meson_venc_hdmi_venc_repeat(cea_mode)) ++ venc_freq *= 2; ++ ++ vclk_freq = max(venc_freq, hdmi_freq); ++ ++ if (mode->flags & DRM_MODE_FLAG_DBLCLK) ++ venc_freq /= 2; ++ ++ drm_dbg(hdmi->drm, "vclk:%d venc=%d hdmi=%d enci=%d\n", ++ vclk_freq, venc_freq, hdmi_freq, ++ priv->venc.hdmi_use_enci); ++ ++ meson_vclk_setup(priv, MESON_VCLK_TARGET_HDMI, phy_freq, ++ vclk_freq, venc_freq, hdmi_freq, ++ priv->venc.hdmi_use_enci); ++ } ++} ++ ++static const struct drm_bridge_funcs meson_mx_hdmi_bridge_funcs = { ++ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ++ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, ++ .atomic_get_input_bus_fmts = meson_mx_hdmi_bridge_get_input_bus_fmts, ++ .atomic_reset = drm_atomic_helper_bridge_reset, ++ .atomic_check = meson_mx_hdmi_bridge_atomic_check, ++ .enable = meson_mx_hdmi_bridge_enable, ++ .disable = meson_mx_hdmi_bridge_disable, ++ .mode_set = meson_mx_hdmi_bridge_mode_set, ++}; ++ ++static const struct drm_encoder_funcs meson_mx_hdmi_encoder_funcs = { ++ .destroy = drm_encoder_cleanup, ++}; ++ ++static void meson_mx_hdmi_exit(struct meson_mx_hdmi *hdmi) ++{ ++ if (hdmi->txc_48352) ++ txc_48352_unbind(hdmi->txc_48352); ++ ++ drm_encoder_cleanup(&hdmi->encoder); ++ ++ clk_disable_unprepare(hdmi->pclk); ++} ++ ++static int meson_mx_hdmi_init(struct meson_mx_hdmi *hdmi, void __iomem *base) ++{ ++ struct drm_bridge *next_bridge; ++ u32 regval; ++ int ret; ++ ++ ret = clk_prepare_enable(hdmi->pclk); ++ if (ret) { ++ drm_err(hdmi->drm, "Failed to enable the pclk: %d\n", ret); ++ return ret; ++ } ++ ++ regval = readl(base + HDMI_CTRL_PORT); ++ regval |= HDMI_CTRL_PORT_APB3_ERR_EN; ++ writel(regval, base + HDMI_CTRL_PORT); ++ ++ hdmi->encoder.possible_crtcs = BIT(0); ++ ret = drm_encoder_init(hdmi->drm, &hdmi->encoder, ++ &meson_mx_hdmi_encoder_funcs, ++ DRM_MODE_ENCODER_TMDS, NULL); ++ if (ret) { ++ return ret; ++ goto err_disable_clk; ++ } ++ ++ hdmi->bridge.funcs = &meson_mx_hdmi_bridge_funcs; ++ ret = drm_bridge_attach(&hdmi->encoder, &hdmi->bridge, NULL, 0); ++ if (ret) { ++ return ret; ++ goto err_cleanup_encoder; ++ } ++ ++ hdmi->txc_48352 = txc_48352_bind(&hdmi->encoder, hdmi->dev); ++ if (IS_ERR(hdmi->txc_48352)) { ++ ret = PTR_ERR(hdmi->txc_48352); ++ goto err_cleanup_encoder; ++ } ++ ++ next_bridge = of_drm_find_bridge(hdmi->dev->of_node); ++ if (next_bridge) ++ drm_bridge_attach(&hdmi->encoder, next_bridge, ++ &hdmi->bridge, 0); ++ ++ return 0; ++ ++err_disable_clk: ++ clk_disable_unprepare(hdmi->pclk); ++err_cleanup_encoder: ++ drm_encoder_cleanup(&hdmi->encoder); ++ return ret; ++} ++ ++static int meson_mx_hdmi_bind(struct device *dev, struct device *master, ++ void *data) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct regmap *bus_regmap, *child_regmap; ++ struct drm_device *drm = data; ++ struct meson_mx_hdmi *hdmi; ++ struct resource *res; ++ void __iomem *base; ++ int ret; ++ ++ hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); ++ if (!hdmi) ++ return -ENOMEM; ++ ++ hdmi->dev = dev; ++ hdmi->drm = drm; ++ ++ dev_set_drvdata(dev, hdmi); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ bus_regmap = devm_regmap_init(dev, NULL, base, ++ &meson_mx_hdmi_bus_regmap_config); ++ if (IS_ERR(bus_regmap)) ++ return PTR_ERR(bus_regmap); ++ ++ child_regmap = devm_regmap_init(dev, NULL, bus_regmap, ++ &meson_mx_hdmi_bridge_regmap_config); ++ if (IS_ERR(child_regmap)) ++ return PTR_ERR(child_regmap); ++ ++ child_regmap = devm_regmap_init(dev, NULL, bus_regmap, ++ &meson_mx_hdmi_other_regmap_config); ++ if (IS_ERR(child_regmap)) ++ return PTR_ERR(child_regmap); ++ ++ hdmi->pclk = devm_clk_get(hdmi->dev, "pclk"); ++ if (IS_ERR(hdmi->pclk)) { ++ ret = PTR_ERR(hdmi->pclk); ++ drm_err(drm, "Failed to get the pclk: %d\n", ret); ++ return ret; ++ } ++ ++ ret = meson_mx_hdmi_init(hdmi, base); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void meson_mx_hdmi_unbind(struct device *dev, struct device *master, ++ void *data) ++{ ++ struct meson_mx_hdmi *hdmi = dev_get_drvdata(dev); ++ ++ hdmi->encoder.funcs->destroy(&hdmi->encoder); ++ ++ meson_mx_hdmi_exit(hdmi); ++} ++ ++static const struct component_ops meson_mx_hdmi_component_ops = { ++ .bind = meson_mx_hdmi_bind, ++ .unbind = meson_mx_hdmi_unbind, ++}; ++ ++static int meson_mx_hdmi_probe(struct platform_device *pdev) ++{ ++ return component_add(&pdev->dev, &meson_mx_hdmi_component_ops); ++} ++ ++static int meson_mx_hdmi_remove(struct platform_device *pdev) ++{ ++ component_del(&pdev->dev, &meson_mx_hdmi_component_ops); ++ ++ return 0; ++} ++ ++static const struct of_device_id meson_mx_hdmi_of_table[] = { ++ { .compatible = "amlogic,meson8-hdmi-tx" }, ++ { .compatible = "amlogic,meson8b-hdmi-tx" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, meson_mx_hdmi_of_table); ++ ++static struct platform_driver meson_mx_hdmi_platform_driver = { ++ .probe = meson_mx_hdmi_probe, ++ .remove = meson_mx_hdmi_remove, ++ .driver = { ++ .name = "meson-mx-hdmi", ++ .of_match_table = meson_mx_hdmi_of_table, ++ }, ++}; ++module_platform_driver(meson_mx_hdmi_platform_driver); ++ ++MODULE_AUTHOR("Martin Blumenstingl "); ++MODULE_DESCRIPTION("Amlogic Meson8 and Meson8b HDMI TX DRM driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/gpu/drm/meson/meson_osd_afbcd.c b/drivers/gpu/drm/meson/meson_osd_afbcd.c +index 0cdbe8994..ffc6b584d 100644 +--- a/drivers/gpu/drm/meson/meson_osd_afbcd.c ++++ b/drivers/gpu/drm/meson/meson_osd_afbcd.c +@@ -79,6 +79,11 @@ static bool meson_gxm_afbcd_supported_fmt(u64 modifier, uint32_t format) + return meson_gxm_afbcd_pixel_fmt(modifier, format) >= 0; + } + ++static int meson_gxm_afbcd_init(struct meson_drm *priv) ++{ ++ return 0; ++} ++ + static int meson_gxm_afbcd_reset(struct meson_drm *priv) + { + writel_relaxed(VIU_SW_RESET_OSD1_AFBCD, +@@ -88,16 +93,6 @@ static int meson_gxm_afbcd_reset(struct meson_drm *priv) + return 0; + } + +-static int meson_gxm_afbcd_init(struct meson_drm *priv) +-{ +- return 0; +-} +- +-static void meson_gxm_afbcd_exit(struct meson_drm *priv) +-{ +- meson_gxm_afbcd_reset(priv); +-} +- + static int meson_gxm_afbcd_enable(struct meson_drm *priv) + { + writel_relaxed(FIELD_PREP(OSD1_AFBCD_ID_FIFO_THRD, 0x40) | +@@ -177,7 +172,6 @@ static int meson_gxm_afbcd_setup(struct meson_drm *priv) + + struct meson_afbcd_ops meson_afbcd_gxm_ops = { + .init = meson_gxm_afbcd_init, +- .exit = meson_gxm_afbcd_exit, + .reset = meson_gxm_afbcd_reset, + .enable = meson_gxm_afbcd_enable, + .disable = meson_gxm_afbcd_disable, +@@ -275,18 +269,6 @@ static bool meson_g12a_afbcd_supported_fmt(u64 modifier, uint32_t format) + return meson_g12a_afbcd_pixel_fmt(modifier, format) >= 0; + } + +-static int meson_g12a_afbcd_reset(struct meson_drm *priv) +-{ +- meson_rdma_reset(priv); +- +- meson_rdma_writel_sync(priv, VIU_SW_RESET_G12A_AFBC_ARB | +- VIU_SW_RESET_G12A_OSD1_AFBCD, +- VIU_SW_RESET); +- meson_rdma_writel_sync(priv, 0, VIU_SW_RESET); +- +- return 0; +-} +- + static int meson_g12a_afbcd_init(struct meson_drm *priv) + { + int ret; +@@ -304,10 +286,16 @@ static int meson_g12a_afbcd_init(struct meson_drm *priv) + return 0; + } + +-static void meson_g12a_afbcd_exit(struct meson_drm *priv) ++static int meson_g12a_afbcd_reset(struct meson_drm *priv) + { +- meson_g12a_afbcd_reset(priv); +- meson_rdma_free(priv); ++ meson_rdma_reset(priv); ++ ++ meson_rdma_writel_sync(priv, VIU_SW_RESET_G12A_AFBC_ARB | ++ VIU_SW_RESET_G12A_OSD1_AFBCD, ++ VIU_SW_RESET); ++ meson_rdma_writel_sync(priv, 0, VIU_SW_RESET); ++ ++ return 0; + } + + static int meson_g12a_afbcd_enable(struct meson_drm *priv) +@@ -392,7 +380,6 @@ static int meson_g12a_afbcd_setup(struct meson_drm *priv) + + struct meson_afbcd_ops meson_afbcd_g12a_ops = { + .init = meson_g12a_afbcd_init, +- .exit = meson_g12a_afbcd_exit, + .reset = meson_g12a_afbcd_reset, + .enable = meson_g12a_afbcd_enable, + .disable = meson_g12a_afbcd_disable, +diff --git a/drivers/gpu/drm/meson/meson_osd_afbcd.h b/drivers/gpu/drm/meson/meson_osd_afbcd.h +index e77ddeb64..5e5523304 100644 +--- a/drivers/gpu/drm/meson/meson_osd_afbcd.h ++++ b/drivers/gpu/drm/meson/meson_osd_afbcd.h +@@ -14,7 +14,6 @@ + + struct meson_afbcd_ops { + int (*init)(struct meson_drm *priv); +- void (*exit)(struct meson_drm *priv); + int (*reset)(struct meson_drm *priv); + int (*enable)(struct meson_drm *priv); + int (*disable)(struct meson_drm *priv); +diff --git a/drivers/gpu/drm/meson/meson_plane.c b/drivers/gpu/drm/meson/meson_plane.c +index 35338ed18..2c403624e 100644 +--- a/drivers/gpu/drm/meson/meson_plane.c ++++ b/drivers/gpu/drm/meson/meson_plane.c +@@ -194,8 +194,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 && +@@ -226,17 +229,21 @@ static void meson_plane_atomic_update(struct drm_plane *plane, + } + } + +- switch (fb->format->format) { +- case DRM_FORMAT_XRGB8888: +- case DRM_FORMAT_XBGR8888: +- /* For XRGB, replace the pixel's alpha by 0xFF */ +- priv->viu.osd1_ctrl_stat2 |= OSD_REPLACE_EN; +- break; +- case DRM_FORMAT_ARGB8888: +- case DRM_FORMAT_ABGR8888: +- /* For ARGB, use the pixel's alpha */ +- priv->viu.osd1_ctrl_stat2 &= ~OSD_REPLACE_EN; +- break; ++ 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)) { ++ switch (fb->format->format) { ++ case DRM_FORMAT_XRGB8888: ++ case DRM_FORMAT_XBGR8888: ++ /* For XRGB, replace the pixel's alpha by 0xFF */ ++ priv->viu.osd1_ctrl_stat2 |= OSD_REPLACE_EN; ++ break; ++ case DRM_FORMAT_ARGB8888: ++ case DRM_FORMAT_ABGR8888: ++ /* For ARGB, use the pixel's alpha */ ++ priv->viu.osd1_ctrl_stat2 &= ~OSD_REPLACE_EN; ++ break; ++ } + } + + /* Default scaler parameters */ +diff --git a/drivers/gpu/drm/meson/meson_registers.h b/drivers/gpu/drm/meson/meson_registers.h +index 0f3cafab8..446e7961d 100644 +--- a/drivers/gpu/drm/meson/meson_registers.h ++++ b/drivers/gpu/drm/meson/meson_registers.h +@@ -634,11 +634,6 @@ + #define VPP_WRAP_OSD3_MATRIX_PRE_OFFSET2 0x3dbc + #define VPP_WRAP_OSD3_MATRIX_EN_CTRL 0x3dbd + +-/* osd1 HDR */ +-#define OSD1_HDR2_CTRL 0x38a0 +-#define OSD1_HDR2_CTRL_VDIN0_HDR2_TOP_EN BIT(13) +-#define OSD1_HDR2_CTRL_REG_ONLY_MAT BIT(16) +- + /* osd2 scaler */ + #define OSD2_VSC_PHASE_STEP 0x3d00 + #define OSD2_VSC_INI_PHASE 0x3d01 +diff --git a/drivers/gpu/drm/meson/meson_vclk.c b/drivers/gpu/drm/meson/meson_vclk.c +index 0eb86943a..e14f5b84a 100644 +--- a/drivers/gpu/drm/meson/meson_vclk.c ++++ b/drivers/gpu/drm/meson/meson_vclk.c +@@ -131,7 +131,7 @@ enum { + VID_PLL_DIV_15, + }; + +-void meson_vid_pll_set(struct meson_drm *priv, unsigned int div) ++static void meson_vid_pll_set(struct meson_drm *priv, unsigned int div) + { + unsigned int shift_val = 0; + unsigned int shift_sel = 0; +@@ -487,9 +487,9 @@ static inline unsigned int pll_od_to_reg(unsigned int od) + return 0; + } + +-void meson_hdmi_pll_set_params(struct meson_drm *priv, unsigned int m, +- unsigned int frac, unsigned int od1, +- unsigned int od2, unsigned int od3) ++static void meson_hdmi_pll_set_params(struct meson_drm *priv, unsigned int m, ++ unsigned int frac, unsigned int od1, ++ unsigned int od2, unsigned int od3) + { + unsigned int val; + +@@ -1024,6 +1024,89 @@ 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 void meson_vclk_setup_ccf(struct meson_drm *priv, unsigned int target, ++ bool hdmi_use_enci, unsigned int phy_freq, ++ unsigned int dac_freq, unsigned int venc_freq) ++{ ++ unsigned int i; ++ int ret; ++ ++ 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); ++ ++ clk_rate_exclusive_put(priv->clk_venc); ++ clk_rate_exclusive_put(priv->vid_clks[VPU_VID_CLK_TMDS].clk); ++ clk_disable(priv->clk_dac); ++ clk_disable(priv->clk_venc); ++ ++ /* ++ * 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 = clk_set_rate_exclusive(priv->vid_clks[VPU_VID_CLK_TMDS].clk, ++ phy_freq * 1000UL); ++ if (ret) { ++ dev_err(priv->dev, "Failed to set TMDS clock to %ukHz\n", ++ phy_freq); ++ clk_rate_exclusive_get(priv->clk_venc); ++ clk_rate_exclusive_get(priv->vid_clks[VPU_VID_CLK_TMDS].clk); ++ goto out_enable_clocks; ++ } ++ ++ if (target == MESON_VCLK_TARGET_CVBS || hdmi_use_enci) ++ priv->clk_venc = priv->vid_clks[VPU_VID_CLK_CTS_ENCI].clk; ++ else ++ priv->clk_venc = priv->vid_clks[VPU_VID_CLK_CTS_ENCP].clk; ++ ++ if (target == MESON_VCLK_TARGET_CVBS) ++ priv->clk_dac = priv->vid_clks[VPU_VID_CLK_CTS_VDAC0].clk; ++ else ++ priv->clk_dac = priv->vid_clks[VPU_VID_CLK_HDMI_TX_PIXEL].clk; ++ ++ /* ++ * 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 = clk_set_rate_exclusive(priv->clk_venc, venc_freq * 1000UL); ++ if (ret) { ++ dev_warn(priv->dev, ++ "Failed to set VENC clock to %ukHz while TMDS clock is %ukHz\n", ++ venc_freq, phy_freq); ++ clk_rate_exclusive_get(priv->clk_venc); ++ goto out_enable_clocks; ++ } ++ ++ /* ++ * after changing any of the VID_PLL_* clocks (which can happen when ++ * update the VENC clock rate) we need to assert and then deassert the ++ * VID_DIVIDER_CNTL_* reset lines. ++ */ ++ for (i = 0; i < ARRAY_SIZE(priv->vid_pll_resets); i++) ++ reset_control_assert(priv->vid_pll_resets[i]); ++ for (i = 0; i < ARRAY_SIZE(priv->vid_pll_resets); i++) ++ reset_control_deassert(priv->vid_pll_resets[i]); ++ ++ ret = clk_set_rate(priv->clk_dac, dac_freq * 1000UL); ++ if (ret) { ++ dev_warn(priv->dev, ++ "Failed to set pixel clock to %ukHz while TMDS clock is %ukHz\n", ++ dac_freq, phy_freq); ++ goto out_enable_clocks; ++ } ++ ++out_enable_clocks: ++ ret = clk_enable(priv->clk_venc); ++ if (ret) ++ dev_err(priv->dev, ++ "Failed to re-enable the VENC clock at %ukHz\n", venc_freq); ++ ret = clk_enable(priv->clk_dac); ++ if (ret) ++ dev_err(priv->dev, ++ "Failed to re-enable the pixel clock at %ukHz\n", ++ dac_freq); ++} ++ + 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 +1117,15 @@ 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)) { ++ meson_vclk_setup_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 f93c725b6..145e1d64b 100644 +--- a/drivers/gpu/drm/meson/meson_venc.c ++++ b/drivers/gpu/drm/meson/meson_venc.c +@@ -890,8 +890,8 @@ bool meson_venc_hdmi_supported_vic(int vic) + } + EXPORT_SYMBOL_GPL(meson_venc_hdmi_supported_vic); + +-void meson_venc_hdmi_get_dmt_vmode(const struct drm_display_mode *mode, +- union meson_hdmi_venc_mode *dmt_mode) ++static void meson_venc_hdmi_get_dmt_vmode(const struct drm_display_mode *mode, ++ union meson_hdmi_venc_mode *dmt_mode) + { + memset(dmt_mode, 0, sizeof(*dmt_mode)); + +@@ -945,6 +945,66 @@ bool meson_venc_hdmi_venc_repeat(int vic) + } + EXPORT_SYMBOL_GPL(meson_venc_hdmi_venc_repeat); + ++void meson_venc_hdmi_bridge_reset(struct meson_drm *priv) ++{ ++ u32 wr_clk = readl_relaxed(priv->io_base + _REG(VPU_HDMI_SETTING)); ++ enum meson_venc_source { ++ MESON_VENC_SOURCE_NONE = 0, ++ MESON_VENC_SOURCE_ENCI = 1, ++ MESON_VENC_SOURCE_ENCP = 2, ++ }; ++ ++ /* Temporary Disable VENC video stream */ ++ if (priv->venc.hdmi_use_enci) ++ writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN)); ++ else ++ writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN)); ++ ++ /* Temporary Disable HDMI video stream to HDMI-TX */ ++ writel_bits_relaxed(0x3, 0, ++ priv->io_base + _REG(VPU_HDMI_SETTING)); ++ writel_bits_relaxed(0xf << 8, 0, ++ priv->io_base + _REG(VPU_HDMI_SETTING)); ++ ++ /* Re-Enable VENC video stream */ ++ if (priv->venc.hdmi_use_enci) ++ writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN)); ++ else ++ writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN)); ++ ++ /* Push back HDMI clock settings */ ++ writel_bits_relaxed(0xf << 8, wr_clk & (0xf << 8), ++ priv->io_base + _REG(VPU_HDMI_SETTING)); ++ ++ /* Enable and Select HDMI video source for HDMI-TX */ ++ if (priv->venc.hdmi_use_enci) ++ writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCI, ++ priv->io_base + _REG(VPU_HDMI_SETTING)); ++ else ++ writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCP, ++ priv->io_base + _REG(VPU_HDMI_SETTING)); ++} ++EXPORT_SYMBOL_GPL(meson_venc_hdmi_bridge_reset); ++ ++void meson_venc_hdmi_encoder_enable(struct meson_drm *priv) ++{ ++ if (priv->venc.hdmi_use_enci) ++ writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN)); ++ else ++ writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN)); ++} ++EXPORT_SYMBOL_GPL(meson_venc_hdmi_encoder_enable); ++ ++void meson_venc_hdmi_encoder_disable(struct meson_drm *priv) ++{ ++ writel_bits_relaxed(0x3, 0, ++ priv->io_base + _REG(VPU_HDMI_SETTING)); ++ ++ writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN)); ++ writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN)); ++} ++EXPORT_SYMBOL_GPL(meson_venc_hdmi_encoder_disable); ++ + void meson_venc_hdmi_mode_set(struct meson_drm *priv, int vic, + unsigned int ycrcb_map, + bool yuv420_mode, +diff --git a/drivers/gpu/drm/meson/meson_venc.h b/drivers/gpu/drm/meson/meson_venc.h +index 9138255ff..0c4df5d5c 100644 +--- a/drivers/gpu/drm/meson/meson_venc.h ++++ b/drivers/gpu/drm/meson/meson_venc.h +@@ -15,6 +15,7 @@ + #define __MESON_VENC_H + + struct drm_display_mode; ++struct meson_drm; + + enum { + MESON_VENC_MODE_NONE = 0, +@@ -59,6 +60,9 @@ extern struct meson_cvbs_enci_mode meson_cvbs_enci_ntsc; + + void meson_venci_cvbs_mode_set(struct meson_drm *priv, + struct meson_cvbs_enci_mode *mode); ++void meson_venc_hdmi_bridge_reset(struct meson_drm *priv); ++void meson_venc_hdmi_encoder_enable(struct meson_drm *priv); ++void meson_venc_hdmi_encoder_disable(struct meson_drm *priv); + void meson_venc_hdmi_mode_set(struct meson_drm *priv, int vic, + unsigned int ycrcb_map, + bool yuv420_mode, +diff --git a/drivers/gpu/drm/meson/meson_venc_cvbs.c b/drivers/gpu/drm/meson/meson_venc_cvbs.c +index f1747fde1..fe99d175f 100644 +--- a/drivers/gpu/drm/meson/meson_venc_cvbs.c ++++ b/drivers/gpu/drm/meson/meson_venc_cvbs.c +@@ -185,17 +185,18 @@ static void meson_venc_cvbs_encoder_enable(struct drm_encoder *encoder) + 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)) { ++ 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)) + 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)) { ++ 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)) { ++ 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); +- } ++ ++ regmap_write(priv->hhi, HHI_VDAC_CNTL1, priv->cvbs.cntl1); + } + + static void meson_venc_cvbs_encoder_mode_set(struct drm_encoder *encoder, +diff --git a/drivers/gpu/drm/meson/meson_viu.c b/drivers/gpu/drm/meson/meson_viu.c +index 259f3e6be..69757e784 100644 +--- a/drivers/gpu/drm/meson/meson_viu.c ++++ b/drivers/gpu/drm/meson/meson_viu.c +@@ -425,21 +425,28 @@ void meson_viu_init(struct meson_drm *priv) + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL)) + meson_viu_load_matrix(priv); +- else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { ++ else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) + meson_viu_set_g12a_osd1_matrix(priv, RGB709_to_YUV709l_coeff, + true); +- /* fix green/pink color distortion from vendor u-boot */ +- writel_bits_relaxed(OSD1_HDR2_CTRL_REG_ONLY_MAT | +- OSD1_HDR2_CTRL_VDIN0_HDR2_TOP_EN, 0, +- priv->io_base + _REG(OSD1_HDR2_CTRL)); +- } + + /* Initialize OSD1 fifo control register */ + reg = VIU_OSD_DDR_PRIORITY_URGENT | +- VIU_OSD_HOLD_FIFO_LINES(31) | +- VIU_OSD_FIFO_DEPTH_VAL(32) | /* fifo_depth_val: 32*8=256 */ +- VIU_OSD_WORDS_PER_BURST(4) | /* 4 words in 1 burst */ +- VIU_OSD_FIFO_LIMITS(2); /* fifo_lim: 2*16=32 */ ++ VIU_OSD_FIFO_DEPTH_VAL(32) | /* fifo_depth_val: 32*8=256 */ ++ VIU_OSD_WORDS_PER_BURST(4) | /* 4 words in 1 burst */ ++ VIU_OSD_FIFO_LIMITS(2); /* fifo_lim: 2*16=32 */ ++ ++ /* ++ * When using AFBC on newer SoCs the AFBC encoder has to be reset. To ++ * leave time for that we need hold more lines to avoid glitches. ++ * On the 32-bit SoCs however we need to hold fewer lines because ++ * otherwise screen tearing can occur (for example in kmscube). ++ */ ++ 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)) ++ reg |= VIU_OSD_HOLD_FIFO_LINES(12); ++ else ++ reg |= VIU_OSD_HOLD_FIFO_LINES(31); + + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) + reg |= VIU_OSD_BURST_LENGTH_32; +@@ -449,13 +456,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 617cf073e..3f5e32a0f 100644 +--- a/drivers/phy/amlogic/Kconfig ++++ b/drivers/phy/amlogic/Kconfig +@@ -2,6 +2,17 @@ + # + # Phy drivers for Amlogic platforms + # ++config PHY_MESON8_HDMI_TX ++ tristate "Meson8, Meson8b and Meson8m2 HDMI TX PHY driver" ++ default ARCH_MESON ++ depends on (ARCH_MESON && ARM) || COMPILE_TEST ++ depends on OF ++ select MFD_SYSCON ++ help ++ Enable this to support the HDMI TX PHYs found in Meson8, ++ Meson8b and Meson8m SoCs. ++ If unsure, say N. ++ + config PHY_MESON8B_USB2 + tristate "Meson8, Meson8b, Meson8m2 and GXBB USB2 PHY driver" + default ARCH_MESON +diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile +index 99702a45e..bdf139f57 100644 +--- a/drivers/phy/amlogic/Makefile ++++ b/drivers/phy/amlogic/Makefile +@@ -1,4 +1,5 @@ + # 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_GXL_USB2) += phy-meson-gxl-usb2.o + obj-$(CONFIG_PHY_MESON_G12A_USB2) += phy-meson-g12a-usb2.o +diff --git a/drivers/phy/amlogic/phy-meson8-hdmi-tx.c b/drivers/phy/amlogic/phy-meson8-hdmi-tx.c +new file mode 100644 +index 000000000..8f13960a4 +--- /dev/null ++++ b/drivers/phy/amlogic/phy-meson8-hdmi-tx.c +@@ -0,0 +1,150 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Meson8, Meson8b and Meson8m2 HDMI TX PHY. ++ * ++ * Copyright (C) 2020 Martin Blumenstingl ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define HHI_HDMI_PHY_CNTL0 0x3a0 ++ #define HHI_HDMI_PHY_CNTL0_HDMI_CTL1 GENMASK(31, 16) ++ #define HHI_HDMI_PHY_CNTL0_HDMI_CTL0 GENMASK(15, 0) ++ ++#define HHI_HDMI_PHY_CNTL1 0x3a4 ++ #define HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE BIT(1) ++ #define HHI_HDMI_PHY_CNTL1_SOFT_RESET BIT(0) ++ ++#define HHI_HDMI_PHY_CNTL2 0x3a8 ++ ++struct phy_meson8_hdmi_tx_priv { ++ struct regmap *hhi; ++ struct clk *tmds_clk; ++}; ++ ++static int phy_meson8_hdmi_tx_init(struct phy *phy) ++{ ++ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); ++ ++ return clk_prepare_enable(priv->tmds_clk); ++} ++ ++static int phy_meson8_hdmi_tx_exit(struct phy *phy) ++{ ++ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); ++ ++ clk_disable_unprepare(priv->tmds_clk); ++ ++ return 0; ++} ++ ++static int phy_meson8_hdmi_tx_power_on(struct phy *phy) ++{ ++ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); ++ unsigned int i; ++ u16 hdmi_ctl0; ++ ++ if (clk_get_rate(priv->tmds_clk) >= 2970UL * 1000 * 1000) ++ hdmi_ctl0 = 0x1e8b; ++ else ++ hdmi_ctl0 = 0x4d0b; ++ ++ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, ++ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x08c3) | ++ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, hdmi_ctl0)); ++ ++ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, 0x00000000); ++ ++ /* Reset three times, just like the vendor driver does */ ++ for (i = 0; i < 3; i++) { ++ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, ++ HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE | ++ HHI_HDMI_PHY_CNTL1_SOFT_RESET); ++ usleep_range(1000, 2000); ++ ++ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, ++ HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE); ++ usleep_range(1000, 2000); ++ } ++ ++ return 0; ++} ++ ++static int phy_meson8_hdmi_tx_power_off(struct phy *phy) ++{ ++ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); ++ ++ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, ++ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x0841) | ++ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, 0x8d00)); ++ ++ return 0; ++} ++ ++static const struct phy_ops phy_meson8_hdmi_tx_ops = { ++ .init = phy_meson8_hdmi_tx_init, ++ .exit = phy_meson8_hdmi_tx_exit, ++ .power_on = phy_meson8_hdmi_tx_power_on, ++ .power_off = phy_meson8_hdmi_tx_power_off, ++ .owner = THIS_MODULE, ++}; ++ ++static int phy_meson8_hdmi_tx_probe(struct platform_device *pdev) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ struct phy_meson8_hdmi_tx_priv *priv; ++ struct phy_provider *phy_provider; ++ struct phy *phy; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->hhi = syscon_node_to_regmap(np->parent); ++ if (IS_ERR(priv->hhi)) ++ return PTR_ERR(priv->hhi); ++ ++ priv->tmds_clk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(priv->tmds_clk)) ++ return PTR_ERR(priv->tmds_clk); ++ ++ phy = devm_phy_create(&pdev->dev, np, &phy_meson8_hdmi_tx_ops); ++ if (IS_ERR(phy)) ++ return PTR_ERR(phy); ++ ++ phy_set_drvdata(phy, priv); ++ ++ phy_provider = devm_of_phy_provider_register(&pdev->dev, ++ of_phy_simple_xlate); ++ ++ return PTR_ERR_OR_ZERO(phy_provider); ++} ++ ++static const struct of_device_id phy_meson8_hdmi_tx_of_match[] = { ++ { .compatible = "amlogic,meson8-hdmi-tx-phy" }, ++ { .compatible = "amlogic,meson8b-hdmi-tx-phy" }, ++ { .compatible = "amlogic,meson8m2-hdmi-tx-phy" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, phy_meson8_hdmi_tx_of_match); ++ ++static struct platform_driver phy_meson8_hdmi_tx_driver = { ++ .probe = phy_meson8_hdmi_tx_probe, ++ .driver = { ++ .name = "phy-meson8-hdmi-tx", ++ .of_match_table = phy_meson8_hdmi_tx_of_match, ++ }, ++}; ++module_platform_driver(phy_meson8_hdmi_tx_driver); ++ ++MODULE_AUTHOR("Martin Blumenstingl "); ++MODULE_DESCRIPTION("Meson8, Meson8b and Meson8m2 HDMI TX PHY driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/include/drm/bridge/txccq_txc_48352.h b/include/drm/bridge/txccq_txc_48352.h +new file mode 100644 +index 000000000..1ba9f4f24 +--- /dev/null ++++ b/include/drm/bridge/txccq_txc_48352.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (C) 2020 Martin Blumenstingl ++ */ ++ ++#ifndef __DRM_BRIDGE_TXCCQ_TXC_48352__ ++#define __DRM_BRIDGE_TXCCQ_TXC_48352__ ++ ++struct txc_48352; ++struct drm_encoder; ++ ++struct txc_48352 *txc_48352_bind(struct drm_encoder *encoder, ++ struct device *dev); ++void txc_48352_unbind(struct txc_48352 *priv); ++ ++#endif /* __DRM_BRIDGE_TXCCQ_TXC_48352__ */ +diff --git a/include/dt-bindings/clock/meson8b-clkc.h b/include/dt-bindings/clock/meson8b-clkc.h +index 4c5965ae1..d273e41dc 100644 +--- a/include/dt-bindings/clock/meson8b-clkc.h ++++ b/include/dt-bindings/clock/meson8b-clkc.h +@@ -107,6 +107,13 @@ + #define CLKID_PERIPH 126 + #define CLKID_AXI 128 + #define CLKID_L2_DRAM 130 ++#define CLKID_HDMI_PLL_HDMI_OUT 132 ++#define CLKID_CTS_ENCT 161 ++#define CLKID_CTS_ENCP 163 ++#define CLKID_CTS_ENCI 165 ++#define CLKID_HDMI_TX_PIXEL 167 ++#define CLKID_CTS_ENCL 169 ++#define CLKID_CTS_VDAC0 171 + #define CLKID_HDMI_SYS 174 + #define CLKID_VPU 190 + #define CLKID_VDEC_1 196 +-- +2.25.1 + diff --git a/patch/kernel/archive/meson-5.15/board_odroidc1/dts-Enable-HDMI.patch b/patch/kernel/archive/meson-5.15/board_odroidc1/dts-Enable-HDMI.patch new file mode 100644 index 0000000000..e8fd3518c0 --- /dev/null +++ b/patch/kernel/archive/meson-5.15/board_odroidc1/dts-Enable-HDMI.patch @@ -0,0 +1,121 @@ +From 2954b682a71fad9d912c68737556259dabf3dcec Mon Sep 17 00:00:00 2001 +From: Martin Blumenstingl +Date: Fri, 20 Mar 2020 15:17:51 +0100 +Subject: [PATCH 065/122] ARM: dts: meson8b: odroid-c1: enable HDMI for the + Odroid-C1 - WiP + +WiP + +Signed-off-by: Martin Blumenstingl +--- + arch/arm/boot/dts/meson8b-odroidc1.dts | 73 ++++++++++++++++++++++++++ + 1 file changed, 73 insertions(+) + +diff --git a/arch/arm/boot/dts/meson8b-odroidc1.dts b/arch/arm/boot/dts/meson8b-odroidc1.dts +index 04cce559980..ef52033561c 100644 +--- a/arch/arm/boot/dts/meson8b-odroidc1.dts ++++ b/arch/arm/boot/dts/meson8b-odroidc1.dts +@@ -32,6 +32,17 @@ emmc_pwrseq: emmc-pwrseq { + reset-gpios = <&gpio BOOT_9 GPIO_ACTIVE_LOW>; + }; + ++ hdmi-connector { ++ compatible = "hdmi-connector"; ++ type = "a"; ++ ++ port { ++ hdmi_connector_in: endpoint { ++ remote-endpoint = <&hdmi_tx_tmds_out>; ++ }; ++ }; ++ }; ++ + leds { + compatible = "gpio-leds"; + blue { +@@ -93,6 +104,50 @@ rtc32k_xtal: rtc32k-xtal-clk { + #clock-cells = <0>; + }; + ++ sound { ++ compatible = "amlogic,gx-sound-card"; ++ model = "M8B-ODROID-C1"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-rates = <294912000>, ++ <270950400>; ++ ++ dai-link-0 { ++ sound-dai = <&aiu AIU_CPU CPU_I2S_FIFO>; ++ }; ++ ++ dai-link-1 { ++ sound-dai = <&aiu AIU_CPU CPU_SPDIF_FIFO>; ++ }; ++ ++ dai-link-2 { ++ sound-dai = <&aiu AIU_CPU CPU_I2S_ENCODER>; ++ dai-format = "i2s"; ++ mclk-fs = <256>; ++ ++ codec-0 { ++ sound-dai = <&aiu AIU_HDMI CTRL_I2S>; ++ }; ++ }; ++ ++ dai-link-3 { ++ sound-dai = <&aiu AIU_CPU CPU_SPDIF_ENCODER>; ++ ++ codec-0 { ++ sound-dai = <&hdmi_tx 1>; ++ }; ++ }; ++ ++ dai-link-4 { ++ sound-dai = <&aiu AIU_HDMI CTRL_OUT>; ++ ++ codec-0 { ++ sound-dai = <&hdmi_tx 0>; ++ }; ++ }; ++ }; ++ + vcc_1v8: regulator-vcc-1v8 { + /* + * RICHTEK RT9179 configured for a fixed output voltage of +@@ -187,6 +242,12 @@ vdd_rtc: regulator-vdd-rtc { + }; + }; + ++&aiu { ++ status = "okay"; ++ pinctrl-0 = <&spdif_out_1_pins>; ++ pinctrl-names = "default"; ++}; ++ + &cpu0 { + cpu-supply = <&vcck>; + }; +@@ -298,6 +359,18 @@ usb-hub { + }; + }; + ++&hdmi_tx { ++ status = "okay"; ++ pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; ++ pinctrl-names = "default"; ++}; ++ ++&hdmi_tx_tmds_port { ++ hdmi_tx_tmds_out: endpoint { ++ remote-endpoint = <&hdmi_connector_in>; ++ }; ++}; ++ + &ir_receiver { + status = "okay"; + pinctrl-0 = <&ir_recv_pins>; +-- +2.25.1 + diff --git a/patch/kernel/archive/meson-5.15/board_onecloud/add-dts.patch b/patch/kernel/archive/meson-5.15/board_onecloud/0001-add-dts.patch similarity index 100% rename from patch/kernel/archive/meson-5.15/board_onecloud/add-dts.patch rename to patch/kernel/archive/meson-5.15/board_onecloud/0001-add-dts.patch diff --git a/patch/kernel/archive/meson-5.15/board_onecloud/0002-dts-Support-HDMI.patch b/patch/kernel/archive/meson-5.15/board_onecloud/0002-dts-Support-HDMI.patch new file mode 100644 index 0000000000..b901f6dd5a --- /dev/null +++ b/patch/kernel/archive/meson-5.15/board_onecloud/0002-dts-Support-HDMI.patch @@ -0,0 +1,92 @@ +dts: Support HDMI + +--- + arch/arm/boot/dts/meson8b-onecloud.dts | 58 ++++++++++++++++++++++++++ + 1 file changed, 58 insertions(+) + +diff --git a/arch/arm/boot/dts/meson8b-onecloud.dts b/arch/arm/boot/dts/meson8b-onecloud.dts +index 050b2e65348..69fff33c496 100644 +--- a/arch/arm/boot/dts/meson8b-onecloud.dts ++++ b/arch/arm/boot/dts/meson8b-onecloud.dts +@@ -32,6 +32,48 @@ emmc_pwrseq: emmc-pwrseq { + reset-gpios = <&gpio BOOT_9 GPIO_ACTIVE_LOW>; + }; + ++ hdmi-connector { ++ compatible = "hdmi-connector"; ++ type = "a"; ++ ++ port { ++ hdmi_connector_in: endpoint { ++ remote-endpoint = <&hdmi_tx_tmds_out>; ++ }; ++ }; ++ }; ++ ++ sound { ++ compatible = "amlogic,gx-sound-card"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-rates = <294912000>, ++ <270950400>; ++ ++ dai-link-0 { ++ sound-dai = <&aiu AIU_CPU CPU_I2S_FIFO>; ++ }; ++ ++ dai-link-1 { ++ sound-dai = <&aiu AIU_CPU CPU_I2S_ENCODER>; ++ dai-format = "i2s"; ++ mclk-fs = <256>; ++ ++ codec-0 { ++ sound-dai = <&aiu AIU_HDMI CTRL_I2S>; ++ }; ++ }; ++ ++ dai-link-2 { ++ sound-dai = <&aiu AIU_HDMI CTRL_OUT>; ++ ++ codec-0 { ++ sound-dai = <&hdmi_tx 0>; ++ }; ++ }; ++ }; ++ + button { + // compatible = "gpio-keys-polled"; + // poll-interval = <100>; +@@ -142,6 +184,10 @@ vcc_core: regulator-vcc-core { + }; + }; + ++&aiu { ++ status = "okay"; ++}; ++ + &cpu0 { + cpu-supply = <&vcc_core>; + }; +@@ -150,6 +196,18 @@ &mali { + mali-supply = <&vcc_core>; + }; + ++&hdmi_tx { ++ status = "okay"; ++ pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; ++ pinctrl-names = "default"; ++}; ++ ++&hdmi_tx_tmds_port { ++ hdmi_tx_tmds_out: endpoint { ++ remote-endpoint = <&hdmi_connector_in>; ++ }; ++}; ++ + &gpio { + gpio-line-names = + // 0-3 Bank GPIOX 0-3 +-- +2.25.1 + diff --git a/patch/kernel/archive/meson-5.15/m8-m8b-m8m2-Support-HDMI.patch b/patch/kernel/archive/meson-5.15/m8-m8b-m8m2-Support-HDMI.patch new file mode 100644 index 0000000000..74de18e20f --- /dev/null +++ b/patch/kernel/archive/meson-5.15/m8-m8b-m8m2-Support-HDMI.patch @@ -0,0 +1,5704 @@ +meson8/meson8b/meson8m2: Support HDMI + +The following codes are come from https://github.com/xdarklight/linux/tree/meson-mx-integration-5.15-20211031. + +Special thank. + +--- + .../bindings/display/amlogic,meson-vpu.yaml | 13 + + .../phy/amlogic,meson-cvbs-dac-phy.yaml | 81 + + .../phy/amlogic,meson8-hdmi-tx-phy.yaml | 65 + + 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/clk/meson/meson8b.c | 163 +- + drivers/clk/meson/meson8b.h | 26 +- + drivers/gpu/drm/bridge/display-connector.c | 86 + + drivers/gpu/drm/meson/Kconfig | 8 + + drivers/gpu/drm/meson/Makefile | 3 +- + drivers/gpu/drm/meson/meson_drv.c | 290 ++- + drivers/gpu/drm/meson/meson_drv.h | 53 +- + drivers/gpu/drm/meson/meson_encoder_cvbs.c | 318 ++++ + ...meson_venc_cvbs.h => meson_encoder_cvbs.h} | 2 +- + drivers/gpu/drm/meson/meson_encoder_hdmi.c | 20 +- + drivers/gpu/drm/meson/meson_plane.c | 33 +- + drivers/gpu/drm/meson/meson_transwitch_hdmi.c | 1602 +++++++++++++++++ + drivers/gpu/drm/meson/meson_transwitch_hdmi.h | 536 ++++++ + drivers/gpu/drm/meson/meson_vclk.c | 146 ++ + drivers/gpu/drm/meson/meson_venc.c | 45 +- + drivers/gpu/drm/meson/meson_venc_cvbs.c | 293 --- + drivers/gpu/drm/meson/meson_viu.c | 38 +- + drivers/phy/amlogic/Kconfig | 20 + + drivers/phy/amlogic/Makefile | 2 + + drivers/phy/amlogic/phy-meson-cvbs-dac.c | 310 ++++ + drivers/phy/amlogic/phy-meson8-hdmi-tx.c | 160 ++ + include/dt-bindings/clock/meson8b-clkc.h | 10 + + 29 files changed, 4203 insertions(+), 476 deletions(-) + create mode 100644 Documentation/devicetree/bindings/phy/amlogic,meson-cvbs-dac-phy.yaml + create mode 100644 Documentation/devicetree/bindings/phy/amlogic,meson8-hdmi-tx-phy.yaml + create mode 100644 drivers/gpu/drm/meson/meson_encoder_cvbs.c + rename drivers/gpu/drm/meson/{meson_venc_cvbs.h => meson_encoder_cvbs.h} (92%) + create mode 100644 drivers/gpu/drm/meson/meson_transwitch_hdmi.c + create mode 100644 drivers/gpu/drm/meson/meson_transwitch_hdmi.h + delete mode 100644 drivers/gpu/drm/meson/meson_venc_cvbs.c + create mode 100644 drivers/phy/amlogic/phy-meson-cvbs-dac.c + create mode 100644 drivers/phy/amlogic/phy-meson8-hdmi-tx.c + +diff --git a/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml b/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml +index 047fd69e0377..7cb14c12f115 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 +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 000000000000..d73cb12c0d9f +--- /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 ++ ++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/Documentation/devicetree/bindings/phy/amlogic,meson8-hdmi-tx-phy.yaml b/Documentation/devicetree/bindings/phy/amlogic,meson8-hdmi-tx-phy.yaml +new file mode 100644 +index 000000000000..1f085cdd1c85 +--- /dev/null ++++ b/Documentation/devicetree/bindings/phy/amlogic,meson8-hdmi-tx-phy.yaml +@@ -0,0 +1,65 @@ ++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: "http://devicetree.org/schemas/phy/amlogic,meson8-hdmi-tx-phy.yaml#" ++$schema: "http://devicetree.org/meta-schemas/core.yaml#" ++ ++title: Amlogic Meson8, Meson8b and Meson8m2 HDMI TX PHY ++ ++maintainers: ++ - Martin Blumenstingl ++ ++description: |+ ++ The HDMI TX PHY 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: "^hdmi-phy@[0-9a-f]+$" ++ ++ compatible: ++ oneOf: ++ - items: ++ - enum: ++ - amlogic,meson8b-hdmi-tx-phy ++ - amlogic,meson8m2-hdmi-tx-phy ++ - const: amlogic,meson8-hdmi-tx-phy ++ - const: amlogic,meson8-hdmi-tx-phy ++ ++ reg: ++ maxItems: 1 ++ ++ clocks: ++ minItems: 1 ++ description: ++ HDMI TMDS clock ++ ++ "#phy-cells": ++ const: 0 ++ ++required: ++ - compatible ++ - "#phy-cells" ++ ++additionalProperties: false ++ ++examples: ++ - | ++ hdmi-phy@3a0 { ++ compatible = "amlogic,meson8-hdmi-tx-phy"; ++ reg = <0x3a0 0xc>; ++ clocks = <&tmds_clock>; ++ #phy-cells = <0>; ++ }; ++ - | ++ hdmi-phy@3a0 { ++ compatible = "amlogic,meson8b-hdmi-tx-phy", "amlogic,meson8-hdmi-tx-phy"; ++ reg = <0x3a0 0xc>; ++ clocks = <&tmds_clock>; ++ #phy-cells = <0>; ++ }; +diff --git a/arch/arm/boot/dts/meson.dtsi b/arch/arm/boot/dts/meson.dtsi +index 26eaba3fa96f..28caf20d4181 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 9997a5d0333a..e8073f6abd20 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 = ; ++ 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 = ; ++ ++ 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 = ; ++ // 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>; ++ }; + }; + + ðmac { +@@ -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 94f1c03decce..5519086a1887 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 = ; ++ 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 = ; ++ ++ 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 = ; ++ // 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>; ++ }; + }; + + ðmac { +@@ -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 6725dd9fd825..fcb2ad976098 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/clk/meson/meson8b.c b/drivers/clk/meson/meson8b.c +index a844d35b553a..cd0f5bae24d4 100644 +--- a/drivers/clk/meson/meson8b.c ++++ b/drivers/clk/meson/meson8b.c +@@ -118,6 +118,56 @@ static struct clk_regmap meson8b_fixed_pll = { + }, + }; + ++static struct clk_fixed_factor hdmi_pll_dco_in = { ++ .mult = 2, ++ .div = 1, ++ .hw.init = &(struct clk_init_data){ ++ .name = "hdmi_pll_dco_in", ++ .ops = &clk_fixed_factor_ops, ++ .parent_data = &(const struct clk_parent_data) { ++ .fw_name = "xtal", ++ .index = -1, ++ }, ++ .num_parents = 1, ++ }, ++}; ++ ++/* ++ * Taken from the vendor driver for the 2970/2975MHz (both only differ in the ++ * FRAC part in HHI_VID_PLL_CNTL2) where these values are identical for Meson8, ++ * Meson8b and Meson8m2. This doubles the input (or output - it's not clear ++ * which one but the result is the same) clock. The vendor driver additionally ++ * has the following comment about: "optimise HPLL VCO 2.97GHz performance". ++ */ ++static const struct reg_sequence meson8b_hdmi_pll_init_regs[] = { ++ { .reg = HHI_VID_PLL_CNTL2, .def = 0x69c84000 }, ++ { .reg = HHI_VID_PLL_CNTL3, .def = 0x8a46c023 }, ++ { .reg = HHI_VID_PLL_CNTL4, .def = 0x4123b100 }, ++ { .reg = HHI_VID_PLL_CNTL5, .def = 0x00012385 }, ++ { .reg = HHI_VID2_PLL_CNTL2, .def = 0x0430a800 }, ++}; ++ ++static const struct pll_params_table hdmi_pll_params_table[] = { ++ PLL_PARAMS(40, 1), ++ PLL_PARAMS(42, 1), ++ PLL_PARAMS(44, 1), ++ PLL_PARAMS(45, 1), ++ PLL_PARAMS(49, 1), ++ PLL_PARAMS(52, 1), ++ PLL_PARAMS(54, 1), ++ PLL_PARAMS(56, 1), ++ PLL_PARAMS(59, 1), ++ PLL_PARAMS(60, 1), ++ PLL_PARAMS(61, 1), ++ PLL_PARAMS(62, 1), ++ PLL_PARAMS(64, 1), ++ PLL_PARAMS(66, 1), ++ PLL_PARAMS(68, 1), ++ PLL_PARAMS(71, 1), ++ PLL_PARAMS(82, 1), ++ { /* sentinel */ } ++}; ++ + static struct clk_regmap meson8b_hdmi_pll_dco = { + .data = &(struct meson_clk_pll_data){ + .en = { +@@ -150,15 +200,16 @@ static struct clk_regmap meson8b_hdmi_pll_dco = { + .shift = 29, + .width = 1, + }, ++ .table = hdmi_pll_params_table, ++ .init_regs = meson8b_hdmi_pll_init_regs, ++ .init_count = ARRAY_SIZE(meson8b_hdmi_pll_init_regs), + }, + .hw.init = &(struct clk_init_data){ + /* sometimes also called "HPLL" or "HPLL PLL" */ + .name = "hdmi_pll_dco", +- .ops = &meson_clk_pll_ro_ops, +- .parent_data = &(const struct clk_parent_data) { +- .fw_name = "xtal", +- .name = "xtal", +- .index = -1, ++ .ops = &meson_clk_pll_ops, ++ .parent_hws = (const struct clk_hw *[]) { ++ &hdmi_pll_dco_in.hw + }, + .num_parents = 1, + }, +@@ -173,7 +224,7 @@ static struct clk_regmap meson8b_hdmi_pll_lvds_out = { + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmi_pll_lvds_out", +- .ops = &clk_regmap_divider_ro_ops, ++ .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_hdmi_pll_dco.hw + }, +@@ -191,7 +242,7 @@ static struct clk_regmap meson8b_hdmi_pll_hdmi_out = { + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmi_pll_hdmi_out", +- .ops = &clk_regmap_divider_ro_ops, ++ .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_hdmi_pll_dco.hw + }, +@@ -1045,6 +1096,23 @@ static struct clk_regmap meson8b_l2_dram_clk_gate = { + }, + }; + ++/* also called LVDS_CLK_EN */ ++static struct clk_regmap meson8b_vid_pll_lvds_en = { ++ .data = &(struct clk_regmap_gate_data){ ++ .offset = HHI_VID_DIVIDER_CNTL, ++ .bit_idx = 11, ++ }, ++ .hw.init = &(struct clk_init_data){ ++ .name = "vid_pll_lvds_en", ++ .ops = &clk_regmap_gate_ops, ++ .parent_hws = (const struct clk_hw *[]) { ++ &meson8b_hdmi_pll_lvds_out.hw ++ }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++}; ++ + static struct clk_regmap meson8b_vid_pll_in_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HHI_VID_DIVIDER_CNTL, +@@ -1053,7 +1121,7 @@ static struct clk_regmap meson8b_vid_pll_in_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_in_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + /* + * TODO: depending on the SoC there is also a second parent: + * Meson8: unknown +@@ -1061,7 +1129,7 @@ static struct clk_regmap meson8b_vid_pll_in_sel = { + * Meson8m2: vid2_pll + */ + .parent_hws = (const struct clk_hw *[]) { +- &meson8b_hdmi_pll_lvds_out.hw ++ &meson8b_vid_pll_lvds_en.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, +@@ -1075,7 +1143,7 @@ static struct clk_regmap meson8b_vid_pll_in_en = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_in_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vid_pll_in_sel.hw + }, +@@ -1092,7 +1160,7 @@ static struct clk_regmap meson8b_vid_pll_pre_div = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_pre_div", +- .ops = &clk_regmap_divider_ro_ops, ++ .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vid_pll_in_en.hw + }, +@@ -1109,7 +1177,7 @@ static struct clk_regmap meson8b_vid_pll_post_div = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_post_div", +- .ops = &clk_regmap_divider_ro_ops, ++ .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vid_pll_pre_div.hw + }, +@@ -1126,7 +1194,7 @@ static struct clk_regmap meson8b_vid_pll = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + /* TODO: parent 0x2 is vid_pll_pre_div_mult7_div2 */ + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vid_pll_pre_div.hw, +@@ -1145,7 +1213,7 @@ static struct clk_regmap meson8b_vid_pll_final_div = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_final_div", +- .ops = &clk_regmap_divider_ro_ops, ++ .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vid_pll.hw + }, +@@ -1172,10 +1240,10 @@ static struct clk_regmap meson8b_vclk_in_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_in_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_mux_parent_hws), +- .flags = CLK_SET_RATE_PARENT, ++ .flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT, + }, + }; + +@@ -1186,7 +1254,7 @@ static struct clk_regmap meson8b_vclk_in_en = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_in_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_in_sel.hw + }, +@@ -1202,7 +1270,7 @@ static struct clk_regmap meson8b_vclk_en = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_in_en.hw + }, +@@ -1218,7 +1286,7 @@ static struct clk_regmap meson8b_vclk_div1_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div1_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_en.hw + }, +@@ -1248,7 +1316,7 @@ static struct clk_regmap meson8b_vclk_div2_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div2_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_div2_div.hw + }, +@@ -1278,7 +1346,7 @@ static struct clk_regmap meson8b_vclk_div4_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div4_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_div4_div.hw + }, +@@ -1308,7 +1376,7 @@ static struct clk_regmap meson8b_vclk_div6_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div6_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_div6_div.hw + }, +@@ -1338,7 +1406,7 @@ static struct clk_regmap meson8b_vclk_div12_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div12_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk_div12_div.hw + }, +@@ -1355,10 +1423,10 @@ static struct clk_regmap meson8b_vclk2_in_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_in_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_mux_parent_hws), +- .flags = CLK_SET_RATE_PARENT, ++ .flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT, + }, + }; + +@@ -1369,7 +1437,7 @@ static struct clk_regmap meson8b_vclk2_clk_in_en = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_in_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_in_sel.hw + }, +@@ -1385,7 +1453,7 @@ static struct clk_regmap meson8b_vclk2_clk_en = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_clk_in_en.hw + }, +@@ -1401,7 +1469,7 @@ static struct clk_regmap meson8b_vclk2_div1_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div1_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_clk_en.hw + }, +@@ -1431,7 +1499,7 @@ static struct clk_regmap meson8b_vclk2_div2_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div2_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_div2_div.hw + }, +@@ -1461,7 +1529,7 @@ static struct clk_regmap meson8b_vclk2_div4_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div4_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_div4_div.hw + }, +@@ -1491,7 +1559,7 @@ static struct clk_regmap meson8b_vclk2_div6_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div6_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_div6_div.hw + }, +@@ -1521,7 +1589,7 @@ static struct clk_regmap meson8b_vclk2_div12_div_gate = { + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div12_en", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_vclk2_div12_div.hw + }, +@@ -1546,7 +1614,7 @@ static struct clk_regmap meson8b_cts_enct_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_enct_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1560,7 +1628,7 @@ static struct clk_regmap meson8b_cts_enct = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_enct", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_cts_enct_sel.hw + }, +@@ -1577,7 +1645,7 @@ static struct clk_regmap meson8b_cts_encp_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_encp_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1591,7 +1659,7 @@ static struct clk_regmap meson8b_cts_encp = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_encp", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_cts_encp_sel.hw + }, +@@ -1608,7 +1676,7 @@ static struct clk_regmap meson8b_cts_enci_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_enci_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1622,7 +1690,7 @@ static struct clk_regmap meson8b_cts_enci = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_enci", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_cts_enci_sel.hw + }, +@@ -1639,7 +1707,7 @@ static struct clk_regmap meson8b_hdmi_tx_pixel_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmi_tx_pixel_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1653,7 +1721,7 @@ static struct clk_regmap meson8b_hdmi_tx_pixel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmi_tx_pixel", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_hdmi_tx_pixel_sel.hw + }, +@@ -1678,7 +1746,7 @@ static struct clk_regmap meson8b_cts_encl_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_encl_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk2_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk2_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1692,7 +1760,7 @@ static struct clk_regmap meson8b_cts_encl = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_encl", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_cts_encl_sel.hw + }, +@@ -1709,7 +1777,7 @@ static struct clk_regmap meson8b_cts_vdac0_sel = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_vdac0_sel", +- .ops = &clk_regmap_mux_ro_ops, ++ .ops = &clk_regmap_mux_ops, + .parent_hws = meson8b_vclk2_enc_mux_parent_hws, + .num_parents = ARRAY_SIZE(meson8b_vclk2_enc_mux_parent_hws), + .flags = CLK_SET_RATE_PARENT, +@@ -1723,7 +1791,7 @@ static struct clk_regmap meson8b_cts_vdac0 = { + }, + .hw.init = &(struct clk_init_data){ + .name = "cts_vdac0", +- .ops = &clk_regmap_gate_ro_ops, ++ .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &meson8b_cts_vdac0_sel.hw + }, +@@ -2905,6 +2973,8 @@ static struct clk_hw_onecell_data meson8_hw_onecell_data = { + [CLKID_CTS_MCLK_I958_DIV] = &meson8b_cts_mclk_i958_div.hw, + [CLKID_CTS_MCLK_I958] = &meson8b_cts_mclk_i958.hw, + [CLKID_CTS_I958] = &meson8b_cts_i958.hw, ++ [CLKID_VID_PLL_LVDS_EN] = &meson8b_vid_pll_lvds_en.hw, ++ [CLKID_HDMI_PLL_DCO_IN] = &hdmi_pll_dco_in.hw, + [CLK_NR_CLKS] = NULL, + }, + .num = CLK_NR_CLKS, +@@ -3122,6 +3192,8 @@ static struct clk_hw_onecell_data meson8b_hw_onecell_data = { + [CLKID_CTS_MCLK_I958_DIV] = &meson8b_cts_mclk_i958_div.hw, + [CLKID_CTS_MCLK_I958] = &meson8b_cts_mclk_i958.hw, + [CLKID_CTS_I958] = &meson8b_cts_i958.hw, ++ [CLKID_VID_PLL_LVDS_EN] = &meson8b_vid_pll_lvds_en.hw, ++ [CLKID_HDMI_PLL_DCO_IN] = &hdmi_pll_dco_in.hw, + [CLK_NR_CLKS] = NULL, + }, + .num = CLK_NR_CLKS, +@@ -3341,6 +3413,8 @@ static struct clk_hw_onecell_data meson8m2_hw_onecell_data = { + [CLKID_CTS_MCLK_I958_DIV] = &meson8b_cts_mclk_i958_div.hw, + [CLKID_CTS_MCLK_I958] = &meson8b_cts_mclk_i958.hw, + [CLKID_CTS_I958] = &meson8b_cts_i958.hw, ++ [CLKID_VID_PLL_LVDS_EN] = &meson8b_vid_pll_lvds_en.hw, ++ [CLKID_HDMI_PLL_DCO_IN] = &hdmi_pll_dco_in.hw, + [CLK_NR_CLKS] = NULL, + }, + .num = CLK_NR_CLKS, +@@ -3539,6 +3613,7 @@ static struct clk_regmap *const meson8b_clk_regmaps[] = { + &meson8b_cts_mclk_i958_div, + &meson8b_cts_mclk_i958, + &meson8b_cts_i958, ++ &meson8b_vid_pll_lvds_en, + }; + + static const struct meson8b_clk_reset_line { +diff --git a/drivers/clk/meson/meson8b.h b/drivers/clk/meson/meson8b.h +index b1a5074cf148..ce62ed47cbfc 100644 +--- a/drivers/clk/meson/meson8b.h ++++ b/drivers/clk/meson/meson8b.h +@@ -51,6 +51,16 @@ + #define HHI_SYS_PLL_CNTL 0x300 /* 0xc0 offset in data sheet */ + #define HHI_VID_PLL_CNTL 0x320 /* 0xc8 offset in data sheet */ + #define HHI_VID_PLL_CNTL2 0x324 /* 0xc9 offset in data sheet */ ++#define HHI_VID_PLL_CNTL3 0x328 /* 0xca offset in data sheet */ ++#define HHI_VID_PLL_CNTL4 0x32c /* 0xcb offset in data sheet */ ++#define HHI_VID_PLL_CNTL5 0x330 /* 0xcc offset in data sheet */ ++#define HHI_VID_PLL_CNTL6 0x334 /* 0xcd offset in data sheet */ ++#define HHI_VID2_PLL_CNTL 0x380 /* 0xe0 offset in data sheet */ ++#define HHI_VID2_PLL_CNTL2 0x384 /* 0xe1 offset in data sheet */ ++#define HHI_VID2_PLL_CNTL3 0x388 /* 0xe2 offset in data sheet */ ++#define HHI_VID2_PLL_CNTL4 0x38c /* 0xe3 offset in data sheet */ ++#define HHI_VID2_PLL_CNTL5 0x390 /* 0xe4 offset in data sheet */ ++#define HHI_VID2_PLL_CNTL6 0x394 /* 0xe5 offset in data sheet */ + + /* + * MPLL register offeset taken from the S905 datasheet. Vendor kernel source +@@ -107,14 +117,11 @@ + #define CLKID_PERIPH_SEL 125 + #define CLKID_AXI_SEL 127 + #define CLKID_L2_DRAM_SEL 129 +-#define CLKID_HDMI_PLL_LVDS_OUT 131 +-#define CLKID_HDMI_PLL_HDMI_OUT 132 ++#define CLKID_HDMI_PLL_LVDS_OUT 131 + #define CLKID_VID_PLL_IN_SEL 133 + #define CLKID_VID_PLL_IN_EN 134 + #define CLKID_VID_PLL_PRE_DIV 135 + #define CLKID_VID_PLL_POST_DIV 136 +-#define CLKID_VID_PLL_FINAL_DIV 137 +-#define CLKID_VCLK_IN_SEL 138 + #define CLKID_VCLK_IN_EN 139 + #define CLKID_VCLK_DIV1 140 + #define CLKID_VCLK_DIV2_DIV 141 +@@ -125,7 +132,6 @@ + #define CLKID_VCLK_DIV6 146 + #define CLKID_VCLK_DIV12_DIV 147 + #define CLKID_VCLK_DIV12 148 +-#define CLKID_VCLK2_IN_SEL 149 + #define CLKID_VCLK2_IN_EN 150 + #define CLKID_VCLK2_DIV1 151 + #define CLKID_VCLK2_DIV2_DIV 152 +@@ -137,17 +143,11 @@ + #define CLKID_VCLK2_DIV12_DIV 158 + #define CLKID_VCLK2_DIV12 159 + #define CLKID_CTS_ENCT_SEL 160 +-#define CLKID_CTS_ENCT 161 + #define CLKID_CTS_ENCP_SEL 162 +-#define CLKID_CTS_ENCP 163 + #define CLKID_CTS_ENCI_SEL 164 +-#define CLKID_CTS_ENCI 165 + #define CLKID_HDMI_TX_PIXEL_SEL 166 +-#define CLKID_HDMI_TX_PIXEL 167 + #define CLKID_CTS_ENCL_SEL 168 +-#define CLKID_CTS_ENCL 169 + #define CLKID_CTS_VDAC0_SEL 170 +-#define CLKID_CTS_VDAC0 171 + #define CLKID_HDMI_SYS_SEL 172 + #define CLKID_HDMI_SYS_DIV 173 + #define CLKID_MALI_0_SEL 175 +@@ -182,8 +182,10 @@ + #define CLKID_CTS_MCLK_I958_DIV 211 + #define CLKID_VCLK_EN 214 + #define CLKID_VCLK2_EN 215 ++#define CLKID_VID_PLL_LVDS_EN 216 ++#define CLKID_HDMI_PLL_DCO_IN 217 + +-#define CLK_NR_CLKS 216 ++#define CLK_NR_CLKS 218 + + /* + * include the CLKID and RESETID that have +diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c +index 847a0dce7f1d..d24f5b90feab 100644 +--- a/drivers/gpu/drm/bridge/display-connector.c ++++ b/drivers/gpu/drm/bridge/display-connector.c +@@ -13,6 +13,7 @@ + #include + #include + ++#include + #include + #include + +@@ -87,10 +88,95 @@ static struct edid *display_connector_get_edid(struct drm_bridge *bridge, + return drm_get_edid(connector, conn->bridge.ddc); + } + ++/* ++ * Since this bridge is tied to the connector, it acts like a passthrough, ++ * so concerning the output bus formats, either pass the bus formats from the ++ * previous bridge or return fallback data like done in the bridge function: ++ * drm_atomic_bridge_chain_select_bus_fmts(). ++ * This supports negotiation if the bridge chain has all bits in place. ++ */ ++static u32 *display_connector_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_bridge *prev_bridge = drm_bridge_get_prev_bridge(bridge); ++ struct drm_bridge_state *prev_bridge_state; ++ ++ if (!prev_bridge || !prev_bridge->funcs->atomic_get_output_bus_fmts) { ++ struct drm_connector *conn = conn_state->connector; ++ u32 *out_bus_fmts; ++ ++ *num_output_fmts = 1; ++ out_bus_fmts = kmalloc(sizeof(*out_bus_fmts), GFP_KERNEL); ++ if (!out_bus_fmts) ++ return NULL; ++ ++ if (conn->display_info.num_bus_formats && ++ conn->display_info.bus_formats) ++ out_bus_fmts[0] = conn->display_info.bus_formats[0]; ++ else ++ out_bus_fmts[0] = MEDIA_BUS_FMT_FIXED; ++ ++ return out_bus_fmts; ++ } ++ ++ prev_bridge_state = drm_atomic_get_new_bridge_state(crtc_state->state, ++ prev_bridge); ++ ++ return prev_bridge->funcs->atomic_get_output_bus_fmts(prev_bridge, prev_bridge_state, ++ crtc_state, conn_state, ++ num_output_fmts); ++} ++ ++/* ++ * Since this bridge is tied to the connector, it acts like a passthrough, ++ * so concerning the input bus formats, either pass the bus formats from the ++ * previous bridge or MEDIA_BUS_FMT_FIXED (like select_bus_fmt_recursive()) ++ * when atomic_get_input_bus_fmts is not supported. ++ * This supports negotiation if the bridge chain has all bits in place. ++ */ ++static u32 *display_connector_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) ++{ ++ struct drm_bridge *prev_bridge = drm_bridge_get_prev_bridge(bridge); ++ struct drm_bridge_state *prev_bridge_state; ++ ++ if (!prev_bridge || !prev_bridge->funcs->atomic_get_input_bus_fmts) { ++ u32 *in_bus_fmts; ++ ++ *num_input_fmts = 1; ++ in_bus_fmts = kmalloc(sizeof(*in_bus_fmts), GFP_KERNEL); ++ if (!in_bus_fmts) ++ return NULL; ++ ++ in_bus_fmts[0] = MEDIA_BUS_FMT_FIXED; ++ ++ return in_bus_fmts; ++ } ++ ++ prev_bridge_state = drm_atomic_get_new_bridge_state(crtc_state->state, ++ prev_bridge); ++ ++ return prev_bridge->funcs->atomic_get_input_bus_fmts(prev_bridge, prev_bridge_state, ++ crtc_state, conn_state, output_fmt, ++ num_input_fmts); ++} ++ + static const struct drm_bridge_funcs display_connector_bridge_funcs = { + .attach = display_connector_attach, + .detect = display_connector_detect, + .get_edid = display_connector_get_edid, ++ .atomic_get_output_bus_fmts = display_connector_get_output_bus_fmts, ++ .atomic_get_input_bus_fmts = display_connector_get_input_bus_fmts, ++ .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, + }; + + static irqreturn_t display_connector_hpd_irq(int irq, void *arg) +diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig +index a4e1ed96e5e8..2023bc2b4dec 100644 +--- a/drivers/gpu/drm/meson/Kconfig ++++ b/drivers/gpu/drm/meson/Kconfig +@@ -18,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 523fce45f16b..817a5270aee6 100644 +--- a/drivers/gpu/drm/meson/Makefile ++++ b/drivers/gpu/drm/meson/Makefile +@@ -1,8 +1,9 @@ + # SPDX-License-Identifier: GPL-2.0-only +-meson-drm-y := meson_drv.o meson_plane.o meson_crtc.o meson_venc_cvbs.o ++meson-drm-y := meson_drv.o meson_plane.o meson_crtc.o meson_encoder_cvbs.o + meson-drm-y += meson_viu.o meson_vpp.o meson_venc.o meson_vclk.o meson_overlay.o + meson-drm-y += meson_rdma.o meson_osd_afbcd.o + 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 a56607501d36..0df139d8df8f 100644 +--- a/drivers/gpu/drm/meson/meson_drv.c ++++ b/drivers/gpu/drm/meson/meson_drv.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -31,7 +32,7 @@ + #include "meson_plane.h" + #include "meson_osd_afbcd.h" + #include "meson_registers.h" +-#include "meson_venc_cvbs.h" ++#include "meson_encoder_cvbs.h" + #include "meson_encoder_hdmi.h" + #include "meson_viu.h" + #include "meson_vpp.h" +@@ -133,28 +134,100 @@ static struct regmap_config meson_regmap_config = { + + 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; ++ ++ 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 0; ++ ++ 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(VPU_INTR_CLK_NUM, 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 (!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; ++ ++ if (priv->clk_dac_enabled) ++ clk_disable(priv->clk_dac); ++ ++ if (priv->clk_venc_enabled) ++ clk_disable(priv->clk_venc); ++ ++ clk_bulk_unprepare(VPU_INTR_CLK_NUM, 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 with ++ * 32-bit but missing alpha ??? TODO: better explanation here. ++ * Use 24-bit to get a working framebuffer console. Applications that ++ * can do better (for example: kmscube) will switch to a better format ++ * like DRM_FORMAT_XRGB8888 while passing a sane alpha value. + */ +- 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 { +@@ -163,6 +236,16 @@ struct meson_drm_soc_attr { + }; + + static const struct meson_drm_soc_attr meson_drm_soc_attrs[] = { ++ /* The maximum frequency of HDMI PLL on Meson8/8b/8m2 is ~3GHz */ ++ { ++ .limits = { ++ .max_hdmi_phy_freq = 2976000, ++ }, ++ .attrs = (const struct soc_device_attribute []) { ++ { .soc_id = "Meson8*", }, ++ { /* sentinel */ }, ++ } ++ }, + /* S805X/S805Y HDMI PLL won't lock for HDMI PHY freq > 1,65GHz */ + { + .limits = { +@@ -210,6 +293,46 @@ 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) ++ return ret; ++ ++ priv->intr_clks[VPU_INTR_CLK_VPU].id = "vpu_intr"; ++ priv->intr_clks[VPU_INTR_CLK_HDMI_INTR_SYNC].id = "hdmi_intr_sync"; ++ priv->intr_clks[VPU_INTR_CLK_VENCI].id = "venci_int"; ++ ++ ret = devm_clk_bulk_get(dev, VPU_INTR_CLK_NUM, priv->intr_clks); ++ if (ret) ++ return ret; ++ ++ 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) ++ return ret; ++ ++ ret = meson_video_clock_init(priv); ++ if (ret) ++ goto free_drm; ++ // TODO: error handling below ++ } ++ + regs = devm_platform_ioremap_resource_byname(pdev, "vpu"); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); +@@ -218,24 +341,27 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + + 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 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 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 free_drm; ++ } + } + + priv->canvas = meson_canvas_get(dev); +@@ -266,6 +392,11 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + goto free_drm; + } + ++ priv->cvbs_dac = devm_phy_optional_get(dev, "cvbs-dac"); ++ if (IS_ERR(priv->cvbs_dac)) ++ return dev_err_probe(dev, PTR_ERR(priv->cvbs_dac), ++ "Failed to get the 'cvbs-dac' PHY\n"); ++ + priv->vsync_irq = platform_get_irq(pdev, 0); + + ret = drm_vblank_init(drm, 1); +@@ -310,7 +441,7 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + + /* Encoder Initialization */ + +- ret = meson_venc_cvbs_create(priv); ++ ret = meson_encoder_cvbs_init(priv); + if (ret) + goto exit_afbcd; + +@@ -352,7 +483,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; + +@@ -407,6 +538,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); + } + +@@ -417,6 +550,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); +@@ -435,46 +569,6 @@ static int compare_of(struct device *dev, void *data) + return dev->of_node == data; + } + +-/* Possible connectors nodes to ignore */ +-static const struct of_device_id connectors_match[] = { +- { .compatible = "composite-video-connector" }, +- { .compatible = "svideo-connector" }, +- { .compatible = "hdmi-connector" }, +- { .compatible = "dvi-connector" }, +- {} +-}; +- +-static int meson_probe_remote(struct platform_device *pdev, +- struct component_match **match, +- struct device_node *parent, +- struct device_node *remote) +-{ +- struct device_node *ep, *remote_node; +- int count = 1; +- +- /* If node is a connector, return and do not add to match table */ +- if (of_match_node(connectors_match, remote)) +- return 1; +- +- component_match_add(&pdev->dev, match, compare_of, remote); +- +- for_each_endpoint_of_node(remote, ep) { +- remote_node = of_graph_get_remote_port_parent(ep); +- if (!remote_node || +- remote_node == parent || /* Ignore parent endpoint */ +- !of_device_is_available(remote_node)) { +- of_node_put(remote_node); +- continue; +- } +- +- count += meson_probe_remote(pdev, match, remote, remote_node); +- +- of_node_put(remote_node); +- } +- +- return count; +-} +- + static void meson_drv_shutdown(struct platform_device *pdev) + { + struct meson_drm *priv = dev_get_drvdata(&pdev->dev); +@@ -486,6 +580,13 @@ static void meson_drv_shutdown(struct platform_device *pdev) + drm_atomic_helper_shutdown(priv->drm); + } + ++/* Possible connectors nodes to ignore */ ++static const struct of_device_id connectors_match[] = { ++ { .compatible = "composite-video-connector" }, ++ { .compatible = "svideo-connector" }, ++ {} ++}; ++ + static int meson_drv_probe(struct platform_device *pdev) + { + struct component_match *match = NULL; +@@ -500,8 +601,21 @@ static int meson_drv_probe(struct platform_device *pdev) + continue; + } + +- count += meson_probe_remote(pdev, &match, np, remote); ++ /* If an analog connector is detected, count it as an output */ ++ if (of_match_node(connectors_match, remote)) { ++ ++count; ++ of_node_put(remote); ++ continue; ++ } ++ ++ dev_dbg(&pdev->dev, "parent %pOF remote match add %pOF parent %s\n", ++ np, remote, dev_name(&pdev->dev)); ++ ++ component_match_add(&pdev->dev, &match, compare_of, remote); ++ + of_node_put(remote); ++ ++ ++count; + } + + if (count && !match) +@@ -520,6 +634,18 @@ static int meson_drv_probe(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, + }; +@@ -539,6 +665,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 177dac3ca3be..d1250271c94a 100644 +--- a/drivers/gpu/drm/meson/meson_drv.h ++++ b/drivers/gpu/drm/meson/meson_drv.h +@@ -7,22 +7,28 @@ + #ifndef __MESON_DRV_H + #define __MESON_DRV_H + ++#include + #include + #include + #include + #include ++#include + + struct drm_crtc; + struct drm_device; + struct drm_plane; + struct meson_drm; + struct meson_afbcd_ops; ++struct phy; + + 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, + }; + + struct meson_drm_match_data { +@@ -34,6 +40,32 @@ struct meson_drm_soc_limits { + unsigned int max_hdmi_phy_freq; + }; + ++enum vpu_bulk_intr_clk_id { ++ VPU_INTR_CLK_VPU = 0, ++ VPU_INTR_CLK_HDMI_INTR_SYNC, ++ VPU_INTR_CLK_VENCI, ++ VPU_INTR_CLK_NUM ++}; ++ ++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; +@@ -54,6 +86,19 @@ struct meson_drm { + + const struct meson_drm_soc_limits *limits; + ++ struct phy *cvbs_dac; ++ bool cvbs_dac_enabled; ++ ++ struct clk_bulk_data intr_clks[VPU_INTR_CLK_NUM]; ++ 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 +new file mode 100644 +index 000000000000..6cfbf47dbbc1 +--- /dev/null ++++ b/drivers/gpu/drm/meson/meson_encoder_cvbs.c +@@ -0,0 +1,318 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2016 BayLibre, SAS ++ * Author: Neil Armstrong ++ * Copyright (C) 2015 Amlogic, Inc. All rights reserved. ++ * Copyright (C) 2014 Endless Mobile ++ * ++ * Written by: ++ * Jasper St. Pierre ++ */ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "meson_registers.h" ++#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; ++ struct drm_bridge *next_bridge; ++ struct meson_drm *priv; ++}; ++ ++#define bridge_to_meson_encoder_cvbs(x) \ ++ container_of(x, struct meson_encoder_cvbs, bridge) ++ ++/* Supported Modes */ ++ ++struct meson_cvbs_mode meson_cvbs_modes[MESON_CVBS_MODES_COUNT] = { ++ { /* PAL */ ++ .enci = &meson_cvbs_enci_pal, ++ .mode = { ++ DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500, ++ 720, 732, 795, 864, 0, 576, 580, 586, 625, 0, ++ DRM_MODE_FLAG_INTERLACE), ++ .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, ++ }, ++ }, ++ { /* NTSC */ ++ .enci = &meson_cvbs_enci_ntsc, ++ .mode = { ++ DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500, ++ 720, 739, 801, 858, 0, 480, 488, 494, 525, 0, ++ DRM_MODE_FLAG_INTERLACE), ++ .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, ++ }, ++ }, ++}; ++ ++static const struct meson_cvbs_mode * ++meson_cvbs_get_mode(const struct drm_display_mode *req_mode) ++{ ++ int i; ++ ++ for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) { ++ struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i]; ++ ++ if (drm_mode_match(req_mode, &meson_mode->mode, ++ DRM_MODE_MATCH_TIMINGS | ++ DRM_MODE_MATCH_CLOCK | ++ DRM_MODE_MATCH_FLAGS | ++ DRM_MODE_MATCH_3D_FLAGS)) ++ return meson_mode; ++ } ++ ++ return NULL; ++} ++ ++static int meson_encoder_cvbs_attach(struct drm_bridge *bridge, ++ enum drm_bridge_attach_flags flags) ++{ ++ 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) ++{ ++ struct meson_encoder_cvbs *meson_encoder_cvbs = ++ bridge_to_meson_encoder_cvbs(bridge); ++ struct meson_drm *priv = meson_encoder_cvbs->priv; ++ struct drm_display_mode *mode; ++ int i; ++ ++ for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) { ++ struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i]; ++ ++ mode = drm_mode_duplicate(priv->drm, &meson_mode->mode); ++ if (!mode) { ++ dev_err(priv->dev, "Failed to create a new display mode\n"); ++ return 0; ++ } ++ ++ drm_mode_probed_add(connector, mode); ++ } ++ ++ return i; ++} ++ ++static int meson_encoder_cvbs_mode_valid(struct drm_bridge *bridge, ++ const struct drm_display_info *display_info, ++ const struct drm_display_mode *mode) ++{ ++ if (meson_cvbs_get_mode(mode)) ++ return MODE_OK; ++ ++ return MODE_BAD; ++} ++ ++static int meson_encoder_cvbs_atomic_check(struct drm_bridge *bridge, ++ struct drm_bridge_state *bridge_state, ++ struct drm_crtc_state *crtc_state, ++ struct drm_connector_state *conn_state) ++{ ++ if (meson_cvbs_get_mode(&crtc_state->mode)) ++ return 0; ++ ++ return -EINVAL; ++} ++ ++static void meson_encoder_cvbs_atomic_enable(struct drm_bridge *bridge, ++ struct drm_bridge_state *bridge_state) ++{ ++ struct meson_encoder_cvbs *encoder_cvbs = bridge_to_meson_encoder_cvbs(bridge); ++ struct drm_atomic_state *state = bridge_state->base.state; ++ struct meson_drm *priv = encoder_cvbs->priv; ++ const struct meson_cvbs_mode *meson_mode; ++ struct drm_connector_state *conn_state; ++ struct drm_crtc_state *crtc_state; ++ struct drm_connector *connector; ++ ++ 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; ++ ++ meson_mode = meson_cvbs_get_mode(&crtc_state->adjusted_mode); ++ if (WARN_ON(!meson_mode)) ++ return; ++ ++ meson_venci_cvbs_mode_set(priv, meson_mode->enci); ++ ++ /* Setup 27MHz vclk2 for ENCI and VDAC */ ++ meson_vclk_setup(priv, MESON_VCLK_TARGET_CVBS, ++ MESON_VCLK_CVBS, MESON_VCLK_CVBS, ++ MESON_VCLK_CVBS, MESON_VCLK_CVBS, ++ true); ++ ++ /* VDAC0 source is not from ATV */ ++ writel_bits_relaxed(VENC_VDAC_SEL_ATV_DMD, 0, ++ priv->io_base + _REG(VENC_VDAC_DACSEL0)); ++ ++ if (!priv->cvbs_dac) { ++ if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) ++ regmap_write(priv->hhi, HHI_VDAC_CNTL0, 1); ++ 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); ++ 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, 0x0); ++ } else if (!priv->cvbs_dac_enabled) { ++ int ret = phy_power_on(priv->cvbs_dac); ++ if (ret) ++ dev_err(priv->dev, ++ "Failed to power on the CVBS DAC\n"); ++ ++ priv->cvbs_dac_enabled = true; ++ } ++} ++ ++static void meson_encoder_cvbs_atomic_disable(struct drm_bridge *bridge, ++ struct drm_bridge_state *bridge_state) ++{ ++ struct meson_encoder_cvbs *meson_encoder_cvbs = ++ bridge_to_meson_encoder_cvbs(bridge); ++ struct meson_drm *priv = meson_encoder_cvbs->priv; ++ ++ /* Disable CVBS VDAC */ ++ if (!priv->cvbs_dac) { ++ 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); ++ } ++ } else if (priv->cvbs_dac_enabled) { ++ int ret = phy_power_off(priv->cvbs_dac); ++ if (ret) ++ dev_err(priv->dev, ++ "Failed to power off the CVBS DAC\n"); ++ ++ priv->cvbs_dac_enabled = false; ++ } ++} ++ ++static const struct drm_bridge_funcs meson_encoder_cvbs_bridge_funcs = { ++ .attach = meson_encoder_cvbs_attach, ++ .mode_valid = meson_encoder_cvbs_mode_valid, ++ .get_modes = meson_encoder_cvbs_get_modes, ++ .atomic_enable = meson_encoder_cvbs_atomic_enable, ++ .atomic_disable = meson_encoder_cvbs_atomic_disable, ++ .atomic_check = meson_encoder_cvbs_atomic_check, ++ .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, ++}; ++ ++int meson_encoder_cvbs_init(struct meson_drm *priv) ++{ ++ struct drm_device *drm = priv->drm; ++ struct meson_encoder_cvbs *meson_encoder_cvbs; ++ struct drm_connector *connector; ++ struct device_node *remote; ++ int ret; ++ ++ meson_encoder_cvbs = devm_kzalloc(priv->dev, sizeof(*meson_encoder_cvbs), GFP_KERNEL); ++ if (!meson_encoder_cvbs) ++ return -ENOMEM; ++ ++ /* CVBS Connector Bridge */ ++ remote = of_graph_get_remote_node(priv->dev->of_node, 0, 0); ++ if (!remote) { ++ dev_info(drm->dev, "CVBS Output connector not available\n"); ++ return 0; ++ } ++ ++ meson_encoder_cvbs->next_bridge = of_drm_find_bridge(remote); ++ if (!meson_encoder_cvbs->next_bridge) { ++ dev_err(priv->dev, "Failed to find CVBS Connector bridge\n"); ++ return -EPROBE_DEFER; ++ } ++ ++ /* CVBS Encoder Bridge */ ++ meson_encoder_cvbs->bridge.funcs = &meson_encoder_cvbs_bridge_funcs; ++ meson_encoder_cvbs->bridge.of_node = priv->dev->of_node; ++ meson_encoder_cvbs->bridge.type = DRM_MODE_CONNECTOR_Composite; ++ meson_encoder_cvbs->bridge.ops = DRM_BRIDGE_OP_MODES; ++ meson_encoder_cvbs->bridge.interlace_allowed = true; ++ ++ drm_bridge_add(&meson_encoder_cvbs->bridge); ++ ++ meson_encoder_cvbs->priv = priv; ++ ++ /* Encoder */ ++ ret = drm_simple_encoder_init(priv->drm, &meson_encoder_cvbs->encoder, ++ DRM_MODE_ENCODER_TVDAC); ++ if (ret) { ++ dev_err(priv->dev, "Failed to init CVBS encoder: %d\n", ret); ++ return ret; ++ } ++ ++ meson_encoder_cvbs->encoder.possible_crtcs = BIT(0); ++ ++ /* Attach CVBS Encoder Bridge to Encoder */ ++ ret = drm_bridge_attach(&meson_encoder_cvbs->encoder, &meson_encoder_cvbs->bridge, NULL, ++ DRM_BRIDGE_ATTACH_NO_CONNECTOR); ++ if (ret) { ++ dev_err(priv->dev, "Failed to attach bridge: %d\n", ret); ++ return ret; ++ } ++ ++ /* Initialize & attach Bridge Connector */ ++ connector = drm_bridge_connector_init(priv->drm, &meson_encoder_cvbs->encoder); ++ if (IS_ERR(connector)) { ++ dev_err(priv->dev, "Unable to create CVBS bridge connector\n"); ++ return PTR_ERR(connector); ++ } ++ drm_connector_attach_encoder(connector, &meson_encoder_cvbs->encoder); ++ ++ return 0; ++} +diff --git a/drivers/gpu/drm/meson/meson_venc_cvbs.h b/drivers/gpu/drm/meson/meson_encoder_cvbs.h +similarity index 92% +rename from drivers/gpu/drm/meson/meson_venc_cvbs.h +rename to drivers/gpu/drm/meson/meson_encoder_cvbs.h +index ab7f76ba469c..61d9d183ce7f 100644 +--- a/drivers/gpu/drm/meson/meson_venc_cvbs.h ++++ b/drivers/gpu/drm/meson/meson_encoder_cvbs.h +@@ -24,6 +24,6 @@ struct meson_cvbs_mode { + /* Modes supported by the CVBS output */ + extern struct meson_cvbs_mode meson_cvbs_modes[MESON_CVBS_MODES_COUNT]; + +-int meson_venc_cvbs_create(struct meson_drm *priv); ++int meson_encoder_cvbs_init(struct meson_drm *priv); + + #endif /* __MESON_VENC_CVBS_H */ +diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c +index a7692584487c..62c5cde59224 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,9 +215,18 @@ 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 { ++ ycrcb_map = VPU_HDMI_OUTPUT_CBYCR; + } + + /* VENC + VENC-DVI Mode setup */ +@@ -426,8 +435,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 8640a8a8a469..456e2f421713 100644 +--- a/drivers/gpu/drm/meson/meson_plane.c ++++ b/drivers/gpu/drm/meson/meson_plane.c +@@ -199,8 +199,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 && +@@ -231,17 +234,21 @@ static void meson_plane_atomic_update(struct drm_plane *plane, + } + } + +- switch (fb->format->format) { +- case DRM_FORMAT_XRGB8888: +- case DRM_FORMAT_XBGR8888: +- /* For XRGB, replace the pixel's alpha by 0xFF */ +- priv->viu.osd1_ctrl_stat2 |= OSD_REPLACE_EN; +- break; +- case DRM_FORMAT_ARGB8888: +- case DRM_FORMAT_ABGR8888: +- /* For ARGB, use the pixel's alpha */ +- priv->viu.osd1_ctrl_stat2 &= ~OSD_REPLACE_EN; +- break; ++ 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)) { ++ switch (fb->format->format) { ++ case DRM_FORMAT_XRGB8888: ++ case DRM_FORMAT_XBGR8888: ++ /* For XRGB, replace the pixel's alpha by 0xFF */ ++ priv->viu.osd1_ctrl_stat2 |= OSD_REPLACE_EN; ++ break; ++ case DRM_FORMAT_ARGB8888: ++ case DRM_FORMAT_ABGR8888: ++ /* For ARGB, use the pixel's alpha */ ++ priv->viu.osd1_ctrl_stat2 &= ~OSD_REPLACE_EN; ++ break; ++ } + } + + /* Default scaler parameters */ +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 000000000000..f1eab2201690 +--- /dev/null ++++ b/drivers/gpu/drm/meson/meson_transwitch_hdmi.c +@@ -0,0 +1,1602 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2021 Martin Blumenstingl ++ * ++ * All registers and magic values are taken from Amlogic's GPL kernel sources: ++ * Copyright (C) 2010 Amlogic, Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++ ++#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; ++ uint8_t eld[MAX_ELD_BYTES]; ++ ++ struct drm_connector connector; ++ struct drm_bridge bridge; ++ struct drm_bridge *next_bridge; ++ ++ unsigned int input_bus_format; ++ unsigned int output_bus_format; ++ 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 enum hdmi_colorspace ++meson_txc_hdmi_bus_fmt_hdmi_colorspace(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: ++ return HDMI_COLORSPACE_YUV444; ++ ++ case MEDIA_BUS_FMT_UYVY8_1X16: ++ case MEDIA_BUS_FMT_UYVY10_1X20: ++ case MEDIA_BUS_FMT_UYVY12_1X24: ++ return HDMI_COLORSPACE_YUV422; ++ ++ 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: ++ return HDMI_COLORSPACE_RGB; ++ } ++} ++ ++static u8 meson_txc_hdmi_bus_fmt_to_color_format(unsigned int bus_format) ++{ ++ switch (meson_txc_hdmi_bus_fmt_hdmi_colorspace(bus_format)) { ++ case HDMI_COLORSPACE_YUV422: ++ /* Documented as YCbCr422 */ ++ return 0x3; ++ ++ case HDMI_COLORSPACE_YUV444: ++ /* Documented as YCbCr444 */ ++ return 0x1; ++ ++ case HDMI_COLORSPACE_RGB: ++ default: ++ /* Documented as RGB444 */ ++ return 0x0; ++ } ++} ++ ++static void meson_txc_hdmi_config_color_space(struct meson_txc_hdmi *priv, ++ 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(priv->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(priv->output_bus_format)) | ++ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_FORMAT, ++ meson_txc_hdmi_bus_fmt_to_color_format(priv->input_bus_format)) | ++ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_DEPTH, ++ meson_txc_hdmi_bus_fmt_to_color_depth(priv->output_bus_format)) | ++ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_DEPTH, ++ meson_txc_hdmi_bus_fmt_to_color_depth(priv->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); ++ } ++} ++ ++/* ++ * FIXME - questions for CDNS team: ++ * - what is the name of the 0x0018 register? ++ * - what do BIT(1), BIT(2), BIT(4) and BIT(5) mean? ++ * - if it's not clear from the names of these bits: when to set each of them? ++ * (below code depends on the HDMI_COLORIMETRY and one special case also on ++ * the color depth and a special HDMI VIC) ++ */ ++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, 0x0018, 0x24); ++ else ++ regmap_write(priv->regmap, 0x0018, 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, 0x0018, 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, ++ 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; ++ } ++ ++ /* fixed infoframe configuration not linked to the mode */ ++ frame.colorspace = meson_txc_hdmi_bus_fmt_hdmi_colorspace(priv->output_bus_format); ++ frame.colorimetry = colorimetry; ++ ++ drm_hdmi_avi_infoframe_quant_range(&frame, conn, mode, quant_range); ++ ++ 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); ++} ++ ++static int meson_txc_hdmi_bridge_atomic_check(struct drm_bridge *bridge, ++ struct drm_bridge_state *bridge_state, ++ struct drm_crtc_state *crtc_state, ++ struct drm_connector_state *conn_state) ++{ ++ struct meson_txc_hdmi *priv = bridge->driver_private; ++ ++ priv->output_bus_format = bridge_state->output_bus_cfg.format; ++ priv->input_bus_format = bridge_state->input_bus_cfg.format; ++ ++ drm_dbg(bridge->dev, "input format 0x%04x, output format 0x%04x\n", ++ priv->input_bus_format, priv->output_bus_format); ++ ++ return 0; ++} ++ ++/* 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_YCRCB444) ++ 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_YCRCB422) ++ output_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; ++ ++ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) ++ 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_YCRCB422) ++ output_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; ++ ++ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) ++ output_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; ++ ++ output_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; ++ } ++ ++ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) ++ output_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; ++ ++ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) ++ 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 *bridge_state) ++{ ++ struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge); ++ struct drm_atomic_state *state = bridge_state->base.state; ++ enum hdmi_quantization_range quant_range; ++ struct drm_connector_state *conn_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; ++ ++ 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; ++ ++ mode = &crtc_state->adjusted_mode; ++ ++ memcpy(connector->eld, priv->eld, MAX_ELD_BYTES); ++ ++ if (priv->input_bus_format == MEDIA_BUS_FMT_FIXED) ++ priv->input_bus_format = MEDIA_BUS_FMT_RGB888_1X24; ++ ++ 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, ++ 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, 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 *bridge_state) ++{ ++ struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge); ++ ++ 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); ++ ++ memset(priv->eld, 0, MAX_ELD_BYTES); ++} ++ ++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, ++ ®val); ++ 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_check = meson_txc_hdmi_bridge_atomic_check, ++ .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); ++ ++ /* ++ * FIXME - questions for CDNS team: ++ * - what is the name of the 0x0010 register? ++ * - what do bit [7:0] stand for? ++ */ ++ regmap_write(priv->regmap, 0x0010, 0xff); ++ ++ regmap_write(priv->regmap, TX_HDCP_MODE, 0x40); ++ ++ /* ++ * FIXME - questions for CDNS team: ++ * - what is the name of the 0x0017 register? ++ * - is there a description for BIT(0), BIT(2), BIT(3) and BIT(4) or ++ * are 0x1d and 0x0 the only allowed values? ++ */ ++ /* Band-gap and main-bias. 0x1d = power-up, 0x00 = power-down */ ++ regmap_write(priv->regmap, 0x0017, 0x1d); ++ ++ meson_txc_hdmi_config_serializer_clock(priv, HDMI_COLORIMETRY_NONE); ++ ++ /* ++ * FIXME - questions for CDNS team: ++ * - what is the name of the 0x001a register? ++ * - is there a description for BIT(3), BIT(4), BIT(5), BIT(6) and ++ * BIT(7)? ++ */ ++ /* ++ * bit[2:0]=011: CK channel output TMDS CLOCK ++ * bit[2:0]=101, ck channel output PHYCLCK ++ */ ++ regmap_write(priv->regmap, 0x001a, 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; ++ ++ memcpy(buf, priv->eld, min_t(size_t, MAX_ELD_BYTES, 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; ++ priv->input_bus_format = MEDIA_BUS_FMT_FIXED; ++ priv->output_bus_format = MEDIA_BUS_FMT_FIXED; ++ ++ 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 "); ++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 000000000000..14929475c0c8 +--- /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 ++ * ++ * All registers and magic values are taken from Amlogic's GPL kernel sources: ++ * Copyright (C) 2010 Amlogic, Inc. ++ */ ++ ++#include ++#include ++ ++#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 2a82119eb58e..a2c1bf1aed77 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 3c55ed003359..4acabe394f42 100644 +--- a/drivers/gpu/drm/meson/meson_venc.c ++++ b/drivers/gpu/drm/meson/meson_venc.c +@@ -1749,31 +1749,56 @@ 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(VPU_INTR_CLK_NUM, 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(VPU_INTR_CLK_NUM, 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); ++ if (priv->hhi) { ++ /* 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_venc_cvbs.c b/drivers/gpu/drm/meson/meson_venc_cvbs.c +deleted file mode 100644 +index f1747fde1fe0..000000000000 +--- a/drivers/gpu/drm/meson/meson_venc_cvbs.c ++++ /dev/null +@@ -1,293 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (C) 2016 BayLibre, SAS +- * Author: Neil Armstrong +- * Copyright (C) 2015 Amlogic, Inc. All rights reserved. +- * Copyright (C) 2014 Endless Mobile +- * +- * Written by: +- * Jasper St. Pierre +- */ +- +-#include +-#include +- +-#include +-#include +-#include +-#include +-#include +- +-#include "meson_registers.h" +-#include "meson_vclk.h" +-#include "meson_venc_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_venc_cvbs { +- struct drm_encoder encoder; +- struct drm_connector connector; +- struct meson_drm *priv; +-}; +-#define encoder_to_meson_venc_cvbs(x) \ +- container_of(x, struct meson_venc_cvbs, encoder) +- +-#define connector_to_meson_venc_cvbs(x) \ +- container_of(x, struct meson_venc_cvbs, connector) +- +-/* Supported Modes */ +- +-struct meson_cvbs_mode meson_cvbs_modes[MESON_CVBS_MODES_COUNT] = { +- { /* PAL */ +- .enci = &meson_cvbs_enci_pal, +- .mode = { +- DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500, +- 720, 732, 795, 864, 0, 576, 580, 586, 625, 0, +- DRM_MODE_FLAG_INTERLACE), +- .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, +- }, +- }, +- { /* NTSC */ +- .enci = &meson_cvbs_enci_ntsc, +- .mode = { +- DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500, +- 720, 739, 801, 858, 0, 480, 488, 494, 525, 0, +- DRM_MODE_FLAG_INTERLACE), +- .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, +- }, +- }, +-}; +- +-static const struct meson_cvbs_mode * +-meson_cvbs_get_mode(const struct drm_display_mode *req_mode) +-{ +- int i; +- +- for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) { +- struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i]; +- +- if (drm_mode_match(req_mode, &meson_mode->mode, +- DRM_MODE_MATCH_TIMINGS | +- DRM_MODE_MATCH_CLOCK | +- DRM_MODE_MATCH_FLAGS | +- DRM_MODE_MATCH_3D_FLAGS)) +- return meson_mode; +- } +- +- return NULL; +-} +- +-/* Connector */ +- +-static void meson_cvbs_connector_destroy(struct drm_connector *connector) +-{ +- drm_connector_cleanup(connector); +-} +- +-static enum drm_connector_status +-meson_cvbs_connector_detect(struct drm_connector *connector, bool force) +-{ +- /* FIXME: Add load-detect or jack-detect if possible */ +- return connector_status_connected; +-} +- +-static int meson_cvbs_connector_get_modes(struct drm_connector *connector) +-{ +- struct drm_device *dev = connector->dev; +- struct drm_display_mode *mode; +- int i; +- +- for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) { +- struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i]; +- +- mode = drm_mode_duplicate(dev, &meson_mode->mode); +- if (!mode) { +- DRM_ERROR("Failed to create a new display mode\n"); +- return 0; +- } +- +- drm_mode_probed_add(connector, mode); +- } +- +- return i; +-} +- +-static int meson_cvbs_connector_mode_valid(struct drm_connector *connector, +- struct drm_display_mode *mode) +-{ +- /* Validate the modes added in get_modes */ +- return MODE_OK; +-} +- +-static const struct drm_connector_funcs meson_cvbs_connector_funcs = { +- .detect = meson_cvbs_connector_detect, +- .fill_modes = drm_helper_probe_single_connector_modes, +- .destroy = meson_cvbs_connector_destroy, +- .reset = drm_atomic_helper_connector_reset, +- .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, +- .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +-}; +- +-static const +-struct drm_connector_helper_funcs meson_cvbs_connector_helper_funcs = { +- .get_modes = meson_cvbs_connector_get_modes, +- .mode_valid = meson_cvbs_connector_mode_valid, +-}; +- +-/* Encoder */ +- +-static void meson_venc_cvbs_encoder_destroy(struct drm_encoder *encoder) +-{ +- drm_encoder_cleanup(encoder); +-} +- +-static const struct drm_encoder_funcs meson_venc_cvbs_encoder_funcs = { +- .destroy = meson_venc_cvbs_encoder_destroy, +-}; +- +-static int meson_venc_cvbs_encoder_atomic_check(struct drm_encoder *encoder, +- struct drm_crtc_state *crtc_state, +- struct drm_connector_state *conn_state) +-{ +- if (meson_cvbs_get_mode(&crtc_state->mode)) +- return 0; +- +- return -EINVAL; +-} +- +-static void meson_venc_cvbs_encoder_disable(struct drm_encoder *encoder) +-{ +- struct meson_venc_cvbs *meson_venc_cvbs = +- encoder_to_meson_venc_cvbs(encoder); +- struct meson_drm *priv = meson_venc_cvbs->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, 0); +- } else { +- regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0); +- regmap_write(priv->hhi, HHI_VDAC_CNTL1, 8); +- } +-} +- +-static void meson_venc_cvbs_encoder_enable(struct drm_encoder *encoder) +-{ +- struct meson_venc_cvbs *meson_venc_cvbs = +- encoder_to_meson_venc_cvbs(encoder); +- struct meson_drm *priv = meson_venc_cvbs->priv; +- +- /* VDAC0 source is not from ATV */ +- 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); +- } +-} +- +-static void meson_venc_cvbs_encoder_mode_set(struct drm_encoder *encoder, +- struct drm_display_mode *mode, +- struct drm_display_mode *adjusted_mode) +-{ +- const struct meson_cvbs_mode *meson_mode = meson_cvbs_get_mode(mode); +- struct meson_venc_cvbs *meson_venc_cvbs = +- encoder_to_meson_venc_cvbs(encoder); +- struct meson_drm *priv = meson_venc_cvbs->priv; +- +- if (meson_mode) { +- meson_venci_cvbs_mode_set(priv, meson_mode->enci); +- +- /* Setup 27MHz vclk2 for ENCI and VDAC */ +- meson_vclk_setup(priv, MESON_VCLK_TARGET_CVBS, +- MESON_VCLK_CVBS, MESON_VCLK_CVBS, +- MESON_VCLK_CVBS, MESON_VCLK_CVBS, +- true); +- } +-} +- +-static const struct drm_encoder_helper_funcs +- meson_venc_cvbs_encoder_helper_funcs = { +- .atomic_check = meson_venc_cvbs_encoder_atomic_check, +- .disable = meson_venc_cvbs_encoder_disable, +- .enable = meson_venc_cvbs_encoder_enable, +- .mode_set = meson_venc_cvbs_encoder_mode_set, +-}; +- +-static bool meson_venc_cvbs_connector_is_available(struct meson_drm *priv) +-{ +- struct device_node *remote; +- +- remote = of_graph_get_remote_node(priv->dev->of_node, 0, 0); +- if (!remote) +- return false; +- +- of_node_put(remote); +- return true; +-} +- +-int meson_venc_cvbs_create(struct meson_drm *priv) +-{ +- struct drm_device *drm = priv->drm; +- struct meson_venc_cvbs *meson_venc_cvbs; +- struct drm_connector *connector; +- struct drm_encoder *encoder; +- int ret; +- +- if (!meson_venc_cvbs_connector_is_available(priv)) { +- dev_info(drm->dev, "CVBS Output connector not available\n"); +- return 0; +- } +- +- meson_venc_cvbs = devm_kzalloc(priv->dev, sizeof(*meson_venc_cvbs), +- GFP_KERNEL); +- if (!meson_venc_cvbs) +- return -ENOMEM; +- +- meson_venc_cvbs->priv = priv; +- encoder = &meson_venc_cvbs->encoder; +- connector = &meson_venc_cvbs->connector; +- +- /* Connector */ +- +- drm_connector_helper_add(connector, +- &meson_cvbs_connector_helper_funcs); +- +- ret = drm_connector_init(drm, connector, &meson_cvbs_connector_funcs, +- DRM_MODE_CONNECTOR_Composite); +- if (ret) { +- dev_err(priv->dev, "Failed to init CVBS connector\n"); +- return ret; +- } +- +- connector->interlace_allowed = 1; +- +- /* Encoder */ +- +- drm_encoder_helper_add(encoder, &meson_venc_cvbs_encoder_helper_funcs); +- +- ret = drm_encoder_init(drm, encoder, &meson_venc_cvbs_encoder_funcs, +- DRM_MODE_ENCODER_TVDAC, "meson_venc_cvbs"); +- if (ret) { +- dev_err(priv->dev, "Failed to init CVBS encoder\n"); +- return ret; +- } +- +- encoder->possible_crtcs = BIT(0); +- +- drm_connector_attach_encoder(connector, encoder); +- +- return 0; +-} +diff --git a/drivers/gpu/drm/meson/meson_viu.c b/drivers/gpu/drm/meson/meson_viu.c +index bb7e109534de..406498ba2f79 100644 +--- a/drivers/gpu/drm/meson/meson_viu.c ++++ b/drivers/gpu/drm/meson/meson_viu.c +@@ -436,10 +436,22 @@ void meson_viu_init(struct meson_drm *priv) + + /* Initialize OSD1 fifo control register */ + reg = VIU_OSD_DDR_PRIORITY_URGENT | +- VIU_OSD_HOLD_FIFO_LINES(31) | +- VIU_OSD_FIFO_DEPTH_VAL(32) | /* fifo_depth_val: 32*8=256 */ +- VIU_OSD_WORDS_PER_BURST(4) | /* 4 words in 1 burst */ +- VIU_OSD_FIFO_LIMITS(2); /* fifo_lim: 2*16=32 */ ++ VIU_OSD_FIFO_DEPTH_VAL(32) | /* fifo_depth_val: 32*8=256 */ ++ VIU_OSD_WORDS_PER_BURST(4) | /* 4 words in 1 burst */ ++ VIU_OSD_FIFO_LIMITS(2); /* fifo_lim: 2*16=32 */ ++ ++ /* ++ * When using AFBC on newer SoCs the AFBC encoder has to be reset. To ++ * leave time for that we need hold more lines to avoid glitches. ++ * On the 32-bit SoCs however we need to hold fewer lines because ++ * otherwise screen tearing can occur (for example in kmscube). ++ */ ++ 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)) ++ reg |= VIU_OSD_HOLD_FIFO_LINES(12); ++ else ++ reg |= VIU_OSD_HOLD_FIFO_LINES(31); + + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) + reg |= VIU_OSD_BURST_LENGTH_32; +@@ -449,13 +461,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 db5d0cd757e3..53ea95336fc8 100644 +--- a/drivers/phy/amlogic/Kconfig ++++ b/drivers/phy/amlogic/Kconfig +@@ -2,6 +2,16 @@ + # + # Phy drivers for Amlogic platforms + # ++config PHY_MESON8_HDMI_TX ++ tristate "Meson8, Meson8b and Meson8m2 HDMI TX PHY driver" ++ depends on (ARCH_MESON && ARM) || COMPILE_TEST ++ depends on OF ++ select MFD_SYSCON ++ help ++ Enable this to support the HDMI TX PHYs found in Meson8, ++ Meson8b and Meson8m2 SoCs. ++ If unsure, say N. ++ + config PHY_MESON8B_USB2 + tristate "Meson8, Meson8b, Meson8m2 and GXBB USB2 PHY driver" + default ARCH_MESON +@@ -15,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 8fa07fbd0d92..f5e1145e983b 100644 +--- a/drivers/phy/amlogic/Makefile ++++ b/drivers/phy/amlogic/Makefile +@@ -1,5 +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 000000000000..3287d53c768a +--- /dev/null ++++ b/drivers/phy/amlogic/phy-meson-cvbs-dac.c +@@ -0,0 +1,310 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2016 BayLibre, SAS ++ * Copyright (C) 2021 Martin Blumenstingl ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 has_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, ++ .has_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, ++ .has_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, ++ .has_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, ++ .has_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)) ++ return dev_err_probe(dev, PTR_ERR(trimming), ++ "Failed to read the 'cvbs_trimming' nvmem-cell\n"); ++ ++ if (len != 2) ++ 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; ++ ++ priv->data = device_get_match_data(dev); ++ if (!priv->data) ++ return dev_err_probe(dev, -EINVAL, ++ "Could not find match data\n"); ++ ++ hhi = syscon_node_to_regmap(np->parent); ++ if (IS_ERR(hhi)) ++ return PTR_ERR(hhi); ++ ++ if (priv->data->has_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); ++ ++ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); ++ ++ return PTR_ERR_OR_ZERO(phy_provider); ++} ++ ++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); ++ ++static struct platform_driver phy_meson_cvbs_dac_driver = { ++ .probe = phy_meson_cvbs_dac_probe, ++ .driver = { ++ .name = "phy-meson-cvbs-dac", ++ .of_match_table = phy_meson_cvbs_dac_of_match, ++ }, ++}; ++module_platform_driver(phy_meson_cvbs_dac_driver); ++ ++MODULE_AUTHOR("Martin Blumenstingl "); ++MODULE_DESCRIPTION("Amlogic Meson CVBS DAC driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/phy/amlogic/phy-meson8-hdmi-tx.c b/drivers/phy/amlogic/phy-meson8-hdmi-tx.c +new file mode 100644 +index 000000000000..f9a6572c27d8 +--- /dev/null ++++ b/drivers/phy/amlogic/phy-meson8-hdmi-tx.c +@@ -0,0 +1,160 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Meson8, Meson8b and Meson8m2 HDMI TX PHY. ++ * ++ * Copyright (C) 2021 Martin Blumenstingl ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * Unfortunately there is no detailed documentation available for the ++ * HHI_HDMI_PHY_CNTL0 register. CTL0 and CTL1 is all we know about. ++ * Magic register values in the driver below are taken from the vendor ++ * BSP / kernel. ++ */ ++#define HHI_HDMI_PHY_CNTL0 0x3a0 ++ #define HHI_HDMI_PHY_CNTL0_HDMI_CTL1 GENMASK(31, 16) ++ #define HHI_HDMI_PHY_CNTL0_HDMI_CTL0 GENMASK(15, 0) ++ ++#define HHI_HDMI_PHY_CNTL1 0x3a4 ++ #define HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE BIT(1) ++ #define HHI_HDMI_PHY_CNTL1_SOFT_RESET BIT(0) ++ ++#define HHI_HDMI_PHY_CNTL2 0x3a8 ++ ++struct phy_meson8_hdmi_tx_priv { ++ struct regmap *hhi; ++ struct clk *tmds_clk; ++}; ++ ++static int phy_meson8_hdmi_tx_init(struct phy *phy) ++{ ++ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); ++ ++ return clk_prepare_enable(priv->tmds_clk); ++} ++ ++static int phy_meson8_hdmi_tx_exit(struct phy *phy) ++{ ++ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); ++ ++ clk_disable_unprepare(priv->tmds_clk); ++ ++ return 0; ++} ++ ++static int phy_meson8_hdmi_tx_power_on(struct phy *phy) ++{ ++ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); ++ unsigned int i; ++ u16 hdmi_ctl0; ++ ++ if (clk_get_rate(priv->tmds_clk) >= 2970UL * 1000 * 1000) ++ hdmi_ctl0 = 0x1e8b; ++ else ++ hdmi_ctl0 = 0x4d0b; ++ ++ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, ++ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x08c3) | ++ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, hdmi_ctl0)); ++ ++ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, 0x0); ++ ++ /* Reset three times, just like the vendor driver does */ ++ for (i = 0; i < 3; i++) { ++ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, ++ HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE | ++ HHI_HDMI_PHY_CNTL1_SOFT_RESET); ++ usleep_range(1000, 2000); ++ ++ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, ++ HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE); ++ usleep_range(1000, 2000); ++ } ++ ++ return 0; ++} ++ ++static int phy_meson8_hdmi_tx_power_off(struct phy *phy) ++{ ++ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); ++ ++ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, ++ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x0841) | ++ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, 0x8d00)); ++ ++ return 0; ++} ++ ++static const struct phy_ops phy_meson8_hdmi_tx_ops = { ++ .init = phy_meson8_hdmi_tx_init, ++ .exit = phy_meson8_hdmi_tx_exit, ++ .power_on = phy_meson8_hdmi_tx_power_on, ++ .power_off = phy_meson8_hdmi_tx_power_off, ++ .owner = THIS_MODULE, ++}; ++ ++static int phy_meson8_hdmi_tx_probe(struct platform_device *pdev) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ struct phy_meson8_hdmi_tx_priv *priv; ++ struct phy_provider *phy_provider; ++ struct resource *res; ++ struct phy *phy; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) ++ return -EINVAL; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->hhi = syscon_node_to_regmap(np->parent); ++ if (IS_ERR(priv->hhi)) ++ return PTR_ERR(priv->hhi); ++ ++ priv->tmds_clk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(priv->tmds_clk)) ++ return PTR_ERR(priv->tmds_clk); ++ ++ phy = devm_phy_create(&pdev->dev, np, &phy_meson8_hdmi_tx_ops); ++ if (IS_ERR(phy)) ++ return PTR_ERR(phy); ++ ++ phy_set_drvdata(phy, priv); ++ ++ phy_provider = devm_of_phy_provider_register(&pdev->dev, ++ of_phy_simple_xlate); ++ ++ return PTR_ERR_OR_ZERO(phy_provider); ++} ++ ++static const struct of_device_id phy_meson8_hdmi_tx_of_match[] = { ++ { .compatible = "amlogic,meson8-hdmi-tx-phy" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, phy_meson8_hdmi_tx_of_match); ++ ++static struct platform_driver phy_meson8_hdmi_tx_driver = { ++ .probe = phy_meson8_hdmi_tx_probe, ++ .driver = { ++ .name = "phy-meson8-hdmi-tx", ++ .of_match_table = phy_meson8_hdmi_tx_of_match, ++ }, ++}; ++module_platform_driver(phy_meson8_hdmi_tx_driver); ++ ++MODULE_AUTHOR("Martin Blumenstingl "); ++MODULE_DESCRIPTION("Meson8, Meson8b and Meson8m2 HDMI TX PHY driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/include/dt-bindings/clock/meson8b-clkc.h b/include/dt-bindings/clock/meson8b-clkc.h +index f33781338eda..78aa07fd7cc0 100644 +--- a/include/dt-bindings/clock/meson8b-clkc.h ++++ b/include/dt-bindings/clock/meson8b-clkc.h +@@ -105,6 +105,16 @@ + #define CLKID_PERIPH 126 + #define CLKID_AXI 128 + #define CLKID_L2_DRAM 130 ++#define CLKID_HDMI_PLL_HDMI_OUT 132 ++#define CLKID_VID_PLL_FINAL_DIV 137 ++#define CLKID_VCLK_IN_SEL 138 ++#define CLKID_VCLK2_IN_SEL 149 ++#define CLKID_CTS_ENCT 161 ++#define CLKID_CTS_ENCP 163 ++#define CLKID_CTS_ENCI 165 ++#define CLKID_HDMI_TX_PIXEL 167 ++#define CLKID_CTS_ENCL 169 ++#define CLKID_CTS_VDAC0 171 + #define CLKID_HDMI_SYS 174 + #define CLKID_VPU 190 + #define CLKID_VDEC_1 196 +-- +2.34.1 +