meson8/meson8b/meson8m2: Support HDMI The following codes are come from https://github.com/xdarklight/linux/commits/meson-mx-integration-5.18-20220516. Special thank to Martin Blumenstingl. --- .../bindings/display/amlogic,meson-vpu.yaml | 16 + .../phy/amlogic,meson-cvbs-dac-phy.yaml | 81 + arch/arm/boot/dts/meson.dtsi | 13 + arch/arm/boot/dts/meson8.dtsi | 168 +- arch/arm/boot/dts/meson8b.dtsi | 171 +- arch/arm/boot/dts/meson8m2.dtsi | 4 + drivers/gpu/drm/meson/Kconfig | 9 + drivers/gpu/drm/meson/Makefile | 1 + drivers/gpu/drm/meson/meson_drv.c | 315 +++- drivers/gpu/drm/meson/meson_drv.h | 49 +- drivers/gpu/drm/meson/meson_encoder_cvbs.c | 61 +- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 69 +- drivers/gpu/drm/meson/meson_plane.c | 37 +- drivers/gpu/drm/meson/meson_transwitch_hdmi.c | 1579 +++++++++++++++++ drivers/gpu/drm/meson/meson_transwitch_hdmi.h | 536 ++++++ drivers/gpu/drm/meson/meson_vclk.c | 146 ++ drivers/gpu/drm/meson/meson_venc.c | 44 +- drivers/gpu/drm/meson/meson_viu.c | 18 +- drivers/phy/amlogic/Kconfig | 10 + drivers/phy/amlogic/Makefile | 1 + drivers/phy/amlogic/phy-meson-cvbs-dac.c | 375 ++++ 21 files changed, 3577 insertions(+), 126 deletions(-) create mode 100644 Documentation/devicetree/bindings/phy/amlogic,meson-cvbs-dac-phy.yaml create mode 100644 drivers/gpu/drm/meson/meson_transwitch_hdmi.c create mode 100644 drivers/gpu/drm/meson/meson_transwitch_hdmi.h create mode 100644 drivers/phy/amlogic/phy-meson-cvbs-dac.c diff --git a/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml b/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml index 0c72120a..160b84f8 100644 --- a/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml +++ b/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml @@ -66,8 +66,12 @@ properties: - const: amlogic,meson-gx-vpu - enum: - amlogic,meson-g12a-vpu # G12A (S905X2, S905Y2, S905D2) + - amlogic,meson8-vpu + - amlogic,meson8b-vpu + - amlogic,meson8m2-vpu reg: + minItems: 1 maxItems: 2 reg-names: @@ -82,6 +86,15 @@ properties: description: should point to a canvas provider node $ref: /schemas/types.yaml#/definitions/phandle + phys: + maxItems: 1 + description: + PHY specifier for the CVBS DAC + + phy-names: + items: + - const: cvbs-dac + power-domains: maxItems: 1 description: phandle to the associated power domain @@ -125,6 +138,9 @@ examples: #size-cells = <0>; amlogic,canvas = <&canvas>; + phys = <&cvbs_dac_phy>; + phy-names = "cvbs-dac"; + /* CVBS VDAC output port */ port@0 { reg = <0>; diff --git a/Documentation/devicetree/bindings/phy/amlogic,meson-cvbs-dac-phy.yaml b/Documentation/devicetree/bindings/phy/amlogic,meson-cvbs-dac-phy.yaml new file mode 100644 index 00000000..d73cb12c --- /dev/null +++ b/Documentation/devicetree/bindings/phy/amlogic,meson-cvbs-dac-phy.yaml @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/phy/amlogic,meson-cvbs-dac-phy.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Amlogic Meson Composite Video Baseband Signal DAC + +maintainers: + - Martin Blumenstingl + +description: |+ + The CVBS DAC node should be the child of a syscon node with the + required property: + + compatible = "amlogic,meson-hhi-sysctrl", "simple-mfd", "syscon" + + Refer to the bindings described in + Documentation/devicetree/bindings/mfd/syscon.yaml + +properties: + $nodename: + pattern: "^video-dac@[0-9a-f]+$" + + compatible: + oneOf: + - items: + - enum: + - amlogic,meson8-cvbs-dac + - amlogic,meson-gxbb-cvbs-dac + - amlogic,meson-gxl-cvbs-dac + - amlogic,meson-g12a-cvbs-dac + - const: amlogic,meson-cvbs-dac + - const: amlogic,meson-cvbs-dac + + reg: + maxItems: 1 + + clocks: + minItems: 1 + + nvmem-cells: + minItems: 1 + + nvmem-cell-names: + items: + - const: cvbs_trimming + + "#phy-cells": + const: 0 + +required: + - compatible + - reg + - clocks + - "#phy-cells" + +additionalProperties: false + +examples: + - | + video-dac@2f4 { + compatible = "amlogic,meson8-cvbs-dac", "amlogic,meson-cvbs-dac"; + reg = <0x2f4 0x8>; + + #phy-cells = <0>; + + clocks = <&vdac_clock>; + + nvmem-cells = <&cvbs_trimming>; + nvmem-cell-names = "cvbs_trimming"; + }; + - | + video-dac@2ec { + compatible = "amlogic,meson-g12a-cvbs-dac", "amlogic,meson-cvbs-dac"; + reg = <0x2ec 0x8>; + + #phy-cells = <0>; + + clocks = <&vdac_clock>; + }; diff --git a/arch/arm/boot/dts/meson.dtsi b/arch/arm/boot/dts/meson.dtsi index 8e3860d5..9a56cdf7 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 4f22ab45..a9ed099f 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", @@ -601,6 +741,17 @@ smp-sram@1ff80 { }; }; +&cvbs_dac { + compatible = "amlogic,meson8-cvbs-dac", "amlogic,meson-cvbs-dac"; + + clocks = <&clkc CLKID_CTS_VDAC0>; + + nvmem-cells = <&cvbs_trimming>; + nvmem-cell-names = "cvbs_trimming"; + + status = "okay"; +}; + &efuse { compatible = "amlogic,meson8-efuse"; clocks = <&clkc CLKID_EFUSE>; @@ -610,6 +761,10 @@ temperature_calib: calib@1f4 { /* only the upper two bytes are relevant */ reg = <0x1f4 0x4>; }; + + cvbs_trimming: calib@1f8 { + reg = <0x1f8 0x2>; + }; }; ðmac { @@ -625,16 +780,18 @@ &gpio_intc { }; &hhi { - clkc: clock-controller { + clkc: clock-controller@0 { compatible = "amlogic,meson8-clkc"; + reg = <0x0 0x39c>; clocks = <&xtal>, <&ddr_clkc DDR_CLKID_DDR_PLL>; clock-names = "xtal", "ddr_pll"; #clock-cells = <1>; #reset-cells = <1>; }; - pwrc: power-controller { + pwrc: power-controller@100 { compatible = "amlogic,meson8-pwrc"; + reg = <0x100 0x10>; #power-domain-cells = <1>; amlogic,ao-sysctrl = <&pmu>; clocks = <&clkc CLKID_VPU>; @@ -642,6 +799,13 @@ pwrc: power-controller { assigned-clocks = <&clkc CLKID_VPU>; assigned-clock-rates = <364285714>; }; + + hdmi_tx_phy: hdmi-phy@3a0 { + compatible = "amlogic,meson8-hdmi-tx-phy"; + clocks = <&clkc CLKID_HDMI_PLL_HDMI_OUT>; + reg = <0x3a0 0xc>; + #phy-cells = <0>; + }; }; &hwrng { diff --git a/arch/arm/boot/dts/meson8b.dtsi b/arch/arm/boot/dts/meson8b.dtsi index 5979209f..e75d07b5 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 6725dd9f..fcb2ad97 100644 --- a/arch/arm/boot/dts/meson8m2.dtsi +++ b/arch/arm/boot/dts/meson8m2.dtsi @@ -96,6 +96,10 @@ &usb1_phy { compatible = "amlogic,meson8m2-usb2-phy", "amlogic,meson-mx-usb2-phy"; }; +&vpu { + compatible = "amlogic,meson8m2-vpu"; +}; + &wdt { compatible = "amlogic,meson8m2-wdt", "amlogic,meson8b-wdt"; }; diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig index 823909da..ba9f1bc8 100644 --- a/drivers/gpu/drm/meson/Kconfig +++ b/drivers/gpu/drm/meson/Kconfig @@ -10,6 +10,7 @@ config DRM_MESON select REGMAP_MMIO select MESON_CANVAS select CEC_CORE if CEC_NOTIFIER + imply PHY_MESON_CVBS_DAC config DRM_MESON_DW_HDMI tristate "HDMI Synopsys Controller support for Amlogic Meson Display" @@ -17,3 +18,11 @@ config DRM_MESON_DW_HDMI default y if DRM_MESON select DRM_DW_HDMI imply DRM_DW_HDMI_I2S_AUDIO + +config DRM_MESON_TRANSWITCH_HDMI + tristate "Amlogic Meson8/8b/8m2 TranSwitch HDMI 1.4 Controller support" + depends on ARM || COMPILE_TEST + depends on DRM_MESON + default y if DRM_MESON + select REGMAP_MMIO + select SND_SOC_HDMI_CODEC if SND_SOC diff --git a/drivers/gpu/drm/meson/Makefile b/drivers/gpu/drm/meson/Makefile index 3afa31bd..817a5270 100644 --- a/drivers/gpu/drm/meson/Makefile +++ b/drivers/gpu/drm/meson/Makefile @@ -6,3 +6,4 @@ meson-drm-y += meson_encoder_hdmi.o obj-$(CONFIG_DRM_MESON) += meson-drm.o obj-$(CONFIG_DRM_MESON_DW_HDMI) += meson_dw_hdmi.o +obj-$(CONFIG_DRM_MESON_TRANSWITCH_HDMI) += meson_transwitch_hdmi.o diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c index bb72fda9..15f019ac 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 @@ -132,30 +133,147 @@ static struct regmap_config meson_regmap_config = { .max_register = 0x1000, }; +static int meson_cvbs_dac_phy_init(struct meson_drm *priv) +{ + struct platform_device *pdev; + const char *platform_id_name; + + priv->cvbs_dac = devm_phy_optional_get(priv->dev, "cvbs-dac"); + if (IS_ERR(priv->cvbs_dac)) + return dev_err_probe(priv->dev, PTR_ERR(priv->cvbs_dac), + "Failed to get the 'cvbs-dac' PHY\n"); + else if (priv->cvbs_dac) + return 0; + + switch (priv->compat) { + case VPU_COMPATIBLE_GXBB: + platform_id_name = "meson-gxbb-cvbs-dac"; + break; + case VPU_COMPATIBLE_GXL: + case VPU_COMPATIBLE_GXM: + platform_id_name = "meson-gxl-cvbs-dac"; + break; + case VPU_COMPATIBLE_G12A: + platform_id_name = "meson-g12a-cvbs-dac"; + break; + default: + return dev_err_probe(priv->dev, -EINVAL, + "No CVBS DAC platform ID found\n"); + } + + pdev = platform_device_register_data(priv->dev, platform_id_name, + PLATFORM_DEVID_AUTO, NULL, 0); + if (IS_ERR(pdev)) + return dev_err_probe(priv->dev, PTR_ERR(pdev), + "Failed to register fallback CVBS DAC PHY platform device\n"); + + priv->cvbs_dac = platform_get_drvdata(pdev); + if (IS_ERR(priv->cvbs_dac)) { + platform_device_unregister(pdev); + return dev_err_probe(priv->dev, PTR_ERR(priv->cvbs_dac), + "Failed to get the 'cvbs-dac' PHY from it's platform device\n"); + } + + dev_warn(priv->dev, "Using fallback for old .dtbs without CVBS DAC\n"); + + priv->cvbs_dac_pdev = pdev; + + return 0; +} + +static void meson_cvbs_dac_phy_exit(struct meson_drm *priv) +{ + platform_device_unregister(priv->cvbs_dac_pdev); +} + static void meson_vpu_init(struct meson_drm *priv) { - u32 value; + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) { + writel(0x0, priv->io_base + _REG(VPU_MEM_PD_REG0)); + writel(0x0, priv->io_base + _REG(VPU_MEM_PD_REG1)); + } else { + u32 value; + + /* + * Slave dc0 and dc5 connected to master port 1. + * By default other slaves are connected to master port 0. + */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1) | + VPU_RDARB_SLAVE_TO_MASTER_PORT(5, 1); + writel_relaxed(value, + priv->io_base + _REG(VPU_RDARB_MODE_L1C1)); + + /* Slave dc0 connected to master port 1 */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1); + writel_relaxed(value, + priv->io_base + _REG(VPU_RDARB_MODE_L1C2)); + + /* Slave dc4 and dc7 connected to master port 1 */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(4, 1) | + VPU_RDARB_SLAVE_TO_MASTER_PORT(7, 1); + writel_relaxed(value, + priv->io_base + _REG(VPU_RDARB_MODE_L2C1)); + + /* Slave dc1 connected to master port 1 */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(1, 1); + writel_relaxed(value, + priv->io_base + _REG(VPU_WRARB_MODE_L2C1)); + } +} + +static int meson_video_clock_init(struct meson_drm *priv) +{ + int ret; + + ret = clk_bulk_prepare(VPU_VID_CLK_NUM, priv->vid_clks); + if (ret) + return dev_err_probe(priv->dev, ret, + "Failed to prepare the video clocks\n"); + + ret = clk_bulk_prepare(priv->num_intr_clks, priv->intr_clks); + if (ret) + return dev_err_probe(priv->dev, ret, + "Failed to prepare the interrupt clocks\n"); + + return 0; +} + +static void meson_video_clock_exit(struct meson_drm *priv) +{ + if (priv->clk_dac_enabled) + clk_disable(priv->clk_dac); + + if (priv->clk_venc_enabled) + clk_disable(priv->clk_venc); + + clk_bulk_unprepare(priv->num_intr_clks, priv->intr_clks); + clk_bulk_unprepare(VPU_VID_CLK_NUM, priv->vid_clks); +} + +static void meson_fbdev_setup(struct meson_drm *priv) +{ + unsigned int preferred_bpp; /* - * Slave dc0 and dc5 connected to master port 1. - * By default other slaves are connected to master port 0. + * All SoC generations before GXBB don't have a way to configure the + * alpha value for DRM_FORMAT_XRGB8888 and DRM_FORMAT_XBGR8888. These + * formats have an X component instead of an alpha component. On + * Meson8/8b/8m2 there is no way to configure the alpha value to use + * instead of the X component. This results in the fact that the + * formats with X component are only supported on GXBB and newer. Use + * 24 bits per pixel and therefore DRM_FORMAT_RGB888 to get a + * working framebuffer console. */ - value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1) | - VPU_RDARB_SLAVE_TO_MASTER_PORT(5, 1); - writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C1)); - - /* Slave dc0 connected to master port 1 */ - value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1); - writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C2)); - - /* Slave dc4 and dc7 connected to master port 1 */ - value = VPU_RDARB_SLAVE_TO_MASTER_PORT(4, 1) | - VPU_RDARB_SLAVE_TO_MASTER_PORT(7, 1); - writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L2C1)); - - /* Slave dc1 connected to master port 1 */ - value = VPU_RDARB_SLAVE_TO_MASTER_PORT(1, 1); - writel_relaxed(value, priv->io_base + _REG(VPU_WRARB_MODE_L2C1)); + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) + preferred_bpp = 24; + else + preferred_bpp = 32; + + drm_fbdev_dma_setup(priv->drm, preferred_bpp); } struct meson_drm_soc_attr { @@ -164,13 +282,29 @@ struct meson_drm_soc_attr { }; static const struct meson_drm_soc_attr meson_drm_soc_attrs[] = { - /* S805X/S805Y HDMI PLL won't lock for HDMI PHY freq > 1,65GHz */ + /* The maximum frequency of HDMI PHY on Meson8 and Meson8m2 is ~3GHz */ + { + .limits = { + .max_hdmi_phy_freq = 2976000, + }, + .attrs = (const struct soc_device_attribute []) { + { .soc_id = "Meson8 (S802)", }, + { .soc_id = "Meson8m2 (S812)", }, + { /* sentinel */ }, + } + }, + /* + * GXL S805X/S805Y HDMI PLL won't lock for HDMI PHY freq > 1,65GHz. + * Meson8b (S805) only supports "1200p@60 max resolution" according to + * the public datasheet. + */ { .limits = { .max_hdmi_phy_freq = 1650000, }, .attrs = (const struct soc_device_attribute []) { { .soc_id = "GXL (S805*)", }, + { .soc_id = "Meson8b (S805)", }, { /* sentinel */ } } }, @@ -211,67 +345,123 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) priv->compat = match->compat; priv->afbcd.ops = match->afbcd_ops; + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) { + priv->vid_pll_resets[VPU_RESET_VID_PLL_PRE].id = "vid_pll_pre"; + priv->vid_pll_resets[VPU_RESET_VID_PLL_POST].id = "vid_pll_post"; + priv->vid_pll_resets[VPU_RESET_VID_PLL_SOFT_PRE].id = "vid_pll_soft_pre"; + priv->vid_pll_resets[VPU_RESET_VID_PLL_SOFT_POST].id = "vid_pll_soft_post"; + + ret = devm_reset_control_bulk_get_exclusive(dev, + VPU_RESET_VID_PLL_NUM, + priv->vid_pll_resets); + if (ret) + goto free_drm; + + priv->intr_clks[0].id = "vpu_intr"; + priv->intr_clks[1].id = "hdmi_intr_sync"; + priv->intr_clks[2].id = "venci_int"; + priv->num_intr_clks = 3; + + ret = devm_clk_bulk_get(dev, priv->num_intr_clks, + priv->intr_clks); + if (ret) + goto free_drm; + + priv->vid_clks[VPU_VID_CLK_TMDS].id = "tmds"; + priv->vid_clks[VPU_VID_CLK_HDMI_TX_PIXEL].id = "hdmi_tx_pixel"; + priv->vid_clks[VPU_VID_CLK_CTS_ENCP].id = "cts_encp"; + priv->vid_clks[VPU_VID_CLK_CTS_ENCI].id = "cts_enci"; + priv->vid_clks[VPU_VID_CLK_CTS_ENCT].id = "cts_enct"; + priv->vid_clks[VPU_VID_CLK_CTS_ENCL].id = "cts_encl"; + priv->vid_clks[VPU_VID_CLK_CTS_VDAC0].id = "cts_vdac0"; + + ret = devm_clk_bulk_get(dev, VPU_VID_CLK_NUM, priv->vid_clks); + if (ret) + goto free_drm; + } else { + priv->intr_clks[0].id = "vpu_intr"; + priv->num_intr_clks = 1; + + ret = devm_clk_bulk_get_optional(dev, priv->num_intr_clks, + priv->intr_clks); + if (ret) + goto free_drm; + } + + ret = meson_video_clock_init(priv); + if (ret) + goto free_drm; + regs = devm_platform_ioremap_resource_byname(pdev, "vpu"); if (IS_ERR(regs)) { ret = PTR_ERR(regs); - goto free_drm; + goto video_clock_exit; } priv->io_base = regs; + /* + * The HHI resource is optional because it contains the clocks and CVBS + * encoder registers. These are managed by separate drivers though. + */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hhi"); - if (!res) { - ret = -EINVAL; - goto free_drm; - } - /* Simply ioremap since it may be a shared register zone */ - regs = devm_ioremap(dev, res->start, resource_size(res)); - if (!regs) { - ret = -EADDRNOTAVAIL; - goto free_drm; - } + if (res) { + /* Simply ioremap since it may be a shared register zone */ + regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!regs) { + ret = -EADDRNOTAVAIL; + goto video_clock_exit; + } - priv->hhi = devm_regmap_init_mmio(dev, regs, - &meson_regmap_config); - if (IS_ERR(priv->hhi)) { - dev_err(&pdev->dev, "Couldn't create the HHI regmap\n"); - ret = PTR_ERR(priv->hhi); - goto free_drm; + priv->hhi = devm_regmap_init_mmio(dev, regs, + &meson_regmap_config); + if (IS_ERR(priv->hhi)) { + dev_err(&pdev->dev, + "Couldn't create the HHI regmap\n"); + ret = PTR_ERR(priv->hhi); + goto video_clock_exit; + } } priv->canvas = meson_canvas_get(dev); if (IS_ERR(priv->canvas)) { ret = PTR_ERR(priv->canvas); - goto free_drm; + goto video_clock_exit; } ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1); if (ret) - goto free_drm; + goto video_clock_exit; ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_0); if (ret) { meson_canvas_free(priv->canvas, priv->canvas_id_osd1); - goto free_drm; + goto video_clock_exit; } ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_1); if (ret) { meson_canvas_free(priv->canvas, priv->canvas_id_osd1); meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); - goto free_drm; + goto video_clock_exit; } ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_2); if (ret) { meson_canvas_free(priv->canvas, priv->canvas_id_osd1); meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1); - goto free_drm; + goto video_clock_exit; } + ret = meson_cvbs_dac_phy_init(priv); + if (ret) + goto free_drm; + priv->vsync_irq = platform_get_irq(pdev, 0); ret = drm_vblank_init(drm, 1); if (ret) - goto free_drm; + goto exit_cvbs_dac_phy; /* Assign limits per soc revision/package */ for (i = 0 ; i < ARRAY_SIZE(meson_drm_soc_attrs) ; ++i) { @@ -287,11 +477,11 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) */ ret = drm_aperture_remove_framebuffers(false, &meson_driver); if (ret) - goto free_drm; + goto exit_cvbs_dac_phy; ret = drmm_mode_config_init(drm); if (ret) - goto free_drm; + goto exit_cvbs_dac_phy; drm->mode_config.max_width = 3840; drm->mode_config.max_height = 2160; drm->mode_config.funcs = &meson_mode_config_funcs; @@ -306,7 +496,7 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) if (priv->afbcd.ops) { ret = priv->afbcd.ops->init(priv); if (ret) - goto free_drm; + goto exit_cvbs_dac_phy; } /* Encoder Initialization */ @@ -319,7 +509,7 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) ret = component_bind_all(drm->dev, drm); if (ret) { dev_err(drm->dev, "Couldn't bind all components\n"); - goto exit_afbcd; + goto exit_cvbs_dac_phy; } } @@ -353,7 +543,7 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) if (ret) goto uninstall_irq; - drm_fbdev_dma_setup(drm, 32); + meson_fbdev_setup(priv); return 0; @@ -365,6 +555,10 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) exit_afbcd: if (priv->afbcd.ops) priv->afbcd.ops->exit(priv); +exit_cvbs_dac_phy: + meson_cvbs_dac_phy_exit(priv); +video_clock_exit: + meson_video_clock_exit(priv); free_drm: drm_dev_put(drm); @@ -401,6 +595,10 @@ static void meson_drv_unbind(struct device *dev) if (priv->afbcd.ops) priv->afbcd.ops->exit(priv); + + meson_cvbs_dac_phy_exit(priv); + + meson_video_clock_exit(priv); } static const struct component_master_ops meson_drv_master_ops = { @@ -415,6 +613,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); } @@ -425,6 +625,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); @@ -507,6 +708,18 @@ static int meson_drv_remove(struct platform_device *pdev) return 0; } +static struct meson_drm_match_data meson_drm_m8_data = { + .compat = VPU_COMPATIBLE_M8, +}; + +static struct meson_drm_match_data meson_drm_m8b_data = { + .compat = VPU_COMPATIBLE_M8B, +}; + +static struct meson_drm_match_data meson_drm_m8m2_data = { + .compat = VPU_COMPATIBLE_M8M2, +}; + static struct meson_drm_match_data meson_drm_gxbb_data = { .compat = VPU_COMPATIBLE_GXBB, }; @@ -526,6 +739,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 c62ee358..fe0a8f87 100644 --- a/drivers/gpu/drm/meson/meson_drv.h +++ b/drivers/gpu/drm/meson/meson_drv.h @@ -7,22 +7,29 @@ #ifndef __MESON_DRV_H #define __MESON_DRV_H +#include #include #include #include #include +#include struct drm_crtc; struct drm_device; struct drm_plane; struct meson_drm; struct meson_afbcd_ops; +struct phy; +struct platform_device; enum vpu_compatible { - VPU_COMPATIBLE_GXBB = 0, - VPU_COMPATIBLE_GXL = 1, - VPU_COMPATIBLE_GXM = 2, - VPU_COMPATIBLE_G12A = 3, + VPU_COMPATIBLE_M8 = 0, + VPU_COMPATIBLE_M8B = 1, + VPU_COMPATIBLE_M8M2 = 2, + VPU_COMPATIBLE_GXBB = 3, + VPU_COMPATIBLE_GXL = 4, + VPU_COMPATIBLE_GXM = 5, + VPU_COMPATIBLE_G12A = 6, }; enum { @@ -40,6 +47,25 @@ struct meson_drm_soc_limits { unsigned int max_hdmi_phy_freq; }; +enum vpu_bulk_clk_id { + VPU_VID_CLK_TMDS = 0, + VPU_VID_CLK_HDMI_TX_PIXEL, + VPU_VID_CLK_CTS_ENCP, + VPU_VID_CLK_CTS_ENCI, + VPU_VID_CLK_CTS_ENCT, + VPU_VID_CLK_CTS_ENCL, + VPU_VID_CLK_CTS_VDAC0, + VPU_VID_CLK_NUM +}; + +enum vpu_bulk_vid_pll_reset_id { + VPU_RESET_VID_PLL_PRE = 0, + VPU_RESET_VID_PLL_POST, + VPU_RESET_VID_PLL_SOFT_PRE, + VPU_RESET_VID_PLL_SOFT_POST, + VPU_RESET_VID_PLL_NUM +}; + struct meson_drm { struct device *dev; enum vpu_compatible compat; @@ -61,6 +87,21 @@ struct meson_drm { const struct meson_drm_soc_limits *limits; + struct phy *cvbs_dac; + bool cvbs_dac_enabled; + struct platform_device *cvbs_dac_pdev; + + struct clk_bulk_data intr_clks[3]; + unsigned int num_intr_clks; + bool intr_clks_enabled; + struct clk_bulk_data vid_clks[VPU_VID_CLK_NUM]; + bool vid_clk_rate_exclusive[VPU_VID_CLK_NUM]; + struct clk *clk_venc; + bool clk_venc_enabled; + struct clk *clk_dac; + bool clk_dac_enabled; + struct reset_control_bulk_data vid_pll_resets[VPU_RESET_VID_PLL_NUM]; + /* Components Data */ struct { bool osd1_enabled; diff --git a/drivers/gpu/drm/meson/meson_encoder_cvbs.c b/drivers/gpu/drm/meson/meson_encoder_cvbs.c index 3f73b211..833f701f 100644 --- a/drivers/gpu/drm/meson/meson_encoder_cvbs.c +++ b/drivers/gpu/drm/meson/meson_encoder_cvbs.c @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -24,12 +25,6 @@ #include "meson_vclk.h" #include "meson_encoder_cvbs.h" -/* HHI VDAC Registers */ -#define HHI_VDAC_CNTL0 0x2F4 /* 0xbd offset in data sheet */ -#define HHI_VDAC_CNTL0_G12A 0x2EC /* 0xbd offset in data sheet */ -#define HHI_VDAC_CNTL1 0x2F8 /* 0xbe offset in data sheet */ -#define HHI_VDAC_CNTL1_G12A 0x2F0 /* 0xbe offset in data sheet */ - struct meson_encoder_cvbs { struct drm_encoder encoder; struct drm_bridge bridge; @@ -87,11 +82,28 @@ static int meson_encoder_cvbs_attach(struct drm_bridge *bridge, { struct meson_encoder_cvbs *meson_encoder_cvbs = bridge_to_meson_encoder_cvbs(bridge); + int ret; + + ret = phy_init(meson_encoder_cvbs->priv->cvbs_dac); + if (ret) + return ret; return drm_bridge_attach(bridge->encoder, meson_encoder_cvbs->next_bridge, &meson_encoder_cvbs->bridge, flags); } +static void meson_encoder_cvbs_detach(struct drm_bridge *bridge) +{ + struct meson_encoder_cvbs *meson_encoder_cvbs = + bridge_to_meson_encoder_cvbs(bridge); + int ret; + + ret = phy_exit(meson_encoder_cvbs->priv->cvbs_dac); + if (ret) + dev_err(meson_encoder_cvbs->priv->dev, + "Failed to exit the CVBS DAC\n"); +} + static int meson_encoder_cvbs_get_modes(struct drm_bridge *bridge, struct drm_connector *connector) { @@ -148,6 +160,7 @@ static void meson_encoder_cvbs_atomic_enable(struct drm_bridge *bridge, struct drm_connector_state *conn_state; struct drm_crtc_state *crtc_state; struct drm_connector *connector; + int ret; connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); if (WARN_ON(!connector)) @@ -177,16 +190,13 @@ static void meson_encoder_cvbs_atomic_enable(struct drm_bridge *bridge, writel_bits_relaxed(VENC_VDAC_SEL_ATV_DMD, 0, priv->io_base + _REG(VENC_VDAC_DACSEL0)); - if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) { - regmap_write(priv->hhi, HHI_VDAC_CNTL0, 1); - regmap_write(priv->hhi, HHI_VDAC_CNTL1, 0); - } else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) || - meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL)) { - regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0xf0001); - regmap_write(priv->hhi, HHI_VDAC_CNTL1, 0); - } else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { - regmap_write(priv->hhi, HHI_VDAC_CNTL0_G12A, 0x906001); - regmap_write(priv->hhi, HHI_VDAC_CNTL1_G12A, 0); + if (!priv->cvbs_dac_enabled) { + ret = phy_power_on(priv->cvbs_dac); + if (ret) + dev_err(priv->dev, + "Failed to power on the CVBS DAC\n"); + else + priv->cvbs_dac_enabled = true; } } @@ -196,19 +206,22 @@ static void meson_encoder_cvbs_atomic_disable(struct drm_bridge *bridge, struct meson_encoder_cvbs *meson_encoder_cvbs = bridge_to_meson_encoder_cvbs(bridge); struct meson_drm *priv = meson_encoder_cvbs->priv; + int ret; - /* Disable CVBS VDAC */ - if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { - regmap_write(priv->hhi, HHI_VDAC_CNTL0_G12A, 0); - regmap_write(priv->hhi, HHI_VDAC_CNTL1_G12A, 0); - } else { - regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0); - regmap_write(priv->hhi, HHI_VDAC_CNTL1, 8); - } + if (!priv->cvbs_dac_enabled) + return; + + ret = phy_power_off(priv->cvbs_dac); + if (ret) + dev_err(priv->dev, + "Failed to power off the CVBS DAC\n"); + else + priv->cvbs_dac_enabled = false; } static const struct drm_bridge_funcs meson_encoder_cvbs_bridge_funcs = { .attach = meson_encoder_cvbs_attach, + .detach = meson_encoder_cvbs_detach, .mode_valid = meson_encoder_cvbs_mode_valid, .get_modes = meson_encoder_cvbs_get_modes, .atomic_enable = meson_encoder_cvbs_atomic_enable, diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index 53231bfd..f950c557 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -188,13 +188,13 @@ static void meson_encoder_hdmi_atomic_enable(struct drm_bridge *bridge, { struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); struct drm_atomic_state *state = bridge_state->base.state; - unsigned int ycrcb_map = VPU_HDMI_OUTPUT_CBYCR; struct meson_drm *priv = encoder_hdmi->priv; struct drm_connector_state *conn_state; const struct drm_display_mode *mode; struct drm_crtc_state *crtc_state; struct drm_connector *connector; bool yuv420_mode = false; + unsigned int ycrcb_map; int vic; connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); @@ -215,7 +215,14 @@ static void meson_encoder_hdmi_atomic_enable(struct drm_bridge *bridge, dev_dbg(priv->dev, "\"%s\" vic %d\n", mode->name, vic); - if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) { + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) { + if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_RGB888_1X24) + ycrcb_map = VPU_HDMI_OUTPUT_YCBCR; + else + ycrcb_map = VPU_HDMI_OUTPUT_CRYCB; + } else if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) { ycrcb_map = VPU_HDMI_OUTPUT_CRYCB; yuv420_mode = true; } else if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYVY8_1X16) @@ -227,17 +234,22 @@ static void meson_encoder_hdmi_atomic_enable(struct drm_bridge *bridge, /* VCLK Set clock */ meson_encoder_hdmi_set_vclk(encoder_hdmi, mode); - if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) - /* Setup YUV420 to HDMI-TX, no 10bit diphering */ - writel_relaxed(2 | (2 << 2), - priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); - else if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYVY8_1X16) - /* Setup YUV422 to HDMI-TX, no 10bit diphering */ - writel_relaxed(1 | (2 << 2), - priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); - else - /* Setup YUV444 to HDMI-TX, no 10bit diphering */ - writel_relaxed(0, priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); + if (!meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) && + !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) && + !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) { + if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) + /* Setup YUV420 to HDMI-TX, no 10bit diphering */ + writel_relaxed(2 | (2 << 2), + priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); + else if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYVY8_1X16) + /* Setup YUV422 to HDMI-TX, no 10bit diphering */ + writel_relaxed(1 | (2 << 2), + priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); + else + /* Setup YUV444 to HDMI-TX, no 10bit diphering */ + writel_relaxed(0, + priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); + } dev_dbg(priv->dev, "%s\n", priv->venc.hdmi_use_enci ? "VENCI" : "VENCP"); @@ -260,7 +272,11 @@ static void meson_encoder_hdmi_atomic_disable(struct drm_bridge *bridge, writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN)); } -static const u32 meson_encoder_hdmi_out_bus_fmts[] = { +static const u32 meson8_encoder_hdmi_out_bus_fmts[] = { + MEDIA_BUS_FMT_YUV8_1X24, +}; + +static const u32 meson_gx_encoder_hdmi_out_bus_fmts[] = { MEDIA_BUS_FMT_YUV8_1X24, MEDIA_BUS_FMT_UYVY8_1X16, MEDIA_BUS_FMT_UYYVYY8_0_5X24, @@ -274,13 +290,27 @@ meson_encoder_hdmi_get_inp_bus_fmts(struct drm_bridge *bridge, u32 output_fmt, unsigned int *num_input_fmts) { + struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); + struct meson_drm *priv = encoder_hdmi->priv; + unsigned int num_out_bus_fmts; + const u32 *out_bus_fmts; u32 *input_fmts = NULL; int i; + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) { + num_out_bus_fmts = ARRAY_SIZE(meson8_encoder_hdmi_out_bus_fmts); + out_bus_fmts = meson8_encoder_hdmi_out_bus_fmts; + } else { + num_out_bus_fmts = ARRAY_SIZE(meson_gx_encoder_hdmi_out_bus_fmts); + out_bus_fmts = meson_gx_encoder_hdmi_out_bus_fmts; + } + *num_input_fmts = 0; - for (i = 0 ; i < ARRAY_SIZE(meson_encoder_hdmi_out_bus_fmts) ; ++i) { - if (output_fmt == meson_encoder_hdmi_out_bus_fmts[i]) { + for (i = 0 ; i < num_out_bus_fmts ; ++i) { + if (output_fmt == out_bus_fmts[i]) { *num_input_fmts = 1; input_fmts = kcalloc(*num_input_fmts, sizeof(*input_fmts), @@ -432,8 +462,11 @@ int meson_encoder_hdmi_init(struct meson_drm *priv) drm_connector_attach_max_bpc_property(meson_encoder_hdmi->connector, 8, 8); - /* Handle this here until handled by drm_bridge_connector_init() */ - meson_encoder_hdmi->connector->ycbcr_420_allowed = true; + if (!meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) && + !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) && + !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) + /* Handle this here until handled by drm_bridge_connector_init() */ + meson_encoder_hdmi->connector->ycbcr_420_allowed = true; pdev = of_find_device_by_node(remote); of_node_put(remote); diff --git a/drivers/gpu/drm/meson/meson_plane.c b/drivers/gpu/drm/meson/meson_plane.c index 815dfe30..27e39577 100644 --- a/drivers/gpu/drm/meson/meson_plane.c +++ b/drivers/gpu/drm/meson/meson_plane.c @@ -200,8 +200,11 @@ static void meson_plane_atomic_update(struct drm_plane *plane, priv->viu.osd1_ctrl_stat2 &= ~OSD_DPATH_MALI_AFBCD; } - /* On GXBB, Use the old non-HDR RGB2YUV converter */ - if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) + /* On GXBB and earlier, Use the old non-HDR RGB2YUV converter */ + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) priv->viu.osd1_blk0_cfg[0] |= OSD_OUTPUT_COLOR_RGB; if (priv->viu.osd1_afbcd && @@ -471,7 +474,20 @@ static const struct drm_plane_funcs meson_plane_funcs = { .format_mod_supported = meson_plane_format_mod_supported, }; -static const uint32_t supported_drm_formats[] = { +/* + * X components (for example in DRM_FORMAT_XRGB8888 and DRM_FORMAT_XBGR8888) + * are not supported because these older SoC's are lacking the OSD_REPLACE_EN + * bit to replace the X alpha component with a static value, leaving the alpha + * component in an undefined state. + */ +static const uint32_t supported_drm_formats_m8[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB565, +}; + +static const uint32_t supported_drm_formats_gx[] = { DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888, DRM_FORMAT_XRGB8888, @@ -533,6 +549,8 @@ int meson_plane_create(struct meson_drm *priv) { struct meson_plane *meson_plane; struct drm_plane *plane; + unsigned int num_drm_formats; + const uint32_t *drm_formats; const uint64_t *format_modifiers = format_modifiers_default; meson_plane = devm_kzalloc(priv->drm->dev, sizeof(*meson_plane), @@ -548,10 +566,19 @@ int meson_plane_create(struct meson_drm *priv) else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) format_modifiers = format_modifiers_afbc_g12a; + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) { + drm_formats = supported_drm_formats_m8; + num_drm_formats = ARRAY_SIZE(supported_drm_formats_m8); + } else { + drm_formats = supported_drm_formats_gx; + num_drm_formats = ARRAY_SIZE(supported_drm_formats_gx); + } + drm_universal_plane_init(priv->drm, plane, 0xFF, &meson_plane_funcs, - supported_drm_formats, - ARRAY_SIZE(supported_drm_formats), + drm_formats, num_drm_formats, format_modifiers, DRM_PLANE_TYPE_PRIMARY, "meson_primary_plane"); diff --git a/drivers/gpu/drm/meson/meson_transwitch_hdmi.c b/drivers/gpu/drm/meson/meson_transwitch_hdmi.c new file mode 100644 index 00000000..e88bdba7 --- /dev/null +++ b/drivers/gpu/drm/meson/meson_transwitch_hdmi.c @@ -0,0 +1,1579 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Martin Blumenstingl + * + * 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 "meson_transwitch_hdmi.h" + +#define HDMI_ADDR_PORT 0x0 +#define HDMI_DATA_PORT 0x4 +#define HDMI_CTRL_PORT 0x8 + #define HDMI_CTRL_PORT_APB3_ERR_EN BIT(15) + +struct meson_txc_hdmi { + struct device *dev; + + struct regmap *regmap; + + struct clk *pclk; + struct clk *sys_clk; + + struct phy *phy; + bool phy_is_on; + + struct mutex codec_mutex; + enum drm_connector_status last_connector_status; + hdmi_codec_plugged_cb codec_plugged_cb; + struct device *codec_dev; + + struct platform_device *hdmi_codec_pdev; + + struct drm_connector *current_connector; + + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + + bool sink_is_hdmi; +}; + +#define bridge_to_meson_txc_hdmi(x) container_of(x, struct meson_txc_hdmi, bridge) + +static const struct regmap_range meson_txc_hdmi_regmap_ranges[] = { + regmap_reg_range(0x0000, 0x07ff), + regmap_reg_range(0x8000, 0x800c), +}; + +static const struct regmap_access_table meson_txc_hdmi_regmap_access = { + .yes_ranges = meson_txc_hdmi_regmap_ranges, + .n_yes_ranges = ARRAY_SIZE(meson_txc_hdmi_regmap_ranges), +}; + +static int meson_txc_hdmi_reg_read(void *context, unsigned int addr, + unsigned int *data) +{ + void __iomem *base = context; + + writel(addr, base + HDMI_ADDR_PORT); + writel(addr, base + HDMI_ADDR_PORT); + + *data = readl(base + HDMI_DATA_PORT); + + return 0; +} + +static int meson_txc_hdmi_reg_write(void *context, unsigned int addr, + unsigned int data) +{ + void __iomem *base = context; + + writel(addr, base + HDMI_ADDR_PORT); + writel(addr, base + HDMI_ADDR_PORT); + + writel(data, base + HDMI_DATA_PORT); + + return 0; +} + +static const struct regmap_config meson_txc_hdmi_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + .reg_stride = 1, + .reg_read = meson_txc_hdmi_reg_read, + .reg_write = meson_txc_hdmi_reg_write, + .rd_table = &meson_txc_hdmi_regmap_access, + .wr_table = &meson_txc_hdmi_regmap_access, + .max_register = HDMI_OTHER_RX_PACKET_INTR_CLR, + .fast_io = true, +}; + +static void meson_txc_hdmi_write_infoframe(struct regmap *regmap, + unsigned int tx_pkt_reg, u8 *buf, + unsigned int len, bool enable) +{ + unsigned int i; + + /* Write the data bytes by starting at register offset 1 */ + for (i = HDMI_INFOFRAME_HEADER_SIZE; i < len; i++) + regmap_write(regmap, + tx_pkt_reg + i - HDMI_INFOFRAME_HEADER_SIZE + 1, + buf[i]); + + /* Zero all remaining data bytes */ + for (; i < 0x1c; i++) + regmap_write(regmap, tx_pkt_reg + i, 0x00); + + /* Write the header (which we skipped above) */ + regmap_write(regmap, tx_pkt_reg + 0x00, buf[3]); + regmap_write(regmap, tx_pkt_reg + 0x1c, buf[0]); + regmap_write(regmap, tx_pkt_reg + 0x1d, buf[1]); + regmap_write(regmap, tx_pkt_reg + 0x1e, buf[2]); + + regmap_write(regmap, tx_pkt_reg + 0x1f, enable ? 0xff : 0x00); +} + +static void meson_txc_hdmi_disable_infoframe(struct meson_txc_hdmi *priv, + unsigned int tx_pkt_reg) +{ + u8 buf[HDMI_INFOFRAME_HEADER_SIZE] = { 0 }; + + meson_txc_hdmi_write_infoframe(priv->regmap, tx_pkt_reg, buf, + HDMI_INFOFRAME_HEADER_SIZE, false); +} + +static void meson_txc_hdmi_sys5_reset_assert(struct meson_txc_hdmi *priv) +{ + /* A comment in the vendor driver says: bit5,6 is converted */ + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2, + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH3_RST_IN | + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH0_RST_IN); + usleep_range(10, 20); + + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2, + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN | + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN); + usleep_range(10, 20); + + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, + TX_SYS5_TX_SOFT_RESET_1_TX_PIXEL_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_MASTER_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_RESAMPLE_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_I2S_RESET_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH2 | + TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH1 | + TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH0); + usleep_range(10, 20); +} + +static void meson_txc_hdmi_sys5_reset_deassert(struct meson_txc_hdmi *priv) +{ + /* Release the resets except tmds_clk */ + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, + TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN); + usleep_range(10, 20); + + /* Release the tmds_clk reset as well */ + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, 0x0); + usleep_range(10, 20); + + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2, + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN | + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN | + TX_SYS5_TX_SOFT_RESET_2_HDMI_SR_RST); + usleep_range(10, 20); + + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2, + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN | + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN); + usleep_range(10, 20); +} + +static void meson_txc_hdmi_config_hdcp_registers(struct meson_txc_hdmi *priv) +{ + regmap_write(priv->regmap, TX_HDCP_CONFIG0, + FIELD_PREP(TX_HDCP_CONFIG0_ROM_ENCRYPT_OFF, 0x3)); + regmap_write(priv->regmap, TX_HDCP_MEM_CONFIG, 0x0); + regmap_write(priv->regmap, TX_HDCP_ENCRYPT_BYTE, 0x0); + + regmap_write(priv->regmap, TX_HDCP_MODE, TX_HDCP_MODE_CLEAR_AVMUTE); + + regmap_write(priv->regmap, TX_HDCP_MODE, TX_HDCP_MODE_ESS_CONFIG); +} + +static u8 meson_txc_hdmi_bus_fmt_to_color_depth(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYVY8_1X16: + /* 8 bit */ + return 0x0; + + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYVY10_1X20: + /* 10 bit */ + return 0x1; + + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_UYVY12_1X24: + /* 12 bit */ + return 0x2; + + case MEDIA_BUS_FMT_RGB161616_1X48: + case MEDIA_BUS_FMT_YUV16_1X48: + /* 16 bit */ + return 0x3; + + default: + /* unknown, default to 8 bit */ + return 0x0; + } +} + +static u8 meson_txc_hdmi_bus_fmt_to_color_format(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_YUV16_1X48: + /* Documented as YCbCr444 */ + return 0x1; + + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYVY12_1X24: + /* Documented as YCbCr422 */ + return 0x3; + + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_RGB161616_1X48: + default: + /* Documented as RGB444 */ + return 0x0; + } +} + +static void meson_txc_hdmi_config_color_space(struct meson_txc_hdmi *priv, + unsigned int input_bus_format, + unsigned int output_bus_format, + enum hdmi_quantization_range quant_range, + enum hdmi_colorimetry colorimetry) +{ + unsigned int regval; + + regmap_write(priv->regmap, TX_VIDEO_DTV_MODE, + FIELD_PREP(TX_VIDEO_DTV_MODE_COLOR_DEPTH, + meson_txc_hdmi_bus_fmt_to_color_depth(output_bus_format))); + + regmap_write(priv->regmap, TX_VIDEO_DTV_OPTION_L, + FIELD_PREP(TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_FORMAT, + meson_txc_hdmi_bus_fmt_to_color_format(output_bus_format)) | + FIELD_PREP(TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_FORMAT, + meson_txc_hdmi_bus_fmt_to_color_format(input_bus_format)) | + FIELD_PREP(TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_DEPTH, + meson_txc_hdmi_bus_fmt_to_color_depth(output_bus_format)) | + FIELD_PREP(TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_DEPTH, + meson_txc_hdmi_bus_fmt_to_color_depth(input_bus_format))); + + if (quant_range == HDMI_QUANTIZATION_RANGE_LIMITED) + regval = FIELD_PREP(TX_VIDEO_DTV_OPTION_H_OUTPUT_COLOR_RANGE, + TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_16_235) | + FIELD_PREP(TX_VIDEO_DTV_OPTION_H_INPUT_COLOR_RANGE, + TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_16_235); + else + regval = FIELD_PREP(TX_VIDEO_DTV_OPTION_H_OUTPUT_COLOR_RANGE, + TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_0_255) | + FIELD_PREP(TX_VIDEO_DTV_OPTION_H_INPUT_COLOR_RANGE, + TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_0_255); + + regmap_write(priv->regmap, TX_VIDEO_DTV_OPTION_H, regval); + + if (colorimetry == HDMI_COLORIMETRY_ITU_601) { + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B0, 0x2f); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B1, 0x1d); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R0, 0x8b); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R1, 0x4c); + + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB0, 0x18); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB1, 0x58); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR0, 0xd0); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR1, 0xb6); + } else { + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B0, 0x7b); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B1, 0x12); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R0, 0x6c); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R1, 0x36); + + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB0, 0xf2); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB1, 0x2f); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR0, 0xd4); + regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR1, 0x77); + } +} + +static void meson_txc_hdmi_config_serializer_clock(struct meson_txc_hdmi *priv, + enum hdmi_colorimetry colorimetry) +{ + /* Serializer Internal clock setting */ + if (colorimetry == HDMI_COLORIMETRY_ITU_601) + regmap_write(priv->regmap, TX_SYS1_PLL, 0x24); + else + regmap_write(priv->regmap, TX_SYS1_PLL, 0x22); + +#if 0 + // TODO: not ported yet + if ((param->VIC==HDMI_1080p60)&&(param->color_depth==COLOR_30BIT)&&(hdmi_rd_reg(0x018)==0x22)) { + regmap_write(priv->regmap, TX_SYS1_PLL, 0x12); + } +#endif +} + +static void meson_txc_hdmi_reconfig_packet_setting(struct meson_txc_hdmi *priv, + u8 cea_mode) +{ + u8 alloc_active2, alloc_eof1, alloc_sof1, alloc_sof2; + + regmap_write(priv->regmap, TX_PACKET_CONTROL_1, + FIELD_PREP(TX_PACKET_CONTROL_1_PACKET_START_LATENCY, 58)); + regmap_write(priv->regmap, TX_PACKET_CONTROL_2, + TX_PACKET_CONTROL_2_HORIZONTAL_GC_PACKET_TRANSPORT_EN); + + switch (cea_mode) { + case 31: + /* 1920x1080p50 */ + alloc_active2 = 0x12; + alloc_eof1 = 0x10; + alloc_sof1 = 0xb6; + alloc_sof2 = 0x11; + break; + case 93: + /* 3840x2160p24 */ + alloc_active2 = 0x12; + alloc_eof1 = 0x47; + alloc_sof1 = 0xf8; + alloc_sof2 = 0x52; + break; + case 94: + /* 3840x2160p25 */ + alloc_active2 = 0x12; + alloc_eof1 = 0x44; + alloc_sof1 = 0xda; + alloc_sof2 = 0x52; + break; + case 95: + /* 3840x2160p30 */ + alloc_active2 = 0x0f; + alloc_eof1 = 0x3a; + alloc_sof1 = 0x60; + alloc_sof2 = 0x52; + break; + case 98: + /* 4096x2160p24 */ + alloc_active2 = 0x12; + alloc_eof1 = 0x47; + alloc_sof1 = 0xf8; + alloc_sof2 = 0x52; + break; + default: + /* Disable the special packet settings only */ + regmap_write(priv->regmap, TX_PACKET_ALLOC_ACTIVE_1, 0x00); + return; + } + + /* + * The vendor driver says: manually configure these register to get + * stable video timings. + */ + regmap_write(priv->regmap, TX_PACKET_ALLOC_ACTIVE_1, 0x01); + regmap_write(priv->regmap, TX_PACKET_ALLOC_ACTIVE_2, alloc_active2); + regmap_write(priv->regmap, TX_PACKET_ALLOC_EOF_1, alloc_eof1); + regmap_write(priv->regmap, TX_PACKET_ALLOC_EOF_2, 0x12); + regmap_write(priv->regmap, TX_CORE_ALLOC_VSYNC_0, 0x01); + regmap_write(priv->regmap, TX_CORE_ALLOC_VSYNC_1, 0x00); + regmap_write(priv->regmap, TX_CORE_ALLOC_VSYNC_2, 0x0a); + regmap_write(priv->regmap, TX_PACKET_ALLOC_SOF_1, alloc_sof1); + regmap_write(priv->regmap, TX_PACKET_ALLOC_SOF_2, alloc_sof2); + regmap_update_bits(priv->regmap, TX_PACKET_CONTROL_1, + TX_PACKET_CONTROL_1_FORCE_PACKET_TIMING, + TX_PACKET_CONTROL_1_FORCE_PACKET_TIMING); +} + +static void meson_txc_hdmi_set_avi_infoframe(struct meson_txc_hdmi *priv, + struct drm_connector *conn, + const struct drm_display_mode *mode, + const struct drm_connector_state *conn_state, + unsigned int output_bus_format, + enum hdmi_quantization_range quant_range, + enum hdmi_colorimetry colorimetry) +{ + u8 buf[HDMI_INFOFRAME_SIZE(AVI)], *video_code; + struct hdmi_avi_infoframe frame; + int ret; + + ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, conn, mode); + if (ret < 0) { + drm_err(priv->bridge.dev, + "Failed to setup AVI infoframe: %d\n", ret); + return; + } + + switch (output_bus_format) { + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_YUV16_1X48: + frame.colorspace = HDMI_COLORSPACE_YUV444; + break; + + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYVY12_1X24: + frame.colorspace = HDMI_COLORSPACE_YUV422; + break; + + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_RGB161616_1X48: + default: + frame.colorspace = HDMI_COLORSPACE_RGB; + break; + } + + drm_hdmi_avi_infoframe_colorimetry(&frame, conn_state); + drm_hdmi_avi_infoframe_quant_range(&frame, conn, mode, quant_range); + drm_hdmi_avi_infoframe_bars(&frame, conn_state); + + ret = hdmi_avi_infoframe_pack(&frame, buf, sizeof(buf)); + if (ret < 0) { + drm_err(priv->bridge.dev, + "Failed to pack AVI infoframe: %d\n", ret); + return; + } + + video_code = &buf[HDMI_INFOFRAME_HEADER_SIZE + 3]; + if (*video_code > 108) { + regmap_write(priv->regmap, TX_PKT_REG_EXCEPT0_BASE_ADDR, + *video_code); + *video_code = 0x00; + } else { + regmap_write(priv->regmap, TX_PKT_REG_EXCEPT0_BASE_ADDR, + 0x00); + } + + meson_txc_hdmi_write_infoframe(priv->regmap, + TX_PKT_REG_AVI_INFO_BASE_ADDR, buf, + sizeof(buf), true); +} + +static void meson_txc_hdmi_set_vendor_infoframe(struct meson_txc_hdmi *priv, + struct drm_connector *conn, + const struct drm_display_mode *mode) +{ + u8 buf[HDMI_INFOFRAME_HEADER_SIZE + 6]; + struct hdmi_vendor_infoframe frame; + int ret; + + ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame, conn, mode); + if (ret) { + drm_dbg(priv->bridge.dev, + "Failed to setup vendor infoframe: %d\n", ret); + return; + } + + ret = hdmi_vendor_infoframe_pack(&frame, buf, sizeof(buf)); + if (ret < 0) { + drm_err(priv->bridge.dev, + "Failed to pack vendor infoframe: %d\n", ret); + return; + } + + meson_txc_hdmi_write_infoframe(priv->regmap, + TX_PKT_REG_VEND_INFO_BASE_ADDR, buf, + sizeof(buf), true); +} + +static void meson_txc_hdmi_set_spd_infoframe(struct meson_txc_hdmi *priv) +{ + u8 buf[HDMI_INFOFRAME_SIZE(SPD)]; + struct hdmi_spd_infoframe frame; + int ret; + + ret = hdmi_spd_infoframe_init(&frame, "Amlogic", "Meson TXC HDMI"); + if (ret < 0) { + drm_err(priv->bridge.dev, + "Failed to setup SPD infoframe: %d\n", ret); + return; + } + + ret = hdmi_spd_infoframe_pack(&frame, buf, sizeof(buf)); + if (ret < 0) { + drm_err(priv->bridge.dev, + "Failed to pack SDP infoframe: %d\n", ret); + return; + } + + meson_txc_hdmi_write_infoframe(priv->regmap, + TX_PKT_REG_SPD_INFO_BASE_ADDR, buf, + sizeof(buf), true); +} + +static void meson_txc_hdmi_handle_plugged_change(struct meson_txc_hdmi *priv) +{ + bool plugged; + + plugged = priv->last_connector_status == connector_status_connected; + + if (priv->codec_dev && priv->codec_plugged_cb) + priv->codec_plugged_cb(priv->codec_dev, plugged); +} + +static int meson_txc_hdmi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct meson_txc_hdmi *priv = bridge->driver_private; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + drm_err(bridge->dev, + "DRM_BRIDGE_ATTACH_NO_CONNECTOR flag is not set but needed\n"); + return -EINVAL; + } + + return drm_bridge_attach(bridge->encoder, priv->next_bridge, bridge, + flags); +} + +/* Can return a maximum of 11 possible output formats for a mode/connector */ +#define MAX_OUTPUT_SEL_FORMATS 11 + +static u32 * +meson_txc_hdmi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + unsigned int *num_output_fmts) +{ + struct drm_connector *conn = conn_state->connector; + struct drm_display_info *info = &conn->display_info; + u8 max_bpc = conn_state->max_requested_bpc; + unsigned int i = 0; + u32 *output_fmts; + + *num_output_fmts = 0; + + output_fmts = kcalloc(MAX_OUTPUT_SEL_FORMATS, sizeof(*output_fmts), + GFP_KERNEL); + if (!output_fmts) + return NULL; + + /* If we are the only bridge, avoid negotiating with ourselves */ + if (list_is_singular(&bridge->encoder->bridge_chain)) { + *num_output_fmts = 1; + output_fmts[0] = MEDIA_BUS_FMT_FIXED; + + return output_fmts; + } + + /* + * Order bus formats from 16bit to 8bit and from YUV422 to RGB + * if supported. In any case the default RGB888 format is added + */ + + if (max_bpc >= 16 && info->bpc == 16) { + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + } + + if (max_bpc >= 12 && info->bpc >= 12) { + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + } + + if (max_bpc >= 10 && info->bpc >= 10) { + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + } + + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + + /* Default 8bit RGB fallback */ + output_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + + *num_output_fmts = i; + + return output_fmts; +} + +/* Can return a maximum of 3 possible input formats for an output format */ +#define MAX_INPUT_SEL_FORMATS 3 + +static u32 * +meson_txc_hdmi_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + u32 *input_fmts; + unsigned int i = 0; + + *num_input_fmts = 0; + + input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + switch (output_fmt) { + /* If MEDIA_BUS_FMT_FIXED is tested, return default bus format */ + case MEDIA_BUS_FMT_FIXED: + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + + /* 8bit */ + case MEDIA_BUS_FMT_RGB888_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + break; + case MEDIA_BUS_FMT_YUV8_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + + /* 10bit */ + case MEDIA_BUS_FMT_RGB101010_1X30: + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + break; + + /* 12bit */ + case MEDIA_BUS_FMT_RGB121212_1X36: + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + break; + case MEDIA_BUS_FMT_YUV12_1X36: + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + break; + case MEDIA_BUS_FMT_UYVY12_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + break; + + /* 16bit */ + case MEDIA_BUS_FMT_RGB161616_1X48: + input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + break; + case MEDIA_BUS_FMT_YUV16_1X48: + input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + break; + } + + *num_input_fmts = i; + + if (*num_input_fmts == 0) { + kfree(input_fmts); + input_fmts = NULL; + } + + return input_fmts; +} + +static void meson_txc_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge); + struct drm_atomic_state *state = old_bridge_state->base.state; + enum hdmi_quantization_range quant_range; + struct drm_connector_state *conn_state; + struct drm_bridge_state *bridge_state; + const struct drm_display_mode *mode; + enum hdmi_colorimetry colorimetry; + struct drm_crtc_state *crtc_state; + struct drm_connector *connector; + unsigned int i; + u8 cea_mode; + + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); + + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + if (WARN_ON(!connector)) + return; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (WARN_ON(!conn_state)) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (WARN_ON(!crtc_state)) + return; + + priv->current_connector = connector; + + mode = &crtc_state->adjusted_mode; + + cea_mode = drm_match_cea_mode(mode); + + if (priv->sink_is_hdmi) { + quant_range = drm_default_rgb_quant_range(mode); + + switch (cea_mode) { + case 2 ... 3: + case 6 ... 7: + case 17 ... 18: + case 21 ... 22: + colorimetry = HDMI_COLORIMETRY_ITU_601; + break; + + default: + colorimetry = HDMI_COLORIMETRY_ITU_709; + break; + } + + meson_txc_hdmi_set_avi_infoframe(priv, connector, mode, + conn_state, + bridge_state->output_bus_cfg.format, + quant_range, colorimetry); + meson_txc_hdmi_set_vendor_infoframe(priv, connector, mode); + meson_txc_hdmi_set_spd_infoframe(priv); + } else { + quant_range = HDMI_QUANTIZATION_RANGE_FULL; + colorimetry = HDMI_COLORIMETRY_NONE; + } + + meson_txc_hdmi_sys5_reset_assert(priv); + + meson_txc_hdmi_config_hdcp_registers(priv); + + if (cea_mode == 39) + regmap_write(priv->regmap, TX_VIDEO_DTV_TIMING, 0x0); + else + regmap_write(priv->regmap, TX_VIDEO_DTV_TIMING, + TX_VIDEO_DTV_TIMING_DISABLE_VIC39_CORRECTION); + + regmap_write(priv->regmap, TX_CORE_DATA_CAPTURE_2, + TX_CORE_DATA_CAPTURE_2_INTERNAL_PACKET_ENABLE); + regmap_write(priv->regmap, TX_CORE_DATA_MONITOR_1, + TX_CORE_DATA_MONITOR_1_LANE0 | + FIELD_PREP(TX_CORE_DATA_MONITOR_1_SELECT_LANE0, 0x7)); + regmap_write(priv->regmap, TX_CORE_DATA_MONITOR_2, + FIELD_PREP(TX_CORE_DATA_MONITOR_2_MONITOR_SELECT, 0x2)); + + if (priv->sink_is_hdmi) + regmap_write(priv->regmap, TX_TMDS_MODE, + TX_TMDS_MODE_FORCED_HDMI | + TX_TMDS_MODE_HDMI_CONFIG); + else + regmap_write(priv->regmap, TX_TMDS_MODE, + TX_TMDS_MODE_FORCED_HDMI); + + regmap_write(priv->regmap, TX_SYS4_CONNECT_SEL_1, 0x0); + + /* + * Set tmds_clk pattern to be "0000011111" before being sent to AFE + * clock channel. + */ + regmap_write(priv->regmap, TX_SYS4_CK_INV_VIDEO, + TX_SYS4_CK_INV_VIDEO_TMDS_CLK_PATTERN); + + regmap_write(priv->regmap, TX_SYS5_FIFO_CONFIG, + TX_SYS5_FIFO_CONFIG_CLK_CHANNEL3_OUTPUT_ENABLE | + TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL2_ENABLE | + TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL1_ENABLE | + TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL0_ENABLE); + + meson_txc_hdmi_config_color_space(priv, + bridge_state->input_bus_cfg.format, + bridge_state->output_bus_cfg.format, + quant_range, colorimetry); + + meson_txc_hdmi_sys5_reset_deassert(priv); + + meson_txc_hdmi_config_serializer_clock(priv, colorimetry); + meson_txc_hdmi_reconfig_packet_setting(priv, cea_mode); + + /* all resets need to be applied twice */ + for (i = 0; i < 2; i++) { + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, + TX_SYS5_TX_SOFT_RESET_1_TX_PIXEL_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_MASTER_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_RESAMPLE_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_I2S_RESET_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH2 | + TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH1 | + TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH0); + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2, + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH3_RST_IN | + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN | + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN | + TX_SYS5_TX_SOFT_RESET_2_HDMI_CH0_RST_IN | + TX_SYS5_TX_SOFT_RESET_2_HDMI_SR_RST | + TX_SYS5_TX_SOFT_RESET_2_TX_DDC_HDCP_RSTN | + TX_SYS5_TX_SOFT_RESET_2_TX_DDC_EDID_RSTN | + TX_SYS5_TX_SOFT_RESET_2_TX_DIG_RESET_N_CH3); + usleep_range(5000, 10000); + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, 0x00); + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2, 0x00); + usleep_range(5000, 10000); + } + + if (!priv->phy_is_on) { + int ret; + + ret = phy_power_on(priv->phy); + if (ret) + drm_err(bridge->dev, "Failed to turn on PHY\n"); + else + priv->phy_is_on = true; + } +} + +static void meson_txc_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge); + + priv->current_connector = NULL; + + if (priv->phy_is_on) { + int ret; + + ret = phy_power_off(priv->phy); + if (ret) + drm_err(bridge->dev, "Failed to turn off PHY\n"); + else + priv->phy_is_on = false; + } + + meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_AUDIO_INFO_BASE_ADDR); + meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_AVI_INFO_BASE_ADDR); + meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_EXCEPT0_BASE_ADDR); + meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_VEND_INFO_BASE_ADDR); +} + +static enum drm_mode_status +meson_txc_hdmi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static enum drm_connector_status meson_txc_hdmi_bridge_detect(struct drm_bridge *bridge) +{ + struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge); + enum drm_connector_status status; + unsigned int val; + + regmap_read(priv->regmap, TX_HDCP_ST_EDID_STATUS, &val); + if (val & TX_HDCP_ST_EDID_STATUS_HPD_STATUS) + status = connector_status_connected; + else + status = connector_status_disconnected; + + mutex_lock(&priv->codec_mutex); + if (priv->last_connector_status != status) { + priv->last_connector_status = status; + meson_txc_hdmi_handle_plugged_change(priv); + } + mutex_unlock(&priv->codec_mutex); + + return status; +} + +static int meson_txc_hdmi_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + unsigned int i, regval, start = block * EDID_LENGTH; + struct meson_txc_hdmi *priv = data; + int ret; + + /* Start the DDC transaction */ + regmap_update_bits(priv->regmap, TX_HDCP_EDID_CONFIG, + TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG, 0); + regmap_update_bits(priv->regmap, TX_HDCP_EDID_CONFIG, + TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG, + TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG); + + ret = regmap_read_poll_timeout(priv->regmap, + TX_HDCP_ST_EDID_STATUS, + regval, + (regval & TX_HDCP_ST_EDID_STATUS_EDID_DATA_READY), + 1000, 200000); + + regmap_update_bits(priv->regmap, TX_HDCP_EDID_CONFIG, + TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG, 0); + + if (ret) + return ret; + + for (i = 0; i < len; i++) { + regmap_read(priv->regmap, TX_RX_EDID_OFFSET + start + i, + ®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_get_output_bus_fmts = meson_txc_hdmi_bridge_atomic_get_output_bus_fmts, + .atomic_get_input_bus_fmts = meson_txc_hdmi_bridge_atomic_get_input_bus_fmts, + .atomic_enable = meson_txc_hdmi_bridge_atomic_enable, + .atomic_disable = meson_txc_hdmi_bridge_atomic_disable, + .mode_valid = meson_txc_hdmi_bridge_mode_valid, + .detect = meson_txc_hdmi_bridge_detect, + .get_edid = meson_txc_hdmi_bridge_get_edid, +}; + +static int meson_txc_hdmi_parse_dt(struct meson_txc_hdmi *priv) +{ + struct device_node *endpoint, *remote; + + endpoint = of_graph_get_endpoint_by_regs(priv->dev->of_node, 1, -1); + if (!endpoint) { + dev_err(priv->dev, "Missing endpoint in port@1\n"); + return -ENODEV; + } + + remote = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!remote) { + dev_err(priv->dev, "Endpoint in port@1 unconnected\n"); + return -ENODEV; + } + + if (!of_device_is_available(remote)) { + dev_err(priv->dev, "port@1 remote device is disabled\n"); + of_node_put(remote); + return -ENODEV; + } + + priv->next_bridge = of_drm_find_bridge(remote); + of_node_put(remote); + if (!priv->next_bridge) + return -EPROBE_DEFER; + + return 0; +} + +static int meson_txc_hdmi_hw_init(struct meson_txc_hdmi *priv) +{ + unsigned long ddc_i2c_bus_clk_hz = 500 * 1000; + unsigned long sys_clk_hz = 24 * 1000 * 1000; + int ret; + + ret = phy_init(priv->phy); + if (ret) { + dev_err(priv->dev, "Failed to initialize the PHY: %d\n", ret); + return ret; + } + + ret = clk_set_rate(priv->sys_clk, sys_clk_hz); + if (ret) { + dev_err(priv->dev, "Failed to set HDMI system clock to 24MHz\n"); + goto err_phy_exit; + } + + ret = clk_prepare_enable(priv->sys_clk); + if (ret) { + dev_err(priv->dev, "Failed to enable the sys clk\n"); + goto err_phy_exit; + } + + regmap_update_bits(priv->regmap, HDMI_OTHER_CTRL1, + HDMI_OTHER_CTRL1_POWER_ON, + HDMI_OTHER_CTRL1_POWER_ON); + + regmap_write(priv->regmap, TX_HDMI_PHY_CONFIG0, + TX_HDMI_PHY_CONFIG0_HDMI_COMMON_B7_B0); + + regmap_write(priv->regmap, TX_HDCP_MODE, 0x40); + + /* + * The vendor driver comments that this is a setting for "Band-gap and + * main-bias". 0x1d = power-up, 0x00 = power-down. + */ + regmap_write(priv->regmap, TX_SYS1_AFE_TEST, 0x1d); + + meson_txc_hdmi_config_serializer_clock(priv, HDMI_COLORIMETRY_NONE); + + /* + * The vendor driver has a comment with the following information for + * the magic value: + * bit[2:0]=011: CK channel output TMDS CLOCK + * bit[2:0]=101, ck channel output PHYCLCK + */ + regmap_write(priv->regmap, TX_SYS1_AFE_CONNECT, 0xfb); + + /* Termination resistor calib value */ + regmap_write(priv->regmap, TX_CORE_CALIB_VALUE, 0x0f); + + /* HPD glitch filter */ + regmap_write(priv->regmap, TX_HDCP_HPD_FILTER_L, 0xa0); + regmap_write(priv->regmap, TX_HDCP_HPD_FILTER_H, 0xa0); + + /* Disable MEM power-down */ + regmap_write(priv->regmap, TX_MEM_PD_REG0, 0x0); + + regmap_write(priv->regmap, TX_HDCP_CONFIG3, + FIELD_PREP(TX_HDCP_CONFIG3_DDC_I2C_BUS_CLOCK_TIME_DIVIDER, + (sys_clk_hz / ddc_i2c_bus_clk_hz) - 1)); + + /* Enable software controlled DDC transaction */ + regmap_write(priv->regmap, TX_HDCP_EDID_CONFIG, + TX_HDCP_EDID_CONFIG_FORCED_MEM_COPY_DONE | + TX_HDCP_EDID_CONFIG_MEM_COPY_DONE_CONFIG); + regmap_write(priv->regmap, TX_CORE_EDID_CONFIG_MORE, + TX_CORE_EDID_CONFIG_MORE_SYS_TRIGGER_CONFIG_SEMI_MANU); + + /* mask (= disable) all interrupts */ + regmap_write(priv->regmap, HDMI_OTHER_INTR_MASKN, 0x0); + + /* clear any pending interrupt */ + regmap_write(priv->regmap, HDMI_OTHER_INTR_STAT_CLR, + HDMI_OTHER_INTR_STAT_CLR_EDID_RISING | + HDMI_OTHER_INTR_STAT_CLR_HPD_FALLING | + HDMI_OTHER_INTR_STAT_CLR_HPD_RISING); + + return 0; + +err_phy_exit: + phy_exit(priv->phy); + return 0; +} + +static void meson_txc_hdmi_hw_exit(struct meson_txc_hdmi *priv) +{ + int ret; + + /* mask (= disable) all interrupts */ + regmap_write(priv->regmap, HDMI_OTHER_INTR_MASKN, + HDMI_OTHER_INTR_MASKN_TX_EDID_INT_RISE | + HDMI_OTHER_INTR_MASKN_TX_HPD_INT_FALL | + HDMI_OTHER_INTR_MASKN_TX_HPD_INT_RISE); + + regmap_update_bits(priv->regmap, HDMI_OTHER_CTRL1, + HDMI_OTHER_CTRL1_POWER_ON, 0); + + clk_disable_unprepare(priv->sys_clk); + + ret = phy_exit(priv->phy); + if (ret) + dev_err(priv->dev, "Failed to exit the PHY: %d\n", ret); +} + +static u32 meson_txc_hdmi_hdmi_codec_calc_audio_n(struct hdmi_codec_params *hparms) +{ + u32 audio_n; + + if ((hparms->sample_rate % 44100) == 0) + audio_n = (128 * hparms->sample_rate) / 900; + else + audio_n = (128 * hparms->sample_rate) / 1000; + + if (hparms->cea.coding_type == HDMI_AUDIO_CODING_TYPE_EAC3 || + hparms->cea.coding_type == HDMI_AUDIO_CODING_TYPE_DTS_HD) + audio_n *= 4; + + return audio_n; +} + +static u8 meson_txc_hdmi_hdmi_codec_coding_type(struct hdmi_codec_params *hparms) +{ + switch (hparms->cea.coding_type) { + case HDMI_AUDIO_CODING_TYPE_MLP: + return TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_HBR_AUDIO_PACKET; + case HDMI_AUDIO_CODING_TYPE_DSD: + return TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_ONE_BIT_AUDIO; + case HDMI_AUDIO_CODING_TYPE_DST: + return TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_DST_AUDIO_PACKET; + default: + return TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_AUDIO_SAMPLE_PACKET; + } +} + +static int meson_txc_hdmi_hdmi_codec_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + u8 buf[HDMI_INFOFRAME_SIZE(AUDIO)]; + struct meson_txc_hdmi *priv = data; + u16 audio_tx_format; + u32 audio_n; + int len, i; + + if (hparms->cea.coding_type == HDMI_AUDIO_CODING_TYPE_MLP) { + /* + * TODO: fixed CTS is not supported yet, it needs special + * TX_SYS1_ACR_N_* settings + */ + return -EINVAL; + } + + switch (hparms->sample_width) { + case 16: + audio_tx_format = FIELD_PREP(TX_AUDIO_FORMAT_BIT_WIDTH_MASK, + TX_AUDIO_FORMAT_BIT_WIDTH_16); + break; + + case 20: + audio_tx_format = FIELD_PREP(TX_AUDIO_FORMAT_BIT_WIDTH_MASK, + TX_AUDIO_FORMAT_BIT_WIDTH_20); + break; + + case 24: + audio_tx_format = FIELD_PREP(TX_AUDIO_FORMAT_BIT_WIDTH_MASK, + TX_AUDIO_FORMAT_BIT_WIDTH_24); + break; + + default: + return -EINVAL; + } + + switch (fmt->fmt) { + case HDMI_I2S: + regmap_update_bits(priv->regmap, HDMI_OTHER_CTRL1, + HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON, + HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON); + + audio_tx_format |= TX_AUDIO_FORMAT_SPDIF_OR_I2S | + TX_AUDIO_FORMAT_I2S_ONE_BIT_OR_I2S | + FIELD_PREP(TX_AUDIO_FORMAT_I2S_FORMAT, 0x2); + + if (hparms->channels > 2) + audio_tx_format |= TX_AUDIO_FORMAT_I2S_2_OR_8_CH; + + regmap_write(priv->regmap, TX_AUDIO_FORMAT, + audio_tx_format); + + regmap_write(priv->regmap, TX_AUDIO_I2S, TX_AUDIO_I2S_ENABLE); + regmap_write(priv->regmap, TX_AUDIO_SPDIF, 0x0); + break; + + case HDMI_SPDIF: + regmap_update_bits(priv->regmap, HDMI_OTHER_CTRL1, + HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON, 0x0); + + if (hparms->cea.coding_type == HDMI_AUDIO_CODING_TYPE_STREAM) + audio_tx_format |= TX_AUDIO_FORMAT_SPDIF_CHANNEL_STATUS_FROM_DATA_OR_REG; + + regmap_write(priv->regmap, TX_AUDIO_FORMAT, + audio_tx_format); + + regmap_write(priv->regmap, TX_AUDIO_I2S, 0x0); + regmap_write(priv->regmap, TX_AUDIO_SPDIF, TX_AUDIO_SPDIF_ENABLE); + break; + + default: + return -EINVAL; + } + + if (hparms->channels > 2) + regmap_write(priv->regmap, TX_AUDIO_HEADER, + TX_AUDIO_HEADER_AUDIO_SAMPLE_PACKET_HEADER_LAYOUT1); + else + regmap_write(priv->regmap, TX_AUDIO_HEADER, 0x0); + + regmap_write(priv->regmap, TX_AUDIO_SAMPLE, + FIELD_PREP(TX_AUDIO_SAMPLE_CHANNEL_VALID, + BIT(hparms->channels) - 1)); + + audio_n = meson_txc_hdmi_hdmi_codec_calc_audio_n(hparms); + + regmap_write(priv->regmap, TX_SYS1_ACR_N_0, + FIELD_PREP(TX_SYS1_ACR_N_0_N_BYTE0, + (audio_n >> 0) & 0xff)); + regmap_write(priv->regmap, TX_SYS1_ACR_N_1, + FIELD_PREP(TX_SYS1_ACR_N_1_N_BYTE1, + (audio_n >> 8) & 0xff)); + regmap_update_bits(priv->regmap, TX_SYS1_ACR_N_2, + TX_SYS1_ACR_N_2_N_UPPER_NIBBLE, + FIELD_PREP(TX_SYS1_ACR_N_2_N_UPPER_NIBBLE, + (audio_n >> 16) & 0xf)); + + regmap_write(priv->regmap, TX_SYS0_ACR_CTS_0, 0x0); + regmap_write(priv->regmap, TX_SYS0_ACR_CTS_1, 0x0); + regmap_write(priv->regmap, TX_SYS0_ACR_CTS_2, + TX_SYS0_ACR_CTS_2_FORCE_ARC_STABLE); + + regmap_write(priv->regmap, TX_AUDIO_CONTROL, + TX_AUDIO_CONTROL_AUTO_AUDIO_FIFO_CLEAR | + FIELD_PREP(TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_MASK, + meson_txc_hdmi_hdmi_codec_coding_type(hparms)) | + TX_AUDIO_CONTROL_AUDIO_SAMPLE_PACKET_FLAT); + + len = hdmi_audio_infoframe_pack(&hparms->cea, buf, sizeof(buf)); + if (len < 0) + return len; + + meson_txc_hdmi_write_infoframe(priv->regmap, + TX_PKT_REG_AUDIO_INFO_BASE_ADDR, + buf, len, true); + + for (i = 0; i < ARRAY_SIZE(hparms->iec.status); i++) { + unsigned char sub1, sub2; + + sub1 = sub2 = hparms->iec.status[i]; + + if (i == 2) { + sub1 |= FIELD_PREP(IEC958_AES2_CON_CHANNEL, 1); + sub2 |= FIELD_PREP(IEC958_AES2_CON_CHANNEL, 2); + } + + regmap_write(priv->regmap, TX_IEC60958_SUB1_OFFSET + i, sub1); + regmap_write(priv->regmap, TX_IEC60958_SUB2_OFFSET + i, sub2); + } + + return 0; +} + +static int meson_txc_hdmi_hdmi_codec_audio_startup(struct device *dev, + void *data) +{ + struct meson_txc_hdmi *priv = data; + + regmap_update_bits(priv->regmap, TX_PACKET_CONTROL_2, + TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE, 0x0); + + /* reset audio master and sample */ + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, + TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_RESAMPLE_RSTN | + TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_MASTER_RSTN); + regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, 0x0); + + regmap_write(priv->regmap, TX_AUDIO_CONTROL_MORE, + TX_AUDIO_CONTROL_MORE_ENABLE); + + regmap_write(priv->regmap, TX_AUDIO_FIFO, + FIELD_PREP(TX_AUDIO_FIFO_FIFO_DEPTH_MASK, + TX_AUDIO_FIFO_FIFO_DEPTH_512) | + FIELD_PREP(TX_AUDIO_FIFO_CRITICAL_THRESHOLD_MASK, + TX_AUDIO_FIFO_CRITICAL_THRESHOLD_DEPTH_DIV16) | + FIELD_PREP(TX_AUDIO_FIFO_NORMAL_THRESHOLD_MASK, + TX_AUDIO_FIFO_NORMAL_THRESHOLD_DEPTH_DIV8)); + + regmap_write(priv->regmap, TX_AUDIO_LIPSYNC, 0x0); + + regmap_write(priv->regmap, TX_SYS1_ACR_N_2, + FIELD_PREP(TX_SYS1_ACR_N_2_N_MEAS_TOLERANCE, 0x3)); + + return 0; +} + +static void meson_txc_hdmi_hdmi_codec_audio_shutdown(struct device *dev, + void *data) +{ + struct meson_txc_hdmi *priv = data; + + meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_AUDIO_INFO_BASE_ADDR); + + regmap_write(priv->regmap, TX_AUDIO_CONTROL_MORE, 0x0); + regmap_update_bits(priv->regmap, HDMI_OTHER_CTRL1, + HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON, 0x0); + + regmap_update_bits(priv->regmap, TX_PACKET_CONTROL_2, + TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE, + TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE); +} + +static int meson_txc_hdmi_hdmi_codec_mute_stream(struct device *dev, + void *data, + bool enable, int direction) +{ + struct meson_txc_hdmi *priv = data; + + regmap_write(priv->regmap, TX_AUDIO_PACK, + enable ? 0 : TX_AUDIO_PACK_AUDIO_SAMPLE_PACKETS_ENABLE); + + return 0; +} + +static int meson_txc_hdmi_hdmi_codec_get_eld(struct device *dev, void *data, + uint8_t *buf, size_t len) +{ + struct meson_txc_hdmi *priv = data; + + if (priv->current_connector) + memcpy(buf, priv->current_connector->eld, + min_t(size_t, MAX_ELD_BYTES, len)); + else + memset(buf, 0, len); + + return 0; +} + +static int meson_txc_hdmi_hdmi_codec_get_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + struct of_endpoint of_ep; + int ret; + + ret = of_graph_parse_endpoint(endpoint, &of_ep); + if (ret < 0) + return ret; + + /* + * HDMI sound should be located as reg = <2> + * Then, it is sound port 0 + */ + if (of_ep.port == 2) + return 0; + + return -EINVAL; +} + +static int meson_txc_hdmi_hdmi_codec_hook_plugged_cb(struct device *dev, + void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + struct meson_txc_hdmi *priv = data; + + mutex_lock(&priv->codec_mutex); + priv->codec_plugged_cb = fn; + priv->codec_dev = codec_dev; + meson_txc_hdmi_handle_plugged_change(priv); + mutex_unlock(&priv->codec_mutex); + + return 0; +} + +static struct hdmi_codec_ops meson_txc_hdmi_hdmi_codec_ops = { + .hw_params = meson_txc_hdmi_hdmi_codec_hw_params, + .audio_startup = meson_txc_hdmi_hdmi_codec_audio_startup, + .audio_shutdown = meson_txc_hdmi_hdmi_codec_audio_shutdown, + .mute_stream = meson_txc_hdmi_hdmi_codec_mute_stream, + .get_eld = meson_txc_hdmi_hdmi_codec_get_eld, + .get_dai_id = meson_txc_hdmi_hdmi_codec_get_dai_id, + .hook_plugged_cb = meson_txc_hdmi_hdmi_codec_hook_plugged_cb, +}; + +static int meson_txc_hdmi_hdmi_codec_init(struct meson_txc_hdmi *priv) +{ + struct hdmi_codec_pdata pdata = { + .ops = &meson_txc_hdmi_hdmi_codec_ops, + .i2s = 1, + .spdif = 1, + .max_i2s_channels = 8, + .data = priv, + }; + + priv->hdmi_codec_pdev = platform_device_register_data(priv->dev, + HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, + &pdata, sizeof(pdata)); + return PTR_ERR_OR_ZERO(priv->hdmi_codec_pdev); +} + +static int meson_txc_hdmi_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct meson_txc_hdmi *priv; + void __iomem *base; + u32 regval; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + mutex_init(&priv->codec_mutex); + + dev_set_drvdata(dev, priv); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init(dev, NULL, base, + &meson_txc_hdmi_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(priv->pclk)) { + ret = PTR_ERR(priv->pclk); + return dev_err_probe(dev, ret, "Failed to get the pclk\n"); + } + + priv->sys_clk = devm_clk_get(dev, "sys"); + if (IS_ERR(priv->sys_clk)) { + ret = PTR_ERR(priv->sys_clk); + return dev_err_probe(dev, ret, + "Failed to get the sys clock\n"); + } + + priv->phy = devm_phy_get(dev, "hdmi"); + if (IS_ERR(priv->phy)) { + ret = PTR_ERR(priv->phy); + return dev_err_probe(dev, ret, "Failed to get the HDMI PHY\n"); + } + + ret = meson_txc_hdmi_parse_dt(priv); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->pclk); + if (ret) { + dev_err_probe(dev, ret, "Failed to enable the pclk\n"); + return ret; + } + + regval = readl(base + HDMI_CTRL_PORT); + regval |= HDMI_CTRL_PORT_APB3_ERR_EN; + writel(regval, base + HDMI_CTRL_PORT); + + ret = meson_txc_hdmi_hw_init(priv); + if (ret) + goto err_disable_clk; + + ret = meson_txc_hdmi_hdmi_codec_init(priv); + if (ret) + goto err_hw_exit; + + priv->bridge.driver_private = priv; + priv->bridge.funcs = &meson_txc_hdmi_bridge_funcs; + priv->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID; + priv->bridge.of_node = dev->of_node; + priv->bridge.interlace_allowed = true; + + drm_bridge_add(&priv->bridge); + + return 0; + +err_hw_exit: + meson_txc_hdmi_hw_exit(priv); +err_disable_clk: + clk_disable_unprepare(priv->pclk); + return ret; +} + +static void meson_txc_hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct meson_txc_hdmi *priv = dev_get_drvdata(dev); + + platform_device_unregister(priv->hdmi_codec_pdev); + + drm_bridge_remove(&priv->bridge); + + meson_txc_hdmi_hw_exit(priv); + + clk_disable_unprepare(priv->pclk); +} + +static const struct component_ops meson_txc_hdmi_component_ops = { + .bind = meson_txc_hdmi_bind, + .unbind = meson_txc_hdmi_unbind, +}; + +static int meson_txc_hdmi_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &meson_txc_hdmi_component_ops); +} + +static int meson_txc_hdmi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &meson_txc_hdmi_component_ops); + + return 0; +} + +static const struct of_device_id meson_txc_hdmi_of_table[] = { + { .compatible = "amlogic,meson8-hdmi-tx" }, + { .compatible = "amlogic,meson8b-hdmi-tx" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, meson_txc_hdmi_of_table); + +static struct platform_driver meson_txc_hdmi_platform_driver = { + .probe = meson_txc_hdmi_probe, + .remove = meson_txc_hdmi_remove, + .driver = { + .name = "meson-transwitch-hdmi", + .of_match_table = meson_txc_hdmi_of_table, + }, +}; +module_platform_driver(meson_txc_hdmi_platform_driver); + +MODULE_AUTHOR("Martin Blumenstingl "); +MODULE_DESCRIPTION("Amlogic Meson8 and Meson8b TranSwitch HDMI 1.4 TX driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/meson/meson_transwitch_hdmi.h b/drivers/gpu/drm/meson/meson_transwitch_hdmi.h new file mode 100644 index 00000000..14929475 --- /dev/null +++ b/drivers/gpu/drm/meson/meson_transwitch_hdmi.h @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Martin Blumenstingl + * + * 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 2a82119e..a2c1bf1a 100644 --- a/drivers/gpu/drm/meson/meson_vclk.c +++ b/drivers/gpu/drm/meson/meson_vclk.c @@ -732,6 +732,11 @@ meson_vclk_dmt_supported_freq(struct meson_drm *priv, unsigned int freq) return MODE_CLOCK_HIGH; } + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) + return MODE_OK; + if (meson_hdmi_pll_find_params(priv, freq, &m, &frac, &od)) return MODE_OK; @@ -784,6 +789,11 @@ meson_vclk_vic_supported_freq(struct meson_drm *priv, unsigned int phy_freq, return MODE_CLOCK_HIGH; } + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) + return MODE_OK; + for (i = 0 ; params[i].pixel_freq ; ++i) { DRM_DEBUG_DRIVER("i = %d pixel_freq = %d alt = %d\n", i, params[i].pixel_freq, @@ -1024,6 +1034,128 @@ static void meson_vclk_set(struct meson_drm *priv, unsigned int pll_base_freq, regmap_update_bits(priv->hhi, HHI_VID_CLK_CNTL, VCLK_EN, VCLK_EN); } +static int meson_vclk_set_rate_exclusive(struct meson_drm *priv, + enum vpu_bulk_clk_id clk_id, + unsigned int rate_khz) +{ + struct clk *clk = priv->vid_clks[clk_id].clk; + int ret; + + ret = clk_set_rate_exclusive(clk, rate_khz * 1000UL); + if (ret) + return ret; + + priv->vid_clk_rate_exclusive[clk_id] = true; + + return 0; +} + +static void meson_vclk_disable_ccf(struct meson_drm *priv) +{ + unsigned int i; + + /* allow all clocks to be changed in _enable again */ + for (i = 0; i < VPU_VID_CLK_NUM; i++) { + if (!priv->vid_clk_rate_exclusive[i]) + continue; + + clk_rate_exclusive_put(priv->vid_clks[i].clk); + priv->vid_clk_rate_exclusive[i] = false; + } + + if (priv->clk_dac_enabled) { + clk_disable(priv->clk_dac); + priv->clk_dac_enabled = false; + } + + if (priv->clk_venc_enabled) { + clk_disable(priv->clk_venc); + priv->clk_venc_enabled = false; + } +} + +static int meson_vclk_enable_ccf(struct meson_drm *priv, unsigned int target, + bool hdmi_use_enci, unsigned int phy_freq, + unsigned int dac_freq, unsigned int venc_freq) +{ + enum vpu_bulk_clk_id venc_clk_id, dac_clk_id; + int ret; + + if (target == MESON_VCLK_TARGET_CVBS || hdmi_use_enci) + venc_clk_id = VPU_VID_CLK_CTS_ENCI; + else + venc_clk_id = VPU_VID_CLK_CTS_ENCP; + + if (target == MESON_VCLK_TARGET_CVBS) + dac_clk_id = VPU_VID_CLK_CTS_VDAC0; + else + dac_clk_id = VPU_VID_CLK_HDMI_TX_PIXEL; + + /* + * The TMDS clock also updates the PLL. Protect the PLL rate so all + * following clocks are derived from the PLL setting which matches the + * TMDS clock. + */ + ret = meson_vclk_set_rate_exclusive(priv, VPU_VID_CLK_TMDS, phy_freq); + if (ret) { + dev_err(priv->dev, "Failed to set TMDS clock to %ukHz: %d\n", + phy_freq, ret); + goto out_enable_clocks; + } + + /* + * The DAC clock may be derived from a parent of the VENC clock so we + * must protect the VENC clock from changing it's rate. This works + * because the DAC freq can be divided by the VENC clock. + */ + ret = meson_vclk_set_rate_exclusive(priv, venc_clk_id, venc_freq); + if (ret) { + dev_warn(priv->dev, + "Failed to set VENC clock to %ukHz while TMDS clock is %ukHz: %d\n", + venc_freq, phy_freq, ret); + goto out_enable_clocks; + } + + priv->clk_venc = priv->vid_clks[venc_clk_id].clk; + + /* + * after changing any of the VID_PLL_* clocks (which can happen when + * update the VENC clock rate) we need to assert and then de-assert the + * VID_DIVIDER_CNTL_* reset lines. + */ + reset_control_bulk_assert(VPU_RESET_VID_PLL_NUM, priv->vid_pll_resets); + reset_control_bulk_deassert(VPU_RESET_VID_PLL_NUM, priv->vid_pll_resets); + + ret = meson_vclk_set_rate_exclusive(priv, dac_clk_id, dac_freq); + if (ret) { + dev_warn(priv->dev, + "Failed to set pixel clock to %ukHz while TMDS clock is %ukHz: %d\n", + dac_freq, phy_freq, ret); + goto out_enable_clocks; + } + + priv->clk_dac = priv->vid_clks[dac_clk_id].clk; + +out_enable_clocks: + ret = clk_enable(priv->clk_venc); + if (ret) + dev_err(priv->dev, + "Failed to re-enable the VENC clock at %ukHz: %d\n", + venc_freq, ret); + else + priv->clk_venc_enabled = true; + + ret = clk_enable(priv->clk_dac); + if (ret) + dev_err(priv->dev, + "Failed to re-enable the pixel clock at %ukHz: %d\n", + dac_freq, ret); + else + priv->clk_dac_enabled = true; + + return ret; +} + void meson_vclk_setup(struct meson_drm *priv, unsigned int target, unsigned int phy_freq, unsigned int vclk_freq, unsigned int venc_freq, unsigned int dac_freq, @@ -1034,6 +1166,20 @@ void meson_vclk_setup(struct meson_drm *priv, unsigned int target, unsigned int hdmi_tx_div; unsigned int venc_div; + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) || + meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) { + /* CVBS video clocks are generated off a 1296MHz base clock */ + if (target == MESON_VCLK_TARGET_CVBS) + phy_freq = 1296000; + + dev_err(priv->dev, "%s(target: %u, phy: %u, dac: %u, venc: %u, hdmi_use_enci: %u)\n", __func__, target, phy_freq, dac_freq, venc_freq, hdmi_use_enci); + meson_vclk_disable_ccf(priv); + meson_vclk_enable_ccf(priv, target, hdmi_use_enci, phy_freq, + dac_freq, venc_freq); + return; + } + if (target == MESON_VCLK_TARGET_CVBS) { meson_venci_cvbs_clock_config(priv); return; diff --git a/drivers/gpu/drm/meson/meson_venc.c b/drivers/gpu/drm/meson/meson_venc.c index fcd532db..b28d4890 100644 --- a/drivers/gpu/drm/meson/meson_venc.c +++ b/drivers/gpu/drm/meson/meson_venc.c @@ -60,10 +60,6 @@ /* HHI Registers */ #define HHI_GCLK_MPEG2 0x148 /* 0x52 offset in data sheet */ -#define HHI_VDAC_CNTL0 0x2F4 /* 0xbd offset in data sheet */ -#define HHI_VDAC_CNTL0_G12A 0x2EC /* 0xbb offset in data sheet */ -#define HHI_VDAC_CNTL1 0x2F8 /* 0xbe offset in data sheet */ -#define HHI_VDAC_CNTL1_G12A 0x2F0 /* 0xbc offset in data sheet */ #define HHI_HDMI_PHY_CNTL0 0x3a0 /* 0xe8 offset in data sheet */ struct meson_cvbs_enci_mode meson_cvbs_enci_pal = { @@ -1749,31 +1745,47 @@ void meson_venc_enable_vsync(struct meson_drm *priv) { writel_relaxed(VENC_INTCTRL_ENCI_LNRST_INT_EN, priv->io_base + _REG(VENC_INTCTRL)); - regmap_update_bits(priv->hhi, HHI_GCLK_MPEG2, BIT(25), BIT(25)); + + if (priv->intr_clks[0].clk) { + if (!priv->intr_clks_enabled) { + int ret; + + ret = clk_bulk_enable(priv->num_intr_clks, + priv->intr_clks); + if (ret) + dev_err(priv->dev, + "Failed to enable the interrupt clocks\n"); + else + priv->intr_clks_enabled = true; + } + } else { + regmap_update_bits(priv->hhi, HHI_GCLK_MPEG2, BIT(25), BIT(25)); + } } void meson_venc_disable_vsync(struct meson_drm *priv) { - regmap_update_bits(priv->hhi, HHI_GCLK_MPEG2, BIT(25), 0); + if (priv->intr_clks[0].clk) { + if (priv->intr_clks_enabled) { + clk_bulk_disable(priv->num_intr_clks, + priv->intr_clks); + priv->intr_clks_enabled = false; + } + } else { + regmap_update_bits(priv->hhi, HHI_GCLK_MPEG2, BIT(25), 0); + } + writel_relaxed(0, priv->io_base + _REG(VENC_INTCTRL)); } void meson_venc_init(struct meson_drm *priv) { - /* Disable CVBS VDAC */ - if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { - regmap_write(priv->hhi, HHI_VDAC_CNTL0_G12A, 0); - regmap_write(priv->hhi, HHI_VDAC_CNTL1_G12A, 8); - } else { - regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0); - regmap_write(priv->hhi, HHI_VDAC_CNTL1, 8); - } - /* Power Down Dacs */ writel_relaxed(0xff, priv->io_base + _REG(VENC_VDAC_SETTING)); /* Disable HDMI PHY */ - regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0); + if (priv->hhi) + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0); /* Disable HDMI */ writel_bits_relaxed(VPU_HDMI_ENCI_DATA_TO_HDMI | diff --git a/drivers/gpu/drm/meson/meson_viu.c b/drivers/gpu/drm/meson/meson_viu.c index cd399b0b..bdfa342c 100644 --- a/drivers/gpu/drm/meson/meson_viu.c +++ b/drivers/gpu/drm/meson/meson_viu.c @@ -448,13 +448,17 @@ void meson_viu_init(struct meson_drm *priv) writel_relaxed(reg, priv->io_base + _REG(VIU_OSD1_FIFO_CTRL_STAT)); writel_relaxed(reg, priv->io_base + _REG(VIU_OSD2_FIFO_CTRL_STAT)); - /* Set OSD alpha replace value */ - writel_bits_relaxed(0xff << OSD_REPLACE_SHIFT, - 0xff << OSD_REPLACE_SHIFT, - priv->io_base + _REG(VIU_OSD1_CTRL_STAT2)); - writel_bits_relaxed(0xff << OSD_REPLACE_SHIFT, - 0xff << OSD_REPLACE_SHIFT, - priv->io_base + _REG(VIU_OSD2_CTRL_STAT2)); + if (!meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) && + !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) && + !meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) { + /* Set OSD alpha replace value */ + writel_bits_relaxed(0xff << OSD_REPLACE_SHIFT, + 0xff << OSD_REPLACE_SHIFT, + priv->io_base + _REG(VIU_OSD1_CTRL_STAT2)); + writel_bits_relaxed(0xff << OSD_REPLACE_SHIFT, + 0xff << OSD_REPLACE_SHIFT, + priv->io_base + _REG(VIU_OSD2_CTRL_STAT2)); + } /* Disable VD1 AFBC */ /* di_mif0_en=0 mif0_to_vpp_en=0 di_mad_en=0 and afbc vd1 set=0*/ diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig index ce7ba3eb..671435b6 100644 --- a/drivers/phy/amlogic/Kconfig +++ b/drivers/phy/amlogic/Kconfig @@ -25,6 +25,16 @@ config PHY_MESON8B_USB2 Meson8b and GXBB SoCs. If unsure, say N. +config PHY_MESON_CVBS_DAC + tristate "Amlogic Meson CVBS DAC PHY driver" + depends on ARCH_MESON || COMPILE_TEST + depends on OF + select MFD_SYSCON + help + Enable this to support the CVBS DAC (PHY) found in Amlogic + Meson SoCs. + If unsure, say N. + config PHY_MESON_GXL_USB2 tristate "Meson GXL and GXM USB2 PHY drivers" default ARCH_MESON diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile index 91e3b979..f6c38f73 100644 --- a/drivers/phy/amlogic/Makefile +++ b/drivers/phy/amlogic/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_PHY_MESON8_HDMI_TX) += phy-meson8-hdmi-tx.o obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o +obj-$(CONFIG_PHY_MESON_CVBS_DAC) += phy-meson-cvbs-dac.o obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o obj-$(CONFIG_PHY_MESON_G12A_USB2) += phy-meson-g12a-usb2.o obj-$(CONFIG_PHY_MESON_G12A_USB3_PCIE) += phy-meson-g12a-usb3-pcie.o diff --git a/drivers/phy/amlogic/phy-meson-cvbs-dac.c b/drivers/phy/amlogic/phy-meson-cvbs-dac.c new file mode 100644 index 00000000..96549e63 --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-cvbs-dac.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 BayLibre, SAS + * Copyright (C) 2021 Martin Blumenstingl + */ + +#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 needs_cvbs_trimming_nvmem_cell; +}; + +struct phy_meson_cvbs_dac_priv { + struct regmap_field *regs[MESON_CVBS_DAC_NUM_REGS]; + const struct phy_meson_cvbs_dac_data *data; + u8 cdac_gsw_enable_val; +}; + +static const struct reg_field phy_meson8_cvbs_dac_reg_fields[] = { + [MESON_CDAC_CTRL_RESV1] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 0, 7), + [MESON_CDAC_CTRL_RESV2] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 8, 15), + [MESON_CDAC_VREF_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 16, 20), + [MESON_CDAC_RL_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 21, 23), + [MESON_CDAC_CLK_PHASE_SEL] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 24, 24), + [MESON_CDAC_DRIVER_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 25, 25), + [MESON_CDAC_EXT_VREF_EN] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 26, 26), + [MESON_CDAC_BIAS_C] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 27, 27), + [MESON_VDAC_CNTL0_RESERVED] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 28, 31), + [MESON_CDAC_GSW] = REG_FIELD(HHI_VDAC_CNTL1_MESON8, 0, 2), + [MESON_CDAC_PWD] = REG_FIELD(HHI_VDAC_CNTL1_MESON8, 3, 3), + [MESON_VDAC_CNTL1_RESERVED] = REG_FIELD(HHI_VDAC_CNTL1_MESON8, 4, 31), +}; + +static const struct reg_field phy_meson_g12a_cvbs_dac_reg_fields[] = { + [MESON_CDAC_CTRL_RESV1] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 0, 7), + [MESON_CDAC_CTRL_RESV2] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 8, 15), + [MESON_CDAC_VREF_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 16, 20), + [MESON_CDAC_RL_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 21, 23), + [MESON_CDAC_CLK_PHASE_SEL] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 24, 24), + [MESON_CDAC_DRIVER_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 25, 25), + [MESON_CDAC_EXT_VREF_EN] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 26, 26), + [MESON_CDAC_BIAS_C] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 27, 27), + [MESON_VDAC_CNTL0_RESERVED] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 28, 31), + [MESON_CDAC_GSW] = REG_FIELD(HHI_VDAC_CNTL1_G12A, 0, 2), + [MESON_CDAC_PWD] = REG_FIELD(HHI_VDAC_CNTL1_G12A, 3, 3), + [MESON_VDAC_CNTL1_RESERVED] = REG_FIELD(HHI_VDAC_CNTL1_G12A, 4, 31), +}; + +static const struct phy_meson_cvbs_dac_data phy_meson8_cvbs_dac_data = { + .reg_fields = phy_meson8_cvbs_dac_reg_fields, + .cdac_ctrl_resv2_enable_val = 0x0, + .cdac_vref_adj_enable_val = 0x0, + .cdac_rl_adj_enable_val = 0x0, + .disable_ignore_cdac_pwd = false, + .needs_cvbs_trimming_nvmem_cell = true, +}; + +static const struct phy_meson_cvbs_dac_data phy_meson_gxbb_cvbs_dac_data = { + .reg_fields = phy_meson8_cvbs_dac_reg_fields, + .cdac_ctrl_resv2_enable_val = 0x0, + .cdac_vref_adj_enable_val = 0x0, + .cdac_rl_adj_enable_val = 0x0, + .disable_ignore_cdac_pwd = false, + .needs_cvbs_trimming_nvmem_cell = false, +}; + +static const struct phy_meson_cvbs_dac_data phy_meson_gxl_cvbs_dac_data = { + .reg_fields = phy_meson8_cvbs_dac_reg_fields, + .cdac_ctrl_resv2_enable_val = 0x0, + .cdac_vref_adj_enable_val = 0xf, + .cdac_rl_adj_enable_val = 0x0, + .disable_ignore_cdac_pwd = false, + .needs_cvbs_trimming_nvmem_cell = false, +}; + +static const struct phy_meson_cvbs_dac_data phy_meson_g12a_cvbs_dac_data = { + .reg_fields = phy_meson_g12a_cvbs_dac_reg_fields, + .cdac_ctrl_resv2_enable_val = 0x60, + .cdac_vref_adj_enable_val = 0x10, + .cdac_rl_adj_enable_val = 0x4, + .disable_ignore_cdac_pwd = true, + .needs_cvbs_trimming_nvmem_cell = false, +}; + +static int phy_meson_cvbs_dac_power_on(struct phy *phy) +{ + struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy); + + regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV1], 0x1); + regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV2], + priv->data->cdac_ctrl_resv2_enable_val); + regmap_field_write(priv->regs[MESON_CDAC_VREF_ADJ], + priv->data->cdac_vref_adj_enable_val); + regmap_field_write(priv->regs[MESON_CDAC_RL_ADJ], + priv->data->cdac_rl_adj_enable_val); + regmap_field_write(priv->regs[MESON_CDAC_GSW], + priv->cdac_gsw_enable_val); + regmap_field_write(priv->regs[MESON_CDAC_PWD], 0x0); + + return 0; +} + +static int phy_meson_cvbs_dac_power_off(struct phy *phy) +{ + struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy); + + regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV1], 0x0); + regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV2], 0x0); + regmap_field_write(priv->regs[MESON_CDAC_VREF_ADJ], 0x0); + regmap_field_write(priv->regs[MESON_CDAC_RL_ADJ], 0x0); + regmap_field_write(priv->regs[MESON_CDAC_GSW], 0x0); + + if (priv->data->disable_ignore_cdac_pwd) + regmap_field_write(priv->regs[MESON_CDAC_PWD], 0x0); + else + regmap_field_write(priv->regs[MESON_CDAC_PWD], 0x1); + + return 0; +} + +static int phy_meson_cvbs_dac_init(struct phy *phy) +{ + struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy); + + regmap_field_write(priv->regs[MESON_CDAC_CLK_PHASE_SEL], 0x0); + regmap_field_write(priv->regs[MESON_CDAC_DRIVER_ADJ], 0x0); + regmap_field_write(priv->regs[MESON_CDAC_EXT_VREF_EN], 0x0); + regmap_field_write(priv->regs[MESON_CDAC_BIAS_C], 0x0); + regmap_field_write(priv->regs[MESON_VDAC_CNTL0_RESERVED], 0x0); + regmap_field_write(priv->regs[MESON_VDAC_CNTL1_RESERVED], 0x0); + + return phy_meson_cvbs_dac_power_off(phy); +} + +static const struct phy_ops phy_meson_cvbs_dac_ops = { + .init = phy_meson_cvbs_dac_init, + .power_on = phy_meson_cvbs_dac_power_on, + .power_off = phy_meson_cvbs_dac_power_off, + .owner = THIS_MODULE, +}; + +static u8 phy_meson_cvbs_trimming_version(u8 trimming1) +{ + if ((trimming1 & 0xf0) == 0xa0) + return 5; + else if ((trimming1 & 0xf0) == 0x40) + return 2; + else if ((trimming1 & 0xc0) == 0x80) + return 1; + else if ((trimming1 & 0xc0) == 0x00) + return 0; + else + return 0xff; +} + +static int phy_meson_cvbs_read_trimming(struct device *dev, + struct phy_meson_cvbs_dac_priv *priv) +{ + struct nvmem_cell *cell; + u8 *trimming; + size_t len; + + cell = devm_nvmem_cell_get(dev, "cvbs_trimming"); + if (IS_ERR(cell)) + return dev_err_probe(dev, PTR_ERR(cell), + "Failed to get the 'cvbs_trimming' nvmem-cell\n"); + + trimming = nvmem_cell_read(cell, &len); + if (IS_ERR(trimming)) { + /* + * TrustZone firmware may block access to the CVBS trimming + * data stored in the eFuse. On those devices the trimming data + * is stored in the u-boot environment. However, the known + * examples of trimming data in the u-boot environment are all + * zero. + */ + dev_dbg(dev, + "Failed to read the 'cvbs_trimming' nvmem-cell - falling back to a default value\n"); + priv->cdac_gsw_enable_val = 0x0; + return 0; + } + + if (len != 2) { + kfree(trimming); + return dev_err_probe(dev, -EINVAL, + "Read the 'cvbs_trimming' nvmem-cell with invalid length\n"); + } + + switch (phy_meson_cvbs_trimming_version(trimming[1])) { + case 1: + case 2: + case 5: + priv->cdac_gsw_enable_val = trimming[0] & 0x7; + break; + default: + priv->cdac_gsw_enable_val = 0x0; + break; + } + + kfree(trimming); + + return 0; +} + +static int phy_meson_cvbs_dac_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct phy_meson_cvbs_dac_priv *priv; + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct regmap *hhi; + struct phy *phy; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (np) { + priv->data = device_get_match_data(dev); + if (!priv->data) + return dev_err_probe(dev, -EINVAL, + "Could not find the OF match data\n"); + + hhi = syscon_node_to_regmap(np->parent); + if (IS_ERR(hhi)) + return dev_err_probe(dev, PTR_ERR(hhi), + "Failed to get the parent syscon\n"); + } else { + const struct platform_device_id *pdev_id; + + pdev_id = platform_get_device_id(pdev); + if (!pdev_id) + return dev_err_probe(dev, -EINVAL, + "Failed to find platform device ID\n"); + + priv->data = (void *)pdev_id->driver_data; + if (!priv->data) + return dev_err_probe(dev, -EINVAL, + "Could not find the platform driver data\n"); + + hhi = syscon_regmap_lookup_by_compatible("amlogic,meson-gx-hhi-sysctrl"); + if (IS_ERR(hhi)) + return dev_err_probe(dev, PTR_ERR(hhi), + "Failed to get the \"amlogic,meson-gx-hhi-sysctrl\" syscon\n"); + } + + if (priv->data->needs_cvbs_trimming_nvmem_cell) { + ret = phy_meson_cvbs_read_trimming(dev, priv); + if (ret) + return ret; + } + + ret = devm_regmap_field_bulk_alloc(dev, hhi, priv->regs, + priv->data->reg_fields, + MESON_CVBS_DAC_NUM_REGS); + if (ret) + return dev_err_probe(dev, ret, + "Failed to create regmap fields\n"); + + phy = devm_phy_create(dev, np, &phy_meson_cvbs_dac_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + + if (np) { + phy_provider = devm_of_phy_provider_register(dev, + of_phy_simple_xlate); + ret = PTR_ERR_OR_ZERO(phy_provider); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register PHY provider\n"); + } + + platform_set_drvdata(pdev, phy); + + return 0; +} + +static const struct of_device_id phy_meson_cvbs_dac_of_match[] = { + { + .compatible = "amlogic,meson8-cvbs-dac", + .data = &phy_meson8_cvbs_dac_data, + }, + { + .compatible = "amlogic,meson-gxbb-cvbs-dac", + .data = &phy_meson_gxbb_cvbs_dac_data, + }, + { + .compatible = "amlogic,meson-gxl-cvbs-dac", + .data = &phy_meson_gxl_cvbs_dac_data, + }, + { + .compatible = "amlogic,meson-g12a-cvbs-dac", + .data = &phy_meson_g12a_cvbs_dac_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, phy_meson_cvbs_dac_of_match); + +/* + * The platform_device_id table is used for backwards compatibility with old + * .dtbs which don't have a CVBS DAC node (where the VPU DRM driver registers + * this as a platform device. Support for additional SoCs should only be added + * to the of_device_id table above. + */ +static const struct platform_device_id phy_meson_cvbs_dac_device_ids[] = { + { + .name = "meson-gxbb-cvbs-dac", + .driver_data = (kernel_ulong_t)&phy_meson_gxbb_cvbs_dac_data, + }, + { + .name = "meson-gxl-cvbs-dac", + .driver_data = (kernel_ulong_t)&phy_meson_gxl_cvbs_dac_data, + }, + { + .name = "meson-g12a-cvbs-dac", + .driver_data = (kernel_ulong_t)&phy_meson_g12a_cvbs_dac_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, phy_meson_cvbs_dac_device_ids); + +static struct platform_driver phy_meson_cvbs_dac_driver = { + .driver = { + .name = "phy-meson-cvbs-dac", + .of_match_table = phy_meson_cvbs_dac_of_match, + }, + .id_table = phy_meson_cvbs_dac_device_ids, + .probe = phy_meson_cvbs_dac_probe, +}; +module_platform_driver(phy_meson_cvbs_dac_driver); + +MODULE_AUTHOR("Martin Blumenstingl "); +MODULE_DESCRIPTION("Amlogic Meson CVBS DAC driver"); +MODULE_LICENSE("GPL v2"); -- 2.34.1