4407 lines
137 KiB
Diff
4407 lines
137 KiB
Diff
meson8/meson8b/meson8m2: Support HDMI
|
|
|
|
The following codes are come from https://github.com/xdarklight/linux/commits/meson-mx-integration-5.18-20220516.
|
|
|
|
Special thank to Martin Blumenstingl.
|
|
|
|
---
|
|
.../bindings/display/amlogic,meson-vpu.yaml | 16 +
|
|
.../phy/amlogic,meson-cvbs-dac-phy.yaml | 81 +
|
|
arch/arm/boot/dts/meson.dtsi | 13 +
|
|
arch/arm/boot/dts/meson8.dtsi | 168 +-
|
|
arch/arm/boot/dts/meson8b.dtsi | 171 +-
|
|
arch/arm/boot/dts/meson8m2.dtsi | 4 +
|
|
drivers/gpu/drm/meson/Kconfig | 9 +
|
|
drivers/gpu/drm/meson/Makefile | 1 +
|
|
drivers/gpu/drm/meson/meson_drv.c | 315 +++-
|
|
drivers/gpu/drm/meson/meson_drv.h | 49 +-
|
|
drivers/gpu/drm/meson/meson_encoder_cvbs.c | 61 +-
|
|
drivers/gpu/drm/meson/meson_encoder_hdmi.c | 69 +-
|
|
drivers/gpu/drm/meson/meson_plane.c | 37 +-
|
|
drivers/gpu/drm/meson/meson_transwitch_hdmi.c | 1579 +++++++++++++++++
|
|
drivers/gpu/drm/meson/meson_transwitch_hdmi.h | 536 ++++++
|
|
drivers/gpu/drm/meson/meson_vclk.c | 146 ++
|
|
drivers/gpu/drm/meson/meson_venc.c | 44 +-
|
|
drivers/gpu/drm/meson/meson_viu.c | 18 +-
|
|
drivers/phy/amlogic/Kconfig | 10 +
|
|
drivers/phy/amlogic/Makefile | 1 +
|
|
drivers/phy/amlogic/phy-meson-cvbs-dac.c | 375 ++++
|
|
21 files changed, 3577 insertions(+), 126 deletions(-)
|
|
create mode 100644 Documentation/devicetree/bindings/phy/amlogic,meson-cvbs-dac-phy.yaml
|
|
create mode 100644 drivers/gpu/drm/meson/meson_transwitch_hdmi.c
|
|
create mode 100644 drivers/gpu/drm/meson/meson_transwitch_hdmi.h
|
|
create mode 100644 drivers/phy/amlogic/phy-meson-cvbs-dac.c
|
|
|
|
diff --git a/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml b/Documentation/devicetree/bindings/display/amlogic,meson-vpu.yaml
|
|
index 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 <martin.blumenstingl@googlemail.com>
|
|
+
|
|
+description: |+
|
|
+ The CVBS DAC node should be the child of a syscon node with the
|
|
+ required property:
|
|
+
|
|
+ compatible = "amlogic,meson-hhi-sysctrl", "simple-mfd", "syscon"
|
|
+
|
|
+ Refer to the bindings described in
|
|
+ Documentation/devicetree/bindings/mfd/syscon.yaml
|
|
+
|
|
+properties:
|
|
+ $nodename:
|
|
+ pattern: "^video-dac@[0-9a-f]+$"
|
|
+
|
|
+ compatible:
|
|
+ oneOf:
|
|
+ - items:
|
|
+ - enum:
|
|
+ - amlogic,meson8-cvbs-dac
|
|
+ - amlogic,meson-gxbb-cvbs-dac
|
|
+ - amlogic,meson-gxl-cvbs-dac
|
|
+ - amlogic,meson-g12a-cvbs-dac
|
|
+ - const: amlogic,meson-cvbs-dac
|
|
+ - const: amlogic,meson-cvbs-dac
|
|
+
|
|
+ reg:
|
|
+ maxItems: 1
|
|
+
|
|
+ clocks:
|
|
+ minItems: 1
|
|
+
|
|
+ nvmem-cells:
|
|
+ minItems: 1
|
|
+
|
|
+ nvmem-cell-names:
|
|
+ items:
|
|
+ - const: cvbs_trimming
|
|
+
|
|
+ "#phy-cells":
|
|
+ const: 0
|
|
+
|
|
+required:
|
|
+ - compatible
|
|
+ - reg
|
|
+ - clocks
|
|
+ - "#phy-cells"
|
|
+
|
|
+additionalProperties: false
|
|
+
|
|
+examples:
|
|
+ - |
|
|
+ video-dac@2f4 {
|
|
+ compatible = "amlogic,meson8-cvbs-dac", "amlogic,meson-cvbs-dac";
|
|
+ reg = <0x2f4 0x8>;
|
|
+
|
|
+ #phy-cells = <0>;
|
|
+
|
|
+ clocks = <&vdac_clock>;
|
|
+
|
|
+ nvmem-cells = <&cvbs_trimming>;
|
|
+ nvmem-cell-names = "cvbs_trimming";
|
|
+ };
|
|
+ - |
|
|
+ video-dac@2ec {
|
|
+ compatible = "amlogic,meson-g12a-cvbs-dac", "amlogic,meson-cvbs-dac";
|
|
+ reg = <0x2ec 0x8>;
|
|
+
|
|
+ #phy-cells = <0>;
|
|
+
|
|
+ clocks = <&vdac_clock>;
|
|
+ };
|
|
diff --git a/arch/arm/boot/dts/meson.dtsi b/arch/arm/boot/dts/meson.dtsi
|
|
index 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 = <GIC_SPI 57 IRQ_TYPE_EDGE_RISING>;
|
|
+ phys = <&hdmi_tx_phy>;
|
|
+ phy-names = "hdmi";
|
|
+ clocks = <&clkc CLKID_HDMI_PCLK>,
|
|
+ <&clkc CLKID_HDMI_SYS>;
|
|
+ clock-names = "pclk", "sys";
|
|
+
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ #sound-dai-cells = <1>;
|
|
+ sound-name-prefix = "HDMITX";
|
|
+
|
|
+ status = "disabled";
|
|
+
|
|
+ /* VPU VENC Input */
|
|
+ hdmi_tx_venc_port: port@0 {
|
|
+ reg = <0>;
|
|
+
|
|
+ hdmi_tx_in: endpoint {
|
|
+ remote-endpoint = <&hdmi_tx_out>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* TMDS Output */
|
|
+ hdmi_tx_tmds_port: port@1 {
|
|
+ reg = <1>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ vpu: vpu@100000 {
|
|
+ compatible = "amlogic,meson8-vpu";
|
|
+
|
|
+ reg = <0x100000 0x10000>;
|
|
+ reg-names = "vpu";
|
|
+
|
|
+ interrupts = <GIC_SPI 3 IRQ_TYPE_EDGE_RISING>;
|
|
+
|
|
+ amlogic,canvas = <&canvas>;
|
|
+
|
|
+ /*
|
|
+ * The VCLK{,2}_IN path always needs to derived from
|
|
+ * the CLKID_VID_PLL_FINAL_DIV so other clocks like
|
|
+ * MPLL1 are not used (MPLL1 is reserved for audio
|
|
+ * purposes).
|
|
+ */
|
|
+ assigned-clocks = <&clkc CLKID_VCLK_IN_SEL>,
|
|
+ <&clkc CLKID_VCLK2_IN_SEL>;
|
|
+ assigned-clock-parents = <&clkc CLKID_VID_PLL_FINAL_DIV>,
|
|
+ <&clkc CLKID_VID_PLL_FINAL_DIV>;
|
|
+
|
|
+ clocks = <&clkc CLKID_VPU_INTR>,
|
|
+ <&clkc CLKID_HDMI_INTR_SYNC>,
|
|
+ <&clkc CLKID_GCLK_VENCI_INT>,
|
|
+ <&clkc CLKID_HDMI_PLL_HDMI_OUT>,
|
|
+ <&clkc CLKID_HDMI_TX_PIXEL>,
|
|
+ <&clkc CLKID_CTS_ENCP>,
|
|
+ <&clkc CLKID_CTS_ENCI>,
|
|
+ <&clkc CLKID_CTS_ENCT>,
|
|
+ <&clkc CLKID_CTS_ENCL>,
|
|
+ <&clkc CLKID_CTS_VDAC0>;
|
|
+ clock-names = "vpu_intr",
|
|
+ "hdmi_intr_sync",
|
|
+ "venci_int",
|
|
+ "tmds",
|
|
+ "hdmi_tx_pixel",
|
|
+ "cts_encp",
|
|
+ "cts_enci",
|
|
+ "cts_enct",
|
|
+ "cts_encl",
|
|
+ "cts_vdac0";
|
|
+
|
|
+ resets = <&clkc CLKC_RESET_VID_DIVIDER_CNTL_RESET_N_PRE>,
|
|
+ <&clkc CLKC_RESET_VID_DIVIDER_CNTL_RESET_N_POST>,
|
|
+ <&clkc CLKC_RESET_VID_DIVIDER_CNTL_SOFT_RESET_PRE>,
|
|
+ <&clkc CLKC_RESET_VID_DIVIDER_CNTL_SOFT_RESET_POST>;
|
|
+ reset-names = "vid_pll_pre",
|
|
+ "vid_pll_post",
|
|
+ "vid_pll_soft_pre",
|
|
+ "vid_pll_soft_post";
|
|
+
|
|
+ phys = <&cvbs_dac>;
|
|
+ phy-names = "cvbs-dac";
|
|
+
|
|
+ power-domains = <&pwrc PWRC_MESON8_VPU_ID>;
|
|
+
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ /* CVBS VDAC output port */
|
|
+ cvbs_vdac_port: port@0 {
|
|
+ reg = <0>;
|
|
+ };
|
|
+
|
|
+ /* HDMI-TX output port */
|
|
+ hdmi_tx_port: port@1 {
|
|
+ reg = <1>;
|
|
+
|
|
+ hdmi_tx_out: endpoint {
|
|
+ remote-endpoint = <&hdmi_tx_in>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
};
|
|
}; /* end of / */
|
|
|
|
@@ -363,6 +470,14 @@ gpio_ao: ao-bank@14 {
|
|
gpio-ranges = <&pinctrl_aobus 0 0 16>;
|
|
};
|
|
|
|
+ hdmi_cec_ao_pins: hdmi-cec-ao {
|
|
+ mux {
|
|
+ groups = "hdmi_cec_ao";
|
|
+ function = "hdmi_cec_ao";
|
|
+ bias-pull-up;
|
|
+ };
|
|
+ };
|
|
+
|
|
i2s_am_clk_pins: i2s-am-clk-out {
|
|
mux {
|
|
groups = "i2s_am_clk_out_ao";
|
|
@@ -427,6 +542,15 @@ mux {
|
|
};
|
|
};
|
|
};
|
|
+
|
|
+ cec_AO: cec@100 {
|
|
+ compatible = "amlogic,meson-gx-ao-cec"; // FIXME
|
|
+ reg = <0x100 0x14>;
|
|
+ interrupts = <GIC_SPI 151 IRQ_TYPE_EDGE_RISING>;
|
|
+ // TODO: 32768HZ clock
|
|
+ hdmi-phandle = <&hdmi_tx>;
|
|
+ status = "disabled";
|
|
+ };
|
|
};
|
|
|
|
&ao_arc_rproc {
|
|
@@ -479,6 +603,22 @@ gpio: banks@80b0 {
|
|
gpio-ranges = <&pinctrl_cbus 0 0 120>;
|
|
};
|
|
|
|
+ hdmi_hpd_pins: hdmi-hpd {
|
|
+ mux {
|
|
+ groups = "hdmi_hpd";
|
|
+ function = "hdmi";
|
|
+ bias-disable;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ hdmi_i2c_pins: hdmi-i2c {
|
|
+ mux {
|
|
+ groups = "hdmi_sda", "hdmi_scl";
|
|
+ function = "hdmi";
|
|
+ bias-disable;
|
|
+ };
|
|
+ };
|
|
+
|
|
sd_a_pins: sd-a {
|
|
mux {
|
|
groups = "sd_d0_a", "sd_d1_a", "sd_d2_a",
|
|
@@ -601,6 +741,17 @@ smp-sram@1ff80 {
|
|
};
|
|
};
|
|
|
|
+&cvbs_dac {
|
|
+ compatible = "amlogic,meson8-cvbs-dac", "amlogic,meson-cvbs-dac";
|
|
+
|
|
+ clocks = <&clkc CLKID_CTS_VDAC0>;
|
|
+
|
|
+ nvmem-cells = <&cvbs_trimming>;
|
|
+ nvmem-cell-names = "cvbs_trimming";
|
|
+
|
|
+ status = "okay";
|
|
+};
|
|
+
|
|
&efuse {
|
|
compatible = "amlogic,meson8-efuse";
|
|
clocks = <&clkc CLKID_EFUSE>;
|
|
@@ -610,6 +761,10 @@ temperature_calib: calib@1f4 {
|
|
/* only the upper two bytes are relevant */
|
|
reg = <0x1f4 0x4>;
|
|
};
|
|
+
|
|
+ cvbs_trimming: calib@1f8 {
|
|
+ reg = <0x1f8 0x2>;
|
|
+ };
|
|
};
|
|
|
|
ð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 = <GIC_SPI 57 IRQ_TYPE_EDGE_RISING>;
|
|
+ phys = <&hdmi_tx_phy>;
|
|
+ phy-names = "hdmi";
|
|
+ clocks = <&clkc CLKID_HDMI_PCLK>,
|
|
+ <&clkc CLKID_HDMI_SYS>;
|
|
+ clock-names = "pclk", "sys";
|
|
+
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ #sound-dai-cells = <1>;
|
|
+ sound-name-prefix = "HDMITX";
|
|
+
|
|
+ status = "disabled";
|
|
+
|
|
+ /* VPU VENC Input */
|
|
+ hdmi_tx_venc_port: port@0 {
|
|
+ reg = <0>;
|
|
+
|
|
+ hdmi_tx_in: endpoint {
|
|
+ remote-endpoint = <&hdmi_tx_out>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* TMDS Output */
|
|
+ hdmi_tx_tmds_port: port@1 {
|
|
+ reg = <1>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ vpu: vpu@100000 {
|
|
+ compatible = "amlogic,meson8b-vpu";
|
|
+
|
|
+ reg = <0x100000 0x10000>;
|
|
+ reg-names = "vpu";
|
|
+
|
|
+ interrupts = <GIC_SPI 3 IRQ_TYPE_EDGE_RISING>;
|
|
+
|
|
+ amlogic,canvas = <&canvas>;
|
|
+
|
|
+ /*
|
|
+ * The VCLK{,2}_IN path always needs to derived from
|
|
+ * the CLKID_VID_PLL_FINAL_DIV so other clocks like
|
|
+ * MPLL1 are not used (MPLL1 is reserved for audio
|
|
+ * purposes).
|
|
+ */
|
|
+ assigned-clocks = <&clkc CLKID_VCLK_IN_SEL>,
|
|
+ <&clkc CLKID_VCLK2_IN_SEL>;
|
|
+ assigned-clock-parents = <&clkc CLKID_VID_PLL_FINAL_DIV>,
|
|
+ <&clkc CLKID_VID_PLL_FINAL_DIV>;
|
|
+
|
|
+ clocks = <&clkc CLKID_VPU_INTR>,
|
|
+ <&clkc CLKID_HDMI_INTR_SYNC>,
|
|
+ <&clkc CLKID_GCLK_VENCI_INT>,
|
|
+ <&clkc CLKID_HDMI_PLL_HDMI_OUT>,
|
|
+ <&clkc CLKID_HDMI_TX_PIXEL>,
|
|
+ <&clkc CLKID_CTS_ENCP>,
|
|
+ <&clkc CLKID_CTS_ENCI>,
|
|
+ <&clkc CLKID_CTS_ENCT>,
|
|
+ <&clkc CLKID_CTS_ENCL>,
|
|
+ <&clkc CLKID_CTS_VDAC0>;
|
|
+ clock-names = "vpu_intr",
|
|
+ "hdmi_intr_sync",
|
|
+ "venci_int",
|
|
+ "tmds",
|
|
+ "hdmi_tx_pixel",
|
|
+ "cts_encp",
|
|
+ "cts_enci",
|
|
+ "cts_enct",
|
|
+ "cts_encl",
|
|
+ "cts_vdac0";
|
|
+
|
|
+ resets = <&clkc CLKC_RESET_VID_DIVIDER_CNTL_RESET_N_PRE>,
|
|
+ <&clkc CLKC_RESET_VID_DIVIDER_CNTL_RESET_N_POST>,
|
|
+ <&clkc CLKC_RESET_VID_DIVIDER_CNTL_SOFT_RESET_PRE>,
|
|
+ <&clkc CLKC_RESET_VID_DIVIDER_CNTL_SOFT_RESET_POST>;
|
|
+ reset-names = "vid_pll_pre",
|
|
+ "vid_pll_post",
|
|
+ "vid_pll_soft_pre",
|
|
+ "vid_pll_soft_post";
|
|
+
|
|
+ phys = <&cvbs_dac>;
|
|
+ phy-names = "cvbs-dac";
|
|
+
|
|
+ power-domains = <&pwrc PWRC_MESON8_VPU_ID>;
|
|
+
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ #sound-dai-cells = <0>;
|
|
+ sound-name-prefix = "HDMITX";
|
|
+
|
|
+ /* CVBS VDAC output port */
|
|
+ cvbs_vdac_port: port@0 {
|
|
+ reg = <0>;
|
|
+ };
|
|
+
|
|
+ /* HDMI-TX output port */
|
|
+ hdmi_tx_port: port@1 {
|
|
+ reg = <1>;
|
|
+
|
|
+ hdmi_tx_out: endpoint {
|
|
+ remote-endpoint = <&hdmi_tx_in>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
};
|
|
}; /* end of / */
|
|
|
|
@@ -325,6 +435,14 @@ gpio_ao: ao-bank@14 {
|
|
gpio-ranges = <&pinctrl_aobus 0 0 16>;
|
|
};
|
|
|
|
+ hdmi_cec_ao_pins: hdmi-cec-ao {
|
|
+ mux {
|
|
+ groups = "hdmi_cec_1";
|
|
+ function = "hdmi_cec";
|
|
+ bias-pull-up;
|
|
+ };
|
|
+ };
|
|
+
|
|
i2s_am_clk_pins: i2s-am-clk-out {
|
|
mux {
|
|
groups = "i2s_am_clk_out";
|
|
@@ -381,6 +499,15 @@ mux {
|
|
};
|
|
};
|
|
};
|
|
+
|
|
+ cec_AO: cec@100 {
|
|
+ compatible = "amlogic,meson-gx-ao-cec"; // FIXME
|
|
+ reg = <0x100 0x14>;
|
|
+ interrupts = <GIC_SPI 151 IRQ_TYPE_EDGE_RISING>;
|
|
+ // TODO: 32768HZ clock
|
|
+ hdmi-phandle = <&hdmi_tx>;
|
|
+ status = "disabled";
|
|
+ };
|
|
};
|
|
|
|
&ao_arc_rproc {
|
|
@@ -471,6 +598,22 @@ mux {
|
|
};
|
|
};
|
|
|
|
+ hdmi_hpd_pins: hdmi-hpd {
|
|
+ mux {
|
|
+ groups = "hdmi_hpd";
|
|
+ function = "hdmi";
|
|
+ bias-disable;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ hdmi_i2c_pins: hdmi-i2c {
|
|
+ mux {
|
|
+ groups = "hdmi_sda", "hdmi_scl";
|
|
+ function = "hdmi";
|
|
+ bias-disable;
|
|
+ };
|
|
+ };
|
|
+
|
|
i2c_a_pins: i2c-a {
|
|
mux {
|
|
groups = "i2c_sda_a", "i2c_sck_a";
|
|
@@ -547,6 +690,16 @@ smp-sram@1ff80 {
|
|
};
|
|
};
|
|
|
|
+&cvbs_dac {
|
|
+ compatible = "amlogic,meson8-cvbs-dac", "amlogic,meson-cvbs-dac";
|
|
+
|
|
+ clocks = <&clkc CLKID_CTS_VDAC0>;
|
|
+
|
|
+ nvmem-cells = <&cvbs_trimming>;
|
|
+ nvmem-cell-names = "cvbs_trimming";
|
|
+
|
|
+ status = "okay";
|
|
+};
|
|
|
|
&efuse {
|
|
compatible = "amlogic,meson8b-efuse";
|
|
@@ -557,6 +710,10 @@ temperature_calib: calib@1f4 {
|
|
/* only the upper two bytes are relevant */
|
|
reg = <0x1f4 0x4>;
|
|
};
|
|
+
|
|
+ cvbs_trimming: calib@1f8 {
|
|
+ reg = <0x1f8 0x2>;
|
|
+ };
|
|
};
|
|
|
|
ð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 <linux/module.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/sys_soc.h>
|
|
+#include <linux/phy/phy.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/soc/amlogic/meson-canvas.h>
|
|
|
|
@@ -132,30 +133,147 @@ static struct regmap_config meson_regmap_config = {
|
|
.max_register = 0x1000,
|
|
};
|
|
|
|
+static int meson_cvbs_dac_phy_init(struct meson_drm *priv)
|
|
+{
|
|
+ struct platform_device *pdev;
|
|
+ const char *platform_id_name;
|
|
+
|
|
+ priv->cvbs_dac = devm_phy_optional_get(priv->dev, "cvbs-dac");
|
|
+ if (IS_ERR(priv->cvbs_dac))
|
|
+ return dev_err_probe(priv->dev, PTR_ERR(priv->cvbs_dac),
|
|
+ "Failed to get the 'cvbs-dac' PHY\n");
|
|
+ else if (priv->cvbs_dac)
|
|
+ return 0;
|
|
+
|
|
+ switch (priv->compat) {
|
|
+ case VPU_COMPATIBLE_GXBB:
|
|
+ platform_id_name = "meson-gxbb-cvbs-dac";
|
|
+ break;
|
|
+ case VPU_COMPATIBLE_GXL:
|
|
+ case VPU_COMPATIBLE_GXM:
|
|
+ platform_id_name = "meson-gxl-cvbs-dac";
|
|
+ break;
|
|
+ case VPU_COMPATIBLE_G12A:
|
|
+ platform_id_name = "meson-g12a-cvbs-dac";
|
|
+ break;
|
|
+ default:
|
|
+ return dev_err_probe(priv->dev, -EINVAL,
|
|
+ "No CVBS DAC platform ID found\n");
|
|
+ }
|
|
+
|
|
+ pdev = platform_device_register_data(priv->dev, platform_id_name,
|
|
+ PLATFORM_DEVID_AUTO, NULL, 0);
|
|
+ if (IS_ERR(pdev))
|
|
+ return dev_err_probe(priv->dev, PTR_ERR(pdev),
|
|
+ "Failed to register fallback CVBS DAC PHY platform device\n");
|
|
+
|
|
+ priv->cvbs_dac = platform_get_drvdata(pdev);
|
|
+ if (IS_ERR(priv->cvbs_dac)) {
|
|
+ platform_device_unregister(pdev);
|
|
+ return dev_err_probe(priv->dev, PTR_ERR(priv->cvbs_dac),
|
|
+ "Failed to get the 'cvbs-dac' PHY from it's platform device\n");
|
|
+ }
|
|
+
|
|
+ dev_warn(priv->dev, "Using fallback for old .dtbs without CVBS DAC\n");
|
|
+
|
|
+ priv->cvbs_dac_pdev = pdev;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void meson_cvbs_dac_phy_exit(struct meson_drm *priv)
|
|
+{
|
|
+ platform_device_unregister(priv->cvbs_dac_pdev);
|
|
+}
|
|
+
|
|
static void meson_vpu_init(struct meson_drm *priv)
|
|
{
|
|
- u32 value;
|
|
+ if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
|
|
+ meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
|
|
+ meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) {
|
|
+ writel(0x0, priv->io_base + _REG(VPU_MEM_PD_REG0));
|
|
+ writel(0x0, priv->io_base + _REG(VPU_MEM_PD_REG1));
|
|
+ } else {
|
|
+ u32 value;
|
|
+
|
|
+ /*
|
|
+ * Slave dc0 and dc5 connected to master port 1.
|
|
+ * By default other slaves are connected to master port 0.
|
|
+ */
|
|
+ value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1) |
|
|
+ VPU_RDARB_SLAVE_TO_MASTER_PORT(5, 1);
|
|
+ writel_relaxed(value,
|
|
+ priv->io_base + _REG(VPU_RDARB_MODE_L1C1));
|
|
+
|
|
+ /* Slave dc0 connected to master port 1 */
|
|
+ value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1);
|
|
+ writel_relaxed(value,
|
|
+ priv->io_base + _REG(VPU_RDARB_MODE_L1C2));
|
|
+
|
|
+ /* Slave dc4 and dc7 connected to master port 1 */
|
|
+ value = VPU_RDARB_SLAVE_TO_MASTER_PORT(4, 1) |
|
|
+ VPU_RDARB_SLAVE_TO_MASTER_PORT(7, 1);
|
|
+ writel_relaxed(value,
|
|
+ priv->io_base + _REG(VPU_RDARB_MODE_L2C1));
|
|
+
|
|
+ /* Slave dc1 connected to master port 1 */
|
|
+ value = VPU_RDARB_SLAVE_TO_MASTER_PORT(1, 1);
|
|
+ writel_relaxed(value,
|
|
+ priv->io_base + _REG(VPU_WRARB_MODE_L2C1));
|
|
+ }
|
|
+}
|
|
+
|
|
+static int meson_video_clock_init(struct meson_drm *priv)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_bulk_prepare(VPU_VID_CLK_NUM, priv->vid_clks);
|
|
+ if (ret)
|
|
+ return dev_err_probe(priv->dev, ret,
|
|
+ "Failed to prepare the video clocks\n");
|
|
+
|
|
+ ret = clk_bulk_prepare(priv->num_intr_clks, priv->intr_clks);
|
|
+ if (ret)
|
|
+ return dev_err_probe(priv->dev, ret,
|
|
+ "Failed to prepare the interrupt clocks\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void meson_video_clock_exit(struct meson_drm *priv)
|
|
+{
|
|
+ if (priv->clk_dac_enabled)
|
|
+ clk_disable(priv->clk_dac);
|
|
+
|
|
+ if (priv->clk_venc_enabled)
|
|
+ clk_disable(priv->clk_venc);
|
|
+
|
|
+ clk_bulk_unprepare(priv->num_intr_clks, priv->intr_clks);
|
|
+ clk_bulk_unprepare(VPU_VID_CLK_NUM, priv->vid_clks);
|
|
+}
|
|
+
|
|
+static void meson_fbdev_setup(struct meson_drm *priv)
|
|
+{
|
|
+ unsigned int preferred_bpp;
|
|
|
|
/*
|
|
- * Slave dc0 and dc5 connected to master port 1.
|
|
- * By default other slaves are connected to master port 0.
|
|
+ * All SoC generations before GXBB don't have a way to configure the
|
|
+ * alpha value for DRM_FORMAT_XRGB8888 and DRM_FORMAT_XBGR8888. These
|
|
+ * formats have an X component instead of an alpha component. On
|
|
+ * Meson8/8b/8m2 there is no way to configure the alpha value to use
|
|
+ * instead of the X component. This results in the fact that the
|
|
+ * formats with X component are only supported on GXBB and newer. Use
|
|
+ * 24 bits per pixel and therefore DRM_FORMAT_RGB888 to get a
|
|
+ * working framebuffer console.
|
|
*/
|
|
- value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1) |
|
|
- VPU_RDARB_SLAVE_TO_MASTER_PORT(5, 1);
|
|
- writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C1));
|
|
-
|
|
- /* Slave dc0 connected to master port 1 */
|
|
- value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1);
|
|
- writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C2));
|
|
-
|
|
- /* Slave dc4 and dc7 connected to master port 1 */
|
|
- value = VPU_RDARB_SLAVE_TO_MASTER_PORT(4, 1) |
|
|
- VPU_RDARB_SLAVE_TO_MASTER_PORT(7, 1);
|
|
- writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L2C1));
|
|
-
|
|
- /* Slave dc1 connected to master port 1 */
|
|
- value = VPU_RDARB_SLAVE_TO_MASTER_PORT(1, 1);
|
|
- writel_relaxed(value, priv->io_base + _REG(VPU_WRARB_MODE_L2C1));
|
|
+ if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
|
|
+ meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
|
|
+ meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2))
|
|
+ preferred_bpp = 24;
|
|
+ else
|
|
+ preferred_bpp = 32;
|
|
+
|
|
+ drm_fbdev_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 <linux/clk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/regmap.h>
|
|
+#include <linux/reset.h>
|
|
|
|
struct drm_crtc;
|
|
struct drm_device;
|
|
struct drm_plane;
|
|
struct meson_drm;
|
|
struct meson_afbcd_ops;
|
|
+struct phy;
|
|
+struct platform_device;
|
|
|
|
enum vpu_compatible {
|
|
- VPU_COMPATIBLE_GXBB = 0,
|
|
- VPU_COMPATIBLE_GXL = 1,
|
|
- VPU_COMPATIBLE_GXM = 2,
|
|
- VPU_COMPATIBLE_G12A = 3,
|
|
+ VPU_COMPATIBLE_M8 = 0,
|
|
+ VPU_COMPATIBLE_M8B = 1,
|
|
+ VPU_COMPATIBLE_M8M2 = 2,
|
|
+ VPU_COMPATIBLE_GXBB = 3,
|
|
+ VPU_COMPATIBLE_GXL = 4,
|
|
+ VPU_COMPATIBLE_GXM = 5,
|
|
+ VPU_COMPATIBLE_G12A = 6,
|
|
};
|
|
|
|
enum {
|
|
@@ -40,6 +47,25 @@ struct meson_drm_soc_limits {
|
|
unsigned int max_hdmi_phy_freq;
|
|
};
|
|
|
|
+enum vpu_bulk_clk_id {
|
|
+ VPU_VID_CLK_TMDS = 0,
|
|
+ VPU_VID_CLK_HDMI_TX_PIXEL,
|
|
+ VPU_VID_CLK_CTS_ENCP,
|
|
+ VPU_VID_CLK_CTS_ENCI,
|
|
+ VPU_VID_CLK_CTS_ENCT,
|
|
+ VPU_VID_CLK_CTS_ENCL,
|
|
+ VPU_VID_CLK_CTS_VDAC0,
|
|
+ VPU_VID_CLK_NUM
|
|
+};
|
|
+
|
|
+enum vpu_bulk_vid_pll_reset_id {
|
|
+ VPU_RESET_VID_PLL_PRE = 0,
|
|
+ VPU_RESET_VID_PLL_POST,
|
|
+ VPU_RESET_VID_PLL_SOFT_PRE,
|
|
+ VPU_RESET_VID_PLL_SOFT_POST,
|
|
+ VPU_RESET_VID_PLL_NUM
|
|
+};
|
|
+
|
|
struct meson_drm {
|
|
struct device *dev;
|
|
enum vpu_compatible compat;
|
|
@@ -61,6 +87,21 @@ struct meson_drm {
|
|
|
|
const struct meson_drm_soc_limits *limits;
|
|
|
|
+ struct phy *cvbs_dac;
|
|
+ bool cvbs_dac_enabled;
|
|
+ struct platform_device *cvbs_dac_pdev;
|
|
+
|
|
+ struct clk_bulk_data intr_clks[3];
|
|
+ unsigned int num_intr_clks;
|
|
+ bool intr_clks_enabled;
|
|
+ struct clk_bulk_data vid_clks[VPU_VID_CLK_NUM];
|
|
+ bool vid_clk_rate_exclusive[VPU_VID_CLK_NUM];
|
|
+ struct clk *clk_venc;
|
|
+ bool clk_venc_enabled;
|
|
+ struct clk *clk_dac;
|
|
+ bool clk_dac_enabled;
|
|
+ struct reset_control_bulk_data vid_pll_resets[VPU_RESET_VID_PLL_NUM];
|
|
+
|
|
/* Components Data */
|
|
struct {
|
|
bool osd1_enabled;
|
|
diff --git a/drivers/gpu/drm/meson/meson_encoder_cvbs.c b/drivers/gpu/drm/meson/meson_encoder_cvbs.c
|
|
index 3f73b211..833f701f 100644
|
|
--- a/drivers/gpu/drm/meson/meson_encoder_cvbs.c
|
|
+++ b/drivers/gpu/drm/meson/meson_encoder_cvbs.c
|
|
@@ -11,6 +11,7 @@
|
|
|
|
#include <linux/export.h>
|
|
#include <linux/of_graph.h>
|
|
+#include <linux/phy/phy.h>
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_bridge.h>
|
|
@@ -24,12 +25,6 @@
|
|
#include "meson_vclk.h"
|
|
#include "meson_encoder_cvbs.h"
|
|
|
|
-/* HHI VDAC Registers */
|
|
-#define HHI_VDAC_CNTL0 0x2F4 /* 0xbd offset in data sheet */
|
|
-#define HHI_VDAC_CNTL0_G12A 0x2EC /* 0xbd offset in data sheet */
|
|
-#define HHI_VDAC_CNTL1 0x2F8 /* 0xbe offset in data sheet */
|
|
-#define HHI_VDAC_CNTL1_G12A 0x2F0 /* 0xbe offset in data sheet */
|
|
-
|
|
struct meson_encoder_cvbs {
|
|
struct drm_encoder encoder;
|
|
struct drm_bridge bridge;
|
|
@@ -87,11 +82,28 @@ static int meson_encoder_cvbs_attach(struct drm_bridge *bridge,
|
|
{
|
|
struct meson_encoder_cvbs *meson_encoder_cvbs =
|
|
bridge_to_meson_encoder_cvbs(bridge);
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_init(meson_encoder_cvbs->priv->cvbs_dac);
|
|
+ if (ret)
|
|
+ return ret;
|
|
|
|
return drm_bridge_attach(bridge->encoder, meson_encoder_cvbs->next_bridge,
|
|
&meson_encoder_cvbs->bridge, flags);
|
|
}
|
|
|
|
+static void meson_encoder_cvbs_detach(struct drm_bridge *bridge)
|
|
+{
|
|
+ struct meson_encoder_cvbs *meson_encoder_cvbs =
|
|
+ bridge_to_meson_encoder_cvbs(bridge);
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_exit(meson_encoder_cvbs->priv->cvbs_dac);
|
|
+ if (ret)
|
|
+ dev_err(meson_encoder_cvbs->priv->dev,
|
|
+ "Failed to exit the CVBS DAC\n");
|
|
+}
|
|
+
|
|
static int meson_encoder_cvbs_get_modes(struct drm_bridge *bridge,
|
|
struct drm_connector *connector)
|
|
{
|
|
@@ -148,6 +160,7 @@ static void meson_encoder_cvbs_atomic_enable(struct drm_bridge *bridge,
|
|
struct drm_connector_state *conn_state;
|
|
struct drm_crtc_state *crtc_state;
|
|
struct drm_connector *connector;
|
|
+ int ret;
|
|
|
|
connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
|
|
if (WARN_ON(!connector))
|
|
@@ -177,16 +190,13 @@ static void meson_encoder_cvbs_atomic_enable(struct drm_bridge *bridge,
|
|
writel_bits_relaxed(VENC_VDAC_SEL_ATV_DMD, 0,
|
|
priv->io_base + _REG(VENC_VDAC_DACSEL0));
|
|
|
|
- if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) {
|
|
- regmap_write(priv->hhi, HHI_VDAC_CNTL0, 1);
|
|
- regmap_write(priv->hhi, HHI_VDAC_CNTL1, 0);
|
|
- } else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
|
|
- meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL)) {
|
|
- regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0xf0001);
|
|
- regmap_write(priv->hhi, HHI_VDAC_CNTL1, 0);
|
|
- } else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) {
|
|
- regmap_write(priv->hhi, HHI_VDAC_CNTL0_G12A, 0x906001);
|
|
- regmap_write(priv->hhi, HHI_VDAC_CNTL1_G12A, 0);
|
|
+ if (!priv->cvbs_dac_enabled) {
|
|
+ ret = phy_power_on(priv->cvbs_dac);
|
|
+ if (ret)
|
|
+ dev_err(priv->dev,
|
|
+ "Failed to power on the CVBS DAC\n");
|
|
+ else
|
|
+ priv->cvbs_dac_enabled = true;
|
|
}
|
|
}
|
|
|
|
@@ -196,19 +206,22 @@ static void meson_encoder_cvbs_atomic_disable(struct drm_bridge *bridge,
|
|
struct meson_encoder_cvbs *meson_encoder_cvbs =
|
|
bridge_to_meson_encoder_cvbs(bridge);
|
|
struct meson_drm *priv = meson_encoder_cvbs->priv;
|
|
+ int ret;
|
|
|
|
- /* Disable CVBS VDAC */
|
|
- if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) {
|
|
- regmap_write(priv->hhi, HHI_VDAC_CNTL0_G12A, 0);
|
|
- regmap_write(priv->hhi, HHI_VDAC_CNTL1_G12A, 0);
|
|
- } else {
|
|
- regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0);
|
|
- regmap_write(priv->hhi, HHI_VDAC_CNTL1, 8);
|
|
- }
|
|
+ if (!priv->cvbs_dac_enabled)
|
|
+ return;
|
|
+
|
|
+ ret = phy_power_off(priv->cvbs_dac);
|
|
+ if (ret)
|
|
+ dev_err(priv->dev,
|
|
+ "Failed to power off the CVBS DAC\n");
|
|
+ else
|
|
+ priv->cvbs_dac_enabled = false;
|
|
}
|
|
|
|
static const struct drm_bridge_funcs meson_encoder_cvbs_bridge_funcs = {
|
|
.attach = meson_encoder_cvbs_attach,
|
|
+ .detach = meson_encoder_cvbs_detach,
|
|
.mode_valid = meson_encoder_cvbs_mode_valid,
|
|
.get_modes = meson_encoder_cvbs_get_modes,
|
|
.atomic_enable = meson_encoder_cvbs_atomic_enable,
|
|
diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c
|
|
index 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 <martin.blumenstingl@googlemail.com>
|
|
+ *
|
|
+ * All registers and magic values are taken from Amlogic's GPL kernel sources:
|
|
+ * Copyright (C) 2010 Amlogic, Inc.
|
|
+ */
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/component.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/phy/phy.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/regmap.h>
|
|
+
|
|
+#include <drm/display/drm_hdmi_helper.h>
|
|
+#include <drm/drm_atomic_helper.h>
|
|
+#include <drm/drm_bridge.h>
|
|
+#include <drm/drm_connector.h>
|
|
+#include <drm/drm_device.h>
|
|
+#include <drm/drm_probe_helper.h>
|
|
+#include <drm/drm_print.h>
|
|
+
|
|
+#include <sound/hdmi-codec.h>
|
|
+
|
|
+#include <uapi/linux/media-bus-format.h>
|
|
+
|
|
+#include "meson_transwitch_hdmi.h"
|
|
+
|
|
+#define HDMI_ADDR_PORT 0x0
|
|
+#define HDMI_DATA_PORT 0x4
|
|
+#define HDMI_CTRL_PORT 0x8
|
|
+ #define HDMI_CTRL_PORT_APB3_ERR_EN BIT(15)
|
|
+
|
|
+struct meson_txc_hdmi {
|
|
+ struct device *dev;
|
|
+
|
|
+ struct regmap *regmap;
|
|
+
|
|
+ struct clk *pclk;
|
|
+ struct clk *sys_clk;
|
|
+
|
|
+ struct phy *phy;
|
|
+ bool phy_is_on;
|
|
+
|
|
+ struct mutex codec_mutex;
|
|
+ enum drm_connector_status last_connector_status;
|
|
+ hdmi_codec_plugged_cb codec_plugged_cb;
|
|
+ struct device *codec_dev;
|
|
+
|
|
+ struct platform_device *hdmi_codec_pdev;
|
|
+
|
|
+ struct drm_connector *current_connector;
|
|
+
|
|
+ struct drm_bridge bridge;
|
|
+ struct drm_bridge *next_bridge;
|
|
+
|
|
+ bool sink_is_hdmi;
|
|
+};
|
|
+
|
|
+#define bridge_to_meson_txc_hdmi(x) container_of(x, struct meson_txc_hdmi, bridge)
|
|
+
|
|
+static const struct regmap_range meson_txc_hdmi_regmap_ranges[] = {
|
|
+ regmap_reg_range(0x0000, 0x07ff),
|
|
+ regmap_reg_range(0x8000, 0x800c),
|
|
+};
|
|
+
|
|
+static const struct regmap_access_table meson_txc_hdmi_regmap_access = {
|
|
+ .yes_ranges = meson_txc_hdmi_regmap_ranges,
|
|
+ .n_yes_ranges = ARRAY_SIZE(meson_txc_hdmi_regmap_ranges),
|
|
+};
|
|
+
|
|
+static int meson_txc_hdmi_reg_read(void *context, unsigned int addr,
|
|
+ unsigned int *data)
|
|
+{
|
|
+ void __iomem *base = context;
|
|
+
|
|
+ writel(addr, base + HDMI_ADDR_PORT);
|
|
+ writel(addr, base + HDMI_ADDR_PORT);
|
|
+
|
|
+ *data = readl(base + HDMI_DATA_PORT);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int meson_txc_hdmi_reg_write(void *context, unsigned int addr,
|
|
+ unsigned int data)
|
|
+{
|
|
+ void __iomem *base = context;
|
|
+
|
|
+ writel(addr, base + HDMI_ADDR_PORT);
|
|
+ writel(addr, base + HDMI_ADDR_PORT);
|
|
+
|
|
+ writel(data, base + HDMI_DATA_PORT);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct regmap_config meson_txc_hdmi_regmap_config = {
|
|
+ .reg_bits = 16,
|
|
+ .val_bits = 16,
|
|
+ .reg_stride = 1,
|
|
+ .reg_read = meson_txc_hdmi_reg_read,
|
|
+ .reg_write = meson_txc_hdmi_reg_write,
|
|
+ .rd_table = &meson_txc_hdmi_regmap_access,
|
|
+ .wr_table = &meson_txc_hdmi_regmap_access,
|
|
+ .max_register = HDMI_OTHER_RX_PACKET_INTR_CLR,
|
|
+ .fast_io = true,
|
|
+};
|
|
+
|
|
+static void meson_txc_hdmi_write_infoframe(struct regmap *regmap,
|
|
+ unsigned int tx_pkt_reg, u8 *buf,
|
|
+ unsigned int len, bool enable)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ /* Write the data bytes by starting at register offset 1 */
|
|
+ for (i = HDMI_INFOFRAME_HEADER_SIZE; i < len; i++)
|
|
+ regmap_write(regmap,
|
|
+ tx_pkt_reg + i - HDMI_INFOFRAME_HEADER_SIZE + 1,
|
|
+ buf[i]);
|
|
+
|
|
+ /* Zero all remaining data bytes */
|
|
+ for (; i < 0x1c; i++)
|
|
+ regmap_write(regmap, tx_pkt_reg + i, 0x00);
|
|
+
|
|
+ /* Write the header (which we skipped above) */
|
|
+ regmap_write(regmap, tx_pkt_reg + 0x00, buf[3]);
|
|
+ regmap_write(regmap, tx_pkt_reg + 0x1c, buf[0]);
|
|
+ regmap_write(regmap, tx_pkt_reg + 0x1d, buf[1]);
|
|
+ regmap_write(regmap, tx_pkt_reg + 0x1e, buf[2]);
|
|
+
|
|
+ regmap_write(regmap, tx_pkt_reg + 0x1f, enable ? 0xff : 0x00);
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_disable_infoframe(struct meson_txc_hdmi *priv,
|
|
+ unsigned int tx_pkt_reg)
|
|
+{
|
|
+ u8 buf[HDMI_INFOFRAME_HEADER_SIZE] = { 0 };
|
|
+
|
|
+ meson_txc_hdmi_write_infoframe(priv->regmap, tx_pkt_reg, buf,
|
|
+ HDMI_INFOFRAME_HEADER_SIZE, false);
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_sys5_reset_assert(struct meson_txc_hdmi *priv)
|
|
+{
|
|
+ /* A comment in the vendor driver says: bit5,6 is converted */
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2,
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH3_RST_IN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH0_RST_IN);
|
|
+ usleep_range(10, 20);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2,
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN);
|
|
+ usleep_range(10, 20);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1,
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_PIXEL_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_MASTER_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_RESAMPLE_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_I2S_RESET_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH2 |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH1 |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH0);
|
|
+ usleep_range(10, 20);
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_sys5_reset_deassert(struct meson_txc_hdmi *priv)
|
|
+{
|
|
+ /* Release the resets except tmds_clk */
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1,
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN);
|
|
+ usleep_range(10, 20);
|
|
+
|
|
+ /* Release the tmds_clk reset as well */
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, 0x0);
|
|
+ usleep_range(10, 20);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2,
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_SR_RST);
|
|
+ usleep_range(10, 20);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2,
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN);
|
|
+ usleep_range(10, 20);
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_config_hdcp_registers(struct meson_txc_hdmi *priv)
|
|
+{
|
|
+ regmap_write(priv->regmap, TX_HDCP_CONFIG0,
|
|
+ FIELD_PREP(TX_HDCP_CONFIG0_ROM_ENCRYPT_OFF, 0x3));
|
|
+ regmap_write(priv->regmap, TX_HDCP_MEM_CONFIG, 0x0);
|
|
+ regmap_write(priv->regmap, TX_HDCP_ENCRYPT_BYTE, 0x0);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_HDCP_MODE, TX_HDCP_MODE_CLEAR_AVMUTE);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_HDCP_MODE, TX_HDCP_MODE_ESS_CONFIG);
|
|
+}
|
|
+
|
|
+static u8 meson_txc_hdmi_bus_fmt_to_color_depth(unsigned int bus_format)
|
|
+{
|
|
+ switch (bus_format) {
|
|
+ case MEDIA_BUS_FMT_RGB888_1X24:
|
|
+ case MEDIA_BUS_FMT_YUV8_1X24:
|
|
+ case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
+ /* 8 bit */
|
|
+ return 0x0;
|
|
+
|
|
+ case MEDIA_BUS_FMT_RGB101010_1X30:
|
|
+ case MEDIA_BUS_FMT_YUV10_1X30:
|
|
+ case MEDIA_BUS_FMT_UYVY10_1X20:
|
|
+ /* 10 bit */
|
|
+ return 0x1;
|
|
+
|
|
+ case MEDIA_BUS_FMT_RGB121212_1X36:
|
|
+ case MEDIA_BUS_FMT_YUV12_1X36:
|
|
+ case MEDIA_BUS_FMT_UYVY12_1X24:
|
|
+ /* 12 bit */
|
|
+ return 0x2;
|
|
+
|
|
+ case MEDIA_BUS_FMT_RGB161616_1X48:
|
|
+ case MEDIA_BUS_FMT_YUV16_1X48:
|
|
+ /* 16 bit */
|
|
+ return 0x3;
|
|
+
|
|
+ default:
|
|
+ /* unknown, default to 8 bit */
|
|
+ return 0x0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static u8 meson_txc_hdmi_bus_fmt_to_color_format(unsigned int bus_format)
|
|
+{
|
|
+ switch (bus_format) {
|
|
+ case MEDIA_BUS_FMT_YUV8_1X24:
|
|
+ case MEDIA_BUS_FMT_YUV10_1X30:
|
|
+ case MEDIA_BUS_FMT_YUV12_1X36:
|
|
+ case MEDIA_BUS_FMT_YUV16_1X48:
|
|
+ /* Documented as YCbCr444 */
|
|
+ return 0x1;
|
|
+
|
|
+ case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
+ case MEDIA_BUS_FMT_UYVY10_1X20:
|
|
+ case MEDIA_BUS_FMT_UYVY12_1X24:
|
|
+ /* Documented as YCbCr422 */
|
|
+ return 0x3;
|
|
+
|
|
+ case MEDIA_BUS_FMT_RGB888_1X24:
|
|
+ case MEDIA_BUS_FMT_RGB101010_1X30:
|
|
+ case MEDIA_BUS_FMT_RGB121212_1X36:
|
|
+ case MEDIA_BUS_FMT_RGB161616_1X48:
|
|
+ default:
|
|
+ /* Documented as RGB444 */
|
|
+ return 0x0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_config_color_space(struct meson_txc_hdmi *priv,
|
|
+ unsigned int input_bus_format,
|
|
+ unsigned int output_bus_format,
|
|
+ enum hdmi_quantization_range quant_range,
|
|
+ enum hdmi_colorimetry colorimetry)
|
|
+{
|
|
+ unsigned int regval;
|
|
+
|
|
+ regmap_write(priv->regmap, TX_VIDEO_DTV_MODE,
|
|
+ FIELD_PREP(TX_VIDEO_DTV_MODE_COLOR_DEPTH,
|
|
+ meson_txc_hdmi_bus_fmt_to_color_depth(output_bus_format)));
|
|
+
|
|
+ regmap_write(priv->regmap, TX_VIDEO_DTV_OPTION_L,
|
|
+ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_FORMAT,
|
|
+ meson_txc_hdmi_bus_fmt_to_color_format(output_bus_format)) |
|
|
+ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_FORMAT,
|
|
+ meson_txc_hdmi_bus_fmt_to_color_format(input_bus_format)) |
|
|
+ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_DEPTH,
|
|
+ meson_txc_hdmi_bus_fmt_to_color_depth(output_bus_format)) |
|
|
+ FIELD_PREP(TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_DEPTH,
|
|
+ meson_txc_hdmi_bus_fmt_to_color_depth(input_bus_format)));
|
|
+
|
|
+ if (quant_range == HDMI_QUANTIZATION_RANGE_LIMITED)
|
|
+ regval = FIELD_PREP(TX_VIDEO_DTV_OPTION_H_OUTPUT_COLOR_RANGE,
|
|
+ TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_16_235) |
|
|
+ FIELD_PREP(TX_VIDEO_DTV_OPTION_H_INPUT_COLOR_RANGE,
|
|
+ TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_16_235);
|
|
+ else
|
|
+ regval = FIELD_PREP(TX_VIDEO_DTV_OPTION_H_OUTPUT_COLOR_RANGE,
|
|
+ TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_0_255) |
|
|
+ FIELD_PREP(TX_VIDEO_DTV_OPTION_H_INPUT_COLOR_RANGE,
|
|
+ TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_0_255);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_VIDEO_DTV_OPTION_H, regval);
|
|
+
|
|
+ if (colorimetry == HDMI_COLORIMETRY_ITU_601) {
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B0, 0x2f);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B1, 0x1d);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R0, 0x8b);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R1, 0x4c);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB0, 0x18);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB1, 0x58);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR0, 0xd0);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR1, 0xb6);
|
|
+ } else {
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B0, 0x7b);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_B1, 0x12);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R0, 0x6c);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_R1, 0x36);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB0, 0xf2);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CB1, 0x2f);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR0, 0xd4);
|
|
+ regmap_write(priv->regmap, TX_VIDEO_CSC_COEFF_CR1, 0x77);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_config_serializer_clock(struct meson_txc_hdmi *priv,
|
|
+ enum hdmi_colorimetry colorimetry)
|
|
+{
|
|
+ /* Serializer Internal clock setting */
|
|
+ if (colorimetry == HDMI_COLORIMETRY_ITU_601)
|
|
+ regmap_write(priv->regmap, TX_SYS1_PLL, 0x24);
|
|
+ else
|
|
+ regmap_write(priv->regmap, TX_SYS1_PLL, 0x22);
|
|
+
|
|
+#if 0
|
|
+ // TODO: not ported yet
|
|
+ if ((param->VIC==HDMI_1080p60)&&(param->color_depth==COLOR_30BIT)&&(hdmi_rd_reg(0x018)==0x22)) {
|
|
+ regmap_write(priv->regmap, TX_SYS1_PLL, 0x12);
|
|
+ }
|
|
+#endif
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_reconfig_packet_setting(struct meson_txc_hdmi *priv,
|
|
+ u8 cea_mode)
|
|
+{
|
|
+ u8 alloc_active2, alloc_eof1, alloc_sof1, alloc_sof2;
|
|
+
|
|
+ regmap_write(priv->regmap, TX_PACKET_CONTROL_1,
|
|
+ FIELD_PREP(TX_PACKET_CONTROL_1_PACKET_START_LATENCY, 58));
|
|
+ regmap_write(priv->regmap, TX_PACKET_CONTROL_2,
|
|
+ TX_PACKET_CONTROL_2_HORIZONTAL_GC_PACKET_TRANSPORT_EN);
|
|
+
|
|
+ switch (cea_mode) {
|
|
+ case 31:
|
|
+ /* 1920x1080p50 */
|
|
+ alloc_active2 = 0x12;
|
|
+ alloc_eof1 = 0x10;
|
|
+ alloc_sof1 = 0xb6;
|
|
+ alloc_sof2 = 0x11;
|
|
+ break;
|
|
+ case 93:
|
|
+ /* 3840x2160p24 */
|
|
+ alloc_active2 = 0x12;
|
|
+ alloc_eof1 = 0x47;
|
|
+ alloc_sof1 = 0xf8;
|
|
+ alloc_sof2 = 0x52;
|
|
+ break;
|
|
+ case 94:
|
|
+ /* 3840x2160p25 */
|
|
+ alloc_active2 = 0x12;
|
|
+ alloc_eof1 = 0x44;
|
|
+ alloc_sof1 = 0xda;
|
|
+ alloc_sof2 = 0x52;
|
|
+ break;
|
|
+ case 95:
|
|
+ /* 3840x2160p30 */
|
|
+ alloc_active2 = 0x0f;
|
|
+ alloc_eof1 = 0x3a;
|
|
+ alloc_sof1 = 0x60;
|
|
+ alloc_sof2 = 0x52;
|
|
+ break;
|
|
+ case 98:
|
|
+ /* 4096x2160p24 */
|
|
+ alloc_active2 = 0x12;
|
|
+ alloc_eof1 = 0x47;
|
|
+ alloc_sof1 = 0xf8;
|
|
+ alloc_sof2 = 0x52;
|
|
+ break;
|
|
+ default:
|
|
+ /* Disable the special packet settings only */
|
|
+ regmap_write(priv->regmap, TX_PACKET_ALLOC_ACTIVE_1, 0x00);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The vendor driver says: manually configure these register to get
|
|
+ * stable video timings.
|
|
+ */
|
|
+ regmap_write(priv->regmap, TX_PACKET_ALLOC_ACTIVE_1, 0x01);
|
|
+ regmap_write(priv->regmap, TX_PACKET_ALLOC_ACTIVE_2, alloc_active2);
|
|
+ regmap_write(priv->regmap, TX_PACKET_ALLOC_EOF_1, alloc_eof1);
|
|
+ regmap_write(priv->regmap, TX_PACKET_ALLOC_EOF_2, 0x12);
|
|
+ regmap_write(priv->regmap, TX_CORE_ALLOC_VSYNC_0, 0x01);
|
|
+ regmap_write(priv->regmap, TX_CORE_ALLOC_VSYNC_1, 0x00);
|
|
+ regmap_write(priv->regmap, TX_CORE_ALLOC_VSYNC_2, 0x0a);
|
|
+ regmap_write(priv->regmap, TX_PACKET_ALLOC_SOF_1, alloc_sof1);
|
|
+ regmap_write(priv->regmap, TX_PACKET_ALLOC_SOF_2, alloc_sof2);
|
|
+ regmap_update_bits(priv->regmap, TX_PACKET_CONTROL_1,
|
|
+ TX_PACKET_CONTROL_1_FORCE_PACKET_TIMING,
|
|
+ TX_PACKET_CONTROL_1_FORCE_PACKET_TIMING);
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_set_avi_infoframe(struct meson_txc_hdmi *priv,
|
|
+ struct drm_connector *conn,
|
|
+ const struct drm_display_mode *mode,
|
|
+ const struct drm_connector_state *conn_state,
|
|
+ unsigned int output_bus_format,
|
|
+ enum hdmi_quantization_range quant_range,
|
|
+ enum hdmi_colorimetry colorimetry)
|
|
+{
|
|
+ u8 buf[HDMI_INFOFRAME_SIZE(AVI)], *video_code;
|
|
+ struct hdmi_avi_infoframe frame;
|
|
+ int ret;
|
|
+
|
|
+ ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, conn, mode);
|
|
+ if (ret < 0) {
|
|
+ drm_err(priv->bridge.dev,
|
|
+ "Failed to setup AVI infoframe: %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ switch (output_bus_format) {
|
|
+ case MEDIA_BUS_FMT_YUV8_1X24:
|
|
+ case MEDIA_BUS_FMT_YUV10_1X30:
|
|
+ case MEDIA_BUS_FMT_YUV12_1X36:
|
|
+ case MEDIA_BUS_FMT_YUV16_1X48:
|
|
+ frame.colorspace = HDMI_COLORSPACE_YUV444;
|
|
+ break;
|
|
+
|
|
+ case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
+ case MEDIA_BUS_FMT_UYVY10_1X20:
|
|
+ case MEDIA_BUS_FMT_UYVY12_1X24:
|
|
+ frame.colorspace = HDMI_COLORSPACE_YUV422;
|
|
+ break;
|
|
+
|
|
+ case MEDIA_BUS_FMT_RGB888_1X24:
|
|
+ case MEDIA_BUS_FMT_RGB101010_1X30:
|
|
+ case MEDIA_BUS_FMT_RGB121212_1X36:
|
|
+ case MEDIA_BUS_FMT_RGB161616_1X48:
|
|
+ default:
|
|
+ frame.colorspace = HDMI_COLORSPACE_RGB;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ drm_hdmi_avi_infoframe_colorimetry(&frame, conn_state);
|
|
+ drm_hdmi_avi_infoframe_quant_range(&frame, conn, mode, quant_range);
|
|
+ drm_hdmi_avi_infoframe_bars(&frame, conn_state);
|
|
+
|
|
+ ret = hdmi_avi_infoframe_pack(&frame, buf, sizeof(buf));
|
|
+ if (ret < 0) {
|
|
+ drm_err(priv->bridge.dev,
|
|
+ "Failed to pack AVI infoframe: %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ video_code = &buf[HDMI_INFOFRAME_HEADER_SIZE + 3];
|
|
+ if (*video_code > 108) {
|
|
+ regmap_write(priv->regmap, TX_PKT_REG_EXCEPT0_BASE_ADDR,
|
|
+ *video_code);
|
|
+ *video_code = 0x00;
|
|
+ } else {
|
|
+ regmap_write(priv->regmap, TX_PKT_REG_EXCEPT0_BASE_ADDR,
|
|
+ 0x00);
|
|
+ }
|
|
+
|
|
+ meson_txc_hdmi_write_infoframe(priv->regmap,
|
|
+ TX_PKT_REG_AVI_INFO_BASE_ADDR, buf,
|
|
+ sizeof(buf), true);
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_set_vendor_infoframe(struct meson_txc_hdmi *priv,
|
|
+ struct drm_connector *conn,
|
|
+ const struct drm_display_mode *mode)
|
|
+{
|
|
+ u8 buf[HDMI_INFOFRAME_HEADER_SIZE + 6];
|
|
+ struct hdmi_vendor_infoframe frame;
|
|
+ int ret;
|
|
+
|
|
+ ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame, conn, mode);
|
|
+ if (ret) {
|
|
+ drm_dbg(priv->bridge.dev,
|
|
+ "Failed to setup vendor infoframe: %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ret = hdmi_vendor_infoframe_pack(&frame, buf, sizeof(buf));
|
|
+ if (ret < 0) {
|
|
+ drm_err(priv->bridge.dev,
|
|
+ "Failed to pack vendor infoframe: %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ meson_txc_hdmi_write_infoframe(priv->regmap,
|
|
+ TX_PKT_REG_VEND_INFO_BASE_ADDR, buf,
|
|
+ sizeof(buf), true);
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_set_spd_infoframe(struct meson_txc_hdmi *priv)
|
|
+{
|
|
+ u8 buf[HDMI_INFOFRAME_SIZE(SPD)];
|
|
+ struct hdmi_spd_infoframe frame;
|
|
+ int ret;
|
|
+
|
|
+ ret = hdmi_spd_infoframe_init(&frame, "Amlogic", "Meson TXC HDMI");
|
|
+ if (ret < 0) {
|
|
+ drm_err(priv->bridge.dev,
|
|
+ "Failed to setup SPD infoframe: %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ret = hdmi_spd_infoframe_pack(&frame, buf, sizeof(buf));
|
|
+ if (ret < 0) {
|
|
+ drm_err(priv->bridge.dev,
|
|
+ "Failed to pack SDP infoframe: %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ meson_txc_hdmi_write_infoframe(priv->regmap,
|
|
+ TX_PKT_REG_SPD_INFO_BASE_ADDR, buf,
|
|
+ sizeof(buf), true);
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_handle_plugged_change(struct meson_txc_hdmi *priv)
|
|
+{
|
|
+ bool plugged;
|
|
+
|
|
+ plugged = priv->last_connector_status == connector_status_connected;
|
|
+
|
|
+ if (priv->codec_dev && priv->codec_plugged_cb)
|
|
+ priv->codec_plugged_cb(priv->codec_dev, plugged);
|
|
+}
|
|
+
|
|
+static int meson_txc_hdmi_bridge_attach(struct drm_bridge *bridge,
|
|
+ enum drm_bridge_attach_flags flags)
|
|
+{
|
|
+ struct meson_txc_hdmi *priv = bridge->driver_private;
|
|
+
|
|
+ if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
|
|
+ drm_err(bridge->dev,
|
|
+ "DRM_BRIDGE_ATTACH_NO_CONNECTOR flag is not set but needed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return drm_bridge_attach(bridge->encoder, priv->next_bridge, bridge,
|
|
+ flags);
|
|
+}
|
|
+
|
|
+/* Can return a maximum of 11 possible output formats for a mode/connector */
|
|
+#define MAX_OUTPUT_SEL_FORMATS 11
|
|
+
|
|
+static u32 *
|
|
+meson_txc_hdmi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *bridge_state,
|
|
+ struct drm_crtc_state *crtc_state,
|
|
+ struct drm_connector_state *conn_state,
|
|
+ unsigned int *num_output_fmts)
|
|
+{
|
|
+ struct drm_connector *conn = conn_state->connector;
|
|
+ struct drm_display_info *info = &conn->display_info;
|
|
+ u8 max_bpc = conn_state->max_requested_bpc;
|
|
+ unsigned int i = 0;
|
|
+ u32 *output_fmts;
|
|
+
|
|
+ *num_output_fmts = 0;
|
|
+
|
|
+ output_fmts = kcalloc(MAX_OUTPUT_SEL_FORMATS, sizeof(*output_fmts),
|
|
+ GFP_KERNEL);
|
|
+ if (!output_fmts)
|
|
+ return NULL;
|
|
+
|
|
+ /* If we are the only bridge, avoid negotiating with ourselves */
|
|
+ if (list_is_singular(&bridge->encoder->bridge_chain)) {
|
|
+ *num_output_fmts = 1;
|
|
+ output_fmts[0] = MEDIA_BUS_FMT_FIXED;
|
|
+
|
|
+ return output_fmts;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Order bus formats from 16bit to 8bit and from YUV422 to RGB
|
|
+ * if supported. In any case the default RGB888 format is added
|
|
+ */
|
|
+
|
|
+ if (max_bpc >= 16 && info->bpc == 16) {
|
|
+ if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444)
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48;
|
|
+
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48;
|
|
+ }
|
|
+
|
|
+ if (max_bpc >= 12 && info->bpc >= 12) {
|
|
+ if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422)
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
|
|
+
|
|
+ if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444)
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
|
|
+
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
|
|
+ }
|
|
+
|
|
+ if (max_bpc >= 10 && info->bpc >= 10) {
|
|
+ if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422)
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
|
|
+
|
|
+ if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444)
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
|
|
+
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
|
|
+ }
|
|
+
|
|
+ if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422)
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
|
|
+
|
|
+ if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444)
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
|
|
+
|
|
+ /* Default 8bit RGB fallback */
|
|
+ output_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
|
|
+
|
|
+ *num_output_fmts = i;
|
|
+
|
|
+ return output_fmts;
|
|
+}
|
|
+
|
|
+/* Can return a maximum of 3 possible input formats for an output format */
|
|
+#define MAX_INPUT_SEL_FORMATS 3
|
|
+
|
|
+static u32 *
|
|
+meson_txc_hdmi_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *bridge_state,
|
|
+ struct drm_crtc_state *crtc_state,
|
|
+ struct drm_connector_state *conn_state,
|
|
+ u32 output_fmt,
|
|
+ unsigned int *num_input_fmts)
|
|
+{
|
|
+ u32 *input_fmts;
|
|
+ unsigned int i = 0;
|
|
+
|
|
+ *num_input_fmts = 0;
|
|
+
|
|
+ input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
|
|
+ GFP_KERNEL);
|
|
+ if (!input_fmts)
|
|
+ return NULL;
|
|
+
|
|
+ switch (output_fmt) {
|
|
+ /* If MEDIA_BUS_FMT_FIXED is tested, return default bus format */
|
|
+ case MEDIA_BUS_FMT_FIXED:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
|
|
+ break;
|
|
+
|
|
+ /* 8bit */
|
|
+ case MEDIA_BUS_FMT_RGB888_1X24:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_YUV8_1X24:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
|
|
+ break;
|
|
+
|
|
+ /* 10bit */
|
|
+ case MEDIA_BUS_FMT_RGB101010_1X30:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_YUV10_1X30:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_UYVY10_1X20:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
|
|
+ break;
|
|
+
|
|
+ /* 12bit */
|
|
+ case MEDIA_BUS_FMT_RGB121212_1X36:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_YUV12_1X36:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_UYVY12_1X24:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
|
|
+ break;
|
|
+
|
|
+ /* 16bit */
|
|
+ case MEDIA_BUS_FMT_RGB161616_1X48:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_YUV16_1X48:
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48;
|
|
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ *num_input_fmts = i;
|
|
+
|
|
+ if (*num_input_fmts == 0) {
|
|
+ kfree(input_fmts);
|
|
+ input_fmts = NULL;
|
|
+ }
|
|
+
|
|
+ return input_fmts;
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *old_bridge_state)
|
|
+{
|
|
+ struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge);
|
|
+ struct drm_atomic_state *state = old_bridge_state->base.state;
|
|
+ enum hdmi_quantization_range quant_range;
|
|
+ struct drm_connector_state *conn_state;
|
|
+ struct drm_bridge_state *bridge_state;
|
|
+ const struct drm_display_mode *mode;
|
|
+ enum hdmi_colorimetry colorimetry;
|
|
+ struct drm_crtc_state *crtc_state;
|
|
+ struct drm_connector *connector;
|
|
+ unsigned int i;
|
|
+ u8 cea_mode;
|
|
+
|
|
+ bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
|
|
+
|
|
+ connector = drm_atomic_get_new_connector_for_encoder(state,
|
|
+ bridge->encoder);
|
|
+ if (WARN_ON(!connector))
|
|
+ return;
|
|
+
|
|
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
|
|
+ if (WARN_ON(!conn_state))
|
|
+ return;
|
|
+
|
|
+ crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
|
|
+ if (WARN_ON(!crtc_state))
|
|
+ return;
|
|
+
|
|
+ priv->current_connector = connector;
|
|
+
|
|
+ mode = &crtc_state->adjusted_mode;
|
|
+
|
|
+ cea_mode = drm_match_cea_mode(mode);
|
|
+
|
|
+ if (priv->sink_is_hdmi) {
|
|
+ quant_range = drm_default_rgb_quant_range(mode);
|
|
+
|
|
+ switch (cea_mode) {
|
|
+ case 2 ... 3:
|
|
+ case 6 ... 7:
|
|
+ case 17 ... 18:
|
|
+ case 21 ... 22:
|
|
+ colorimetry = HDMI_COLORIMETRY_ITU_601;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ colorimetry = HDMI_COLORIMETRY_ITU_709;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ meson_txc_hdmi_set_avi_infoframe(priv, connector, mode,
|
|
+ conn_state,
|
|
+ bridge_state->output_bus_cfg.format,
|
|
+ quant_range, colorimetry);
|
|
+ meson_txc_hdmi_set_vendor_infoframe(priv, connector, mode);
|
|
+ meson_txc_hdmi_set_spd_infoframe(priv);
|
|
+ } else {
|
|
+ quant_range = HDMI_QUANTIZATION_RANGE_FULL;
|
|
+ colorimetry = HDMI_COLORIMETRY_NONE;
|
|
+ }
|
|
+
|
|
+ meson_txc_hdmi_sys5_reset_assert(priv);
|
|
+
|
|
+ meson_txc_hdmi_config_hdcp_registers(priv);
|
|
+
|
|
+ if (cea_mode == 39)
|
|
+ regmap_write(priv->regmap, TX_VIDEO_DTV_TIMING, 0x0);
|
|
+ else
|
|
+ regmap_write(priv->regmap, TX_VIDEO_DTV_TIMING,
|
|
+ TX_VIDEO_DTV_TIMING_DISABLE_VIC39_CORRECTION);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_CORE_DATA_CAPTURE_2,
|
|
+ TX_CORE_DATA_CAPTURE_2_INTERNAL_PACKET_ENABLE);
|
|
+ regmap_write(priv->regmap, TX_CORE_DATA_MONITOR_1,
|
|
+ TX_CORE_DATA_MONITOR_1_LANE0 |
|
|
+ FIELD_PREP(TX_CORE_DATA_MONITOR_1_SELECT_LANE0, 0x7));
|
|
+ regmap_write(priv->regmap, TX_CORE_DATA_MONITOR_2,
|
|
+ FIELD_PREP(TX_CORE_DATA_MONITOR_2_MONITOR_SELECT, 0x2));
|
|
+
|
|
+ if (priv->sink_is_hdmi)
|
|
+ regmap_write(priv->regmap, TX_TMDS_MODE,
|
|
+ TX_TMDS_MODE_FORCED_HDMI |
|
|
+ TX_TMDS_MODE_HDMI_CONFIG);
|
|
+ else
|
|
+ regmap_write(priv->regmap, TX_TMDS_MODE,
|
|
+ TX_TMDS_MODE_FORCED_HDMI);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_SYS4_CONNECT_SEL_1, 0x0);
|
|
+
|
|
+ /*
|
|
+ * Set tmds_clk pattern to be "0000011111" before being sent to AFE
|
|
+ * clock channel.
|
|
+ */
|
|
+ regmap_write(priv->regmap, TX_SYS4_CK_INV_VIDEO,
|
|
+ TX_SYS4_CK_INV_VIDEO_TMDS_CLK_PATTERN);
|
|
+
|
|
+ regmap_write(priv->regmap, TX_SYS5_FIFO_CONFIG,
|
|
+ TX_SYS5_FIFO_CONFIG_CLK_CHANNEL3_OUTPUT_ENABLE |
|
|
+ TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL2_ENABLE |
|
|
+ TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL1_ENABLE |
|
|
+ TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL0_ENABLE);
|
|
+
|
|
+ meson_txc_hdmi_config_color_space(priv,
|
|
+ bridge_state->input_bus_cfg.format,
|
|
+ bridge_state->output_bus_cfg.format,
|
|
+ quant_range, colorimetry);
|
|
+
|
|
+ meson_txc_hdmi_sys5_reset_deassert(priv);
|
|
+
|
|
+ meson_txc_hdmi_config_serializer_clock(priv, colorimetry);
|
|
+ meson_txc_hdmi_reconfig_packet_setting(priv, cea_mode);
|
|
+
|
|
+ /* all resets need to be applied twice */
|
|
+ for (i = 0; i < 2; i++) {
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1,
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_PIXEL_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_MASTER_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_RESAMPLE_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_I2S_RESET_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH2 |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH1 |
|
|
+ TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH0);
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2,
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH3_RST_IN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_CH0_RST_IN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_HDMI_SR_RST |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_TX_DDC_HDCP_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_TX_DDC_EDID_RSTN |
|
|
+ TX_SYS5_TX_SOFT_RESET_2_TX_DIG_RESET_N_CH3);
|
|
+ usleep_range(5000, 10000);
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_1, 0x00);
|
|
+ regmap_write(priv->regmap, TX_SYS5_TX_SOFT_RESET_2, 0x00);
|
|
+ usleep_range(5000, 10000);
|
|
+ }
|
|
+
|
|
+ if (!priv->phy_is_on) {
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_power_on(priv->phy);
|
|
+ if (ret)
|
|
+ drm_err(bridge->dev, "Failed to turn on PHY\n");
|
|
+ else
|
|
+ priv->phy_is_on = true;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void meson_txc_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *old_bridge_state)
|
|
+{
|
|
+ struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge);
|
|
+
|
|
+ priv->current_connector = NULL;
|
|
+
|
|
+ if (priv->phy_is_on) {
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_power_off(priv->phy);
|
|
+ if (ret)
|
|
+ drm_err(bridge->dev, "Failed to turn off PHY\n");
|
|
+ else
|
|
+ priv->phy_is_on = false;
|
|
+ }
|
|
+
|
|
+ meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_AUDIO_INFO_BASE_ADDR);
|
|
+ meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_AVI_INFO_BASE_ADDR);
|
|
+ meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_EXCEPT0_BASE_ADDR);
|
|
+ meson_txc_hdmi_disable_infoframe(priv, TX_PKT_REG_VEND_INFO_BASE_ADDR);
|
|
+}
|
|
+
|
|
+static enum drm_mode_status
|
|
+meson_txc_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
|
|
+ const struct drm_display_info *info,
|
|
+ const struct drm_display_mode *mode)
|
|
+{
|
|
+ return MODE_OK;
|
|
+}
|
|
+
|
|
+static enum drm_connector_status meson_txc_hdmi_bridge_detect(struct drm_bridge *bridge)
|
|
+{
|
|
+ struct meson_txc_hdmi *priv = bridge_to_meson_txc_hdmi(bridge);
|
|
+ enum drm_connector_status status;
|
|
+ unsigned int val;
|
|
+
|
|
+ regmap_read(priv->regmap, TX_HDCP_ST_EDID_STATUS, &val);
|
|
+ if (val & TX_HDCP_ST_EDID_STATUS_HPD_STATUS)
|
|
+ status = connector_status_connected;
|
|
+ else
|
|
+ status = connector_status_disconnected;
|
|
+
|
|
+ mutex_lock(&priv->codec_mutex);
|
|
+ if (priv->last_connector_status != status) {
|
|
+ priv->last_connector_status = status;
|
|
+ meson_txc_hdmi_handle_plugged_change(priv);
|
|
+ }
|
|
+ mutex_unlock(&priv->codec_mutex);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int meson_txc_hdmi_get_edid_block(void *data, u8 *buf, unsigned int block,
|
|
+ size_t len)
|
|
+{
|
|
+ unsigned int i, regval, start = block * EDID_LENGTH;
|
|
+ struct meson_txc_hdmi *priv = data;
|
|
+ int ret;
|
|
+
|
|
+ /* Start the DDC transaction */
|
|
+ regmap_update_bits(priv->regmap, TX_HDCP_EDID_CONFIG,
|
|
+ TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG, 0);
|
|
+ regmap_update_bits(priv->regmap, TX_HDCP_EDID_CONFIG,
|
|
+ TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG,
|
|
+ TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG);
|
|
+
|
|
+ ret = regmap_read_poll_timeout(priv->regmap,
|
|
+ TX_HDCP_ST_EDID_STATUS,
|
|
+ regval,
|
|
+ (regval & TX_HDCP_ST_EDID_STATUS_EDID_DATA_READY),
|
|
+ 1000, 200000);
|
|
+
|
|
+ regmap_update_bits(priv->regmap, TX_HDCP_EDID_CONFIG,
|
|
+ TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG, 0);
|
|
+
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ for (i = 0; i < len; i++) {
|
|
+ regmap_read(priv->regmap, TX_RX_EDID_OFFSET + start + i,
|
|
+ ®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 <martin.blumenstingl@googlemail.com>");
|
|
+MODULE_DESCRIPTION("Amlogic Meson8 and Meson8b TranSwitch HDMI 1.4 TX driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/drivers/gpu/drm/meson/meson_transwitch_hdmi.h b/drivers/gpu/drm/meson/meson_transwitch_hdmi.h
|
|
new file mode 100644
|
|
index 00000000..14929475
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/drm/meson/meson_transwitch_hdmi.h
|
|
@@ -0,0 +1,536 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Copyright (C) 2021 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
|
|
+ *
|
|
+ * All registers and magic values are taken from Amlogic's GPL kernel sources:
|
|
+ * Copyright (C) 2010 Amlogic, Inc.
|
|
+ */
|
|
+
|
|
+#include <linux/bitfield.h>
|
|
+#include <linux/bits.h>
|
|
+
|
|
+#ifndef __MESON_TRANSWITCH_HDMI_H__
|
|
+#define __MESON_TRANSWITCH_HDMI_H__
|
|
+
|
|
+/* HDMI TX register */
|
|
+
|
|
+// System config 0
|
|
+#define TX_SYS0_AFE_SIGNAL 0x0000
|
|
+#define TX_SYS0_AFE_LOOP 0x0001
|
|
+#define TX_SYS0_ACR_CTS_0 0x0002
|
|
+ #define TX_SYS0_ACR_CTS_0_AUDIO_CTS_BYTE0 GENMASK(7, 0)
|
|
+#define TX_SYS0_ACR_CTS_1 0x0003
|
|
+ #define TX_SYS0_ACR_CTS_1_AUDIO_CTS_BYTE1 GENMASK(7, 0)
|
|
+#define TX_SYS0_ACR_CTS_2 0x0004
|
|
+ #define TX_SYS0_ACR_CTS_2_FORCE_ARC_STABLE BIT(5)
|
|
+#define TX_SYS0_BIST_CONTROL 0x0005
|
|
+ #define TX_SYS0_BIST_CONTROL_AFE_BIST_ENABLE BIT(7)
|
|
+ #define TX_SYS0_BIST_CONTROL_TMDS_SHIFT_PATTERN_SELECT BIT(6)
|
|
+ #define TX_SYS0_BIST_CONTROL_TMDS_PRBS_PATTERN_SELECT GENMASK(5, 4)
|
|
+ #define TX_SYS0_BIST_CONTROL_TMDS_REPEAT_BIST_PATTERN GENMASK(2, 0)
|
|
+
|
|
+#define TX_SYS0_BIST_DATA_0 0x0006
|
|
+#define TX_SYS0_BIST_DATA_1 0x0007
|
|
+#define TX_SYS0_BIST_DATA_2 0x0008
|
|
+#define TX_SYS0_BIST_DATA_3 0x0009
|
|
+#define TX_SYS0_BIST_DATA_4 0x000A
|
|
+#define TX_SYS0_BIST_DATA_5 0x000B
|
|
+#define TX_SYS0_BIST_DATA_6 0x000C
|
|
+#define TX_SYS0_BIST_DATA_7 0x000D
|
|
+#define TX_SYS0_BIST_DATA_8 0x000E
|
|
+#define TX_SYS0_BIST_DATA_9 0x000F
|
|
+
|
|
+// system config 1
|
|
+#define TX_HDMI_PHY_CONFIG0 0x0010
|
|
+ #define TX_HDMI_PHY_CONFIG0_HDMI_COMMON_B7_B0 GENMASK(7, 0)
|
|
+#define TX_HDMI_PHY_CONFIG1 0x0010
|
|
+ #define TX_HDMI_PHY_CONFIG1_HDMI_COMMON_B11_B8 GENMASK(3, 0)
|
|
+ #define TX_HDMI_PHY_CONFIG1_HDMI_CTL_REG_B3_B0 GENMASK(7, 4)
|
|
+#define TX_HDMI_PHY_CONFIG2 0x0012
|
|
+ #define TX_HDMI_PHY_CONFIG_HDMI_CTL_REG_B11_B4 GENMASK(7, 0)
|
|
+#define TX_HDMI_PHY_CONFIG3 0x0013
|
|
+ #define TX_HDMI_PHY_CONFIG3_HDMI_L2H_CTL GENMASK(3, 0)
|
|
+ #define TX_HDMI_PHY_CONFIG3_HDMI_MDR_PU GENMASK(7, 4)
|
|
+#define TX_HDMI_PHY_CONFIG4 0x0014
|
|
+ #define TX_HDMI_PHY_CONFIG4_HDMI_LF_PD BIT(0)
|
|
+ #define TX_HDMI_PHY_CONFIG4_HDMI_PHY_CLK_EN BIT(1)
|
|
+ #define TX_HDMI_PHY_CONFIG4_HDMI_MODE GENMASK(3, 2)
|
|
+ #define TX_HDMI_PHY_CONFIG4_HDMI_MODE_NORMAL 0x0
|
|
+ #define TX_HDMI_PHY_CONFIG4_HDMI_MODE_CLK_CH3_EQUAL_CH0 0x1
|
|
+ #define TX_HDMI_PHY_CONFIG4_HDMI_MODE_ALTERNATE_HIGH_LOW 0x2
|
|
+ #define TX_HDMI_PHY_CONFIG4_HDMI_MODE_ALTERNATE_LOW_HIGH 0x3
|
|
+ #define TX_HDMI_PHY_CONFIG4_HDMI_PREM_CTL GENMASK(7, 4)
|
|
+#define TX_HDMI_PHY_CONFIG5 0x0015
|
|
+ #define TX_HDMI_PHY_CONFIG5_HDMI_VCM_CTL GENMASK(7, 5)
|
|
+ #define TX_HDMI_PHY_CONFIG5_HDMI_PREFCTL GENMASK(2, 0)
|
|
+#define TX_HDMI_PHY_CONFIG6 0x0016
|
|
+ #define TX_HDMI_PHY_CONFIG6_HDMI_RTERM_CTL GENMASK(3, 0)
|
|
+ #define TX_HDMI_PHY_CONFIG6_HDMI_SWING_CTL GENMASK(7, 4)
|
|
+#define TX_SYS1_AFE_TEST 0x0017
|
|
+#define TX_SYS1_PLL 0x0018
|
|
+#define TX_SYS1_TUNE 0x0019
|
|
+#define TX_SYS1_AFE_CONNECT 0x001A
|
|
+
|
|
+#define TX_SYS1_ACR_N_0 0x001C
|
|
+ #define TX_SYS1_ACR_N_0_N_BYTE0 GENMASK(7, 0)
|
|
+#define TX_SYS1_ACR_N_1 0x001D
|
|
+ #define TX_SYS1_ACR_N_1_N_BYTE1 GENMASK(7, 0)
|
|
+#define TX_SYS1_ACR_N_2 0x001E
|
|
+ #define TX_SYS1_ACR_N_2_N_MEAS_TOLERANCE GENMASK(7, 4)
|
|
+ #define TX_SYS1_ACR_N_2_N_UPPER_NIBBLE GENMASK(3, 0)
|
|
+#define TX_SYS1_PRBS_DATA 0x001F
|
|
+ #define TX_SYS1_PRBS_DATA_PRBS_MODE GENMASK(1, 0)
|
|
+ #define TX_SYS1_PRBS_DATA_PRBS_MODE_11 0x0
|
|
+ #define TX_SYS1_PRBS_DATA_PRBS_MODE_15 0x1
|
|
+ #define TX_SYS1_PRBS_DATA_PRBS_MODE_7 0x2
|
|
+ #define TX_SYS1_PRBS_DATA_PRBS_MODE_31 0x3
|
|
+
|
|
+// HDCP CONFIG
|
|
+#define TX_HDCP_ECC_CONFIG 0x0024
|
|
+#define TX_HDCP_CRC_CONFIG 0x0025
|
|
+#define TX_HDCP_EDID_CONFIG 0x0026
|
|
+ #define TX_HDCP_EDID_CONFIG_FORCED_SYS_TRIGGER BIT(7)
|
|
+ #define TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG BIT(6)
|
|
+ #define TX_HDCP_EDID_CONFIG_MEM_ACC_SEQ_MODE BIT(5)
|
|
+ #define TX_HDCP_EDID_CONFIG_MEM_ACC_SEQ_START BIT(4)
|
|
+ #define TX_HDCP_EDID_CONFIG_FORCED_MEM_COPY_DONE BIT(3)
|
|
+ #define TX_HDCP_EDID_CONFIG_MEM_COPY_DONE_CONFIG BIT(2)
|
|
+ #define TX_HDCP_EDID_CONFIG_SYS_TRIGGER_CONFIG_SEMI_MANU BIT(1)
|
|
+
|
|
+#define TX_HDCP_MEM_CONFIG 0x0027
|
|
+ #define TX_HDCP_MEM_CONFIG_READ_DECRYPT BIT(3)
|
|
+
|
|
+#define TX_HDCP_HPD_FILTER_L 0x0028
|
|
+#define TX_HDCP_HPD_FILTER_H 0x0029
|
|
+#define TX_HDCP_ENCRYPT_BYTE 0x002A
|
|
+#define TX_HDCP_CONFIG0 0x002B
|
|
+ #define TX_HDCP_CONFIG0_ROM_ENCRYPT_OFF GENMASK(4, 3)
|
|
+
|
|
+#define TX_HDCP_CONFIG1 0x002C
|
|
+#define TX_HDCP_CONFIG2 0x002D
|
|
+#define TX_HDCP_CONFIG3 0x002E
|
|
+ #define TX_HDCP_CONFIG3_DDC_I2C_BUS_CLOCK_TIME_DIVIDER GENMASK(7, 0)
|
|
+
|
|
+#define TX_HDCP_MODE 0x002F
|
|
+ #define TX_HDCP_MODE_CP_DESIRED BIT(7)
|
|
+ #define TX_HDCP_MODE_ESS_CONFIG BIT(6)
|
|
+ #define TX_HDCP_MODE_SET_AVMUTE BIT(5)
|
|
+ #define TX_HDCP_MODE_CLEAR_AVMUTE BIT(4)
|
|
+ #define TX_HDCP_MODE_HDCP_1_1 BIT(3)
|
|
+ #define TX_HDCP_MODE_VSYNC_HSYNC_FORCED_POLARITY_SELECT BIT(2)
|
|
+ #define TX_HDCP_MODE_FORCED_VSYNC_POLARITY BIT(1)
|
|
+ #define TX_HDCP_MODE_FORCED_HSYNC_POLARITY BIT(0)
|
|
+
|
|
+// Video config, part 1
|
|
+#define TX_VIDEO_ACTIVE_PIXELS_0 0x0030
|
|
+#define TX_VIDEO_ACTIVE_PIXELS_1 0x0031
|
|
+#define TX_VIDEO_FRONT_PIXELS 0x0032
|
|
+#define TX_VIDEO_HSYNC_PIXELS 0x0033
|
|
+#define TX_VIDEO_BACK_PIXELS 0x0034
|
|
+#define TX_VIDEO_ACTIVE_LINES_0 0x0035
|
|
+#define TX_VIDEO_ACTIVE_LINES_1 0x0036
|
|
+#define TX_VIDEO_EOF_LINES 0x0037
|
|
+#define TX_VIDEO_VSYNC_LINES 0x0038
|
|
+#define TX_VIDEO_SOF_LINES 0x0039
|
|
+#define TX_VIDEO_DTV_TIMING 0x003A
|
|
+ #define TX_VIDEO_DTV_TIMING_FORCE_DTV_TIMING_AUTO BIT(7)
|
|
+ #define TX_VIDEO_DTV_TIMING_FORCE_VIDEO_SCAN BIT(6)
|
|
+ #define TX_VIDEO_DTV_TIMING_FORCE_VIDEO_FIELD BIT(5)
|
|
+ #define TX_VIDEO_DTV_TIMING_DISABLE_VIC39_CORRECTION BIT(4)
|
|
+
|
|
+#define TX_VIDEO_DTV_MODE 0x003B
|
|
+ #define TX_VIDEO_DTV_MODE_FORCED_DEFAULT_PHASE BIT(7)
|
|
+ #define TX_VIDEO_DTV_MODE_COLOR_DEPTH GENMASK(1, 0)
|
|
+
|
|
+#define TX_VIDEO_DTV_FORMAT0 0x003C
|
|
+#define TX_VIDEO_DTV_FORMAT1 0x003D
|
|
+#define TX_VIDEO_PIXEL_PACK 0x003F
|
|
+// video config, part 2
|
|
+#define TX_VIDEO_CSC_COEFF_B0 0x0040
|
|
+#define TX_VIDEO_CSC_COEFF_B1 0x0041
|
|
+#define TX_VIDEO_CSC_COEFF_R0 0x0042
|
|
+#define TX_VIDEO_CSC_COEFF_R1 0x0043
|
|
+#define TX_VIDEO_CSC_COEFF_CB0 0x0044
|
|
+#define TX_VIDEO_CSC_COEFF_CB1 0x0045
|
|
+#define TX_VIDEO_CSC_COEFF_CR0 0x0046
|
|
+#define TX_VIDEO_CSC_COEFF_CR1 0x0047
|
|
+#define TX_VIDEO_DTV_OPTION_L 0x0048
|
|
+ #define TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_FORMAT GENMASK(7, 6)
|
|
+ #define TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_FORMAT GENMASK(5, 4)
|
|
+ #define TX_VIDEO_DTV_OPTION_L_OUTPUT_COLOR_DEPTH GENMASK(3, 2)
|
|
+ #define TX_VIDEO_DTV_OPTION_L_INPUT_COLOR_DEPTH GENMASK(1, 0)
|
|
+
|
|
+#define TX_VIDEO_DTV_OPTION_H 0x0049
|
|
+ #define TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_16_235 0x0
|
|
+ #define TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_16_240 0x1
|
|
+ #define TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_1_254 0x2
|
|
+ #define TX_VIDEO_DTV_OPTION_H_COLOR_RANGE_0_255 0x3
|
|
+ #define TX_VIDEO_DTV_OPTION_H_OUTPUT_COLOR_RANGE GENMASK(3, 2)
|
|
+ #define TX_VIDEO_DTV_OPTION_H_INPUT_COLOR_RANGE GENMASK(1, 0)
|
|
+
|
|
+#define TX_VIDEO_DTV_FILTER 0x004A
|
|
+#define TX_VIDEO_DTV_DITHER 0x004B
|
|
+#define TX_VIDEO_DTV_DEDITHER 0x004C
|
|
+#define TX_VIDEO_PROC_CONFIG0 0x004E
|
|
+#define TX_VIDEO_PROC_CONFIG1 0x004F
|
|
+
|
|
+// Audio config
|
|
+#define TX_AUDIO_FORMAT 0x0058
|
|
+ #define TX_AUDIO_FORMAT_SPDIF_OR_I2S BIT(7)
|
|
+ #define TX_AUDIO_FORMAT_I2S_2_OR_8_CH BIT(6)
|
|
+ #define TX_AUDIO_FORMAT_I2S_FORMAT GENMASK(5, 4)
|
|
+ #define TX_AUDIO_FORMAT_BIT_WIDTH_MASK GENMASK(3, 2)
|
|
+ #define TX_AUDIO_FORMAT_BIT_WIDTH_16 0x1
|
|
+ #define TX_AUDIO_FORMAT_BIT_WIDTH_20 0x2
|
|
+ #define TX_AUDIO_FORMAT_BIT_WIDTH_24 0x3
|
|
+ #define TX_AUDIO_FORMAT_WS_POLARITY BIT(1)
|
|
+ #define TX_AUDIO_FORMAT_I2S_ONE_BIT_OR_I2S BIT(0)
|
|
+ #define TX_AUDIO_FORMAT_SPDIF_CHANNEL_STATUS_FROM_DATA_OR_REG BIT(0)
|
|
+
|
|
+#define TX_AUDIO_SPDIF 0x0059
|
|
+ #define TX_AUDIO_SPDIF_ENABLE BIT(0)
|
|
+#define TX_AUDIO_I2S 0x005A
|
|
+ #define TX_AUDIO_I2S_ENABLE BIT(0)
|
|
+#define TX_AUDIO_FIFO 0x005B
|
|
+ #define TX_AUDIO_FIFO_FIFO_DEPTH_MASK GENMASK(7, 4)
|
|
+ #define TX_AUDIO_FIFO_FIFO_DEPTH_512 0x4
|
|
+ #define TX_AUDIO_FIFO_CRITICAL_THRESHOLD_MASK GENMASK(3, 2)
|
|
+ #define TX_AUDIO_FIFO_CRITICAL_THRESHOLD_DEPTH_DIV16 0x2
|
|
+ #define TX_AUDIO_FIFO_NORMAL_THRESHOLD_MASK GENMASK(1, 0)
|
|
+ #define TX_AUDIO_FIFO_NORMAL_THRESHOLD_DEPTH_DIV8 0x1
|
|
+#define TX_AUDIO_LIPSYNC 0x005C
|
|
+ #define TX_AUDIO_LIPSYNC_NORMALIZED_LIPSYNC_PARAM GENMASK(7, 0)
|
|
+#define TX_AUDIO_CONTROL 0x005D
|
|
+ #define TX_AUDIO_CONTROL_FORCED_AUDIO_FIFO_CLEAR BIT(7)
|
|
+ #define TX_AUDIO_CONTROL_AUTO_AUDIO_FIFO_CLEAR BIT(6)
|
|
+ #define TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_MASK GENMASK(5, 4)
|
|
+ #define TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_AUDIO_SAMPLE_PACKET 0x0
|
|
+ #define TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_ONE_BIT_AUDIO 0x1
|
|
+ #define TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_HBR_AUDIO_PACKET 0x2
|
|
+ #define TX_AUDIO_CONTROL_AUDIO_PACKET_TYPE_DST_AUDIO_PACKET 0x3
|
|
+ #define TX_AUDIO_CONTROL_AUDIO_SAMPLE_PACKET_VALID BIT(2)
|
|
+ #define TX_AUDIO_CONTROL_AUDIO_SAMPLE_PACKET_USER BIT(1)
|
|
+ #define TX_AUDIO_CONTROL_AUDIO_SAMPLE_PACKET_FLAT BIT(0)
|
|
+#define TX_AUDIO_HEADER 0x005E
|
|
+ #define TX_AUDIO_HEADER_AUDIO_SAMPLE_PACKET_HEADER_LAYOUT1 BIT(7)
|
|
+ #define TX_AUDIO_HEADER_SET_NORMAL_DOUBLE_IN_DST_PACKET_HEADER BIT(6)
|
|
+#define TX_AUDIO_SAMPLE 0x005F
|
|
+ #define TX_AUDIO_SAMPLE_CHANNEL_VALID GENMASK(7, 0)
|
|
+#define TX_AUDIO_VALID 0x0060
|
|
+#define TX_AUDIO_USER 0x0061
|
|
+#define TX_AUDIO_PACK 0x0062
|
|
+ #define TX_AUDIO_PACK_AUDIO_SAMPLE_PACKETS_ENABLE BIT(0)
|
|
+#define TX_AUDIO_CONTROL_MORE 0x0064
|
|
+ #define TX_AUDIO_CONTROL_MORE_ENABLE BIT(0)
|
|
+
|
|
+// tmds config
|
|
+#define TX_TMDS_MODE 0x0068
|
|
+ #define TX_TMDS_MODE_FORCED_HDMI BIT(7)
|
|
+ #define TX_TMDS_MODE_HDMI_CONFIG BIT(6)
|
|
+ #define TX_TMDS_MODE_BIT_SWAP BIT(3)
|
|
+ #define TX_TMDS_MODE_CHANNEL_SWAP GENMASK(2, 0)
|
|
+
|
|
+#define TX_TMDS_CONFIG0 0x006C
|
|
+#define TX_TMDS_CONFIG1 0x006D
|
|
+
|
|
+// packet config
|
|
+#define TX_PACKET_ALLOC_ACTIVE_1 0x0078
|
|
+#define TX_PACKET_ALLOC_ACTIVE_2 0x0079
|
|
+#define TX_PACKET_ALLOC_EOF_1 0x007A
|
|
+#define TX_PACKET_ALLOC_EOF_2 0x007B
|
|
+#define TX_PACKET_ALLOC_SOF_1 0x007C
|
|
+#define TX_PACKET_ALLOC_SOF_2 0x007D
|
|
+#define TX_PACKET_CONTROL_1 0x007E
|
|
+ #define TX_PACKET_CONTROL_1_FORCE_PACKET_TIMING BIT(7)
|
|
+ #define TX_PACKET_CONTROL_1_PACKET_ALLOC_MODE BIT(6)
|
|
+ #define TX_PACKET_CONTROL_1_PACKET_START_LATENCY GENMASK(5, 0)
|
|
+
|
|
+#define TX_PACKET_CONTROL_2 0x007F
|
|
+ #define TX_PACKET_CONTROL_2_AUDIO_REQUEST_DISABLE BIT(3)
|
|
+ #define TX_PACKET_CONTROL_2_HORIZONTAL_GC_PACKET_TRANSPORT_EN BIT(1)
|
|
+
|
|
+#define TX_CORE_EDID_CONFIG_MORE 0x0080
|
|
+ #define TX_CORE_EDID_CONFIG_MORE_KEEP_EDID_ERROR BIT(1)
|
|
+ #define TX_CORE_EDID_CONFIG_MORE_SYS_TRIGGER_CONFIG_SEMI_MANU BIT(0)
|
|
+
|
|
+#define TX_CORE_ALLOC_VSYNC_0 0x0081
|
|
+#define TX_CORE_ALLOC_VSYNC_1 0x0082
|
|
+#define TX_CORE_ALLOC_VSYNC_2 0x0083
|
|
+#define TX_MEM_PD_REG0 0x0084
|
|
+
|
|
+// core config
|
|
+#define TX_CORE_DATA_CAPTURE_1 0x00F0
|
|
+#define TX_CORE_DATA_CAPTURE_2 0x00F1
|
|
+ #define TX_CORE_DATA_CAPTURE_2_AUDIO_SOURCE_SELECT GENMASK(7, 6)
|
|
+ #define TX_CORE_DATA_CAPTURE_2_EXTERNAL_PACKET_ENABLE BIT(5)
|
|
+ #define TX_CORE_DATA_CAPTURE_2_INTERNAL_PACKET_ENABLE BIT(4)
|
|
+ #define TX_CORE_DATA_CAPTURE_2_AFE_FIFO_SRC_LANE1 GENMASK(3, 2)
|
|
+ #define TX_CORE_DATA_CAPTURE_2_AFE_FIFO_SRC_LANE0 GENMASK(1, 0)
|
|
+
|
|
+#define TX_CORE_DATA_MONITOR_1 0x00F2
|
|
+ #define TX_CORE_DATA_MONITOR_1_LANE1 BIT(7)
|
|
+ #define TX_CORE_DATA_MONITOR_1_SELECT_LANE1 GENMASK(6, 4)
|
|
+ #define TX_CORE_DATA_MONITOR_1_LANE0 BIT(3)
|
|
+ #define TX_CORE_DATA_MONITOR_1_SELECT_LANE0 GENMASK(2, 0)
|
|
+
|
|
+#define TX_CORE_DATA_MONITOR_2 0x00F3
|
|
+ #define TX_CORE_DATA_MONITOR_2_MONITOR_SELECT GENMASK(2, 0)
|
|
+
|
|
+#define TX_CORE_CALIB_MODE 0x00F4
|
|
+#define TX_CORE_CALIB_SAMPLE_DELAY 0x00F5
|
|
+#define TX_CORE_CALIB_VALUE_AUTO 0x00F6
|
|
+#define TX_CORE_CALIB_VALUE 0x00F7
|
|
+
|
|
+// system config 4
|
|
+#define TX_SYS4_TX_CKI_DDR 0x00A0
|
|
+#define TX_SYS4_TX_CKO_DDR 0x00A1
|
|
+#define TX_SYS4_RX_CKI_DDR 0x00A2
|
|
+#define TX_SYS4_RX_CKO_DDR 0x00A3
|
|
+#define TX_SYS4_CONNECT_SEL_0 0x00A4
|
|
+#define TX_SYS4_CONNECT_SEL_1 0x00A5
|
|
+ #define TX_SYS4_CONNECT_SEL_1_TX_CONNECT_SEL_UPPER_CHANNEL_DATA BIT(6)
|
|
+
|
|
+#define TX_SYS4_CONNECT_SEL_2 0x00A6
|
|
+#define TX_SYS4_CONNECT_SEL_3 0x00A7
|
|
+#define TX_SYS4_CK_INV_VIDEO 0x00A8
|
|
+ #define TX_SYS4_CK_INV_VIDEO_TMDS_CLK_PATTERN BIT(4)
|
|
+#define TX_SYS4_CK_INV_AUDIO 0x00A9
|
|
+#define TX_SYS4_CK_INV_AFE 0x00AA
|
|
+#define TX_SYS4_CK_INV_CH01 0x00AB
|
|
+#define TX_SYS4_CK_INV_CH2 0x00AC
|
|
+#define TX_SYS4_CK_CEC 0x00AD
|
|
+#define TX_SYS4_CK_SOURCE_1 0x00AE
|
|
+#define TX_SYS4_CK_SOURCE_2 0x00AF
|
|
+
|
|
+#define TX_IEC60958_SUB1_OFFSET 0x00B0
|
|
+#define TX_IEC60958_SUB2_OFFSET 0x00C8
|
|
+
|
|
+// system config 5
|
|
+#define TX_SYS5_TX_SOFT_RESET_1 0x00E0
|
|
+ #define TX_SYS5_TX_SOFT_RESET_1_TX_PIXEL_RSTN BIT(7)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_1_TX_TMDS_RSTN BIT(6)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_MASTER_RSTN BIT(5)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_1_TX_AUDIO_RESAMPLE_RSTN BIT(4)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_1_TX_I2S_RESET_RSTN BIT(3)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH2 BIT(2)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH1 BIT(1)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_1_TX_DIG_RESET_N_CH0 BIT(0)
|
|
+
|
|
+#define TX_SYS5_TX_SOFT_RESET_2 0x00E1
|
|
+ #define TX_SYS5_TX_SOFT_RESET_2_HDMI_CH3_RST_IN BIT(7)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_2_HDMI_CH2_RST_IN BIT(6)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_2_HDMI_CH1_RST_IN BIT(5)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_2_HDMI_CH0_RST_IN BIT(4)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_2_HDMI_SR_RST BIT(3)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_2_TX_DDC_HDCP_RSTN BIT(2)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_2_TX_DDC_EDID_RSTN BIT(1)
|
|
+ #define TX_SYS5_TX_SOFT_RESET_2_TX_DIG_RESET_N_CH3 BIT(0)
|
|
+
|
|
+#define TX_SYS5_RX_SOFT_RESET_1 0x00E2
|
|
+#define TX_SYS5_RX_SOFT_RESET_2 0x00E3
|
|
+#define TX_SYS5_RX_SOFT_RESET_3 0x00E4
|
|
+#define TX_SYS5_SSTL_BIDIR_IN 0x00E5
|
|
+#define TX_SYS5_SSTL_IN 0x00E6
|
|
+#define TX_SYS5_SSTL_DIFF_IN 0x00E7
|
|
+#define TX_SYS5_FIFO_CONFIG 0x00E8
|
|
+ #define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL2_BYPASS BIT(6)
|
|
+ #define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL1_BYPASS BIT(5)
|
|
+ #define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL0_BYPASS BIT(4)
|
|
+ #define TX_SYS5_FIFO_CONFIG_CLK_CHANNEL3_OUTPUT_ENABLE BIT(3)
|
|
+ #define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL2_ENABLE BIT(2)
|
|
+ #define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL1_ENABLE BIT(1)
|
|
+ #define TX_SYS5_FIFO_CONFIG_AFE_FIFO_CHANNEL0_ENABLE BIT(0)
|
|
+
|
|
+#define TX_SYS5_FIFO_SAMP01_CFG 0x00E9
|
|
+#define TX_SYS5_FIFO_SAMP23_CFG 0x00EA
|
|
+#define TX_SYS5_CONNECT_FIFO_CFG 0x00EB
|
|
+#define TX_SYS5_IO_CALIB_CONTROL 0x00EC
|
|
+#define TX_SYS5_SSTL_BIDIR_OUT 0x00ED
|
|
+#define TX_SYS5_SSTL_OUT 0x00EE
|
|
+#define TX_SYS5_SSTL_DIFF_OUT 0x00EF
|
|
+
|
|
+// HDCP shadow register
|
|
+#define TX_HDCP_SHW_BKSV_0 0x0100
|
|
+#define TX_HDCP_SHW_BKSV_1 0x0101
|
|
+#define TX_HDCP_SHW_BKSV_2 0x0102
|
|
+#define TX_HDCP_SHW_BKSV_3 0x0103
|
|
+#define TX_HDCP_SHW_BKSV_4 0x0104
|
|
+#define TX_HDCP_SHW_RI1_0 0x0108
|
|
+#define TX_HDCP_SHW_RI1_1 0x0109
|
|
+#define TX_HDCP_SHW_PJ1 0x010A
|
|
+#define TX_HDCP_SHW_AKSV_0 0x0110
|
|
+#define TX_HDCP_SHW_AKSV_1 0x0111
|
|
+#define TX_HDCP_SHW_AKSV_2 0x0112
|
|
+#define TX_HDCP_SHW_AKSV_3 0x0113
|
|
+#define TX_HDCP_SHW_AKSV_4 0x0114
|
|
+#define TX_HDCP_SHW_AINFO 0x0115
|
|
+#define TX_HDCP_SHW_AN_0 0x0118
|
|
+#define TX_HDCP_SHW_AN_1 0x0119
|
|
+#define TX_HDCP_SHW_AN_2 0x011A
|
|
+#define TX_HDCP_SHW_AN_3 0x011B
|
|
+#define TX_HDCP_SHW_AN_4 0x011C
|
|
+#define TX_HDCP_SHW_AN_5 0x011D
|
|
+#define TX_HDCP_SHW_AN_6 0x011E
|
|
+#define TX_HDCP_SHW_AN_7 0x011F
|
|
+#define TX_HDCP_SHW_V1_H0_0 0x0120
|
|
+#define TX_HDCP_SHW_V1_H0_1 0x0121
|
|
+#define TX_HDCP_SHW_V1_H0_2 0x0122
|
|
+#define TX_HDCP_SHW_V1_H0_3 0x0123
|
|
+#define TX_HDCP_SHW_V1_H1_0 0x0124
|
|
+#define TX_HDCP_SHW_V1_H1_1 0x0125
|
|
+#define TX_HDCP_SHW_V1_H1_2 0x0126
|
|
+#define TX_HDCP_SHW_V1_H1_3 0x0127
|
|
+#define TX_HDCP_SHW_V1_H2_0 0x0128
|
|
+#define TX_HDCP_SHW_V1_H2_1 0x0129
|
|
+#define TX_HDCP_SHW_V1_H2_2 0x012A
|
|
+#define TX_HDCP_SHW_V1_H2_3 0x012B
|
|
+#define TX_HDCP_SHW_V1_H3_0 0x012C
|
|
+#define TX_HDCP_SHW_V1_H3_1 0x012D
|
|
+#define TX_HDCP_SHW_V1_H3_2 0x012E
|
|
+#define TX_HDCP_SHW_V1_H3_3 0x012F
|
|
+#define TX_HDCP_SHW_V1_H4_0 0x0130
|
|
+#define TX_HDCP_SHW_V1_H4_1 0x0131
|
|
+#define TX_HDCP_SHW_V1_H4_2 0x0132
|
|
+#define TX_HDCP_SHW_V1_H4_3 0x0133
|
|
+#define TX_HDCP_SHW_BCAPS 0x0140
|
|
+#define TX_HDCP_SHW_BSTATUS_0 0x0141
|
|
+#define TX_HDCP_SHW_BSTATUS_1 0x0142
|
|
+#define TX_HDCP_SHW_KSV_FIFO 0x0143
|
|
+
|
|
+// system status 0
|
|
+#define TX_SYSST0_CONNECT_FIFO 0x0180
|
|
+#define TX_SYSST0_PLL_MONITOR 0x0181
|
|
+#define TX_SYSST0_AFE_FIFO 0x0182
|
|
+#define TX_SYSST0_ROM_STATUS 0x018F
|
|
+
|
|
+// hdcp status
|
|
+#define TX_HDCP_ST_AUTHENTICATION 0x0190
|
|
+#define TX_HDCP_ST_FRAME_COUNT 0x0191
|
|
+#define TX_HDCP_ST_STATUS_0 0x0192
|
|
+#define TX_HDCP_ST_STATUS_1 0x0193
|
|
+#define TX_HDCP_ST_STATUS_2 0x0194
|
|
+#define TX_HDCP_ST_STATUS_3 0x0195
|
|
+#define TX_HDCP_ST_EDID_STATUS 0x0196
|
|
+ #define TX_HDCP_ST_EDID_STATUS_SYSTEM_STATUS GENMASK(7, 6)
|
|
+ #define TX_HDCP_ST_EDID_STATUS_SYSTEM_STATUS_NO_SINK_ATTACHED 0x0
|
|
+ #define TX_HDCP_ST_EDID_STATUS_SYSTEM_STATUS_READING_EDID 0x1
|
|
+ #define TX_HDCP_ST_EDID_STATUS_SYSTEM_STATUS_DVI_MODE 0x2
|
|
+ #define TX_HDCP_ST_EDID_STATUS_SYSTEM_STATUS_HDMI_MODE 0x3
|
|
+ #define TX_HDCP_ST_EDID_STATUS_EDID_DATA_READY BIT(4)
|
|
+ #define TX_HDCP_ST_EDID_STATUS_HPD_STATUS BIT(1)
|
|
+
|
|
+#define TX_HDCP_ST_MEM_STATUS 0x0197
|
|
+#define TX_HDCP_ST_ST_MODE 0x019F
|
|
+
|
|
+// video status
|
|
+#define TX_VIDEO_ST_ACTIVE_PIXELS_1 0x01A0
|
|
+#define TX_VIDEO_ST_ACTIVE_PIXELS_2 0x01A1
|
|
+#define TX_VIDEO_ST_FRONT_PIXELS 0x01A2
|
|
+#define TX_VIDEO_ST_HSYNC_PIXELS 0x01A3
|
|
+#define TX_VIDEO_ST_BACK_PIXELS 0x01A4
|
|
+#define TX_VIDEO_ST_ACTIVE_LINES_1 0x01A5
|
|
+#define TX_VIDEO_ST_ACTIVE_LINES_2 0x01A6
|
|
+#define TX_VIDEO_ST_EOF_LINES 0x01A7
|
|
+#define TX_VIDEO_ST_VSYNC_LINES 0x01A8
|
|
+#define TX_VIDEO_ST_SOF_LINES 0x01A9
|
|
+#define TX_VIDEO_ST_DTV_TIMING 0x01AA
|
|
+#define TX_VIDEO_ST_DTV_MODE 0x01AB
|
|
+// audio status
|
|
+#define TX_VIDEO_ST_AUDIO_STATUS 0x01AC
|
|
+#define TX_AFE_STATUS_0 0x01AE
|
|
+#define TX_AFE_STATUS_1 0x01AF
|
|
+
|
|
+#define TX_IEC60958_ST_SUB1_OFFSET 0x01B0
|
|
+#define TX_IEC60958_ST_SUB2_OFFSET 0x01C8
|
|
+
|
|
+// system status 1
|
|
+#define TX_SYSST1_CALIB_BIT_RESULT_0 0x01E0
|
|
+#define TX_SYSST1_CALIB_BIT_RESULT_1 0x01E1
|
|
+//HDMI_STATUS_OUT[7:0]
|
|
+#define TX_HDMI_PHY_READBACK_0 0x01E2
|
|
+//HDMI_COMP_OUT[4]
|
|
+//HDMI_STATUS_OUT[11:8]
|
|
+#define TX_HDMI_PHY_READBACK_1 0x01E3
|
|
+#define TX_SYSST1_CALIB_BIT_RESULT_4 0x01E4
|
|
+#define TX_SYSST1_CALIB_BIT_RESULT_5 0x01E5
|
|
+#define TX_SYSST1_CALIB_BIT_RESULT_6 0x01E6
|
|
+#define TX_SYSST1_CALIB_BIT_RESULT_7 0x01E7
|
|
+#define TX_SYSST1_CALIB_BUS_RESULT_0 0x01E8
|
|
+#define TX_SYSST1_CALIB_BUS_RESULT_1 0x01E9
|
|
+#define TX_SYSST1_CALIB_BUS_RESULT_2 0x01EA
|
|
+#define TX_SYSST1_CALIB_BUS_RESULT_3 0x01EB
|
|
+#define TX_SYSST1_CALIB_BUS_RESULT_4 0x01EC
|
|
+#define TX_SYSST1_CALIB_BUS_RESULT_5 0x01ED
|
|
+#define TX_SYSST1_CALIB_BUS_RESULT_6 0x01EE
|
|
+#define TX_SYSST1_CALIB_BUS_RESULT_7 0x01EF
|
|
+
|
|
+// Packet status
|
|
+#define TX_PACKET_ST_REQUEST_STATUS_1 0x01F0
|
|
+#define TX_PACKET_ST_REQUEST_STATUS_2 0x01F1
|
|
+#define TX_PACKET_ST_REQUEST_MISSED_1 0x01F2
|
|
+#define TX_PACKET_ST_REQUEST_MISSED_2 0x01F3
|
|
+#define TX_PACKET_ST_ENCODE_STATUS_0 0x01F4
|
|
+#define TX_PACKET_ST_ENCODE_STATUS_1 0x01F5
|
|
+#define TX_PACKET_ST_ENCODE_STATUS_2 0x01F6
|
|
+#define TX_PACKET_ST_TIMER_STATUS 0x01F7
|
|
+
|
|
+// tmds status
|
|
+#define TX_TMDS_ST_CLOCK_METER_1 0x01F8
|
|
+#define TX_TMDS_ST_CLOCK_METER_2 0x01F9
|
|
+#define TX_TMDS_ST_CLOCK_METER_3 0x01FA
|
|
+#define TX_TMDS_ST_TMDS_STATUS_1 0x01FC
|
|
+#define TX_TMDS_ST_TMDS_STATUS_2 0x01FD
|
|
+#define TX_TMDS_ST_TMDS_STATUS_3 0x01FE
|
|
+#define TX_TMDS_ST_TMDS_STATUS_4 0x01FF
|
|
+
|
|
+// Packet register
|
|
+#define TX_PKT_REG_SPD_INFO_BASE_ADDR 0x0200
|
|
+#define TX_PKT_REG_VEND_INFO_BASE_ADDR 0x0220
|
|
+#define TX_PKT_REG_MPEG_INFO_BASE_ADDR 0x0240
|
|
+#define TX_PKT_REG_AVI_INFO_BASE_ADDR 0x0260
|
|
+#define TX_PKT_REG_AUDIO_INFO_BASE_ADDR 0x0280
|
|
+#define TX_PKT_REG_ACP_INFO_BASE_ADDR 0x02A0
|
|
+#define TX_PKT_REG_ISRC1_BASE_ADDR 0x02C0
|
|
+#define TX_PKT_REG_ISRC2_BASE_ADDR 0x02E0
|
|
+#define TX_PKT_REG_EXCEPT0_BASE_ADDR 0x0300
|
|
+#define TX_PKT_REG_EXCEPT1_BASE_ADDR 0x0320
|
|
+#define TX_PKT_REG_EXCEPT2_BASE_ADDR 0x0340
|
|
+#define TX_PKT_REG_EXCEPT3_BASE_ADDR 0x0360
|
|
+#define TX_PKT_REG_EXCEPT4_BASE_ADDR 0x0380
|
|
+#define TX_PKT_REG_GAMUT_P0_BASE_ADDR 0x03A0
|
|
+#define TX_PKT_REG_GAMUT_P1_1_BASE_ADDR 0x03C0
|
|
+#define TX_PKT_REG_GAMUT_P1_2_BASE_ADDR 0x03E0
|
|
+
|
|
+#define TX_RX_EDID_OFFSET 0x0600
|
|
+
|
|
+/* HDMI OTHER registers */
|
|
+
|
|
+#define HDMI_OTHER_CTRL0 0x8000
|
|
+#define HDMI_OTHER_CTRL1 0x8001
|
|
+ #define HDMI_OTHER_CTRL1_POWER_ON BIT(15)
|
|
+ #define HDMI_OTHER_CTRL1_HDMI_AUDIO_CLOCK_ON BIT(13)
|
|
+
|
|
+#define HDMI_OTHER_STATUS0 0x8002
|
|
+#define HDMI_OTHER_CTRL2 0x8003
|
|
+#define HDMI_OTHER_INTR_MASKN 0x8004
|
|
+ #define HDMI_OTHER_INTR_MASKN_TX_EDID_INT_RISE BIT(2)
|
|
+ #define HDMI_OTHER_INTR_MASKN_TX_HPD_INT_FALL BIT(1)
|
|
+ #define HDMI_OTHER_INTR_MASKN_TX_HPD_INT_RISE BIT(0)
|
|
+
|
|
+#define HDMI_OTHER_INTR_STAT 0x8005
|
|
+ #define HDMI_OTHER_INTR_STAT_EDID_RISING BIT(2)
|
|
+ #define HDMI_OTHER_INTR_STAT_HPD_FALLING BIT(1)
|
|
+ #define HDMI_OTHER_INTR_STAT_HPD_RISING BIT(0)
|
|
+
|
|
+#define HDMI_OTHER_INTR_STAT_CLR 0x8006
|
|
+ #define HDMI_OTHER_INTR_STAT_CLR_EDID_RISING BIT(2)
|
|
+ #define HDMI_OTHER_INTR_STAT_CLR_HPD_FALLING BIT(1)
|
|
+ #define HDMI_OTHER_INTR_STAT_CLR_HPD_RISING BIT(0)
|
|
+
|
|
+#define HDMI_OTHER_AVI_INTR_MASKN0 0x8008
|
|
+#define HDMI_OTHER_AVI_INTR_MASKN1 0x8009
|
|
+#define HDMI_OTHER_RX_AINFO_INTR_MASKN0 0x800a
|
|
+#define HDMI_OTHER_RX_AINFO_INTR_MASKN1 0x800b
|
|
+#define HDMI_OTHER_RX_PACKET_INTR_CLR 0x800c
|
|
+
|
|
+#endif /* __MESON_TRANSWITCH_HDMI_H__ */
|
|
diff --git a/drivers/gpu/drm/meson/meson_vclk.c b/drivers/gpu/drm/meson/meson_vclk.c
|
|
index 2a82119e..a2c1bf1a 100644
|
|
--- a/drivers/gpu/drm/meson/meson_vclk.c
|
|
+++ b/drivers/gpu/drm/meson/meson_vclk.c
|
|
@@ -732,6 +732,11 @@ meson_vclk_dmt_supported_freq(struct meson_drm *priv, unsigned int freq)
|
|
return MODE_CLOCK_HIGH;
|
|
}
|
|
|
|
+ if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
|
|
+ meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
|
|
+ meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2))
|
|
+ return MODE_OK;
|
|
+
|
|
if (meson_hdmi_pll_find_params(priv, freq, &m, &frac, &od))
|
|
return MODE_OK;
|
|
|
|
@@ -784,6 +789,11 @@ meson_vclk_vic_supported_freq(struct meson_drm *priv, unsigned int phy_freq,
|
|
return MODE_CLOCK_HIGH;
|
|
}
|
|
|
|
+ if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
|
|
+ meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
|
|
+ meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2))
|
|
+ return MODE_OK;
|
|
+
|
|
for (i = 0 ; params[i].pixel_freq ; ++i) {
|
|
DRM_DEBUG_DRIVER("i = %d pixel_freq = %d alt = %d\n",
|
|
i, params[i].pixel_freq,
|
|
@@ -1024,6 +1034,128 @@ static void meson_vclk_set(struct meson_drm *priv, unsigned int pll_base_freq,
|
|
regmap_update_bits(priv->hhi, HHI_VID_CLK_CNTL, VCLK_EN, VCLK_EN);
|
|
}
|
|
|
|
+static int meson_vclk_set_rate_exclusive(struct meson_drm *priv,
|
|
+ enum vpu_bulk_clk_id clk_id,
|
|
+ unsigned int rate_khz)
|
|
+{
|
|
+ struct clk *clk = priv->vid_clks[clk_id].clk;
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_set_rate_exclusive(clk, rate_khz * 1000UL);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ priv->vid_clk_rate_exclusive[clk_id] = true;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void meson_vclk_disable_ccf(struct meson_drm *priv)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ /* allow all clocks to be changed in _enable again */
|
|
+ for (i = 0; i < VPU_VID_CLK_NUM; i++) {
|
|
+ if (!priv->vid_clk_rate_exclusive[i])
|
|
+ continue;
|
|
+
|
|
+ clk_rate_exclusive_put(priv->vid_clks[i].clk);
|
|
+ priv->vid_clk_rate_exclusive[i] = false;
|
|
+ }
|
|
+
|
|
+ if (priv->clk_dac_enabled) {
|
|
+ clk_disable(priv->clk_dac);
|
|
+ priv->clk_dac_enabled = false;
|
|
+ }
|
|
+
|
|
+ if (priv->clk_venc_enabled) {
|
|
+ clk_disable(priv->clk_venc);
|
|
+ priv->clk_venc_enabled = false;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int meson_vclk_enable_ccf(struct meson_drm *priv, unsigned int target,
|
|
+ bool hdmi_use_enci, unsigned int phy_freq,
|
|
+ unsigned int dac_freq, unsigned int venc_freq)
|
|
+{
|
|
+ enum vpu_bulk_clk_id venc_clk_id, dac_clk_id;
|
|
+ int ret;
|
|
+
|
|
+ if (target == MESON_VCLK_TARGET_CVBS || hdmi_use_enci)
|
|
+ venc_clk_id = VPU_VID_CLK_CTS_ENCI;
|
|
+ else
|
|
+ venc_clk_id = VPU_VID_CLK_CTS_ENCP;
|
|
+
|
|
+ if (target == MESON_VCLK_TARGET_CVBS)
|
|
+ dac_clk_id = VPU_VID_CLK_CTS_VDAC0;
|
|
+ else
|
|
+ dac_clk_id = VPU_VID_CLK_HDMI_TX_PIXEL;
|
|
+
|
|
+ /*
|
|
+ * The TMDS clock also updates the PLL. Protect the PLL rate so all
|
|
+ * following clocks are derived from the PLL setting which matches the
|
|
+ * TMDS clock.
|
|
+ */
|
|
+ ret = meson_vclk_set_rate_exclusive(priv, VPU_VID_CLK_TMDS, phy_freq);
|
|
+ if (ret) {
|
|
+ dev_err(priv->dev, "Failed to set TMDS clock to %ukHz: %d\n",
|
|
+ phy_freq, ret);
|
|
+ goto out_enable_clocks;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The DAC clock may be derived from a parent of the VENC clock so we
|
|
+ * must protect the VENC clock from changing it's rate. This works
|
|
+ * because the DAC freq can be divided by the VENC clock.
|
|
+ */
|
|
+ ret = meson_vclk_set_rate_exclusive(priv, venc_clk_id, venc_freq);
|
|
+ if (ret) {
|
|
+ dev_warn(priv->dev,
|
|
+ "Failed to set VENC clock to %ukHz while TMDS clock is %ukHz: %d\n",
|
|
+ venc_freq, phy_freq, ret);
|
|
+ goto out_enable_clocks;
|
|
+ }
|
|
+
|
|
+ priv->clk_venc = priv->vid_clks[venc_clk_id].clk;
|
|
+
|
|
+ /*
|
|
+ * after changing any of the VID_PLL_* clocks (which can happen when
|
|
+ * update the VENC clock rate) we need to assert and then de-assert the
|
|
+ * VID_DIVIDER_CNTL_* reset lines.
|
|
+ */
|
|
+ reset_control_bulk_assert(VPU_RESET_VID_PLL_NUM, priv->vid_pll_resets);
|
|
+ reset_control_bulk_deassert(VPU_RESET_VID_PLL_NUM, priv->vid_pll_resets);
|
|
+
|
|
+ ret = meson_vclk_set_rate_exclusive(priv, dac_clk_id, dac_freq);
|
|
+ if (ret) {
|
|
+ dev_warn(priv->dev,
|
|
+ "Failed to set pixel clock to %ukHz while TMDS clock is %ukHz: %d\n",
|
|
+ dac_freq, phy_freq, ret);
|
|
+ goto out_enable_clocks;
|
|
+ }
|
|
+
|
|
+ priv->clk_dac = priv->vid_clks[dac_clk_id].clk;
|
|
+
|
|
+out_enable_clocks:
|
|
+ ret = clk_enable(priv->clk_venc);
|
|
+ if (ret)
|
|
+ dev_err(priv->dev,
|
|
+ "Failed to re-enable the VENC clock at %ukHz: %d\n",
|
|
+ venc_freq, ret);
|
|
+ else
|
|
+ priv->clk_venc_enabled = true;
|
|
+
|
|
+ ret = clk_enable(priv->clk_dac);
|
|
+ if (ret)
|
|
+ dev_err(priv->dev,
|
|
+ "Failed to re-enable the pixel clock at %ukHz: %d\n",
|
|
+ dac_freq, ret);
|
|
+ else
|
|
+ priv->clk_dac_enabled = true;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
void meson_vclk_setup(struct meson_drm *priv, unsigned int target,
|
|
unsigned int phy_freq, unsigned int vclk_freq,
|
|
unsigned int venc_freq, unsigned int dac_freq,
|
|
@@ -1034,6 +1166,20 @@ void meson_vclk_setup(struct meson_drm *priv, unsigned int target,
|
|
unsigned int hdmi_tx_div;
|
|
unsigned int venc_div;
|
|
|
|
+ if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8) ||
|
|
+ meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8B) ||
|
|
+ meson_vpu_is_compatible(priv, VPU_COMPATIBLE_M8M2)) {
|
|
+ /* CVBS video clocks are generated off a 1296MHz base clock */
|
|
+ if (target == MESON_VCLK_TARGET_CVBS)
|
|
+ phy_freq = 1296000;
|
|
+
|
|
+ dev_err(priv->dev, "%s(target: %u, phy: %u, dac: %u, venc: %u, hdmi_use_enci: %u)\n", __func__, target, phy_freq, dac_freq, venc_freq, hdmi_use_enci);
|
|
+ meson_vclk_disable_ccf(priv);
|
|
+ meson_vclk_enable_ccf(priv, target, hdmi_use_enci, phy_freq,
|
|
+ dac_freq, venc_freq);
|
|
+ return;
|
|
+ }
|
|
+
|
|
if (target == MESON_VCLK_TARGET_CVBS) {
|
|
meson_venci_cvbs_clock_config(priv);
|
|
return;
|
|
diff --git a/drivers/gpu/drm/meson/meson_venc.c b/drivers/gpu/drm/meson/meson_venc.c
|
|
index 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 <martin.blumenstingl@googlemail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mod_devicetable.h>
|
|
+#include <linux/phy/phy.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/property.h>
|
|
+#include <linux/mfd/syscon.h>
|
|
+#include <linux/nvmem-consumer.h>
|
|
+
|
|
+#define HHI_VDAC_CNTL0_MESON8 0x2F4 /* 0xbd offset in data sheet */
|
|
+#define HHI_VDAC_CNTL1_MESON8 0x2F8 /* 0xbe offset in data sheet */
|
|
+
|
|
+#define HHI_VDAC_CNTL0_G12A 0x2EC /* 0xbd offset in data sheet */
|
|
+#define HHI_VDAC_CNTL1_G12A 0x2F0 /* 0xbe offset in data sheet */
|
|
+
|
|
+enum phy_meson_cvbs_dac_reg {
|
|
+ MESON_CDAC_CTRL_RESV1,
|
|
+ MESON_CDAC_CTRL_RESV2,
|
|
+ MESON_CDAC_VREF_ADJ,
|
|
+ MESON_CDAC_RL_ADJ,
|
|
+ MESON_CDAC_CLK_PHASE_SEL,
|
|
+ MESON_CDAC_DRIVER_ADJ,
|
|
+ MESON_CDAC_EXT_VREF_EN,
|
|
+ MESON_CDAC_BIAS_C,
|
|
+ MESON_VDAC_CNTL0_RESERVED,
|
|
+ MESON_CDAC_GSW,
|
|
+ MESON_CDAC_PWD,
|
|
+ MESON_VDAC_CNTL1_RESERVED,
|
|
+ MESON_CVBS_DAC_NUM_REGS
|
|
+};
|
|
+
|
|
+struct phy_meson_cvbs_dac_data {
|
|
+ const struct reg_field *reg_fields;
|
|
+ u8 cdac_ctrl_resv2_enable_val;
|
|
+ u8 cdac_vref_adj_enable_val;
|
|
+ u8 cdac_rl_adj_enable_val;
|
|
+ bool disable_ignore_cdac_pwd;
|
|
+ bool needs_cvbs_trimming_nvmem_cell;
|
|
+};
|
|
+
|
|
+struct phy_meson_cvbs_dac_priv {
|
|
+ struct regmap_field *regs[MESON_CVBS_DAC_NUM_REGS];
|
|
+ const struct phy_meson_cvbs_dac_data *data;
|
|
+ u8 cdac_gsw_enable_val;
|
|
+};
|
|
+
|
|
+static const struct reg_field phy_meson8_cvbs_dac_reg_fields[] = {
|
|
+ [MESON_CDAC_CTRL_RESV1] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 0, 7),
|
|
+ [MESON_CDAC_CTRL_RESV2] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 8, 15),
|
|
+ [MESON_CDAC_VREF_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 16, 20),
|
|
+ [MESON_CDAC_RL_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 21, 23),
|
|
+ [MESON_CDAC_CLK_PHASE_SEL] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 24, 24),
|
|
+ [MESON_CDAC_DRIVER_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 25, 25),
|
|
+ [MESON_CDAC_EXT_VREF_EN] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 26, 26),
|
|
+ [MESON_CDAC_BIAS_C] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 27, 27),
|
|
+ [MESON_VDAC_CNTL0_RESERVED] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 28, 31),
|
|
+ [MESON_CDAC_GSW] = REG_FIELD(HHI_VDAC_CNTL1_MESON8, 0, 2),
|
|
+ [MESON_CDAC_PWD] = REG_FIELD(HHI_VDAC_CNTL1_MESON8, 3, 3),
|
|
+ [MESON_VDAC_CNTL1_RESERVED] = REG_FIELD(HHI_VDAC_CNTL1_MESON8, 4, 31),
|
|
+};
|
|
+
|
|
+static const struct reg_field phy_meson_g12a_cvbs_dac_reg_fields[] = {
|
|
+ [MESON_CDAC_CTRL_RESV1] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 0, 7),
|
|
+ [MESON_CDAC_CTRL_RESV2] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 8, 15),
|
|
+ [MESON_CDAC_VREF_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 16, 20),
|
|
+ [MESON_CDAC_RL_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 21, 23),
|
|
+ [MESON_CDAC_CLK_PHASE_SEL] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 24, 24),
|
|
+ [MESON_CDAC_DRIVER_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 25, 25),
|
|
+ [MESON_CDAC_EXT_VREF_EN] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 26, 26),
|
|
+ [MESON_CDAC_BIAS_C] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 27, 27),
|
|
+ [MESON_VDAC_CNTL0_RESERVED] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 28, 31),
|
|
+ [MESON_CDAC_GSW] = REG_FIELD(HHI_VDAC_CNTL1_G12A, 0, 2),
|
|
+ [MESON_CDAC_PWD] = REG_FIELD(HHI_VDAC_CNTL1_G12A, 3, 3),
|
|
+ [MESON_VDAC_CNTL1_RESERVED] = REG_FIELD(HHI_VDAC_CNTL1_G12A, 4, 31),
|
|
+};
|
|
+
|
|
+static const struct phy_meson_cvbs_dac_data phy_meson8_cvbs_dac_data = {
|
|
+ .reg_fields = phy_meson8_cvbs_dac_reg_fields,
|
|
+ .cdac_ctrl_resv2_enable_val = 0x0,
|
|
+ .cdac_vref_adj_enable_val = 0x0,
|
|
+ .cdac_rl_adj_enable_val = 0x0,
|
|
+ .disable_ignore_cdac_pwd = false,
|
|
+ .needs_cvbs_trimming_nvmem_cell = true,
|
|
+};
|
|
+
|
|
+static const struct phy_meson_cvbs_dac_data phy_meson_gxbb_cvbs_dac_data = {
|
|
+ .reg_fields = phy_meson8_cvbs_dac_reg_fields,
|
|
+ .cdac_ctrl_resv2_enable_val = 0x0,
|
|
+ .cdac_vref_adj_enable_val = 0x0,
|
|
+ .cdac_rl_adj_enable_val = 0x0,
|
|
+ .disable_ignore_cdac_pwd = false,
|
|
+ .needs_cvbs_trimming_nvmem_cell = false,
|
|
+};
|
|
+
|
|
+static const struct phy_meson_cvbs_dac_data phy_meson_gxl_cvbs_dac_data = {
|
|
+ .reg_fields = phy_meson8_cvbs_dac_reg_fields,
|
|
+ .cdac_ctrl_resv2_enable_val = 0x0,
|
|
+ .cdac_vref_adj_enable_val = 0xf,
|
|
+ .cdac_rl_adj_enable_val = 0x0,
|
|
+ .disable_ignore_cdac_pwd = false,
|
|
+ .needs_cvbs_trimming_nvmem_cell = false,
|
|
+};
|
|
+
|
|
+static const struct phy_meson_cvbs_dac_data phy_meson_g12a_cvbs_dac_data = {
|
|
+ .reg_fields = phy_meson_g12a_cvbs_dac_reg_fields,
|
|
+ .cdac_ctrl_resv2_enable_val = 0x60,
|
|
+ .cdac_vref_adj_enable_val = 0x10,
|
|
+ .cdac_rl_adj_enable_val = 0x4,
|
|
+ .disable_ignore_cdac_pwd = true,
|
|
+ .needs_cvbs_trimming_nvmem_cell = false,
|
|
+};
|
|
+
|
|
+static int phy_meson_cvbs_dac_power_on(struct phy *phy)
|
|
+{
|
|
+ struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy);
|
|
+
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV1], 0x1);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV2],
|
|
+ priv->data->cdac_ctrl_resv2_enable_val);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_VREF_ADJ],
|
|
+ priv->data->cdac_vref_adj_enable_val);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_RL_ADJ],
|
|
+ priv->data->cdac_rl_adj_enable_val);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_GSW],
|
|
+ priv->cdac_gsw_enable_val);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_PWD], 0x0);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int phy_meson_cvbs_dac_power_off(struct phy *phy)
|
|
+{
|
|
+ struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy);
|
|
+
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV1], 0x0);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV2], 0x0);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_VREF_ADJ], 0x0);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_RL_ADJ], 0x0);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_GSW], 0x0);
|
|
+
|
|
+ if (priv->data->disable_ignore_cdac_pwd)
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_PWD], 0x0);
|
|
+ else
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_PWD], 0x1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int phy_meson_cvbs_dac_init(struct phy *phy)
|
|
+{
|
|
+ struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy);
|
|
+
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_CLK_PHASE_SEL], 0x0);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_DRIVER_ADJ], 0x0);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_EXT_VREF_EN], 0x0);
|
|
+ regmap_field_write(priv->regs[MESON_CDAC_BIAS_C], 0x0);
|
|
+ regmap_field_write(priv->regs[MESON_VDAC_CNTL0_RESERVED], 0x0);
|
|
+ regmap_field_write(priv->regs[MESON_VDAC_CNTL1_RESERVED], 0x0);
|
|
+
|
|
+ return phy_meson_cvbs_dac_power_off(phy);
|
|
+}
|
|
+
|
|
+static const struct phy_ops phy_meson_cvbs_dac_ops = {
|
|
+ .init = phy_meson_cvbs_dac_init,
|
|
+ .power_on = phy_meson_cvbs_dac_power_on,
|
|
+ .power_off = phy_meson_cvbs_dac_power_off,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+static u8 phy_meson_cvbs_trimming_version(u8 trimming1)
|
|
+{
|
|
+ if ((trimming1 & 0xf0) == 0xa0)
|
|
+ return 5;
|
|
+ else if ((trimming1 & 0xf0) == 0x40)
|
|
+ return 2;
|
|
+ else if ((trimming1 & 0xc0) == 0x80)
|
|
+ return 1;
|
|
+ else if ((trimming1 & 0xc0) == 0x00)
|
|
+ return 0;
|
|
+ else
|
|
+ return 0xff;
|
|
+}
|
|
+
|
|
+static int phy_meson_cvbs_read_trimming(struct device *dev,
|
|
+ struct phy_meson_cvbs_dac_priv *priv)
|
|
+{
|
|
+ struct nvmem_cell *cell;
|
|
+ u8 *trimming;
|
|
+ size_t len;
|
|
+
|
|
+ cell = devm_nvmem_cell_get(dev, "cvbs_trimming");
|
|
+ if (IS_ERR(cell))
|
|
+ return dev_err_probe(dev, PTR_ERR(cell),
|
|
+ "Failed to get the 'cvbs_trimming' nvmem-cell\n");
|
|
+
|
|
+ trimming = nvmem_cell_read(cell, &len);
|
|
+ if (IS_ERR(trimming)) {
|
|
+ /*
|
|
+ * TrustZone firmware may block access to the CVBS trimming
|
|
+ * data stored in the eFuse. On those devices the trimming data
|
|
+ * is stored in the u-boot environment. However, the known
|
|
+ * examples of trimming data in the u-boot environment are all
|
|
+ * zero.
|
|
+ */
|
|
+ dev_dbg(dev,
|
|
+ "Failed to read the 'cvbs_trimming' nvmem-cell - falling back to a default value\n");
|
|
+ priv->cdac_gsw_enable_val = 0x0;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (len != 2) {
|
|
+ kfree(trimming);
|
|
+ return dev_err_probe(dev, -EINVAL,
|
|
+ "Read the 'cvbs_trimming' nvmem-cell with invalid length\n");
|
|
+ }
|
|
+
|
|
+ switch (phy_meson_cvbs_trimming_version(trimming[1])) {
|
|
+ case 1:
|
|
+ case 2:
|
|
+ case 5:
|
|
+ priv->cdac_gsw_enable_val = trimming[0] & 0x7;
|
|
+ break;
|
|
+ default:
|
|
+ priv->cdac_gsw_enable_val = 0x0;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ kfree(trimming);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int phy_meson_cvbs_dac_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ struct phy_meson_cvbs_dac_priv *priv;
|
|
+ struct phy_provider *phy_provider;
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct regmap *hhi;
|
|
+ struct phy *phy;
|
|
+ int ret;
|
|
+
|
|
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
+ if (!priv)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ if (np) {
|
|
+ priv->data = device_get_match_data(dev);
|
|
+ if (!priv->data)
|
|
+ return dev_err_probe(dev, -EINVAL,
|
|
+ "Could not find the OF match data\n");
|
|
+
|
|
+ hhi = syscon_node_to_regmap(np->parent);
|
|
+ if (IS_ERR(hhi))
|
|
+ return dev_err_probe(dev, PTR_ERR(hhi),
|
|
+ "Failed to get the parent syscon\n");
|
|
+ } else {
|
|
+ const struct platform_device_id *pdev_id;
|
|
+
|
|
+ pdev_id = platform_get_device_id(pdev);
|
|
+ if (!pdev_id)
|
|
+ return dev_err_probe(dev, -EINVAL,
|
|
+ "Failed to find platform device ID\n");
|
|
+
|
|
+ priv->data = (void *)pdev_id->driver_data;
|
|
+ if (!priv->data)
|
|
+ return dev_err_probe(dev, -EINVAL,
|
|
+ "Could not find the platform driver data\n");
|
|
+
|
|
+ hhi = syscon_regmap_lookup_by_compatible("amlogic,meson-gx-hhi-sysctrl");
|
|
+ if (IS_ERR(hhi))
|
|
+ return dev_err_probe(dev, PTR_ERR(hhi),
|
|
+ "Failed to get the \"amlogic,meson-gx-hhi-sysctrl\" syscon\n");
|
|
+ }
|
|
+
|
|
+ if (priv->data->needs_cvbs_trimming_nvmem_cell) {
|
|
+ ret = phy_meson_cvbs_read_trimming(dev, priv);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = devm_regmap_field_bulk_alloc(dev, hhi, priv->regs,
|
|
+ priv->data->reg_fields,
|
|
+ MESON_CVBS_DAC_NUM_REGS);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret,
|
|
+ "Failed to create regmap fields\n");
|
|
+
|
|
+ phy = devm_phy_create(dev, np, &phy_meson_cvbs_dac_ops);
|
|
+ if (IS_ERR(phy))
|
|
+ return PTR_ERR(phy);
|
|
+
|
|
+ phy_set_drvdata(phy, priv);
|
|
+
|
|
+ if (np) {
|
|
+ phy_provider = devm_of_phy_provider_register(dev,
|
|
+ of_phy_simple_xlate);
|
|
+ ret = PTR_ERR_OR_ZERO(phy_provider);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret,
|
|
+ "Failed to register PHY provider\n");
|
|
+ }
|
|
+
|
|
+ platform_set_drvdata(pdev, phy);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id phy_meson_cvbs_dac_of_match[] = {
|
|
+ {
|
|
+ .compatible = "amlogic,meson8-cvbs-dac",
|
|
+ .data = &phy_meson8_cvbs_dac_data,
|
|
+ },
|
|
+ {
|
|
+ .compatible = "amlogic,meson-gxbb-cvbs-dac",
|
|
+ .data = &phy_meson_gxbb_cvbs_dac_data,
|
|
+ },
|
|
+ {
|
|
+ .compatible = "amlogic,meson-gxl-cvbs-dac",
|
|
+ .data = &phy_meson_gxl_cvbs_dac_data,
|
|
+ },
|
|
+ {
|
|
+ .compatible = "amlogic,meson-g12a-cvbs-dac",
|
|
+ .data = &phy_meson_g12a_cvbs_dac_data,
|
|
+ },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, phy_meson_cvbs_dac_of_match);
|
|
+
|
|
+/*
|
|
+ * The platform_device_id table is used for backwards compatibility with old
|
|
+ * .dtbs which don't have a CVBS DAC node (where the VPU DRM driver registers
|
|
+ * this as a platform device. Support for additional SoCs should only be added
|
|
+ * to the of_device_id table above.
|
|
+ */
|
|
+static const struct platform_device_id phy_meson_cvbs_dac_device_ids[] = {
|
|
+ {
|
|
+ .name = "meson-gxbb-cvbs-dac",
|
|
+ .driver_data = (kernel_ulong_t)&phy_meson_gxbb_cvbs_dac_data,
|
|
+ },
|
|
+ {
|
|
+ .name = "meson-gxl-cvbs-dac",
|
|
+ .driver_data = (kernel_ulong_t)&phy_meson_gxl_cvbs_dac_data,
|
|
+ },
|
|
+ {
|
|
+ .name = "meson-g12a-cvbs-dac",
|
|
+ .driver_data = (kernel_ulong_t)&phy_meson_g12a_cvbs_dac_data,
|
|
+ },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(platform, phy_meson_cvbs_dac_device_ids);
|
|
+
|
|
+static struct platform_driver phy_meson_cvbs_dac_driver = {
|
|
+ .driver = {
|
|
+ .name = "phy-meson-cvbs-dac",
|
|
+ .of_match_table = phy_meson_cvbs_dac_of_match,
|
|
+ },
|
|
+ .id_table = phy_meson_cvbs_dac_device_ids,
|
|
+ .probe = phy_meson_cvbs_dac_probe,
|
|
+};
|
|
+module_platform_driver(phy_meson_cvbs_dac_driver);
|
|
+
|
|
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
|
|
+MODULE_DESCRIPTION("Amlogic Meson CVBS DAC driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
--
|
|
2.34.1
|
|
|