diff --git a/Documentation/devicetree/bindings/clock/allwinner,sun60i-a733-de352-clk.yaml b/Documentation/devicetree/bindings/clock/allwinner,sun60i-a733-de352-clk.yaml new file mode 100644 index 00000000000000..063b69717267e3 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/allwinner,sun60i-a733-de352-clk.yaml @@ -0,0 +1,97 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/allwinner,sun60i-a733-de352-clk.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Allwinner A733 DE352 DETOP Clock Controller + +maintainers: + - Chen-Yu Tsai + +description: | + DE352 keeps mixer/display module clocks in the DETOP register block at + DE + 0x8000 (RESET/CLK/MBUS), not at the DE2/DE3 clock@0 layout. + +properties: + "#clock-cells": + const: 1 + + "#reset-cells": + const: 1 + + compatible: + const: allwinner,sun60i-a733-de352-clk + + reg: + maxItems: 1 + + clocks: + items: + - description: DE AHB fabric gate (CLK_DE_AHB_GATE) + - description: DE MBUS fabric gate (CLK_MBUS_DESYS_GATE) + - description: DE bus clock (CLK_BUS_DE0) + - description: DE module clock (CLK_DE0) + + clock-names: + items: + - const: ahb + - const: mbus + - const: bus + - const: mod + + resets: + items: + - description: DE system reset (RST_BUS_DE_SY) + - description: DE0 bus reset (RST_BUS_DE0) + + reset-names: + items: + - const: sys + - const: bus + + power-domains: + maxItems: 1 + +required: + - "#clock-cells" + - "#reset-cells" + - compatible + - reg + - clocks + - clock-names + - resets + - reset-names + +additionalProperties: false + +examples: + - | + #include + #include + + bus@5000000 { + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x0 0x05000000 0x400000>; + + de352_clocks: clock@8000 { + compatible = "allwinner,sun60i-a733-de352-clk"; + reg = <0x8000 0x100>; + clocks = <&ccu CLK_DE_AHB_GATE>, + <&ccu CLK_MBUS_DESYS_GATE>, + <&ccu CLK_BUS_DE0>, + <&ccu CLK_DE0>; + clock-names = "ahb", + "mbus", + "bus", + "mod"; + resets = <&ccu RST_BUS_DE_SY>, + <&ccu RST_BUS_DE0>; + reset-names = "sys", "bus"; + #clock-cells = <1>; + #reset-cells = <1>; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/display/allwinner,sun4i-a10-tcon.yaml b/Documentation/devicetree/bindings/display/allwinner,sun4i-a10-tcon.yaml index 724d93b9193bc7..00557a1ca183db 100644 --- a/Documentation/devicetree/bindings/display/allwinner,sun4i-a10-tcon.yaml +++ b/Documentation/devicetree/bindings/display/allwinner,sun4i-a10-tcon.yaml @@ -35,6 +35,7 @@ properties: - const: allwinner,sun9i-a80-tcon-tv - const: allwinner,sun20i-d1-tcon-lcd - const: allwinner,sun20i-d1-tcon-tv + - const: allwinner,sun60i-a733-tcon-tv - items: - enum: @@ -254,6 +255,7 @@ allOf: - allwinner,sun8i-r40-tcon-tv - allwinner,sun9i-a80-tcon-tv - allwinner,sun20i-d1-tcon-tv + - allwinner,sun60i-a733-tcon-tv then: properties: diff --git a/Documentation/devicetree/bindings/display/allwinner,sun60i-a733-de352-mixer.yaml b/Documentation/devicetree/bindings/display/allwinner,sun60i-a733-de352-mixer.yaml new file mode 100644 index 00000000000000..134b44b86c8d07 --- /dev/null +++ b/Documentation/devicetree/bindings/display/allwinner,sun60i-a733-de352-mixer.yaml @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/allwinner,sun60i-a733-de352-mixer.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Allwinner A733 DE352 Mixer + +maintainers: + - Chen-Yu Tsai + +description: | + DE352 mixer / display pipe for A733. DT shape follows the T527 A523 + de33-mixer-0 node (engine/top/display/detop), but the driver is sun60i_de + (DE352 RCQ), not sun8i_mixer. + +properties: + compatible: + const: allwinner,sun60i-a733-de352-mixer-0 + + reg: + items: + - description: Channel / overlay engine (DE + 0x100000) + - description: RTMX top (DE + 0x8100) + - description: Display blender / formatter (DE + 0x280000) + - description: DETOP mux block (DE + 0x8000); overlaps de352-clk + + reg-names: + items: + - const: engine + - const: top + - const: display + - const: detop + + clocks: + minItems: 2 + maxItems: 2 + + clock-names: + items: + - const: bus + - const: mod + + resets: + maxItems: 1 + + reset-names: + items: + - const: mod + + iommus: + maxItems: 1 + + power-domains: + maxItems: 1 + + chn_cfg_mode: + $ref: /schemas/types.yaml#/definitions/uint32 + description: DE352 channel topology selected for this mixer. + + allwinner,tcon-id: + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 15 + description: | + Hardware TCON device index programmed into the DE2TCON mux for display + pipe 0. This is a hardware device index, not an OF graph endpoint ID. + A733 TCON-LCD0/1/2 use 0/1/2 respectively. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - resets + - reset-names + +additionalProperties: true + +... diff --git a/Documentation/devicetree/bindings/display/allwinner,sun8i-r40-tcon-top.yaml b/Documentation/devicetree/bindings/display/allwinner,sun8i-r40-tcon-top.yaml index 7d849c4095a36c..1c2839579ed5ea 100644 --- a/Documentation/devicetree/bindings/display/allwinner,sun8i-r40-tcon-top.yaml +++ b/Documentation/devicetree/bindings/display/allwinner,sun8i-r40-tcon-top.yaml @@ -43,6 +43,7 @@ properties: - allwinner,sun8i-r40-tcon-top - allwinner,sun20i-d1-tcon-top - allwinner,sun50i-h6-tcon-top + - allwinner,sun60i-a733-tcon-top reg: maxItems: 1 @@ -60,6 +61,14 @@ properties: maxItems: 3 resets: + minItems: 1 + maxItems: 2 + + reset-names: + minItems: 1 + maxItems: 2 + + power-domains: maxItems: 1 ports: @@ -99,16 +108,12 @@ properties: required: - port@0 - port@1 - - port@4 - - port@5 required: - - "#clock-cells" - compatible - reg - clocks - clock-names - - clock-output-names - resets - ports @@ -152,6 +157,27 @@ allOf: - port@2 - port@3 + - if: + properties: + compatible: + contains: + enum: + - allwinner,sun8i-r40-tcon-top + - allwinner,sun20i-d1-tcon-top + - allwinner,sun50i-h6-tcon-top + - allwinner,sun60i-a733-tcon-top + + then: + required: + - "#clock-cells" + - clock-output-names + + properties: + ports: + required: + - port@4 + - port@5 + - if: properties: compatible: diff --git a/Documentation/gpu/index.rst b/Documentation/gpu/index.rst index 7dcb15850afdb2..9e99a3c9555ace 100644 --- a/Documentation/gpu/index.rst +++ b/Documentation/gpu/index.rst @@ -20,6 +20,7 @@ GPU Driver Developer's Guide vgaarbiter automated_testing implementation_guidelines + sun60i-a733-display todo rfc/index diff --git a/Documentation/gpu/sun60i-a733-display.rst b/Documentation/gpu/sun60i-a733-display.rst new file mode 100644 index 00000000000000..827b48f377ec68 --- /dev/null +++ b/Documentation/gpu/sun60i-a733-display.rst @@ -0,0 +1,22 @@ +.. SPDX-License-Identifier: GPL-2.0-only +.. Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. + +============================= +Allwinner A733 display output +============================= + +The A733 DE352 mixer routes its output to TCON-TV and the DesignWare HDMI +controller through the Video Output 1 timing-controller top block. The +driver exposes one primary plane and an ARGB8888 cursor, with one mixer +output active at a time. + +HDMI board setup +================ + +Include ``sun60i-a733-display.dtsi`` and enable ``de_drm``, ``mixer0``, +``tcon_top``, ``tcon_tv`` and ``hdmi`` in the board description. The DRM +master uses the reserved display CMA pool for DMA allocations. HDMI audio +is connected to I2S3 through the board's simple-audio-card description. + +Panel paths are separate from the HDMI pipeline and require their own +controller, PHY and board-specific panel descriptions. diff --git a/arch/arm64/boot/dts/allwinner/sun60i-a733-demo-aiot.dts b/arch/arm64/boot/dts/allwinner/sun60i-a733-demo-aiot.dts index 8147493d9fb043..67d36784b46a6d 100644 --- a/arch/arm64/boot/dts/allwinner/sun60i-a733-demo-aiot.dts +++ b/arch/arm64/boot/dts/allwinner/sun60i-a733-demo-aiot.dts @@ -1,4 +1,5 @@ #include "sun60i-a733.dtsi" +#include "sun60i-a733-display.dtsi" #include #include #include @@ -81,6 +82,23 @@ }; }; + hdmi_sound: sound-hdmi { + compatible = "simple-audio-card"; + simple-audio-card,name = "HDMI"; + simple-audio-card,format = "i2s"; + simple-audio-card,mclk-fs = <128>; + + simple-audio-card,cpu { + sound-dai = <&i2s3>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + + simple-audio-card,codec { + sound-dai = <&hdmi>; + }; + }; + /* * AMP / remoteproc / rpmsg shared memory (layout aligned with Tina * sun60iw2 / a537 demo_aiot). Keep these regions below DRAM end. @@ -487,6 +505,13 @@ id = <5>; skip; }; + + /* DE0 is IOMMU master 8 and shares the DE_SYS power domain. */ + de0_iommu { + iommu-master; + id = <8>; + power-domains = <&pck600 PD_DE_SYS>; + }; }; &usbphy { @@ -553,3 +578,29 @@ &gpu { status = "okay"; }; + +/* I2S3 is internally routed to the DesignWare HDMI audio interface. */ +&i2s3 { + status = "okay"; +}; + +/* Enable the A733 DE352 to HDMI display pipeline. */ +&de_drm { + status = "okay"; +}; + +&mixer0 { + status = "okay"; +}; + +&tcon_top { + status = "okay"; +}; + +&tcon_tv { + status = "okay"; +}; + +&hdmi { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/allwinner/sun60i-a733-display.dtsi b/arch/arm64/boot/dts/allwinner/sun60i-a733-display.dtsi new file mode 100644 index 00000000000000..785727e375ac60 --- /dev/null +++ b/arch/arm64/boot/dts/allwinner/sun60i-a733-display.dtsi @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR MIT) +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/ { + de: de_drm: display-engine { + compatible = "allwinner,sun60i-a733-display-engine"; + allwinner,pipelines = <&mixer0>; + memory-region = <&drm_cma>; + iommus = <&mmu_aw 8 1>; + status = "disabled"; + }; +}; + +&soc { + bus@5000000 { + compatible = "allwinner,sun60i-a733-de352", "simple-bus"; + reg = <0x0 0x05000000 0x0 0x00400000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x0 0x05000000 0x400000>; + + display_clocks: clock@8000 { + compatible = "allwinner,sun60i-a733-de352-clk"; + reg = <0x8000 0x100>; + clocks = <&ccu CLK_DE_AHB_GATE>, + <&ccu CLK_MBUS_DESYS_GATE>, + <&ccu CLK_BUS_DE0>, + <&ccu CLK_DE0>; + clock-names = "ahb", "mbus", "bus", "mod"; + resets = <&ccu RST_BUS_DE_SY>, <&ccu RST_BUS_DE0>; + reset-names = "sys", "bus"; + power-domains = <&pck600 PD_DE_SYS>; + #clock-cells = <1>; + #reset-cells = <1>; + }; + + mixer0: mixer@100000 { + compatible = "allwinner,sun60i-a733-de352-mixer-0"; + reg = <0x100000 0x100000>, + <0x8100 0x40>, + <0x280000 0x20000>, + <0x8000 0x100>; + reg-names = "engine", "top", "display", "detop"; + chn_cfg_mode = <2>; + allwinner,tcon-id = <4>; + interrupts = , + ; + clocks = <&display_clocks CLK_BUS_MIXER0>, + <&display_clocks CLK_MIXER0>; + clock-names = "bus", "mod"; + resets = <&display_clocks RST_MIXER0>; + reset-names = "mod"; + iommus = <&mmu_aw 8 1>; + nsi = <&nsi0 2>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + mixer0_out: port@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + + mixer0_out_tcon_top: endpoint@0 { + reg = <0>; + remote-endpoint = <&tcon_top_mixer0_in_mixer0>; + }; + }; + }; + }; + }; + + tcon_top: tcon-top@5510000 { + compatible = "allwinner,sun60i-a733-tcon-top"; + reg = <0x0 0x05510000 0x0 0x1000>; + clocks = <&ccu CLK_DPSS_TOP1>, <&ccu CLK_HDMI_TV>, + <&ccu CLK_VID_OUT1_AHB_GATE>; + clock-names = "bus", "tcon-tv0", "ahb-gate"; + clock-output-names = "tcon-top-tv0"; + resets = <&ccu RST_BUS_DPSS_TOP1>, <&ccu RST_BUS_VIDEO_OUT1>; + reset-names = "bus", "rst_bus_reg"; + #clock-cells = <1>; + power-domains = <&pck600 PD_VO1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + tcon_top_mixer0_in: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + tcon_top_mixer0_in_mixer0: endpoint@0 { + reg = <0>; + remote-endpoint = <&mixer0_out_tcon_top>; + }; + }; + + tcon_top_mixer0_out: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + tcon_top_mixer0_out_tcon_tv: endpoint@2 { + reg = <2>; + remote-endpoint = <&tcon_tv_in_tcon_top>; + }; + }; + + tcon_top_hdmi_in: port@4 { + #address-cells = <1>; + #size-cells = <0>; + reg = <4>; + + tcon_top_hdmi_in_tcon_tv: endpoint@0 { + reg = <0>; + remote-endpoint = <&tcon_tv_out_tcon_top>; + }; + }; + + tcon_top_hdmi_out: port@5 { + reg = <5>; + + tcon_top_hdmi_out_hdmi: endpoint { + remote-endpoint = <&hdmi_in_tcon_top>; + }; + }; + }; + }; + + tcon_tv: tcon-tv@5730000 { + compatible = "allwinner,sun60i-a733-tcon-tv"; + reg = <0x0 0x05730000 0x0 0x1000>; + interrupts = ; + clocks = <&ccu CLK_TCONTV0>, <&tcon_top CLK_TCON_TOP_TV0>; + clock-names = "ahb", "tcon-ch1"; + resets = <&ccu RST_BUS_TCONTV0>; + reset-names = "lcd"; + power-domains = <&pck600 PD_VO1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + tcon_tv_in: port@0 { + reg = <0>; + + tcon_tv_in_tcon_top: endpoint { + remote-endpoint = <&tcon_top_mixer0_out_tcon_tv>; + }; + }; + + tcon_tv_out: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + tcon_tv_out_tcon_top: endpoint@1 { + reg = <1>; + remote-endpoint = <&tcon_top_hdmi_in_tcon_tv>; + }; + }; + }; + }; + + hdmi: hdmi@5520000 { + compatible = "allwinner,sun60i-a733-dw-hdmi"; + #sound-dai-cells = <0>; + reg = <0x0 0x05520000 0x0 0x100000>; + reg-io-width = <1>; + interrupts = ; + clocks = <&ccu CLK_HDMI_SFR>, <&ccu CLK_HDMI>, <&ccu CLK_HDMI_REF>, + <&ccu CLK_HDMI_REF>, <&ccu CLK_HDMI_TV>, <&ccu CLK_HDMI>, + <&ccu CLK_HDMI_SFR>, <&ccu CLK_HDCP_ESM>, <&rtc_ccu CLK_DCXO>; + clock-names = "isfr", "iahb", "cec", + "clk_cec", "clk_tcon_tv", "clk_hdmi", "clk_hdmi_24M", + "clk_hdcp", "clk_dcxo"; + resets = <&ccu RST_BUS_HDMI_SUB>, <&ccu RST_BUS_HDMI_MAIN>, + <&ccu RST_BUS_HDMI_HDCP>; + reset-names = "rst_bus_sub", "rst_bus_main", "rst_bus_hdcp"; + assigned-clocks = <&ccu CLK_HDMI>, <&ccu CLK_HDMI_TV>, + <&ccu CLK_HDMI_REF>; + assigned-clock-parents = <0>, <&ccu CLK_PLL_VIDEO0_4X>, + <&ccu CLK_HDMI_CEC_32K>; + power-domains = <&pck600 PD_VO1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + hdmi_in: port@0 { + reg = <0>; + + hdmi_in_tcon_top: endpoint { + remote-endpoint = <&tcon_top_hdmi_out_hdmi>; + }; + }; + + hdmi_out: port@1 { + reg = <1>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi b/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi index 10bf64c730fdf9..d2d4e21de18592 100644 --- a/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -305,6 +306,13 @@ reg = <0x0 0x42000000 0x0 0x00400000>; no-map; }; + + drm_cma: drm-cma@0 { + compatible = "shared-dma-pool"; + reusable; + size = <0x0 0x8000000>; + alignment = <0x0 0x400000>; + }; }; arm_pmu { @@ -1257,6 +1265,13 @@ reg = <0x0 0x07060000 0x0 0xb000>; clocks = <&r_ccu CLK_R_PPU>; #power-domain-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + + /* Required by the IOMMU driver's DE0 power-domain discovery. */ + pd_de_sys@1 { + reg = ; + }; }; }; #include "sun60i-a733-gpu.dtsi" diff --git a/arch/arm64/configs/a733_minimal_defconfig b/arch/arm64/configs/a733_minimal_defconfig index 68327f05d8c547..d724752203d9a5 100644 --- a/arch/arm64/configs/a733_minimal_defconfig +++ b/arch/arm64/configs/a733_minimal_defconfig @@ -43,10 +43,13 @@ CONFIG_CPU_FREQ_STAT=y CONFIG_CPUFREQ_DT=y CONFIG_ARM_ALLWINNER_SUN60I_CPUFREQ_NVMEM=y CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y # CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set # CONFIG_COMPAT_BRK is not set CONFIG_KSM=y CONFIG_TRANSPARENT_HUGEPAGE=y +CONFIG_CMA=y +CONFIG_DMA_CMA=y CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y @@ -259,6 +262,7 @@ CONFIG_PHY_CADENCE_DPHY=y CONFIG_PHY_CADENCE_DPHY_RX=y CONFIG_PHY_CADENCE_SIERRA=y CONFIG_PHY_CADENCE_SALVO=y +CONFIG_SUN60I_DE352_CCU=y CONFIG_ARM_CCI_PMU=y CONFIG_ARM_CCN=y CONFIG_ARM_CMN=y @@ -303,3 +307,101 @@ CONFIG_CORESIGHT_STM=y CONFIG_CORESIGHT_CPU_DEBUG=y CONFIG_CORESIGHT_CTI=y CONFIG_MEMTEST=y +CONFIG_PROC_FS=y +# CONFIG_PROC_KCORE is not set +CONFIG_PROC_VMCORE=y +# CONFIG_PROC_VMCORE_DEVICE_DUMP is not set +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +# CONFIG_PROC_CHILDREN is not set +CONFIG_PROC_PID_ARCH_STATUS=y +CONFIG_KERNFS=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_TMPFS_XATTR=y +CONFIG_TMPFS_INODE64=y +CONFIG_TMPFS_QUOTA=y +CONFIG_I2C_MV64XXX=y +CONFIG_SPI=y +CONFIG_SPI_SUN6I=y +CONFIG_HWSPINLOCK=y +CONFIG_HWSPINLOCK_SUN6I=y +CONFIG_MAILBOX=y +CONFIG_SUNXI_MSGBOX=y +CONFIG_REMOTEPROC=y +CONFIG_REMOTEPROC_CDEV=y +CONFIG_AW_REMOTEPROC=y +CONFIG_AW_REMOTEPROC_ARM_BOOT=y +CONFIG_AW_RPROC_ENHANCED_TRACE=y +CONFIG_AW_RPMSG_VIRTIO=y +CONFIG_AW_RPMSG_CTRL=y +CONFIG_AW_RPMSG_CLASS=y +CONFIG_AW_RPMSG_NOTIFY=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_SUN60I=y +CONFIG_DMADEVICES=y +CONFIG_DMA_SUN6I=y +CONFIG_RESET_CONTROLLER=y +CONFIG_PWM=y +CONFIG_PWM_SUNXI=y +CONFIG_HAS_IOMEM=y +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +CONFIG_SCSI_UFSHCD=y +CONFIG_SCSI_UFSHCD_PLATFORM=y +CONFIG_SCSI_UFS_SUNXI=y +CONFIG_MTD=y +CONFIG_SPI_MASTER=y +CONFIG_MTD_SPI_NOR=y +CONFIG_SPI_SUNXI_SPIF=y +CONFIG_IOMMU_IOVA=y +CONFIG_IOMMU_API=y +CONFIG_IOMMU_DMA=y +CONFIG_IOMMU_SUPPORT=y +CONFIG_IOMMU_IO_PGTABLE=y +CONFIG_IOMMU_DEBUGFS=y +CONFIG_SUNXI_IOMMU=y +CONFIG_PM_GENERIC_DOMAINS=y +CONFIG_SUN55I_PCK600=y +CONFIG_DRM=y +CONFIG_DRM_POWERVR=m +CONFIG_HWMON=y +CONFIG_IIO=y +CONFIG_SUN20I_GPADC=y +CONFIG_KEYBOARD_SUNXI_LRADC=y +CONFIG_RC_CORE=y +CONFIG_RC_DECODERS=y +CONFIG_IR_NEC_DECODER=y +CONFIG_IR_RC5_DECODER=y +CONFIG_RC_DEVICES=y +CONFIG_IR_SUNXI_RX=y +CONFIG_IR_SUNXI_TX=y +CONFIG_SUNXI_NSI=y +CONFIG_THERMAL=y +CONFIG_THERMAL_OF=y +CONFIG_THERMAL_HWMON=y +CONFIG_CPU_THERMAL=y +CONFIG_SUN8I_THERMAL=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SOC=y +CONFIG_SND_SUN4I_I2S=y +CONFIG_SND_SUN50I_DMIC=y +CONFIG_SND_SUN60I_A733_CODEC=y +CONFIG_SND_SOC_DMIC=y +CONFIG_SND_SIMPLE_CARD=y +CONFIG_SND_SIMPLE_CARD_UTILS=y +CONFIG_DRM_FBDEV_EMULATION=y +CONFIG_DRM_SIMPLEDRM=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_DRM_SUN4I=y +# CONFIG_DRM_SUN4I_BACKEND is not set +# CONFIG_DRM_SUN4I_HDMI is not set +CONFIG_DRM_SUN8I_DW_HDMI=y +# CONFIG_DRM_SUN8I_MIXER is not set +CONFIG_DRM_SUN60I_DE=y +CONFIG_DRM_SUN8I_TCON_TOP=y +CONFIG_DRM_DW_HDMI=y +CONFIG_DRM_DW_HDMI_CEC=y +CONFIG_DRM_DW_HDMI_I2S_AUDIO=y diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index 293aaeaacfbccf..3ce99e40b19d84 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -87,6 +87,16 @@ config SUN60I_A733_CPUPLL_CCU default ARCH_SUNXI depends on ARM64 || COMPILE_TEST +config SUN60I_DE352_CCU + tristate "Support for the Allwinner A733 DE352 DETOP CCU" + default ARCH_SUNXI + depends on ARM64 || COMPILE_TEST + help + Enable support for the clock and reset controller in the DE352 + display engine top block found in the Allwinner A733 SoC. It controls + the mixer module clocks, display gates and shared memory bus clock. + This controller is required by the A733 display engine driver. + config SUN4I_A10_CCU tristate "Support for the Allwinner A10/A20 CCU" default ARCH_SUNXI diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index b32ba2fea40f1e..7dbf8b3a6cd4d2 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_SUN60I_A733_CCU) += sun60i-a733-ccu.o obj-$(CONFIG_SUN60I_A733_RTC_CCU) += ccu-sun60i-a733-rtc.o obj-$(CONFIG_SUN60I_A733_R_CCU) += sun60i-a733-r-ccu.o obj-$(CONFIG_SUN60I_A733_CPUPLL_CCU) += sun60i-a733-cpupll-ccu.o +obj-$(CONFIG_SUN60I_DE352_CCU) += sun60i-de352-ccu.o obj-$(CONFIG_SUN4I_A10_CCU) += sun4i-a10-ccu.o obj-$(CONFIG_SUN5I_CCU) += sun5i-ccu.o obj-$(CONFIG_SUN6I_A31_CCU) += sun6i-a31-ccu.o @@ -71,6 +72,7 @@ sun55i-a523-r-ccu-y += ccu-sun55i-a523-r.o sun60i-a733-ccu-y += ccu-sun60i-a733.o sun60i-a733-r-ccu-y += ccu-sun60i-a733-r.o sun60i-a733-cpupll-ccu-y += ccu-sun60i-a733-cpupll.o +sun60i-de352-ccu-y += ccu-sun60i-de352.o sun4i-a10-ccu-y += ccu-sun4i-a10.o sun5i-ccu-y += ccu-sun5i.o sun6i-a31-ccu-y += ccu-sun6i-a31.o diff --git a/drivers/clk/sunxi-ng/ccu-sun60i-a733.c b/drivers/clk/sunxi-ng/ccu-sun60i-a733.c index 8de32b1db11359..8daac6d3aff855 100644 --- a/drivers/clk/sunxi-ng/ccu-sun60i-a733.c +++ b/drivers/clk/sunxi-ng/ccu-sun60i-a733.c @@ -98,6 +98,10 @@ static SUNXI_CCU_M(pll_peri0_2x_clk, "pll-peri0-2x", 20, 3, 0); +static const struct clk_hw *pll_peri0_2x_hws[] = { + &pll_peri0_2x_clk.common.hw, +}; + static SUNXI_CCU_M(pll_peri0_800m_clk, "pll-peri0-800m", "pll-peri0", 0x00A0, 16, 3, @@ -155,7 +159,9 @@ static CLK_FIXED_FACTOR(pll_peri1_300m_clk, "pll-peri1-300m", "pll-peri1-600m", static CLK_FIXED_FACTOR(pll_peri1_200m_clk, "pll-peri1-200m", "pll-peri1-400m", 2, 1, 0); static CLK_FIXED_FACTOR(pll_peri1_160m_clk, "pll-peri1-160m", "pll-peri1-480m", 3, 1, 0); static CLK_FIXED_FACTOR(pll_peri1_150m_clk, "pll-peri1-150m", "pll-peri1-300m", 2, 1, 0); -static CLK_FIXED_FACTOR(hdmi_cec_32k_clk, "hdmi-cec-clk32k", "pll-peri0-2x", 1, 36621, 0); +static SUNXI_CCU_GATE_HWS_WITH_PREDIV(hdmi_cec_32k_clk, + "hdmi-cec-clk32k", pll_peri0_2x_hws, + 0x1680, BIT(30), 36621, 0); #define SUN60IW2_PLL_GPU0_CTRL_REG 0x00E0 @@ -382,9 +388,17 @@ static struct ccu_nm pll_npu_clk = { }; #define SUN60IW2_PLL_DE_CTRL_REG 0x02E0 +/* + * The PLL and both divided outputs share one control register. Keep an + * explicit critical reference at every level: relying only on the DE0 child + * left the CCF enable count live while the hardware bits were incomplete. + */ static struct ccu_nm pll_de_clk = { + .output = BIT(27) | BIT(26), .lock = BIT(28), - .enable = BIT(27), + .lock_enable = BIT(29), + .ldo_en = BIT(30), + .enable = BIT(31), .n = _SUNXI_CCU_MULT_MIN_MAX(8, 8, 53, 105), .min_rate = 1272000000, .max_rate = 2520000000, @@ -393,10 +407,11 @@ static struct ccu_nm pll_de_clk = { 0x02EC, BIT(27)), /* pattern1 */ .common = { .reg = 0x02E0, + .features = CCU_FEATURE_KEEP_PLL_ENABLED, .hw.init = CLK_HW_INIT("pll-de", "pll-ref", &ccu_nm_ops, CLK_SET_RATE_UNGATE | - CLK_IGNORE_UNUSED), + CLK_IS_CRITICAL), }, }; @@ -404,13 +419,13 @@ static SUNXI_CCU_M_WITH_GATE(pll_de_4x_clk, "pll-de-4x", "pll-de", 0x02E0, 20, 3, BIT(27), - CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED); + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL); static SUNXI_CCU_M_WITH_GATE(pll_de_3x_clk, "pll-de-3x", "pll-de", 0x02E0, 16, 3, BIT(26), - CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED); + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL); static const char * const apb0_parents[] = { "sys24M", "osc32k", "iosc", "pll-peri0-600m" }; SUNXI_CCU_M_WITH_MUX(ahb_clk, "ahb", apb0_parents, @@ -879,16 +894,20 @@ static SUNXI_CCU_MUX_WITH_GATE(avs_clk, "avs", static const char * const de0_parents[] = { "pll-de-3x", "pll-de-4x", "pll-peri0-480m", "pll-peri0-400m", "pll-peri0-300m", "pll-video0-4x", "pll-video2-4x" }; +/* + * DETOP MMIO stalls when this chain is gated. Keep the main DE0 functional + * and bus clocks running; the DE352 CCU still owns the per-mixer gates. + */ static SUNXI_CCU_M_WITH_MUX_GATE(de0_clk, "de0", de0_parents, 0x0A00, 0, 5, /* M */ 24, 3, /* mux */ BIT(31), /* gate */ - CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED); + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL); static SUNXI_CCU_GATE(de0_gate_clk, "de0-gate", "ahb", - 0x0A04, BIT(0), CLK_IGNORE_UNUSED); + 0x0A04, BIT(0), CLK_IS_CRITICAL); static const char * const di_parents[] = { "pll-peri0-600m", "pll-peri0-480m", "pll-peri0-400m", "pll-video0-4x", "pll-video2-4x" }; @@ -2254,7 +2273,7 @@ static struct clk_hw_onecell_data sun60iw2_hw_clks = { [CLK_PLL_PERI1_200M] = &pll_peri1_200m_clk.hw, [CLK_PLL_PERI1_160M] = &pll_peri1_160m_clk.hw, [CLK_PLL_PERI1_150M] = &pll_peri1_150m_clk.hw, - [CLK_HDMI_CEC_32K] = &hdmi_cec_32k_clk.hw, + [CLK_HDMI_CEC_32K] = &hdmi_cec_32k_clk.common.hw, [CLK_PLL_GPU0] = &pll_gpu0_clk.common.hw, [CLK_PLL_VIDEO0] = &pll_video0_clk.common.hw, [CLK_PLL_VIDEO0_4X] = &pll_video0_4x_clk.common.hw, @@ -2567,6 +2586,7 @@ static struct ccu_common *sun60iw2_ccu_clks[] = { &pll_peri0_2x_clk.common, &pll_peri0_800m_clk.common, &pll_peri0_480m_clk.common, + &hdmi_cec_32k_clk.common, &pll_peri1_clk.common, &pll_peri1_2x_clk.common, &pll_peri1_800m_clk.common, @@ -2926,6 +2946,11 @@ static int sun60iw2_ccu_really_probe(struct platform_device *pdev) if (ret) return ret; + /* Enable the shared DE PLL outputs after clock registration. */ + val = readl(reg + SUN60IW2_PLL_DE_CTRL_REG); + val |= BIT(31) | BIT(30) | BIT(29) | BIT(27) | BIT(26); + writel(val, reg + SUN60IW2_PLL_DE_CTRL_REG); + return 0; } diff --git a/drivers/clk/sunxi-ng/ccu-sun60i-de352.c b/drivers/clk/sunxi-ng/ccu-sun60i-de352.c new file mode 100644 index 00000000000000..e996740876ecc0 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu-sun60i-de352.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * Allwinner DE352 DETOP clock/reset controller (A733). + * + * Unlike DE2/DE3 (gates at DE+0x0), DE352 module clocks live in DETOP: + * +0x00 RESET — displayN gate at bit (N * 4), writes need BIT(16) key + * +0x04 CLK — same bit layout / key + * +0x08 MBUS — bit0 clk_en, bit4 reset deassert (no key; never set bit8) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ccu_common.h" +#include "ccu_gate.h" +#include "ccu_reset.h" + +#include "ccu-sun60i-de352.h" + +#define DE352_RESET_REG 0x00 +#define DE352_CLK_REG 0x04 +#define DE352_MBUS_REG 0x08 + +#define DE352_KEY BIT(16) +#define DE352_DISP_SHIFT 4 +#define DE352_MBUS_CLK_EN BIT(0) +#define DE352_MBUS_RST_N BIT(4) +#define DE352_MOD_RATE 600000000UL + +static SUNXI_CCU_GATE_WITH_KEY(mixer0_clk, "mixer0", "de0", + DE352_CLK_REG, DE352_KEY, BIT(0), + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED); +static SUNXI_CCU_GATE_WITH_KEY(mixer1_clk, "mixer1", "de0", + DE352_CLK_REG, DE352_KEY, + BIT(1 * DE352_DISP_SHIFT), + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED); + +/* + * MBUS is shared by all mixers. Export as bus-mixer0; enabling sets clk_en and + * reset-deassert together (BSP __de_mbus_*). Auto-gate bit8 must stay clear. + */ +static SUNXI_CCU_GATE(bus_mixer0_clk, "bus-mixer0", "de0-gate", + DE352_MBUS_REG, DE352_MBUS_CLK_EN | DE352_MBUS_RST_N, + CLK_IGNORE_UNUSED); + +static struct ccu_common *sun60i_de352_ccu_clks[] = { + &mixer0_clk.common, + &mixer1_clk.common, + &bus_mixer0_clk.common, +}; + +static struct clk_hw_onecell_data sun60i_de352_hw_clks = { + .hws = { + [CLK_MIXER0] = &mixer0_clk.common.hw, + [CLK_BUS_MIXER0] = &bus_mixer0_clk.common.hw, + [CLK_MIXER1] = &mixer1_clk.common.hw, + }, + .num = CLK_NUMBER, +}; + +static const struct ccu_reset_map sun60i_de352_resets[] = { + [RST_MIXER0] = { DE352_RESET_REG, BIT(0) }, + [RST_MIXER1] = { DE352_RESET_REG, BIT(1 * DE352_DISP_SHIFT) }, +}; + +struct sun60i_de352_clk { + struct device *dev; + void __iomem *base; + spinlock_t lock; + struct ccu_reset reset; + struct clk_bulk_data parents[4]; + struct reset_control *rst_sys; + struct reset_control *rst_bus; + int num_registered; +}; + +static int sun60i_de352_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct ccu_reset *ccu = rcdev_to_ccu_reset(rcdev); + const struct ccu_reset_map *map = &ccu->reset_map[id]; + unsigned long flags; + u32 reg; + + spin_lock_irqsave(ccu->lock, flags); + reg = readl(ccu->base + map->reg); + writel((reg & ~map->bit) | DE352_KEY, ccu->base + map->reg); + spin_unlock_irqrestore(ccu->lock, flags); + + return 0; +} + +static int sun60i_de352_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct ccu_reset *ccu = rcdev_to_ccu_reset(rcdev); + const struct ccu_reset_map *map = &ccu->reset_map[id]; + unsigned long flags; + u32 reg; + + spin_lock_irqsave(ccu->lock, flags); + reg = readl(ccu->base + map->reg); + writel(reg | map->bit | DE352_KEY, ccu->base + map->reg); + spin_unlock_irqrestore(ccu->lock, flags); + + return 0; +} + +static int sun60i_de352_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct ccu_reset *ccu = rcdev_to_ccu_reset(rcdev); + const struct ccu_reset_map *map = &ccu->reset_map[id]; + + /* 0 = deasserted (bit set), matching ccu_reset_status polarity */ + return !(map->bit & readl(ccu->base + map->reg)); +} + +static const struct reset_control_ops sun60i_de352_reset_ops = { + .assert = sun60i_de352_reset_assert, + .deassert = sun60i_de352_reset_deassert, + .status = sun60i_de352_reset_status, +}; + +static void sun60i_de352_clk_remove_action(void *data) +{ + struct sun60i_de352_clk *de352 = data; + int i; + + reset_controller_unregister(&de352->reset.rcdev); + of_clk_del_provider(de352->dev->of_node); + + for (i = 0; i < de352->num_registered; i++) { + struct clk_hw *hw = sun60i_de352_hw_clks.hws[i]; + + if (hw) + clk_hw_unregister(hw); + } + + reset_control_assert(de352->rst_bus); + reset_control_assert(de352->rst_sys); + clk_bulk_disable_unprepare(ARRAY_SIZE(de352->parents), de352->parents); +} + +static int sun60i_de352_clk_probe(struct platform_device *pdev) +{ + struct sun60i_de352_clk *de352; + void __iomem *reg; + int i, ret; + + de352 = devm_kzalloc(&pdev->dev, sizeof(*de352), GFP_KERNEL); + if (!de352) + return -ENOMEM; + + de352->dev = &pdev->dev; + de352->parents[0].id = "ahb"; + de352->parents[1].id = "mbus"; + de352->parents[2].id = "bus"; + de352->parents[3].id = "mod"; + + reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(de352->parents), + de352->parents); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Couldn't get DE parent clocks\n"); + + de352->rst_sys = devm_reset_control_get_shared(&pdev->dev, "sys"); + if (IS_ERR(de352->rst_sys)) + return dev_err_probe(&pdev->dev, PTR_ERR(de352->rst_sys), + "Couldn't get DE system reset\n"); + + de352->rst_bus = devm_reset_control_get_shared(&pdev->dev, "bus"); + if (IS_ERR(de352->rst_bus)) + return dev_err_probe(&pdev->dev, PTR_ERR(de352->rst_bus), + "Couldn't get DE0 bus reset\n"); + + /* + * The DETOP register interface stalls when DE0 has no functional clock. + * Keep the complete parent chain running before changing its rate or + * releasing either DE reset. + */ + ret = clk_bulk_prepare_enable(ARRAY_SIZE(de352->parents), de352->parents); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Couldn't enable DE parent clocks\n"); + + ret = clk_set_rate(de352->parents[3].clk, DE352_MOD_RATE); + if (ret) { + dev_err(&pdev->dev, "Couldn't set DE module rate: %d\n", ret); + goto err_disable_clks; + } + + /* A733 BSP order: release the DE system first, then the DE0 instance. */ + ret = reset_control_deassert(de352->rst_sys); + if (ret) { + dev_err(&pdev->dev, "Couldn't deassert DE system reset: %d\n", + ret); + goto err_disable_clks; + } + + ret = reset_control_deassert(de352->rst_bus); + if (ret) { + dev_err(&pdev->dev, "Couldn't deassert DE0 bus reset: %d\n", + ret); + goto err_assert_sys_reset; + } + + spin_lock_init(&de352->lock); + de352->base = reg; + + for (i = 0; i < ARRAY_SIZE(sun60i_de352_ccu_clks); i++) { + sun60i_de352_ccu_clks[i]->base = reg; + sun60i_de352_ccu_clks[i]->lock = &de352->lock; + } + + for (i = 0; i < sun60i_de352_hw_clks.num; i++) { + struct clk_hw *hw = sun60i_de352_hw_clks.hws[i]; + + if (!hw) + continue; + + ret = clk_hw_register(&pdev->dev, hw); + if (ret) { + dev_err(&pdev->dev, "Couldn't register clock %s: %d\n", + clk_hw_get_name(hw), ret); + de352->num_registered = i; + goto err_unreg_clks; + } + } + de352->num_registered = sun60i_de352_hw_clks.num; + + ret = of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_onecell_get, + &sun60i_de352_hw_clks); + if (ret) + goto err_unreg_clks; + + de352->reset.base = reg; + de352->reset.lock = &de352->lock; + de352->reset.reset_map = sun60i_de352_resets; + de352->reset.rcdev.of_node = pdev->dev.of_node; + de352->reset.rcdev.ops = &sun60i_de352_reset_ops; + de352->reset.rcdev.owner = THIS_MODULE; + de352->reset.rcdev.nr_resets = ARRAY_SIZE(sun60i_de352_resets); + + ret = reset_controller_register(&de352->reset.rcdev); + if (ret) { + of_clk_del_provider(pdev->dev.of_node); + goto err_unreg_clks; + } + + ret = devm_add_action_or_reset(&pdev->dev, + sun60i_de352_clk_remove_action, de352); + if (ret) + return ret; + + return 0; + +err_unreg_clks: + for (i = de352->num_registered - 1; i >= 0; i--) { + struct clk_hw *hw = sun60i_de352_hw_clks.hws[i]; + + if (hw) + clk_hw_unregister(hw); + } + reset_control_assert(de352->rst_bus); +err_assert_sys_reset: + reset_control_assert(de352->rst_sys); +err_disable_clks: + clk_bulk_disable_unprepare(ARRAY_SIZE(de352->parents), de352->parents); + return ret; +} + +static const struct of_device_id sun60i_de352_clk_ids[] = { + { .compatible = "allwinner,sun60i-a733-de352-clk" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun60i_de352_clk_ids); + +static struct platform_driver sun60i_de352_clk_driver = { + .probe = sun60i_de352_clk_probe, + .driver = { + .name = "sun60i-de352-clks", + .of_match_table = sun60i_de352_clk_ids, + }, +}; +module_platform_driver(sun60i_de352_clk_driver); + +MODULE_IMPORT_NS("SUNXI_CCU"); +MODULE_DESCRIPTION("Allwinner DE352 DETOP CCU"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/sunxi-ng/ccu-sun60i-de352.h b/drivers/clk/sunxi-ng/ccu-sun60i-de352.h new file mode 100644 index 00000000000000..74b00f10a22211 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu-sun60i-de352.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ + +#ifndef _CCU_SUN60I_DE352_H_ +#define _CCU_SUN60I_DE352_H_ + +#include +#include + +#define CLK_NUMBER (CLK_MIXER1 + 1) + +#endif /* _CCU_SUN60I_DE352_H_ */ diff --git a/drivers/clk/sunxi-ng/ccu_common.h b/drivers/clk/sunxi-ng/ccu_common.h index b2994f7bae7c64..7ba517797fe67f 100644 --- a/drivers/clk/sunxi-ng/ccu_common.h +++ b/drivers/clk/sunxi-ng/ccu_common.h @@ -22,6 +22,7 @@ #define CCU_FEATURE_DUAL_DIV BIT(10) #define CCU_FEATURE_UPDATE_BIT BIT(11) #define CCU_FEATURE_CLEAR_MOD BIT(12) +#define CCU_FEATURE_KEEP_PLL_ENABLED BIT(13) /* MMC timing mode switch bit */ #define CCU_MMC_NEW_TIMING_MODE BIT(30) diff --git a/drivers/clk/sunxi-ng/ccu_nm.c b/drivers/clk/sunxi-ng/ccu_nm.c index 08ae359c191963..5a4214b8a501da 100644 --- a/drivers/clk/sunxi-ng/ccu_nm.c +++ b/drivers/clk/sunxi-ng/ccu_nm.c @@ -229,6 +229,8 @@ static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate, reg |= (_nm.n - nm->n.offset) << nm->n.shift; reg |= (_nm.m - nm->m.offset) << nm->m.shift; + if (nm->common.features & CCU_FEATURE_KEEP_PLL_ENABLED) + reg |= nm->enable | nm->output | nm->lock_enable | nm->ldo_en; writel(reg, nm->common.base + nm->common.reg); spin_unlock_irqrestore(nm->common.lock, flags); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 206b099a35e9a5..71d7ed422df632 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2457,11 +2457,26 @@ static const struct drm_edid *dw_hdmi_edid_read(struct dw_hdmi *hdmi, { const struct drm_edid *drm_edid; const struct edid *edid; + unsigned int retry = 0; if (!hdmi->ddc) return NULL; - drm_edid = drm_edid_read_ddc(connector, hdmi->ddc); + do { + drm_edid = drm_edid_read_ddc(connector, hdmi->ddc); + if (drm_edid) + break; + + if (retry >= hdmi->plat_data->edid_retry_count || + hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data) != + connector_status_connected) + break; + + dev_dbg(hdmi->dev, "EDID read failed, retrying (%u/%u)\n", + retry + 1, hdmi->plat_data->edid_retry_count); + msleep(hdmi->plat_data->edid_retry_delay_ms); + } while (++retry); + if (!drm_edid) { dev_dbg(hdmi->dev, "failed to get edid\n"); return NULL; @@ -3131,6 +3146,20 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) } if (status != connector_status_unknown) { + /* + * Some sinks raise HPD before their DDC interface is ready. Give + * platform glue a way to defer the connected notification so the + * first EDID probe does not fall back to a generic mode. Recheck + * HPD afterwards in case the cable was removed during the delay. + */ + if (status == connector_status_connected && + hdmi->plat_data->hpd_debounce_ms) { + msleep(hdmi->plat_data->hpd_debounce_ms); + phy_stat = hdmi_readb(hdmi, HDMI_PHY_STAT0); + if (!(phy_stat & HDMI_PHY_HPD)) + status = connector_status_disconnected; + } + dev_dbg(hdmi->dev, "EVENT=%s\n", status == connector_status_connected ? "plugin" : "plugout"); diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index b56ba00aabcaf5..9ddcf9783b6f28 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -80,9 +80,20 @@ config DRM_SUN8I_MIXER config DRM_SUN8I_TCON_TOP tristate - default DRM_SUN4I if DRM_SUN8I_MIXER!=n + default DRM_SUN4I if DRM_SUN8I_MIXER!=n || DRM_SUN60I_DE!=n help TCON TOP is responsible for configuring display pipeline for HDMI, TVE and LCD. +config DRM_SUN60I_DE + tristate "Allwinner A733 Display Engine 3.5.2" + depends on ARM64 || COMPILE_TEST + depends on DMA_CMA + select RESET_CONTROLLER + help + Enable the DE352 display engine found in the Allwinner A733. + The driver provides a primary plane and a cursor plane for the + sun4i DRM pipeline. If M is selected, the module is sun60i-de. + DMA CMA is required for the display engine's reserved memory pool. + endif diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index bad7497a0d11ed..371cbcf568924f 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -13,11 +13,16 @@ sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o sun8i-drm-hdmi-y += sun8i_dw_hdmi.o sun8i-drm-hdmi-y += sun8i_hdmi_phy.o sun8i-drm-hdmi-y += sun8i_hdmi_phy_clk.o +sun8i-drm-hdmi-y += sun60i_hdmi_phy.o +sun8i-drm-hdmi-y += sun60i_hdmi_top_phy.o +sun8i-drm-hdmi-y += sun60i_hdmi_snps_phy.o sun8i-mixer-y += sun8i_mixer.o sun8i_ui_layer.o \ sun8i_vi_layer.o sun8i_ui_scaler.o \ sun8i_vi_scaler.o sun8i_csc.o +sun60i-de-y += sun60i_de.o sun60i_de_hw.o + sun4i-tcon-y += sun4i_crtc.o sun4i-tcon-y += sun4i_tcon_dclk.o sun4i-tcon-y += sun4i_lvds.o @@ -37,4 +42,5 @@ obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o obj-$(CONFIG_DRM_SUN6I_DSI) += sun6i_mipi_dsi.o obj-$(CONFIG_DRM_SUN8I_DW_HDMI) += sun8i-drm-hdmi.o obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o +obj-$(CONFIG_DRM_SUN60I_DE) += sun60i-de.o obj-$(CONFIG_DRM_SUN8I_TCON_TOP) += sun8i_tcon_top.o diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c index 18e74047b0f566..8e6ff75a943af6 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.c +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -113,6 +113,8 @@ static void sun4i_crtc_atomic_disable(struct drm_crtc *crtc, DRM_DEBUG_DRIVER("Disabling the CRTC\n"); + sunxi_engine_quiesce(scrtc->engine, crtc); + drm_crtc_vblank_off(crtc); sun4i_tcon_set_status(scrtc->tcon, encoder, false); @@ -134,6 +136,8 @@ static void sun4i_crtc_atomic_enable(struct drm_crtc *crtc, DRM_DEBUG_DRIVER("Enabling the CRTC\n"); + sunxi_engine_enable(scrtc->engine, &crtc->state->adjusted_mode); + sun4i_tcon_set_status(scrtc->tcon, encoder, true); drm_crtc_vblank_on(crtc); diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index c11dfb2739fa98..b6ef8947b6d04c 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -436,6 +436,7 @@ static const struct of_device_id sun4i_drv_of_table[] = { { .compatible = "allwinner,sun20i-d1-display-engine" }, { .compatible = "allwinner,sun50i-a64-display-engine" }, { .compatible = "allwinner,sun50i-h6-display-engine" }, + { .compatible = "allwinner,sun60i-a733-display-engine" }, { } }; MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 960e83c8291da3..6f2a72795d8654 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -6,9 +6,12 @@ * Maxime Ripard */ +#include #include +#include #include #include +#include #include #include #include @@ -40,6 +43,81 @@ #include "sun8i_tcon_top.h" #include "sunxi_engine.h" +static void sun60i_tcon1_mode_set(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) +{ + unsigned int hdisplay = mode->crtc_hdisplay; + unsigned int hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; + unsigned int hback = mode->crtc_htotal - mode->crtc_hsync_end; + unsigned int vdisplay = mode->crtc_vdisplay; + unsigned int vtotal = mode->crtc_vtotal; + bool interlace = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + unsigned int field_div = interlace ? 2 : 1; + unsigned int vsync = (mode->crtc_vsync_end - mode->crtc_vsync_start) / + field_div; + unsigned int vback = (mode->crtc_vtotal - mode->crtc_vsync_end) / + field_div; + unsigned int visible_lines = vdisplay / field_div; + int start_delay; + u32 val; + + WARN_ON(!tcon->quirks->has_channel_1); + + /* Pixel clock lives on HDMI CLK_HDMI_TV; keep sclk1 as bus helper. */ + clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); + + regmap_write(tcon->regs, SUN60I_TCON_TV_DATA_IO_POL0_REG, 0); + regmap_write(tcon->regs, SUN60I_TCON_TV_DATA_IO_POL1_REG, 0); + regmap_write(tcon->regs, SUN60I_TCON_TV_DATA_IO_TRI0_REG, 0xffffffff); + regmap_write(tcon->regs, SUN60I_TCON_TV_DATA_IO_TRI1_REG, 0xffff0000); + regmap_write(tcon->regs, SUN60I_TCON_PIXEL_DEPTH_MODE_REG, + SUN60I_TCON_PIXELDEPTH_8BIT); + + regmap_write(tcon->regs, SUN60I_TCON_TV_IO_TRI_REG, 0x0fffffff); + regmap_write(tcon->regs, SUN4I_TCON0_IO_POL_REG, 0x07000000); + + /* BASIC1 = VT (A733); skip legacy BASIC0 upscale layout. */ + val = SUN60I_TCON_TV_BASIC1_VT(interlace ? vtotal : vtotal * 2); + regmap_write(tcon->regs, SUN4I_TCON1_BASIC1_REG, val); + + regmap_write(tcon->regs, SUN4I_TCON1_BASIC2_REG, + SUN4I_TCON1_BASIC2_X(hdisplay) | + SUN4I_TCON1_BASIC2_Y(visible_lines)); + + regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, + SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | + SUN4I_TCON1_BASIC3_H_BACKPORCH(hsync + hback)); + + /* 15.2.10.10: BASIC4 is VBP only on A733. */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, + SUN60I_TCON_TV_BASIC4_VBP(vsync + vback)); + + regmap_write(tcon->regs, SUN4I_TCON1_BASIC5_REG, + SUN4I_TCON1_BASIC5_H_SYNC(hsync) | + SUN4I_TCON1_BASIC5_V_SYNC(vsync)); + + /* 2D: fill bypass (15.2.7.4); leave begin/end alone. */ + regmap_write(tcon->regs, SUN4I_TCON1_FILL_CTL_REG, 0); + + /* Disable CEU (identity / off). */ + regmap_write(tcon->regs, SUN4I_TCON_CEU_CTL_REG, 0); + + start_delay = (vtotal - vdisplay) / field_div - 5; + start_delay = clamp(start_delay, 0, 31); + + val = SUN4I_TCON1_CTL_CLK_DELAY(start_delay); + /* Keep EN clear here; channel_set_status() enables the channel. */ + regmap_write(tcon->regs, SUN4I_TCON1_CTL_REG, val); + + /* PIXEL_MODE=1ppc (must match DE GLB); bit31 matches REF GCTL. */ + regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_TCON_ENABLE | + FIELD_PREP(SUN60I_TCON_TV_GCTL_PIXEL_MODE, + SUN60I_TCON_TV_GCTL_PIXEL_1)); + + regmap_write(tcon->regs, SUN60I_TCON_TV_SRC_CTL_REG, 0); +} + static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder) { struct drm_connector *connector; @@ -727,7 +805,10 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, break; case DRM_MODE_ENCODER_TVDAC: case DRM_MODE_ENCODER_TMDS: - sun4i_tcon1_mode_set(tcon, mode); + if (tcon->quirks->sun60i_tv) + sun60i_tcon1_mode_set(tcon, mode); + else + sun4i_tcon1_mode_set(tcon, mode); sun4i_tcon_set_mux(tcon, 1, encoder); break; default: @@ -1542,6 +1623,18 @@ static const struct sun4i_tcon_quirks sun20i_d1_lcd_quirks = { .set_mux = sun8i_r40_tcon_tv_set_mux, }; +static const struct sun4i_tcon_quirks sun60i_a733_tv_quirks = { + .has_channel_1 = true, + .polarity_in_ch0 = true, + .sun60i_tv = true, + /* + * Call R40-style TCON-TOP mux (hdmi gate + PORT_SEL). On A733 VO1, + * PORT_SEL@0x1c is RAZ/WI (devmem write of 0x2 does not stick; GATE@0x20 + * is writable). DE→TCON routing is DE2TCON@0x8010, not PORT_SEL. + */ + .set_mux = sun8i_r40_tcon_tv_set_mux, +}; + /* sun4i_drv uses this list to check if a device node is a TCON */ const struct of_device_id sun4i_tcon_of_table[] = { { .compatible = "allwinner,sun4i-a10-tcon", .data = &sun4i_a10_quirks }, @@ -1561,6 +1654,7 @@ const struct of_device_id sun4i_tcon_of_table[] = { { .compatible = "allwinner,sun9i-a80-tcon-tv", .data = &sun9i_a80_tcon_tv_quirks }, { .compatible = "allwinner,sun20i-d1-tcon-lcd", .data = &sun20i_d1_lcd_quirks }, { .compatible = "allwinner,sun20i-d1-tcon-tv", .data = &sun8i_r40_tv_quirks }, + { .compatible = "allwinner,sun60i-a733-tcon-tv", .data = &sun60i_a733_tv_quirks }, { } }; MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index fa23aa23fe4a45..5e59e1f238d9a2 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -130,6 +130,33 @@ #define SUN4I_TCON1_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON1_CTL_CLK_DELAY_MASK) #define SUN4I_TCON1_CTL_SRC_SEL_MASK GENMASK(1, 0) +/* A733 TCON-TV: 15.2.10.5 TV_SRC_CTL — bit0 VDBIST, [2:1] pattern. */ +#define SUN60I_TCON_TV_SRC_CTL_REG 0x40 +#define SUN60I_TCON_TV_SRC_CTL_VDBIST BIT(0) +#define SUN60I_TCON_TV_SRC_CTL_PATTERN GENMASK(2, 1) + +/* A733 TCON-TV IO at common-module offsets (not 0xf0/0xf4). */ +#define SUN60I_TCON_TV_IO_TRI_REG 0x8c +#define SUN60I_TCON_TV_IO_TRI_DATA_MASK GENMASK(23, 0) +#define SUN60I_TCON_TV_IO_TRI_IO2 BIT(26) +#define SUN60I_TCON_TV_IO_TRI_IO3 BIT(27) +#define SUN60I_TCON_TV_DATA_IO_POL0_REG 0x330 +#define SUN60I_TCON_TV_DATA_IO_POL1_REG 0x334 +#define SUN60I_TCON_TV_DATA_IO_TRI0_REG 0x338 +#define SUN60I_TCON_TV_DATA_IO_TRI1_REG 0x33c +#define SUN60I_TCON_PIXEL_DEPTH_MODE_REG 0x340 +/* 15.2.10.27: PIXELDEPTH 001=8-bit, IPI_FORMAT 000=RGB */ +#define SUN60I_TCON_PIXELDEPTH_8BIT 0x1 + +/* A733 TV_GCTL @0x00: PIXEL_MODE[5:4], UNSTRICT[0]; bit31 used like REF. */ +#define SUN60I_TCON_TV_GCTL_PIXEL_MODE GENMASK(5, 4) +#define SUN60I_TCON_TV_GCTL_PIXEL_1 0 + +/* A733 BASIC1 @0x98 is VT, not the legacy upscale X/Y. */ +#define SUN60I_TCON_TV_BASIC1_VT(vt) ((vt) & 0x1ffff) +/* 15.2.10.10 BASIC4 is VBP only (VT lives in BASIC1). */ +#define SUN60I_TCON_TV_BASIC4_VBP(bp) (((bp) - 1) & 0x7fff) + #define SUN4I_TCON1_BASIC0_REG 0x94 #define SUN4I_TCON1_BASIC0_X(width) ((((width) - 1) & 0xfff) << 16) #define SUN4I_TCON1_BASIC0_Y(height) (((height) - 1) & 0xfff) @@ -243,6 +270,7 @@ struct sun4i_tcon_quirks { bool needs_edp_reset; /* a80 edp reset needed for tcon0 access */ bool supports_lvds; /* Does the TCON support an LVDS output? */ bool polarity_in_ch0; /* some tcon1 channels have polarity bits in tcon0 pol register */ + bool sun60i_tv; /* A733 TCON-TV register map + builtin pattern */ u8 dclk_min_div; /* minimum divider for TCON0 DCLK */ /* callback to handle tcon muxing options */ @@ -299,5 +327,4 @@ void sun4i_tcon_set_status(struct sun4i_tcon *crtc, const struct drm_encoder *encoder, bool enable); extern const struct of_device_id sun4i_tcon_of_table[]; - #endif /* __SUN4I_TCON_H__ */ diff --git a/drivers/gpu/drm/sun4i/sun60i_de.c b/drivers/gpu/drm/sun4i/sun60i_de.c new file mode 100644 index 00000000000000..a41baa1cd1c195 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun60i_de.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * Allwinner sun60i / A733 DE352 display engine for sun4i-drm. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sun4i_drv.h" +#include "sun60i_de.h" +#include "sun60i_de_hw.h" +#include "sunxi_engine.h" + +static const u32 sun60i_de_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB565, +}; + +static const u32 sun60i_de_cursor_formats[] = { + DRM_FORMAT_ARGB8888, +}; + +static const u64 sun60i_de_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID, +}; + +static u8 sun60i_de_bld_port_from_mode(u32 chn_cfg_mode, u8 ui_channel) +{ + if (ui_channel != 0) + return 3 + ui_channel; + + switch (chn_cfg_mode) { + case 1: + case 2: + return 2; + default: + return 3; + } +} + +static void sun60i_de_init_topology(struct sun60i_de *de) +{ + u32 chn_cfg_mode = 0; + u32 tcon_id = 4; + + de->display_id = 0; + /* + * DE2TCON nibble is the TCON *device_index*, not the OF graph + * endpoint reg. Reference A733 HDMI steady state is 0x0000fff4 + * (disp0 → tcon 4). Looking up port@display_id also fails here: + * sun4i output is port@1. Prefer DT "allwinner,tcon-id". + */ + de->tcon_id = 4; + de->ui_channel = 0; + de->chn_cfg_mode = 0; + + if (!of_property_read_u32(de->dev->of_node, "chn_cfg_mode", &chn_cfg_mode)) + de->chn_cfg_mode = chn_cfg_mode; + de->bld_port = sun60i_de_bld_port_from_mode(de->chn_cfg_mode, de->ui_channel); + + if (!of_property_read_u32(de->dev->of_node, "allwinner,tcon-id", &tcon_id)) + de->tcon_id = tcon_id & 0xf; +} + +static int sun60i_de_hw_enable(struct sun60i_de *de) +{ + int ret; + + if (de->enabled) + return 0; + + /* + * Parent clocks/resets are owned by display_clocks. The mixer only + * controls its exported DETOP module/MBUS gates and reset. DE352 must + * see the module clock before its core reset is released; reversing + * these two operations can wedge the DE AHB port before any RTMX MMIO. + */ + ret = clk_prepare_enable(de->clk_mixer); + if (ret) + return ret; + ret = reset_control_deassert(de->rst_mixer); + if (ret) + goto err_disable_clk_mixer; + ret = clk_prepare_enable(de->clk_bus_mixer); + if (ret) + goto err_assert_mixer_reset; + ret = sun60i_de_hw_bus_init(de); + if (ret) + goto err_disable_clk_bus_mixer; + + ret = sun60i_de_hw_bus_verify(de); + if (ret) + goto err_disable_clk_bus_mixer; + + de->enabled = true; + return 0; + +err_disable_clk_bus_mixer: + clk_disable_unprepare(de->clk_bus_mixer); +err_assert_mixer_reset: + reset_control_assert(de->rst_mixer); +err_disable_clk_mixer: + clk_disable_unprepare(de->clk_mixer); + + return ret; +} + +static void sun60i_de_hw_disable_clocks(struct sun60i_de *de) +{ + if (!de->enabled) + return; + + sun60i_de_hw_disable(de); + clk_disable_unprepare(de->clk_bus_mixer); + clk_disable_unprepare(de->clk_mixer); + reset_control_assert(de->rst_mixer); + + de->enabled = false; +} + +static struct sun60i_de_scanout_state * +sun60i_de_plane_scanout(struct sun60i_de *de, struct drm_plane *plane) +{ + return plane->type == DRM_PLANE_TYPE_CURSOR ? &de->cursor_scanout : + &de->scanout; +} + +static void sun60i_de_update_plane_scanout(struct sun60i_de *de, + struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct sun60i_de_scanout_state *scanout = sun60i_de_plane_scanout(de, plane); + struct drm_plane_state *new_state; + struct drm_plane_state *old_state; + + new_state = drm_atomic_get_new_plane_state(state, plane); + if (!new_state) + return; + old_state = drm_atomic_get_old_plane_state(state, plane); + + if (!new_state->crtc || !new_state->visible || !new_state->fb) { + memset(scanout, 0, sizeof(*scanout)); + return; + } + + drm_fb_dma_sync_non_coherent(plane->dev, old_state, new_state); + + scanout->enabled = true; + scanout->addr = drm_fb_dma_get_gem_addr(new_state->fb, new_state, 0); + scanout->pitch = new_state->fb->pitches[0]; + scanout->format = new_state->fb->format->format; + scanout->src_x = new_state->src.x1 >> 16; + scanout->src_y = new_state->src.y1 >> 16; + scanout->src_w = drm_rect_width(&new_state->src) >> 16; + scanout->src_h = drm_rect_height(&new_state->src) >> 16; + scanout->crtc_x = new_state->dst.x1; + scanout->crtc_y = new_state->dst.y1; + scanout->crtc_w = drm_rect_width(&new_state->dst); + scanout->crtc_h = drm_rect_height(&new_state->dst); +} + +static void sun60i_de_commit(struct sunxi_engine *engine, + struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct sun60i_de *de = engine_to_sun60i_de(engine); + int ret; + + sun60i_de_update_plane_scanout(de, &de->primary, state); + sun60i_de_update_plane_scanout(de, &de->cursor, state); + + if (!de->enabled || !de->mode_valid) { + dev_err_once(de->dev, + "de: commit skipped (enabled=%d mode_valid=%d)\n", + de->enabled, de->mode_valid); + return; + } + + ret = sun60i_de_hw_commit(de); + if (ret) + dev_err_ratelimited(de->dev, "Failed to commit display state: %d\n", ret); +} + +static void sun60i_de_mode_set(struct sunxi_engine *engine, + const struct drm_display_mode *mode) +{ + struct sun60i_de *de = engine_to_sun60i_de(engine); + + drm_mode_copy(&de->mode, mode); + de->mode_valid = true; +} + +static void sun60i_de_quiesce(struct sunxi_engine *engine, + struct drm_crtc *crtc) +{ + struct sun60i_de *de = engine_to_sun60i_de(engine); + + memset(&de->scanout, 0, sizeof(de->scanout)); + memset(&de->cursor_scanout, 0, sizeof(de->cursor_scanout)); + /* + * A modeset quiesce is a real hardware disable. Keep the clock + * framework state in lockstep with DETOP: sun60i_de_hw_disable() used + * to close the module/MBUS gates behind the framework's back while + * leaving de->enabled set. The following enable then returned early, + * so RCQ completed against clock-gated BLD/UI blocks and the display + * stayed black after the first mode change. + */ + sun60i_de_hw_disable_clocks(de); +} + +static void sun60i_de_enable(struct sunxi_engine *engine, + const struct drm_display_mode *mode) +{ + struct sun60i_de *de = engine_to_sun60i_de(engine); + int ret; + + drm_mode_copy(&de->mode, mode); + de->mode_valid = true; + + ret = sun60i_de_hw_enable(de); + if (ret) { + dev_err(de->dev, "de: enable failed: %d\n", ret); + return; + } +} + +static int sun60i_de_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state; + struct drm_crtc_state *crtc_state; + int ret; + + new_state = drm_atomic_get_new_plane_state(state, plane); + if (!new_state->crtc) + return 0; + + crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + ret = drm_atomic_helper_check_plane_state(new_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + plane->type == DRM_PLANE_TYPE_CURSOR, + true); + if (ret) + return ret; + + if (plane->type == DRM_PLANE_TYPE_CURSOR && new_state->visible && + (drm_rect_width(&new_state->src) > 256 << 16 || + drm_rect_height(&new_state->src) > 256 << 16)) + return -EINVAL; + + return 0; +} + +static void sun60i_de_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct sun60i_de *de = plane_to_sun60i_de(plane); + struct sun60i_de_scanout_state *scanout = sun60i_de_plane_scanout(de, plane); + + memset(scanout, 0, sizeof(*scanout)); +} + +static void sun60i_de_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + /* + * Scanout is latched in sun60i_de_commit() via the CRTC flush path. + * Provide a non-NULL atomic_update so drm_atomic_helper_commit_planes() + * does not call into a NULL function pointer. + */ +} + +static const struct drm_plane_helper_funcs sun60i_de_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .atomic_check = sun60i_de_plane_atomic_check, + .atomic_update = sun60i_de_plane_atomic_update, + .atomic_disable = sun60i_de_plane_atomic_disable, +}; + +static const struct drm_plane_funcs sun60i_de_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static struct drm_plane **sun60i_de_layers_init(struct drm_device *drm, + struct sunxi_engine *engine) +{ + struct sun60i_de *de = engine_to_sun60i_de(engine); + struct drm_plane **planes; + int ret; + + planes = devm_kcalloc(drm->dev, 3, sizeof(*planes), GFP_KERNEL); + if (!planes) + return ERR_PTR(-ENOMEM); + + ret = drm_universal_plane_init(drm, &de->primary, 0, + &sun60i_de_plane_funcs, + sun60i_de_formats, + ARRAY_SIZE(sun60i_de_formats), + sun60i_de_modifiers, + DRM_PLANE_TYPE_PRIMARY, + "sun60i-primary"); + if (ret) + return ERR_PTR(ret); + + drm_plane_helper_add(&de->primary, &sun60i_de_plane_helper_funcs); + + ret = drm_universal_plane_init(drm, &de->cursor, 0, + &sun60i_de_plane_funcs, + sun60i_de_cursor_formats, + ARRAY_SIZE(sun60i_de_cursor_formats), + sun60i_de_modifiers, + DRM_PLANE_TYPE_CURSOR, + "sun60i-cursor"); + if (ret) + return ERR_PTR(ret); + + drm_plane_helper_add(&de->cursor, &sun60i_de_plane_helper_funcs); + + planes[0] = &de->primary; + planes[1] = &de->cursor; + planes[2] = NULL; + + return planes; +} + +static const struct sunxi_engine_ops sun60i_de_engine_ops = { + .commit = sun60i_de_commit, + .layers_init = sun60i_de_layers_init, + .mode_set = sun60i_de_mode_set, + .quiesce = sun60i_de_quiesce, + .enable = sun60i_de_enable, +}; + +/* + * T527-style mixer@100000 lists engine/top/display/detop. All sun60i_de + * offsets are from DE base (0x05000000); detop is DE+0x8000. Map the full + * 4MiB window without exclusive claim so clock@8000 can own DETOP. + */ +static int sun60i_de_map_regs(struct platform_device *pdev, struct sun60i_de *de) +{ + struct resource *detop, *res; + resource_size_t base; + + detop = platform_get_resource_byname(pdev, IORESOURCE_MEM, "detop"); + if (detop) { + if (detop->start < 0x8000) + return -EINVAL; + base = detop->start - 0x8000; + de->regs = devm_ioremap(&pdev->dev, base, 0x400000); + if (!de->regs) + return -ENOMEM; + return 0; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + de->regs = devm_ioremap_resource(&pdev->dev, res); + return PTR_ERR_OR_ZERO(de->regs); +} + +static int sun60i_de_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun60i_de *de; + int ret; + + de = devm_kzalloc(dev, sizeof(*de), GFP_KERNEL); + if (!de) + return -ENOMEM; + + de->dev = dev; + de->engine.ops = &sun60i_de_engine_ops; + de->engine.node = dev->of_node; + de->engine.id = 0; + + sun60i_de_init_topology(de); + + ret = sun60i_de_map_regs(pdev, de); + if (ret) + return dev_err_probe(dev, ret, "failed to map DE regs\n"); + + de->clk_mixer = devm_clk_get(dev, "mod"); + if (IS_ERR(de->clk_mixer)) + return dev_err_probe(dev, PTR_ERR(de->clk_mixer), + "failed to get mixer module clock\n"); + + de->clk_bus_mixer = devm_clk_get(dev, "bus"); + if (IS_ERR(de->clk_bus_mixer)) + return dev_err_probe(dev, PTR_ERR(de->clk_bus_mixer), + "failed to get mixer bus clock\n"); + + de->rst_mixer = devm_reset_control_get_shared(dev, "mod"); + if (IS_ERR(de->rst_mixer)) + return dev_err_probe(dev, PTR_ERR(de->rst_mixer), + "failed to get mixer reset\n"); + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + list_add_tail(&de->engine.list, &drv->engine_list); + dev_set_drvdata(dev, de); + + return 0; +} + +static void sun60i_de_unbind(struct device *dev, struct device *master, void *data) +{ + struct sun60i_de *de = dev_get_drvdata(dev); + + if (!de) + return; + + sun60i_de_hw_disable_clocks(de); + + /* The DRM master owns plane cleanup through drm_mode_config_cleanup(). */ + list_del(&de->engine.list); + dev_set_drvdata(dev, NULL); +} + +static const struct component_ops sun60i_de_ops = { + .bind = sun60i_de_bind, + .unbind = sun60i_de_unbind, +}; + +static int sun60i_de_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun60i_de_ops); +} + +static void sun60i_de_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun60i_de_ops); +} + +static const struct of_device_id sun60i_de_of_match[] = { + { .compatible = "allwinner,sun60i-a733-de352-mixer-0" }, + { .compatible = "allwinner,sun60i-a733-de" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, sun60i_de_of_match); + +static struct platform_driver sun60i_de_platform_driver = { + .probe = sun60i_de_probe, + .remove = sun60i_de_remove, + .driver = { + .name = "sun60i-de", + .of_match_table = sun60i_de_of_match, + }, +}; +module_platform_driver(sun60i_de_platform_driver); + +MODULE_DESCRIPTION("Allwinner sun60i DE352 display engine"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun60i_de.h b/drivers/gpu/drm/sun4i/sun60i_de.h new file mode 100644 index 00000000000000..490be4eb6da95a --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun60i_de.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ + +#ifndef _SUN60I_DE_H_ +#define _SUN60I_DE_H_ + +#include +#include + +#include +#include +#include + +#include "sunxi_engine.h" + +struct sun60i_de_scanout_state { + bool enabled; + dma_addr_t addr; + u32 pitch; + u32 format; + u32 src_x; + u32 src_y; + u32 src_w; + u32 src_h; + u32 crtc_x; + u32 crtc_y; + u32 crtc_w; + u32 crtc_h; +}; + +struct sun60i_de { + struct sunxi_engine engine; + struct device *dev; + struct drm_plane primary; + struct drm_plane cursor; + struct clk *clk_mixer; + struct clk *clk_bus_mixer; + struct reset_control *rst_mixer; + void __iomem *regs; + struct drm_display_mode mode; + struct sun60i_de_scanout_state scanout; + struct sun60i_de_scanout_state cursor_scanout; + u8 display_id; + u8 tcon_id; + u8 ui_channel; + u8 bld_port; + u8 chn_cfg_mode; + bool mode_valid; + bool enabled; + bool rtmx_running; + u32 programmed_hdisplay; + u32 programmed_vdisplay; + bool rcq_ready; + bool rcq_head_done; + + dma_addr_t rcq_dma; + size_t rcq_alloc_size; + unsigned int rcq_nblocks; + void *rcq_cpu; + void *rcq_bld_shadow; + void *rcq_fmt_shadow; + void *rcq_ovl_shadow; + void *rcq_vsu_shadow; + void *rcq_tfbd_shadow; + void *rcq_ccsc_shadow; +}; + +static inline struct sun60i_de *engine_to_sun60i_de(struct sunxi_engine *engine) +{ + return container_of(engine, struct sun60i_de, engine); +} + +static inline struct sun60i_de *plane_to_sun60i_de(struct drm_plane *plane) +{ + if (plane->type == DRM_PLANE_TYPE_CURSOR) + return container_of(plane, struct sun60i_de, cursor); + + return container_of(plane, struct sun60i_de, primary); +} + +#endif /* _SUN60I_DE_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun60i_de_hw.c b/drivers/gpu/drm/sun4i/sun60i_de_hw.c new file mode 100644 index 00000000000000..60568ea8813be6 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun60i_de_hw.c @@ -0,0 +1,1193 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* Allwinner sun60i / A733 DE352 display engine programming. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sun60i_de.h" +#include "sun60i_de_hw.h" + +#define SUN60I_DE_RESET_OFFSET 0x8000 +#define SUN60I_DE_CLK_OFFSET 0x8004 +#define SUN60I_DE_MBUS_CLK_OFFSET 0x8008 +/* BSP de_top DE_RESERVE_CTL: bit12 enables RCQ FIFO update mode (v3xx) */ +#define SUN60I_DE_RESERVE_CTL_OFFSET 0x800c +#define SUN60I_DE_RESERVE_CTL_RCQ_FIFO_EN BIT(12) +/* sun60iw2 (A733): BSP de_top_set_ahb_read_mode(1) before RCQ/RTMX access */ +#define SUN60I_DE_RESERVE_CTL_AHB_CONFLICT BIT(8) +#define SUN60I_DE_VER_CTL_OFFSET 0x8014 +#define SUN60I_DE_DE2TCON_MUX_OFFSET 0x8010 +#define SUN60I_DE_VCH2CORE_MUX_OFFSET 0x8028 +#define SUN60I_DE_UCH2CORE_MUX_OFFSET 0x802c +#define SUN60I_DE_PORT2CHN_MUX_OFFSET 0x8030 +#define SUN60I_DE_BUF_DEPTH_OFFSET 0x8050 +#define SUN60I_DE_URGENCY_CTL_OFFSET 0x8080 +#define SUN60I_DE_GATING_CTL_OFFSET 0x80e8 +#define SUN60I_DE_GLB_CTL_OFFSET 0x8100 +#define SUN60I_DE_OUT_SIZE_OFFSET 0x8108 +#define SUN60I_DE_AUTO_CLK_OFFSET 0x810c +#define SUN60I_DE_AUTO_CLK_RCQ_GATE BIT(16) +#define SUN60I_DE_RCQ_CTL_OFFSET 0x8110 +#define SUN60I_DE_RCQ_HEADER_LADDR_OFFSET 0x8114 +#define SUN60I_DE_GLB_STS_OFFSET 0x8104 +#define SUN60I_DE_GLB_STS_BUSY BIT(4) +#define SUN60I_DE_FRAME_END BIT(0) +#define SUN60I_DE_RCQ_FINISH BIT(2) +#define SUN60I_DE_RCQ_ACCEPT BIT(3) +#define SUN60I_DE_IRQ_STATE_MASK GENMASK(3, 0) + +#define SUN60I_DE_DISP_CLK_SHIFT 4 + +#define SUN60I_DE_GLB_CTL_RTMX_EN BIT(0) +#define SUN60I_DE_GLB_CTL_FIELD_REVERSE BIT(9) + +#define SUN60I_DE_DISP_STRIDE 0x40 +#define SUN60I_DE_ASYNC_BRIDGE_OFFSET 0x804c +#define SUN60I_DE_CHN_SIZE 0x20000 +#define SUN60I_DE_CHN_BASE(phy) (0x100000 + (phy) * SUN60I_DE_CHN_SIZE) +#define SUN60I_DE_CHN_OVL_OFFSET 0x1000 +#define SUN60I_DE_CHN_SCALER_OFFSET 0x4000 +#define SUN60I_DE_UI_PHY_CHN 6 +#define SUN60I_DE_UI_BASE (SUN60I_DE_CHN_BASE(SUN60I_DE_UI_PHY_CHN) + \ + SUN60I_DE_CHN_OVL_OFFSET) +#define SUN60I_DE_CHN_CCSC_OFFSET 0x0800 +#define SUN60I_DE_CHN_SNR_OFFSET 0x6400 +#define SUN60I_DE_CHN_SHARP_OFFSET 0x6000 +#define SUN60I_DE_UI_CCSC_BASE (SUN60I_DE_CHN_BASE(SUN60I_DE_UI_PHY_CHN) + \ + SUN60I_DE_CHN_CCSC_OFFSET) +#define SUN60I_DE_UI_SNR_BASE (SUN60I_DE_CHN_BASE(SUN60I_DE_UI_PHY_CHN) + \ + SUN60I_DE_CHN_SNR_OFFSET) +#define SUN60I_DE_UI_SHARP_BASE (SUN60I_DE_CHN_BASE(SUN60I_DE_UI_PHY_CHN) + \ + SUN60I_DE_CHN_SHARP_OFFSET) +#define SUN60I_DE_UI_SCALER_BASE (SUN60I_DE_CHN_BASE(SUN60I_DE_UI_PHY_CHN) + \ + SUN60I_DE_CHN_SCALER_OFFSET) +#define SUN60I_DE_DISP_BASE(disp) (0x280000 + (disp) * 0x20000) +#define SUN60I_DE_FMT_BASE(disp) (SUN60I_DE_DISP_BASE(disp) + 0x5000) +#define SUN60I_DE_DITHER_BASE(disp) (SUN60I_DE_DISP_BASE(disp) + 0x8000) +#define SUN60I_DE_GAMMA_BASE(disp) (SUN60I_DE_DISP_BASE(disp) + 0x9000) +#define SUN60I_DE_BLD_BASE(disp) (SUN60I_DE_DISP_BASE(disp) + 0x1000) + +#define SUN60I_DE_BLD_EN 0x0000 +#define SUN60I_DE_BLD_PIPE_FCOLOR(pipe) (0x0004 + (pipe) * 0x10) +#define SUN60I_DE_BLD_PIPE_IN_SIZE(pipe) (0x0008 + (pipe) * 0x10) +#define SUN60I_DE_BLD_PIPE_IN_COORD(pipe) (0x000c + (pipe) * 0x10) +#define SUN60I_DE_BLD_ROUT_CTL 0x0080 +#define SUN60I_DE_BLD_PREMUL_CTL 0x0084 +#define SUN60I_DE_BLD_BG_COLOR 0x0088 +#define SUN60I_DE_BLD_OUT_SIZE 0x008c +#define SUN60I_DE_BLD_BLEND_CTL(pipe) (0x0090 + (pipe) * 0x4) +#define SUN60I_DE_BLD_OUT_CTL 0x00fc + +#define SUN60I_DE_BLD_FCOLOR_EN(pipe) BIT(pipe) +#define SUN60I_DE_BLD_PIPE_EN(pipe) BIT(8 + (pipe)) +#define SUN60I_DE_BLD_MODE_SRCOVER 0x03010301 +#define SUN60I_DE_BLD_MODE_DST 0x01000100 +#define SUN60I_DE_BLD_OUT_CTL_INTERLACE BIT(1) +#define SUN60I_DE_BLD_OUT_CTL_FMT_RGB (0 << 8) + +#define SUN60I_DE_CCSC_CTL_OFF 0x00 +#define SUN60I_DE_CCSC_D0_OFF 0x04 +#define SUN60I_DE_CCSC_C0_OFF 0x10 +#define SUN60I_DE_CCSC_BYPASS_COEFF 0x00000400 +#define SUN60I_DE_SHARP_CTL_OFF 0x00 + +#define SUN60I_DE_RCQ_FMT_SIZE 0x2c +#define SUN60I_DE_FMT_SIZE_REG_OFF 0x04 +#define SUN60I_DE_FMT_LIMIT_Y_OFF 0x20 +#define SUN60I_DE_FMT_LIMIT_C0_OFF 0x24 +#define SUN60I_DE_FMT_LIMIT_C1_OFF 0x28 +#define SUN60I_DE_FMT_LIMIT_FULL 0x0fff0000 +#define SUN60I_DE_RCQ_OVL_DS_OFF 0xe0 +#define SUN60I_DE_RCQ_OVL_DS_SIZE 0x1c +#define SUN60I_DE_CHN_TFBD_OFFSET 0x5400 +#define SUN60I_DE_UI_TFBD_BASE (SUN60I_DE_CHN_BASE(SUN60I_DE_UI_PHY_CHN) + \ + SUN60I_DE_CHN_TFBD_OFFSET) + +#define SUN60I_DE_VSU_CTL_SIZE 0x14 +#define SUN60I_DE_VSU_ATTR_SIZE 0x08 +#define SUN60I_DE_VSU_PARA_SIZE 0x1c +#define SUN60I_DE_VSU_COEFF_SIZE 0x80 +#define SUN60I_DE_VSU8_CPARA_OFF 0x0c0 +#define SUN60I_DE_VSU8_COEFF0_OFF 0x200 +#define SUN60I_DE_VSU8_COEFF1_OFF 0x400 +#define SUN60I_DE_VSU8_COEFF2_OFF 0x600 +#define SUN60I_DE_VSU_SHADOW_SIZE 0x680 + +#define SUN60I_DE_RCQ_TFBD_SIZE 0x4 +#define SUN60I_DE_RCQ_CCSC_CTL_SIZE 0x4 +#define SUN60I_DE_RCQ_ALIGN 32 +#define SUN60I_DE_RCQ_BLOCK_COUNT 19 + +#define SUN60I_DE_UI_LAY_STRIDE 0x20 +#define SUN60I_DE_UI_LAY_CTL(layer) ((layer) * SUN60I_DE_UI_LAY_STRIDE + 0x00) +#define SUN60I_DE_UI_LAY_MBSIZE(layer) ((layer) * SUN60I_DE_UI_LAY_STRIDE + 0x04) +#define SUN60I_DE_UI_LAY_MBCOOR(layer) ((layer) * SUN60I_DE_UI_LAY_STRIDE + 0x08) +#define SUN60I_DE_UI_LAY_PITCH(layer) ((layer) * SUN60I_DE_UI_LAY_STRIDE + 0x0c) +#define SUN60I_DE_UI_LAY_TOP_LADDR(layer) ((layer) * SUN60I_DE_UI_LAY_STRIDE + 0x10) +#define SUN60I_DE_UI_LAY_BOT_LADDR(layer) ((layer) * SUN60I_DE_UI_LAY_STRIDE + 0x14) +#define SUN60I_DE_UI_LAY_FCOLOR(layer) ((layer) * SUN60I_DE_UI_LAY_STRIDE + 0x18) +#define SUN60I_DE_UI_TOP_HADDR 0x0080 +#define SUN60I_DE_UI_BOT_HADDR 0x0084 +#define SUN60I_DE_UI_WIN_SIZE 0x0088 +#define SUN60I_DE_UI_HORI_DS 0x00f0 +#define SUN60I_DE_UI_VERT_DS 0x00f8 + +#define SUN60I_DE_UI_CTL_EN BIT(0) +#define SUN60I_DE_UI_CTL_ALPHA_MODE_SHIFT 1 +#define SUN60I_DE_UI_CTL_FMT_SHIFT 8 +#define SUN60I_DE_UI_CTL_GLB_ALPHA_SHIFT 24 +#define SUN60I_DE_UI_CTL_COLOR_FILL BIT(4) + +#define SUN60I_DE_VSU8_CTL_EN BIT(0) +#define SUN60I_DE_VSU8_SCALE_MODE_OFF 0x10 +#define SUN60I_DE_VSU8_OUT_SIZE_OFF 0x40 +#define SUN60I_DE_VSU8_GLB_ALPHA_OFF 0x44 +#define SUN60I_DE_VSU8_Y_IN_SIZE_OFF 0x80 +#define SUN60I_DE_VSU8_Y_HSTEP_OFF 0x88 +#define SUN60I_DE_VSU8_Y_VSTEP_OFF 0x8c +#define SUN60I_DE_VSU8_Y_HPHASE_OFF 0x90 +#define SUN60I_DE_VSU8_Y_VPHASE_OFF 0x98 +/* VSU8 1:1 step: (1 << 19) << VSU8_STEP_VALID_START_BIT(1) */ +#define SUN60I_DE_VSU8_STEP_1TO1 0x100000 + +#define SUN60I_DE_MBUS_CLK_EN BIT(0) +#define SUN60I_DE_MBUS_RESET_DEASSERT BIT(4) +#define SUN60I_DE_MBUS_AUTO_GATE BIT(8) + +#define SUN60I_DE_RCQ_HEAD_SIZE 16 +#define SUN60I_DE_RCQ_HEAD_COUNT \ + ALIGN(SUN60I_DE_RCQ_BLOCK_COUNT, 2) +#define SUN60I_DE_RCQ_HEAD_LEN (SUN60I_DE_RCQ_HEAD_COUNT * \ + SUN60I_DE_RCQ_HEAD_SIZE) +#define SUN60I_DE_RCQ_HEAD_LEN_ALIGNED (ALIGN(SUN60I_DE_RCQ_HEAD_LEN, \ + SUN60I_DE_RCQ_HEAD_SIZE * 2)) +#define SUN60I_DE_RCQ_OVL_LAY0_SIZE 0x1c +#define SUN60I_DE_RCQ_OVL_PARA_SIZE 0x0c +#define SUN60I_DE_RCQ_OVL_PARA_OFF 0x80 +#define SUN60I_DE_RCQ_BLD_ATTR_SIZE 0x60 +#define SUN60I_DE_RCQ_BLD_CTL_SIZE 0x24 +#define SUN60I_DE_RCQ_BLD_CTL_OFF 0x80 +#define SUN60I_DE_RCQ_BLD_CK_SIZE 0x60 +#define SUN60I_DE_RCQ_BLD_CK_OFF 0xa0 +#define SUN60I_DE_RCQ_OVL_SHADOW_SIZE 0x100 +#define SUN60I_DE_RCQ_BLD_SHADOW_SIZE 0x100 + +#define SUN60I_DE_AHB_TIMEOUT_OFFSET 0x80e4 + +/* HDMI UI phys6: clk gating bit (phy + 2), see de_rtmx_set_chn_mux sun60iw2 */ +#define SUN60I_DE_HDMI_UI_GATING BIT(8) + +#define SUN60I_DE_GLB_CTL_PIXEL_MODE_SHIFT 16 + +#define SUN60I_DE_CHN_CDC_OFFSET 0x8000 +#define SUN60I_DE_UI_CDC_BASE (SUN60I_DE_CHN_BASE(SUN60I_DE_UI_PHY_CHN) + \ + SUN60I_DE_CHN_CDC_OFFSET) + +/* TCON DEBUG @0xfc — current scanline (DE352 RCQ wait window, see BSP rcq_wait_line) */ +#define SUN60I_TCON_DEBUG_OFF 0x00fc +#define SUN60I_TCON_DEBUG_TV_LINE_MASK GENMASK(11, 0) + +struct sun60i_de_rcq_head { + u32 low_addr; + u32 dw0; + u32 dirty; + u32 reg_offset; +}; + +static void sun60i_de_top_auto_clk_write(struct sun60i_de *de, bool enable, + bool rcq_gate); +static void sun60i_de_top_glb_rtmx_write(struct sun60i_de *de, bool enable); +static void sun60i_de_top_set_de2tcon_mux(struct sun60i_de *de, u32 tcon); +static void sun60i_de_top_setup_chn_mux(struct sun60i_de *de); +static void sun60i_de_rcq_program_head(struct sun60i_de *de); +static void sun60i_de_top_rtmx_buf_depth(struct sun60i_de *de); +static void sun60i_de_top_set_out_size(struct sun60i_de *de); +static void sun60i_de_top_set_urgency(struct sun60i_de *de); + +static inline void sun60i_de_writel(struct sun60i_de *de, u32 reg, u32 value) +{ + writel(value, de->regs + reg); +} + +static inline u32 sun60i_de_readl(struct sun60i_de *de, u32 reg) +{ + return readl(de->regs + reg); +} + +/* RMW one field — matches BSP SET_BITS / sun55i_de regmap_read pattern. */ +static void sun60i_de_rmw(struct sun60i_de *de, u32 reg, u32 mask, u32 val) +{ + u32 tmp = sun60i_de_readl(de, reg); + + tmp = (tmp & ~mask) | (val & mask); + sun60i_de_writel(de, reg, tmp); +} + +static void sun60i_de_rmw_bits(struct sun60i_de *de, u32 reg, u32 shift, + u32 width, u32 val) +{ + u32 mask = ((width >= 32) ? ~0U : ((1U << width) - 1U)) << shift; + + sun60i_de_rmw(de, reg, mask, (val << shift) & mask); +} + +static void sun60i_de_shadow_writel(void *shadow, u32 offset, u32 value) +{ + writel(value, (u8 __iomem *)shadow + offset); +} + +static u32 sun60i_de_rcq_pack_dw0(dma_addr_t phy, u32 len) +{ + return (len & 0xffffff) | ((phy >> 32) & 0xff) << 24; +} + +static void sun60i_de_vsu_writel(struct sun60i_de *de, u32 offset, u32 value) +{ + sun60i_de_shadow_writel(de->rcq_vsu_shadow, offset, value); +} + +static void sun60i_de_ccsc_writel(struct sun60i_de *de, u32 offset, u32 value) +{ + sun60i_de_shadow_writel(de->rcq_ccsc_shadow, offset, value); +} + +static void sun60i_de_tfbd_writel(struct sun60i_de *de, u32 offset, u32 value) +{ + sun60i_de_shadow_writel(de->rcq_tfbd_shadow, offset, value); +} + +static void sun60i_de_fmt_writel(struct sun60i_de *de, u32 offset, u32 value) +{ + sun60i_de_shadow_writel(de->rcq_fmt_shadow, offset, value); +} + +static void sun60i_de_ovl_writel(struct sun60i_de *de, u32 offset, u32 value) +{ + sun60i_de_shadow_writel(de->rcq_ovl_shadow, offset, value); +} + +static void sun60i_de_bld_writel(struct sun60i_de *de, u32 offset, u32 value) +{ + if (de->rcq_bld_shadow) + sun60i_de_shadow_writel(de->rcq_bld_shadow, offset, value); +} + +static size_t sun60i_de_rcq_pool_data_size(void) +{ + size_t size = 0; + + size += ALIGN(SUN60I_DE_RCQ_BLD_SHADOW_SIZE, SUN60I_DE_RCQ_ALIGN); + size += ALIGN(SUN60I_DE_RCQ_FMT_SIZE, SUN60I_DE_RCQ_ALIGN); + size += ALIGN(SUN60I_DE_RCQ_OVL_SHADOW_SIZE, SUN60I_DE_RCQ_ALIGN); + size += ALIGN(SUN60I_DE_VSU_SHADOW_SIZE, SUN60I_DE_RCQ_ALIGN); + size += ALIGN(SUN60I_DE_RCQ_TFBD_SIZE, SUN60I_DE_RCQ_ALIGN); + size += ALIGN(SUN60I_DE_RCQ_CCSC_CTL_SIZE, SUN60I_DE_RCQ_ALIGN); + + return size; +} + +static struct device *sun60i_de_dma_dev(struct sun60i_de *de) +{ + /* + * memory-region = <&drm_cma> is on the sun4i_drv master node, not the + * DE component. Allocating via de->dev has no cma_area and dma_alloc_wc + * then wedges the SoC on A733 (hang right after the warn). + */ + if (de->primary.dev) + return de->primary.dev->dev; + + return de->dev; +} + +static size_t sun60i_de_rcq_total_size(void) +{ + size_t size = SUN60I_DE_RCQ_HEAD_LEN_ALIGNED + sun60i_de_rcq_pool_data_size(); + + size = PAGE_ALIGN(size); + if (size <= PAGE_SIZE) + size = PAGE_SIZE * 2; + + return size; +} + +static void sun60i_de_rcq_sync_for_device(struct sun60i_de *de) +{ + /* + * RCQ pool comes from dma_alloc_wc(), not dma_map_single(). Calling + * dma_sync_single_for_device() on a coherent/WC dma_addr is undefined + * and on A733 with the IOMMU can leave the mapping unusable. + * WC already needs only a write barrier so the DE DMA sees CPU stores. + */ + if (!de->rcq_cpu) + return; + + /* Publish WC descriptor and shadow writes before the RCQ doorbell. */ + wmb(); +} + +static u32 sun60i_de_pack_size(u32 width, u32 height) +{ + return (width ? width - 1 : 0) | + (height ? (height - 1) << 16 : 0); +} + +static void sun60i_de_hw_program_fmt(struct sun60i_de *de) +{ + u32 size; + + if (!de->rcq_fmt_shadow || !de->mode_valid) + return; + + size = sun60i_de_pack_size(de->mode.crtc_hdisplay, de->mode.crtc_vdisplay); + + memset(de->rcq_fmt_shadow, 0, SUN60I_DE_RCQ_FMT_SIZE); + sun60i_de_fmt_writel(de, SUN60I_DE_FMT_SIZE_REG_OFF, size); + sun60i_de_fmt_writel(de, SUN60I_DE_FMT_LIMIT_Y_OFF, SUN60I_DE_FMT_LIMIT_FULL); + sun60i_de_fmt_writel(de, SUN60I_DE_FMT_LIMIT_C0_OFF, SUN60I_DE_FMT_LIMIT_FULL); + sun60i_de_fmt_writel(de, SUN60I_DE_FMT_LIMIT_C1_OFF, SUN60I_DE_FMT_LIMIT_FULL); +} + +static void sun60i_de_hw_program_tfbd(struct sun60i_de *de) +{ + if (!de->rcq_tfbd_shadow) + return; + + memset(de->rcq_tfbd_shadow, 0, SUN60I_DE_RCQ_TFBD_SIZE); + sun60i_de_tfbd_writel(de, 0, 0); +} + +static void sun60i_de_rcq_free(struct sun60i_de *de) +{ + struct device *dma_dev = sun60i_de_dma_dev(de); + + if (!de->rcq_cpu) + return; + + dma_free_wc(dma_dev, de->rcq_alloc_size ? de->rcq_alloc_size : + sun60i_de_rcq_total_size(), de->rcq_cpu, de->rcq_dma); + de->rcq_cpu = NULL; + de->rcq_dma = 0; + de->rcq_alloc_size = 0; + de->rcq_nblocks = 0; + de->rcq_bld_shadow = NULL; + de->rcq_fmt_shadow = NULL; + de->rcq_ovl_shadow = NULL; + de->rcq_vsu_shadow = NULL; + de->rcq_tfbd_shadow = NULL; + de->rcq_ccsc_shadow = NULL; + de->rcq_ready = false; + de->rcq_head_done = false; +} + +static void sun60i_de_rcq_fill_head(struct sun60i_de_rcq_head *head, + dma_addr_t phy, u32 len, u32 reg_off) +{ + head->low_addr = lower_32_bits(phy); + head->dw0 = sun60i_de_rcq_pack_dw0(phy, len); + head->dirty = 0; + head->reg_offset = reg_off; +} + +static void sun60i_de_rcq_layout_shadows(u8 *base, dma_addr_t pool_dma, + struct sun60i_de *de, + dma_addr_t *bld_dma, dma_addr_t *fmt_dma, + dma_addr_t *ovl_dma, dma_addr_t *vsu_dma, + dma_addr_t *tfbd_dma, dma_addr_t *ccsc_dma) +{ + size_t off = SUN60I_DE_RCQ_HEAD_LEN_ALIGNED; + + de->rcq_bld_shadow = base + off; + *bld_dma = pool_dma + off; + off += ALIGN(SUN60I_DE_RCQ_BLD_SHADOW_SIZE, SUN60I_DE_RCQ_ALIGN); + + de->rcq_fmt_shadow = base + off; + *fmt_dma = pool_dma + off; + off += ALIGN(SUN60I_DE_RCQ_FMT_SIZE, SUN60I_DE_RCQ_ALIGN); + + de->rcq_ovl_shadow = base + off; + *ovl_dma = pool_dma + off; + off += ALIGN(SUN60I_DE_RCQ_OVL_SHADOW_SIZE, SUN60I_DE_RCQ_ALIGN); + + de->rcq_vsu_shadow = base + off; + *vsu_dma = pool_dma + off; + off += ALIGN(SUN60I_DE_VSU_SHADOW_SIZE, SUN60I_DE_RCQ_ALIGN); + + de->rcq_tfbd_shadow = base + off; + *tfbd_dma = pool_dma + off; + off += ALIGN(SUN60I_DE_RCQ_TFBD_SIZE, SUN60I_DE_RCQ_ALIGN); + + de->rcq_ccsc_shadow = base + off; + *ccsc_dma = pool_dma + off; +} + +static int sun60i_de_rcq_init(struct sun60i_de *de) +{ + struct sun60i_de_rcq_head *heads; + struct device *dma_dev = sun60i_de_dma_dev(de); + dma_addr_t bld_dma, fmt_dma, ovl_dma, vsu_dma, tfbd_dma, ccsc_dma; + dma_addr_t bld_base = SUN60I_DE_BLD_BASE(de->display_id); + dma_addr_t fmt_base = SUN60I_DE_FMT_BASE(de->display_id); + size_t size; + u8 *base; + unsigned int idx = 0; + + if (de->rcq_ready) + return 0; + + size = sun60i_de_rcq_total_size(); + + if (!dma_dev->cma_area) { + dev_err(de->dev, + "de: no cma_area on dma_dev %s (need master memory-region)\n", + dev_name(dma_dev)); + return -ENODEV; + } + + base = dma_alloc_wc(dma_dev, size, &de->rcq_dma, GFP_KERNEL); + if (!base) { + dev_err(de->dev, "de: rcq dma_alloc_wc(%zu) failed\n", size); + return -ENOMEM; + } + + de->rcq_cpu = base; + memset(base, 0, size); + heads = (struct sun60i_de_rcq_head *)base; + + sun60i_de_rcq_layout_shadows(base, de->rcq_dma, de, &bld_dma, &fmt_dma, + &ovl_dma, &vsu_dma, &tfbd_dma, &ccsc_dma); + + /* BLD ×3 */ + sun60i_de_rcq_fill_head(&heads[idx++], bld_dma, SUN60I_DE_RCQ_BLD_ATTR_SIZE, + bld_base); + sun60i_de_rcq_fill_head(&heads[idx++], + bld_dma + SUN60I_DE_RCQ_BLD_CTL_OFF, + SUN60I_DE_RCQ_BLD_CTL_SIZE, + bld_base + SUN60I_DE_RCQ_BLD_CTL_OFF); + sun60i_de_rcq_fill_head(&heads[idx++], + bld_dma + SUN60I_DE_RCQ_BLD_CK_OFF, + SUN60I_DE_RCQ_BLD_CK_SIZE, + bld_base + SUN60I_DE_RCQ_BLD_CK_OFF); + /* FMT */ + sun60i_de_rcq_fill_head(&heads[idx++], fmt_dma, SUN60I_DE_RCQ_FMT_SIZE, + fmt_base); + /* OVL ×6 (LAY0-3 + PARA + DS) */ + sun60i_de_rcq_fill_head(&heads[idx++], ovl_dma, SUN60I_DE_RCQ_OVL_LAY0_SIZE, + SUN60I_DE_UI_BASE); + sun60i_de_rcq_fill_head(&heads[idx++], + ovl_dma + SUN60I_DE_UI_LAY_STRIDE, + SUN60I_DE_RCQ_OVL_LAY0_SIZE, + SUN60I_DE_UI_BASE + SUN60I_DE_UI_LAY_STRIDE); + sun60i_de_rcq_fill_head(&heads[idx++], + ovl_dma + SUN60I_DE_UI_LAY_STRIDE * 2, + SUN60I_DE_RCQ_OVL_LAY0_SIZE, + SUN60I_DE_UI_BASE + SUN60I_DE_UI_LAY_STRIDE * 2); + sun60i_de_rcq_fill_head(&heads[idx++], + ovl_dma + SUN60I_DE_UI_LAY_STRIDE * 3, + SUN60I_DE_RCQ_OVL_LAY0_SIZE, + SUN60I_DE_UI_BASE + SUN60I_DE_UI_LAY_STRIDE * 3); + sun60i_de_rcq_fill_head(&heads[idx++], + ovl_dma + SUN60I_DE_RCQ_OVL_PARA_OFF, + SUN60I_DE_RCQ_OVL_PARA_SIZE, + SUN60I_DE_UI_BASE + SUN60I_DE_RCQ_OVL_PARA_OFF); + sun60i_de_rcq_fill_head(&heads[idx++], + ovl_dma + SUN60I_DE_RCQ_OVL_DS_OFF, + SUN60I_DE_RCQ_OVL_DS_SIZE, + SUN60I_DE_UI_BASE + SUN60I_DE_RCQ_OVL_DS_OFF); + /* VSU8 ×7 */ + sun60i_de_rcq_fill_head(&heads[idx++], vsu_dma, SUN60I_DE_VSU_CTL_SIZE, + SUN60I_DE_UI_SCALER_BASE); + sun60i_de_rcq_fill_head(&heads[idx++], + vsu_dma + SUN60I_DE_VSU8_OUT_SIZE_OFF, + SUN60I_DE_VSU_ATTR_SIZE, + SUN60I_DE_UI_SCALER_BASE + SUN60I_DE_VSU8_OUT_SIZE_OFF); + sun60i_de_rcq_fill_head(&heads[idx++], + vsu_dma + SUN60I_DE_VSU8_Y_IN_SIZE_OFF, + SUN60I_DE_VSU_PARA_SIZE, + SUN60I_DE_UI_SCALER_BASE + SUN60I_DE_VSU8_Y_IN_SIZE_OFF); + sun60i_de_rcq_fill_head(&heads[idx++], + vsu_dma + SUN60I_DE_VSU8_CPARA_OFF, + SUN60I_DE_VSU_PARA_SIZE, + SUN60I_DE_UI_SCALER_BASE + SUN60I_DE_VSU8_CPARA_OFF); + sun60i_de_rcq_fill_head(&heads[idx++], + vsu_dma + SUN60I_DE_VSU8_COEFF0_OFF, + SUN60I_DE_VSU_COEFF_SIZE, + SUN60I_DE_UI_SCALER_BASE + SUN60I_DE_VSU8_COEFF0_OFF); + sun60i_de_rcq_fill_head(&heads[idx++], + vsu_dma + SUN60I_DE_VSU8_COEFF1_OFF, + SUN60I_DE_VSU_COEFF_SIZE, + SUN60I_DE_UI_SCALER_BASE + SUN60I_DE_VSU8_COEFF1_OFF); + sun60i_de_rcq_fill_head(&heads[idx++], + vsu_dma + SUN60I_DE_VSU8_COEFF2_OFF, + SUN60I_DE_VSU_COEFF_SIZE, + SUN60I_DE_UI_SCALER_BASE + SUN60I_DE_VSU8_COEFF2_OFF); + /* TFBD + CCSC disable */ + sun60i_de_rcq_fill_head(&heads[idx++], tfbd_dma, SUN60I_DE_RCQ_TFBD_SIZE, + SUN60I_DE_UI_TFBD_BASE); + sun60i_de_rcq_fill_head(&heads[idx++], ccsc_dma, SUN60I_DE_RCQ_CCSC_CTL_SIZE, + SUN60I_DE_UI_CCSC_BASE); + + WARN_ON(idx != SUN60I_DE_RCQ_BLOCK_COUNT); + + de->rcq_ready = true; + de->rcq_nblocks = SUN60I_DE_RCQ_BLOCK_COUNT; + de->rcq_alloc_size = size; + + return 0; +} + +/* + * DE352 RCQ updates must land in the vertical blanking window (BSP + * rcq_wait_line=true). Pulsing RCQ mid-frame can leave stale/white pixels. + */ +static void sun60i_de_rcq_wait_safe_line(struct sun60i_de *de) +{ + u32 status; + u32 status_reg = SUN60I_DE_GLB_STS_OFFSET + + de->display_id * SUN60I_DE_DISP_STRIDE; + + if (!de->rtmx_running) + return; + + /* + * BSP submits RCQ from the display sync tasklet. Use RTMX frame-end as + * the same synchronization point: clear the stale W1C flag, wait for a + * fresh frame boundary, then pulse RCQ. A timeout is non-fatal during + * the first enable because TCON may not have started scanning yet. + */ + sun60i_de_writel(de, status_reg, SUN60I_DE_FRAME_END); + if (readl_poll_timeout_atomic(de->regs + status_reg, status, + status & SUN60I_DE_FRAME_END, + 50, 50000)) + dev_dbg(de->dev, "de: no frame-end before RCQ update\n"); +} + +/* + * Initialise the RCQ head dirty flags only. The head pointer is pushed into + * the RCQ FIFO by sun60i_de_rcq_submit() on every update (BSP re-pushes each + * request), so we must not push it here as well or the FIFO would double-count. + */ +static void sun60i_de_rcq_program_head(struct sun60i_de *de) +{ + struct sun60i_de_rcq_head *heads = de->rcq_cpu; + u32 head_reg = SUN60I_DE_RCQ_HEADER_LADDR_OFFSET + + de->display_id * SUN60I_DE_DISP_STRIDE; + u32 head_len; + unsigned int i, nblocks; + + nblocks = de->rcq_nblocks ? de->rcq_nblocks : SUN60I_DE_RCQ_BLOCK_COUNT; + head_len = ALIGN(nblocks, 2) * SUN60I_DE_RCQ_HEAD_SIZE; + head_len = ALIGN(head_len, SUN60I_DE_RCQ_HEAD_SIZE * 2); + + for (i = 0; i < nblocks; i++) + heads[i].dirty = 0; + + sun60i_de_rcq_sync_for_device(de); + + /* The hardware requires the RCQ length to be written last. */ + sun60i_de_writel(de, head_reg, lower_32_bits(de->rcq_dma)); + sun60i_de_writel(de, head_reg + 0x4, upper_32_bits(de->rcq_dma)); + sun60i_de_writel(de, head_reg + 0x8, head_len); +} + +static void sun60i_de_rcq_submit(struct sun60i_de *de) +{ + struct sun60i_de_rcq_head *heads = de->rcq_cpu; + u32 ctl_reg = SUN60I_DE_RCQ_CTL_OFFSET + + de->display_id * SUN60I_DE_DISP_STRIDE; + u32 status_reg = SUN60I_DE_GLB_STS_OFFSET + + de->display_id * SUN60I_DE_DISP_STRIDE; + u32 status; + unsigned int i, nblocks; + + if (!de->rcq_head_done) + return; + + nblocks = de->rcq_nblocks ? de->rcq_nblocks : SUN60I_DE_RCQ_BLOCK_COUNT; + + for (i = 0; i < nblocks; i++) + heads[i].dirty = 1; + + /* + * dma_alloc_wc() memory is write-combining: flush the CPU write buffer + * to DRAM so the RCQ DMA engine reads the freshly filled head+shadow, + * not stale/zero data (a zeroed shadow disables BLD -> black screen). + */ + wmb(); + sun60i_de_rcq_sync_for_device(de); + + sun60i_de_rcq_wait_safe_line(de); + + /* Do not mistake a previous request's sticky W1C bits for completion. */ + sun60i_de_writel(de, status_reg, + SUN60I_DE_RCQ_ACCEPT | SUN60I_DE_RCQ_FINISH); + + /* + * DE352 (Android dump): RESERVE_CTL=0 (no RCQ FIFO), head programmed + * once @0x8114, updates via de_top_set_rcq_update() = RCQ_CTL bit0 + * only. RCQ FIFO + head re-push is de355-only. + */ + sun60i_de_writel(de, ctl_reg, BIT(0)); + + if (readl_poll_timeout_atomic(de->regs + status_reg, status, + status & SUN60I_DE_RCQ_FINISH, + 10, 50000)) { + dev_err_ratelimited(de->dev, + "de: RCQ update timed out (status=0x%08x)\n", + status); + return; + } + + /* Ack this request so the next submit must observe a fresh completion. */ + sun60i_de_writel(de, status_reg, + status & (SUN60I_DE_RCQ_ACCEPT | SUN60I_DE_RCQ_FINISH)); +} + +enum sun60i_de_rgb_format { + SUN60I_DE_FMT_ARGB8888 = 0x00, + SUN60I_DE_FMT_XRGB8888 = 0x04, + SUN60I_DE_FMT_RGB565 = 0x0a, +}; + +/* VSU8: BSP disables for RGB 1:1; partial enable → fixed green on A733 */ +static bool sun60i_de_needs_scaler(struct sun60i_de *de) +{ + /* + * BSP de_rtmx_chn_fix_size(): RGB/YUV444 with ovl_out == scn_out + * leaves scale_en=0 and de_scaler_disable() — VSU8 must not run with + * partial setup (missing C-plane + FIR coeffs → fixed green garbage). + */ + if (!de->scanout.enabled) + return false; + + return de->scanout.src_w != de->mode.crtc_hdisplay || + de->scanout.src_h != de->mode.crtc_vdisplay; +} + +static void sun60i_de_hw_program_vsu8(struct sun60i_de *de) +{ + u32 size; + + if (!de->rcq_vsu_shadow) + return; + + memset(de->rcq_vsu_shadow, 0, SUN60I_DE_VSU_SHADOW_SIZE); + + if (!sun60i_de_needs_scaler(de)) { + /* + * A523 patch 0009: 1:1 @1080p bypass VSU (en=0). Line buffer + * is 2560px; unity step is only needed above that width. + */ + sun60i_de_vsu_writel(de, 0, 0); + + return; + } + + size = sun60i_de_pack_size(de->mode.crtc_hdisplay, de->mode.crtc_vdisplay); + + sun60i_de_vsu_writel(de, 0, SUN60I_DE_VSU8_CTL_EN); + sun60i_de_vsu_writel(de, SUN60I_DE_VSU8_SCALE_MODE_OFF, 0); + sun60i_de_vsu_writel(de, SUN60I_DE_VSU8_OUT_SIZE_OFF, size); + sun60i_de_vsu_writel(de, SUN60I_DE_VSU8_GLB_ALPHA_OFF, 0xff); + sun60i_de_vsu_writel(de, SUN60I_DE_VSU8_Y_IN_SIZE_OFF, size); + sun60i_de_vsu_writel(de, SUN60I_DE_VSU8_Y_HSTEP_OFF, SUN60I_DE_VSU8_STEP_1TO1); + sun60i_de_vsu_writel(de, SUN60I_DE_VSU8_Y_VSTEP_OFF, SUN60I_DE_VSU8_STEP_1TO1); + sun60i_de_vsu_writel(de, SUN60I_DE_VSU8_CPARA_OFF, size); + sun60i_de_vsu_writel(de, SUN60I_DE_VSU8_CPARA_OFF + 0x08, SUN60I_DE_VSU8_STEP_1TO1); + sun60i_de_vsu_writel(de, SUN60I_DE_VSU8_CPARA_OFF + 0x0c, SUN60I_DE_VSU8_STEP_1TO1); +} + +/* UI channel: BSP leaves channel CSC off ("ui channel no csc") */ +static void sun60i_de_hw_program_ccsc(struct sun60i_de *de) +{ + if (!de->rcq_ccsc_shadow) + return; + + memset(de->rcq_ccsc_shadow, 0, SUN60I_DE_RCQ_CCSC_CTL_SIZE); + sun60i_de_ccsc_writel(de, SUN60I_DE_CCSC_CTL_OFF, 0); + +} + +static u32 sun60i_de_pack_coord(u32 x, u32 y) +{ + return (x & 0xffff) | ((y & 0xffff) << 16); +} + +int sun60i_de_hw_bus_init(struct sun60i_de *de) +{ + if (!de->regs) + return -EINVAL; + + /* Avoid reads until the DE module clock and reset are enabled. */ + if (!de->clk_mixer) { + u32 bit = BIT(de->display_id * SUN60I_DE_DISP_CLK_SHIFT); + + /* Keyed enable: BIT(16) | display gate — write-only. */ + sun60i_de_writel(de, SUN60I_DE_CLK_OFFSET, bit | BIT(16)); + sun60i_de_writel(de, SUN60I_DE_RESET_OFFSET, bit | BIT(16)); + udelay(50); + } + + if (!de->clk_bus_mixer) { + sun60i_de_writel(de, SUN60I_DE_MBUS_CLK_OFFSET, + SUN60I_DE_MBUS_RESET_DEASSERT); + udelay(10); + sun60i_de_writel(de, SUN60I_DE_MBUS_CLK_OFFSET, + SUN60I_DE_MBUS_RESET_DEASSERT | + SUN60I_DE_MBUS_CLK_EN); + } + + sun60i_de_writel(de, SUN60I_DE_RESERVE_CTL_OFFSET, + SUN60I_DE_RESERVE_CTL_AHB_CONFLICT); + + return 0; +} + +int sun60i_de_hw_bus_verify(struct sun60i_de *de) +{ + if (!de->regs) + return -EINVAL; + + return 0; +} + +/* + * DE352 UI path (DRM de_top_set_chn_mux_v2 + port2chn): + * uch2core @0x802c: RMW width-2 at (type_id<<2), value=disp + * port2chn @0x8030+disp*4: RMW width-4 at (port<<2), value=type_id+8 (UI) + * type_id 0 = first UI (= phys chn 6 on DE352 map). + */ +static void sun60i_de_top_setup_chn_mux(struct sun60i_de *de) +{ + const u32 type_id = de->ui_channel; /* 0 for primary UI */ + const u32 port = de->bld_port; + const u32 hw_disp = de->display_id; + u32 port_reg = SUN60I_DE_PORT2CHN_MUX_OFFSET + de->display_id * 0x4; + + /* Match DRM v2: UI adds +8 in the port→chn table. */ + sun60i_de_rmw_bits(de, SUN60I_DE_UCH2CORE_MUX_OFFSET, + type_id << 2, 2, hw_disp); + sun60i_de_rmw_bits(de, port_reg, port << 2, 4, type_id + 8); +} + +/* + * Reference steady state: PORT2CHN0=0x0000a810, UCH2CORE=0. + * Write-only — RMW of this block historically AHB-stalled on A733. + */ +static void sun60i_de_top_enable_chn_gating(struct sun60i_de *de) +{ + /* + * DRM BSP comments this out for de352; disp2 SUN60IW2 + * de_rtmx_set_chn_mux() sets GATING@0x80e8 per channel before RCQ + * head. HDMI UI phys6 uses gating id phy+2 = 8. + */ + sun60i_de_writel(de, SUN60I_DE_GATING_CTL_OFFSET, + SUN60I_DE_HDMI_UI_GATING); +} + +static void sun60i_de_top_set_de2tcon_mux(struct sun60i_de *de, u32 tcon) +{ + u32 shift = de->display_id * 4; + u32 val; + + /* + * Write-only (RMW read hung A733). Match reference dump width: + * DE2TCON=0x0000fff4 — unused display nibbles = 0xf in the low 16 bits. + * Values outside the low 16 bits can stall the interconnect. + */ + val = 0xffff; + val &= ~(0xfu << shift); + val |= (tcon & 0xf) << shift; + sun60i_de_writel(de, SUN60I_DE_DE2TCON_MUX_OFFSET, val); +} + +static void sun60i_de_top_set_urgency(struct sun60i_de *de) +{ + /* + * Preserve the low-half enable bits while setting the urgency depth. + */ + sun60i_de_writel(de, SUN60I_DE_URGENCY_CTL_OFFSET, 0x0800ffff); +} + +/* + * DE352 display_config_v2 (REF vmlinux disasm) minus RCQ head: + * urgency → BUF → GLB → AUTO(bit0) → out_size → [de2tcon] + * (clk_enable already done in bus_init; support_rcq_gate=0) + */ +static void sun60i_de_top_set_out_size(struct sun60i_de *de) +{ + u32 out_reg = SUN60I_DE_OUT_SIZE_OFFSET + + de->display_id * SUN60I_DE_DISP_STRIDE; + u32 value = sun60i_de_pack_size(de->mode.crtc_hdisplay, + de->mode.crtc_vdisplay); + + sun60i_de_writel(de, out_reg, value); +} + +static void sun60i_de_top_auto_clk_write(struct sun60i_de *de, bool enable, + bool rcq_gate) +{ + u32 auto_reg = SUN60I_DE_AUTO_CLK_OFFSET + + de->display_id * SUN60I_DE_DISP_STRIDE; + u32 val = 0; + + if (enable) + val |= BIT(0); + if (rcq_gate) + val |= SUN60I_DE_AUTO_CLK_RCQ_GATE; + + /* Write-only: avoid DETOP read in RTMX window. */ + sun60i_de_writel(de, auto_reg, val); +} + +static void sun60i_de_top_glb_rtmx_write(struct sun60i_de *de, bool enable) +{ + u32 glb_reg = SUN60I_DE_GLB_CTL_OFFSET + + de->display_id * SUN60I_DE_DISP_STRIDE; + u32 glb_ctl = 0; + + if (enable) { + glb_ctl = SUN60I_DE_GLB_CTL_RTMX_EN | + (0 << SUN60I_DE_GLB_CTL_PIXEL_MODE_SHIFT); + if (de->mode.flags & DRM_MODE_FLAG_INTERLACE) + glb_ctl |= SUN60I_DE_GLB_CTL_FIELD_REVERSE; + } + + sun60i_de_writel(de, glb_reg, glb_ctl); +} + +static void sun60i_de_top_rtmx_buf_depth(struct sun60i_de *de) +{ + sun60i_de_writel(de, SUN60I_DE_BUF_DEPTH_OFFSET, 0x2000); +} + +/* + * DE352 BSP de_top_display_config_v2() order: + * urgency → BUF → GLB → AUTO_CLK → OUT_SIZE → + * DE2TCON → RCQ_HEAD (caller) → channel mux (caller) + * + * This intentionally differs from sun55i. On A733, arming RCQ_HEAD before + * DE2TCON causes the following DETOP write to stall the interconnect during + * the first cold-boot fbdev commit. + * + */ +static void sun60i_de_top_rtmx_enable_atomic(struct sun60i_de *de) +{ + sun60i_de_top_glb_rtmx_write(de, true); + sun60i_de_top_auto_clk_write(de, true, false); + sun60i_de_top_set_out_size(de); + sun60i_de_top_set_de2tcon_mux(de, de->tcon_id); + /* Make all DETOP routing writes visible before RCQ is armed. */ + wmb(); +} + +static void sun60i_de_top_rtmx_enable(struct sun60i_de *de, bool enable) +{ + if (!enable) { + sun60i_de_top_glb_rtmx_write(de, false); + sun60i_de_top_auto_clk_write(de, false, false); + return; + } + + sun60i_de_top_rtmx_enable_atomic(de); +} + +/* + * DE352 top (0x8100 block) — BSP references: + * + * A733 programming order: + * bus_init: mod_clk/mbus/RESERVE_CTL only + * commit: RCQ pool+head (19 blocks) → shadow → hdmi chn_mux → gating + * → async/buf → GLB+AUTO → de2tcon → pulse + */ +static int sun60i_de_map_format(u32 drm_format, u32 *de_format, + u32 *alpha_mode) +{ + switch (drm_format) { + case DRM_FORMAT_ARGB8888: + *de_format = SUN60I_DE_FMT_ARGB8888; + *alpha_mode = 2; + return 0; + case DRM_FORMAT_XRGB8888: + *de_format = SUN60I_DE_FMT_XRGB8888; + *alpha_mode = 1; + return 0; + case DRM_FORMAT_RGB565: + *de_format = SUN60I_DE_FMT_RGB565; + *alpha_mode = 1; + return 0; + default: + return -EINVAL; + } +} + +static void sun60i_de_program_blender(struct sun60i_de *de, bool enabled) +{ + bool interlace = !!(de->mode.flags & DRM_MODE_FLAG_INTERLACE); + u32 route = 0x00ffffff; + u32 out_ctl = SUN60I_DE_BLD_OUT_CTL_FMT_RGB; + u32 blend = SUN60I_DE_BLD_MODE_SRCOVER; + u32 bld_en; + u32 pipe_fcolor; + + if (interlace) + out_ctl |= SUN60I_DE_BLD_OUT_CTL_INTERLACE; + + if (!de->rcq_bld_shadow) + return; + + if (enabled) { + route &= ~GENMASK(3, 0); + route |= de->bld_port; + } + + if (!enabled) { + bld_en = 0; + pipe_fcolor = 0; + } else { + /* BSP de_bld_output_set_attr(): RGB pipes use FCOLOR black bg + SRCOVER */ + bld_en = SUN60I_DE_BLD_PIPE_EN(0) | SUN60I_DE_BLD_FCOLOR_EN(0); + pipe_fcolor = 0xff000000; + blend = SUN60I_DE_BLD_MODE_SRCOVER; + } + + sun60i_de_bld_writel(de, SUN60I_DE_BLD_EN, bld_en); + sun60i_de_bld_writel(de, SUN60I_DE_BLD_PIPE_FCOLOR(0), pipe_fcolor); + sun60i_de_bld_writel(de, SUN60I_DE_BLD_PIPE_IN_SIZE(0), + enabled ? sun60i_de_pack_size(de->mode.crtc_hdisplay, + de->mode.crtc_vdisplay) : 0); + sun60i_de_bld_writel(de, SUN60I_DE_BLD_PIPE_IN_COORD(0), + 0); + sun60i_de_bld_writel(de, SUN60I_DE_BLD_ROUT_CTL, route); + sun60i_de_bld_writel(de, SUN60I_DE_BLD_PREMUL_CTL, 0); + sun60i_de_bld_writel(de, SUN60I_DE_BLD_BG_COLOR, 0); + sun60i_de_bld_writel(de, SUN60I_DE_BLD_OUT_SIZE, + sun60i_de_pack_size(de->mode.crtc_hdisplay, + de->mode.crtc_vdisplay)); + sun60i_de_bld_writel(de, SUN60I_DE_BLD_BLEND_CTL(0), blend); + sun60i_de_bld_writel(de, SUN60I_DE_BLD_OUT_CTL, out_ctl); +} + +static void sun60i_de_disable_overlay(struct sun60i_de *de) +{ + unsigned int i; + + if (!de->rcq_ovl_shadow) + return; + + for (i = 0; i < 4; i++) { + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_CTL(i), 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_MBSIZE(i), 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_MBCOOR(i), 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_PITCH(i), 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_TOP_LADDR(i), 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_BOT_LADDR(i), 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_FCOLOR(i), 0); + } + + sun60i_de_ovl_writel(de, SUN60I_DE_UI_TOP_HADDR, 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_BOT_HADDR, 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_WIN_SIZE, 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_HORI_DS, 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_VERT_DS, 0); + /* DS RCQ block @0xE0 includes hori/vert_ds; clear whole region */ + memset((u8 *)de->rcq_ovl_shadow + SUN60I_DE_RCQ_OVL_DS_OFF, 0, + SUN60I_DE_RCQ_OVL_DS_SIZE); +} + +static int sun60i_de_program_overlay_layer(struct sun60i_de *de, + const struct sun60i_de_scanout_state *scanout, + unsigned int layer, u32 *top_haddr) +{ + u32 de_format; + u32 alpha_mode; + u32 ctl; + int ret; + + if (!scanout->enabled) + return 0; + + if (scanout->src_w != scanout->crtc_w || + scanout->src_h != scanout->crtc_h) + return -EINVAL; + + ret = sun60i_de_map_format(scanout->format, &de_format, &alpha_mode); + if (ret) + return ret; + + ctl = SUN60I_DE_UI_CTL_EN | + (alpha_mode << SUN60I_DE_UI_CTL_ALPHA_MODE_SHIFT) | + (de_format << SUN60I_DE_UI_CTL_FMT_SHIFT) | + (0xff << SUN60I_DE_UI_CTL_GLB_ALPHA_SHIFT); + + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_CTL(layer), ctl); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_MBSIZE(layer), + sun60i_de_pack_size(scanout->src_w, scanout->src_h)); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_MBCOOR(layer), + sun60i_de_pack_coord(scanout->crtc_x, scanout->crtc_y)); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_PITCH(layer), scanout->pitch); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_TOP_LADDR(layer), + lower_32_bits(scanout->addr)); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_BOT_LADDR(layer), 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_LAY_FCOLOR(layer), 0); + + *top_haddr |= (upper_32_bits(scanout->addr) & 0xff) << (layer * 8); + + return 0; +} + +static int sun60i_de_program_overlay(struct sun60i_de *de) +{ + u32 top_haddr = 0; + int ret; + + if (!de->rcq_ovl_shadow) + return -EINVAL; + + memset(de->rcq_ovl_shadow, 0, SUN60I_DE_RCQ_OVL_SHADOW_SIZE); + sun60i_de_disable_overlay(de); + + ret = sun60i_de_program_overlay_layer(de, &de->scanout, 0, &top_haddr); + if (ret) + return ret; + + ret = sun60i_de_program_overlay_layer(de, &de->cursor_scanout, 1, + &top_haddr); + if (ret) + return ret; + + sun60i_de_ovl_writel(de, SUN60I_DE_UI_TOP_HADDR, top_haddr); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_BOT_HADDR, 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_WIN_SIZE, + sun60i_de_pack_size(de->mode.crtc_hdisplay, + de->mode.crtc_vdisplay)); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_HORI_DS, 0); + sun60i_de_ovl_writel(de, SUN60I_DE_UI_VERT_DS, 0); + + return 0; +} + +static void sun60i_de_sync_one_scanout(struct sun60i_de *de, + const struct sun60i_de_scanout_state *scanout) +{ + struct device *dma_dev = sun60i_de_dma_dev(de); + size_t size; + + if (!scanout->enabled || !scanout->addr || !scanout->pitch) + return; + + size = (size_t)scanout->pitch * scanout->src_h; + dma_sync_single_for_device(dma_dev, scanout->addr, size, + DMA_TO_DEVICE); +} + +static void sun60i_de_sync_scanout(struct sun60i_de *de) +{ + sun60i_de_sync_one_scanout(de, &de->scanout); + sun60i_de_sync_one_scanout(de, &de->cursor_scanout); +} + +int sun60i_de_hw_commit(struct sun60i_de *de) +{ + bool full_setup; + bool first_rtmx; + int ret; + + if (!de->mode_valid) + return 0; + + /* + * sun55i_de_enable() order: + * urgency → RCQ head → de2tcon → buf → chn_mux → size → AUTO → GLB + * Then shadows + RCQ submit. All DETOP updates are RMW. + */ + full_setup = !de->rtmx_running || + de->programmed_hdisplay != de->mode.crtc_hdisplay || + de->programmed_vdisplay != de->mode.crtc_vdisplay; + first_rtmx = !de->rtmx_running; + + sun60i_de_top_set_urgency(de); + /* + * Complete the DE352 static display transaction before RCQ_HEAD. The + * BSP enables GLB/AUTO and routes DE2TCON first; doing DE2TCON after the + * head is armed hard-stalls A733 on the first fbdev modeset. + */ + if (first_rtmx) { + sun60i_de_top_rtmx_buf_depth(de); + sun60i_de_top_rtmx_enable_atomic(de); + de->rtmx_running = true; + de->programmed_hdisplay = de->mode.crtc_hdisplay; + de->programmed_vdisplay = de->mode.crtc_vdisplay; + } + + ret = sun60i_de_rcq_init(de); + if (ret) + return ret; + + sun60i_de_sync_scanout(de); + + if (full_setup) { + sun60i_de_hw_program_fmt(de); + sun60i_de_hw_program_vsu8(de); + sun60i_de_hw_program_tfbd(de); + sun60i_de_hw_program_ccsc(de); + de->programmed_hdisplay = de->mode.crtc_hdisplay; + de->programmed_vdisplay = de->mode.crtc_vdisplay; + } + + /* Rebuild the complete layer state because the shadow starts cleared. */ + ret = sun60i_de_program_overlay(de); + if (ret) + return ret; + + sun60i_de_program_blender(de, de->scanout.enabled || + de->cursor_scanout.enabled); + if (!de->rcq_head_done) { + sun60i_de_rcq_program_head(de); + de->rcq_head_done = true; + } + + /* BSP configures channel routing after the RCQ head is installed. */ + if (full_setup) { + sun60i_de_top_setup_chn_mux(de); + sun60i_de_top_enable_chn_gating(de); + } + + /* First setup already programmed OUT_SIZE before RCQ_HEAD. */ + if (!first_rtmx) + sun60i_de_top_set_out_size(de); + + sun60i_de_rcq_submit(de); + + return 0; +} + +void sun60i_de_hw_disable(struct sun60i_de *de) +{ + sun60i_de_disable_overlay(de); + sun60i_de_program_blender(de, false); + /* Latch the disabled plane state before stopping RTMX/freeing its RCQ. */ + sun60i_de_rcq_submit(de); + sun60i_de_top_rtmx_enable(de, false); + /* + * Module, MBUS and reset lifetime belongs to display_clocks and the + * clk/reset framework. Do not modify those gates directly here: the + * caller drops the matching clock/reset references after RTMX is idle. + */ + + de->rtmx_running = false; + de->rcq_head_done = false; + de->programmed_hdisplay = 0; + de->programmed_vdisplay = 0; + sun60i_de_rcq_free(de); +} diff --git a/drivers/gpu/drm/sun4i/sun60i_de_hw.h b/drivers/gpu/drm/sun4i/sun60i_de_hw.h new file mode 100644 index 00000000000000..cb5d382f3011dc --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun60i_de_hw.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ + +#ifndef _SUN60I_DE_HW_H_ +#define _SUN60I_DE_HW_H_ + +#include "sun60i_de.h" + +int sun60i_de_hw_commit(struct sun60i_de *de); +void sun60i_de_hw_disable(struct sun60i_de *de); +int sun60i_de_hw_bus_init(struct sun60i_de *de); +int sun60i_de_hw_bus_verify(struct sun60i_de *de); + +#endif /* _SUN60I_DE_HW_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun60i_hdmi.h b/drivers/gpu/drm/sun4i/sun60i_hdmi.h new file mode 100644 index 00000000000000..2825cfe7e04bbd --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun60i_hdmi.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ + +#ifndef _SUN60I_HDMI_H_ +#define _SUN60I_HDMI_H_ + +#include +#include + +#include +#include + +#define SUN60I_HDMI_TOP_PHY_OFFSET 0x000e0000 +#define SUN60I_HDMI_RESCAL_PHYS 0x03000160 +#define SUN60I_HDMI_RESCAL_SIZE 0x8 + +struct sun60i_hdmi { + struct device *dev; + void __iomem *regs; + void __iomem *rescal_regs; + struct clk *clk_cec; + struct clk *clk_tcon_tv; + struct clk *clk_hdmi; + struct clk *clk_hdmi_24m; + struct clk *clk_hdcp; + struct clk *clk_dcxo; + /* Glue-layer handles used to clock register accesses before dw-hdmi binds. */ + struct clk *clk_isfr; + struct clk *clk_iahb; + struct reset_control *rst_bus_sub; + struct reset_control *rst_bus_main; + struct reset_control *rst_bus_hdcp; + bool top_phy_inited; + bool resistor_src_onboard; + u8 clock_src; + u8 pixel_repeat; + int irq; + bool irq_snps_masked; +}; + +int sun60i_hdmi_rescal_board_init(struct sun60i_hdmi *hdmi); +int sun60i_hdmi_top_phy_init(struct sun60i_hdmi *hdmi); +int sun60i_hdmi_top_phy_config(struct sun60i_hdmi *hdmi, + const struct drm_display_mode *mode); +void sun60i_hdmi_top_phy_disable(struct sun60i_hdmi *hdmi); +bool sun60i_hdmi_top_phy_wait_txready(struct sun60i_hdmi *hdmi, + unsigned int ms); + +int sun60i_hdmi_snps_phy_configure(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi, + const struct drm_display_mode *mode); +void sun60i_hdmi_snps_phy_disable(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi); +void sun60i_hdmi_snps_phy_enable_tmds(struct sun60i_hdmi *hdmi); +void sun60i_hdmi_snps_phy_probe_init(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi); + +void sun60i_hdmi_apply_video_fixup(struct sun60i_hdmi *hdmi); +void sun60i_hdmi_clear_avmute(struct sun60i_hdmi *hdmi); + +const struct dw_hdmi_phy_ops *sun60i_hdmi_phy_ops_get(void); + +#endif /* _SUN60I_HDMI_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun60i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun60i_hdmi_phy.c new file mode 100644 index 00000000000000..32cceff8183e3c --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun60i_hdmi_phy.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * sun60i / A733 DW HDMI PHY glue (Top-PHY V2 + SNPS internal). + */ + +#include +#include +#include + +#include +#include + +#include "sun60i_hdmi.h" + +#define SUN60I_DW_HDMI_FC_GCP 0x1018 +#define SUN60I_DW_HDMI_FC_GCP_CLEAR_AVMUTE BIT(0) +#define SUN60I_DW_HDMI_PHY_CONF0 0x3000 +#define SUN60I_DW_HDMI_PHY_CONF0_ENTMDS BIT(6) + +void sun60i_hdmi_clear_avmute(struct sun60i_hdmi *hdmi) +{ + u8 gcp; + + if (!hdmi->regs) + return; + + gcp = readb(hdmi->regs + SUN60I_DW_HDMI_FC_GCP); + gcp &= ~BIT(1); + gcp |= SUN60I_DW_HDMI_FC_GCP_CLEAR_AVMUTE; + writeb(gcp, hdmi->regs + SUN60I_DW_HDMI_FC_GCP); +} + +static int sun60i_hdmi_phy_init(struct dw_hdmi *dw_hdmi, void *data, + const struct drm_display_info *display, + const struct drm_display_mode *mode) +{ + struct sun60i_hdmi *hdmi = data; + int ret; + + ret = sun60i_hdmi_top_phy_config(hdmi, mode); + if (ret) + return ret; + + ret = sun60i_hdmi_snps_phy_configure(dw_hdmi, hdmi, mode); + if (ret) + return ret; + + mdelay(50); + sun60i_hdmi_clear_avmute(hdmi); + sun60i_hdmi_snps_phy_enable_tmds(hdmi); + sun60i_hdmi_apply_video_fixup(hdmi); + + return 0; +} + +static void sun60i_hdmi_phy_disable(struct dw_hdmi *dw_hdmi, void *data) +{ + struct sun60i_hdmi *hdmi = data; + + sun60i_hdmi_snps_phy_disable(dw_hdmi, hdmi); + sun60i_hdmi_top_phy_disable(hdmi); +} + +static const struct dw_hdmi_phy_ops sun60i_hdmi_phy_ops = { + .init = sun60i_hdmi_phy_init, + .disable = sun60i_hdmi_phy_disable, + .setup_hpd = dw_hdmi_phy_setup_hpd, + .read_hpd = dw_hdmi_phy_read_hpd, + .update_hpd = dw_hdmi_phy_update_hpd, +}; + +const struct dw_hdmi_phy_ops *sun60i_hdmi_phy_ops_get(void) +{ + return &sun60i_hdmi_phy_ops; +} + +void sun60i_hdmi_apply_video_fixup(struct sun60i_hdmi *hdmi) +{ + u8 invid0, invconf, phy; + + if (!hdmi->regs) + return; + + invid0 = readb(hdmi->regs + 0x0200); + invid0 &= ~BIT(7); + writeb(invid0, hdmi->regs + 0x0200); + + invconf = readb(hdmi->regs + 0x1000); + invconf |= BIT(3); + writeb(invconf, hdmi->regs + 0x1000); + + phy = readb(hdmi->regs + SUN60I_DW_HDMI_PHY_CONF0); + phy |= SUN60I_DW_HDMI_PHY_CONF0_ENTMDS; + writeb(phy, hdmi->regs + SUN60I_DW_HDMI_PHY_CONF0); + + sun60i_hdmi_clear_avmute(hdmi); +} diff --git a/drivers/gpu/drm/sun4i/sun60i_hdmi_snps_phy.c b/drivers/gpu/drm/sun4i/sun60i_hdmi_snps_phy.c new file mode 100644 index 00000000000000..347196dd449f6d --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun60i_hdmi_snps_phy.c @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * sun60i internal DWC HDMI PHY programming (from BSP phy_snps.c). + * + * A733 uses top-PHY for the pixel PLL. BSP sun60i skips snps init reset and + * still programs MPLL/drive over internal PHY I2CM, then GEN2 power + ENTMDS. + */ + +#include +#include +#include +#include +#include + +#include +#include + + +#include "sun60i_hdmi.h" + +/* DW-HDMI register offsets (from synopsys/dw-hdmi.h, not in UAPI header) */ +#define SUN60I_DW_HDMI_PHY_CONF0 0x3000 +#define SUN60I_DW_HDMI_PHY_STAT0 0x3004 +#define SUN60I_DW_HDMI_PHY_CONF0_ENTMDS BIT(6) +#define SUN60I_DW_HDMI_PHY_CONF0_SVSRET BIT(5) +#define SUN60I_DW_HDMI_PHY_CONF0_GEN2_PDDQ BIT(4) +#define SUN60I_DW_HDMI_PHY_CONF0_GEN2_TXPWRON BIT(3) +#define SUN60I_DW_HDMI_PHY_CONF0_SELDATAENPOL BIT(1) +#define SUN60I_DW_HDMI_PHY_CONF0_SELDIPIF BIT(0) +#define SUN60I_DW_HDMI_PHY_CONF0_ENHPDRXSENSE BIT(2) +#define SUN60I_DW_HDMI_PHY_TX_PHY_LOCK BIT(0) +#define SUN60I_DW_HDMI_PHY_I2CM_SLAVE 0x3020 +#define SUN60I_DW_HDMI_PHY_I2CM_SLAVE_GEN2 0x69 +#define SUN60I_DW_HDMI_JTAG_PHY_CONFIG 0x3034 +#define SUN60I_DW_HDMI_JTAG_I2C_MODE BIT(4) +#define SUN60I_DW_HDMI_IH_I2CMPHY_STAT0 0x0108 +#define SUN60I_DW_HDMI_IH_MUTE_I2CMPHY_STAT0 0x0188 +#define SUN60I_DW_HDMI_PHY_I2CM_ADDRESS 0x3021 +#define SUN60I_DW_HDMI_PHY_I2CM_DATAO_1 0x3022 +#define SUN60I_DW_HDMI_PHY_I2CM_DATAO_0 0x3023 +#define SUN60I_DW_HDMI_PHY_I2CM_DATAI_1 0x3024 +#define SUN60I_DW_HDMI_PHY_I2CM_DATAI_0 0x3025 +#define SUN60I_DW_HDMI_PHY_I2CM_OPERATION 0x3026 +#define SUN60I_DW_HDMI_PHY_I2CM_INT 0x3027 +#define SUN60I_DW_HDMI_PHY_I2CM_CTLINT 0x3028 +#define SUN60I_DW_HDMI_PHY_I2CM_DIV 0x3029 +#define SUN60I_DW_HDMI_PHY_I2CM_SOFTRSTZ 0x302a +#define SUN60I_PHY_I2CM_DONE_STATUS BIT(0) +#define SUN60I_PHY_I2CM_NACK_STATUS BIT(4) +#define SUN60I_PHY_I2CM_ARB_STATUS BIT(0) +#define SUN60I_I2CMPHY_DONE BIT(1) +#define SUN60I_I2CMPHY_ERROR BIT(0) +#define SUN60I_DW_HDMI_MC_CLKDIS 0x4001 +#define SUN60I_DW_HDMI_MC_PHYRSTZ 0x4005 +#define SUN60I_MC_PHYRSTZ_ASSERT 0x00 +#define SUN60I_MC_PHYRSTZ_DEASSERT 0x01 + +#define SUN60I_SNPS_MPLL_REG1 0x06 +#define SUN60I_SNPS_MPLL_REG2 0x10 +#define SUN60I_SNPS_MPLL_REG3 0x11 +#define SUN60I_SNPS_DRIVE_REG1 0x19 +#define SUN60I_SNPS_DRIVE_REG2 0x0e +#define SUN60I_SNPS_DRIVE_REG3 0x09 + +struct sun60i_snps_mpll_entry { + unsigned int pixel_khz; + u16 data[3]; +}; + +struct sun60i_snps_drive_entry { + unsigned int min_khz; + unsigned int max_khz; + u16 data[3]; +}; + +/* sun60i_mpll_rep0 @ 8bpp (C422-0), NOT sun50i values */ +static const struct sun60i_snps_mpll_entry sun60i_snps_mpll[] = { + { 148500, { 0x0001, 0x2080, 0x020a } }, + { 154000, { 0x0001, 0x2080, 0x020a } }, /* sun60i: same as 148500/165000 */ + { 165000, { 0x0001, 0x2080, 0x020a } }, + { 297000, { 0x0000, 0x3041, 0x0205 } }, +}; + +/* sun60i_drive[] from BSP phy_snps.c */ +static const struct sun60i_snps_drive_entry sun60i_snps_drive[] = { + { 25000, 165000, { 0x0007, 0x8160, 0x8188 } }, + { 165000, 340000, { 0x0004, 0x8040, 0x8e85 } }, + { 340000, 600000, { 0x0000, 0x80c0, 0x82f6 } }, +}; + +static void sun60i_snps_phy_set_bit(struct sun60i_hdmi *hdmi, u32 reg, + u32 mask, bool on) +{ + u8 tmp = readb(hdmi->regs + reg); + + if (on) + tmp |= mask; + else + tmp &= ~mask; + writeb(tmp, hdmi->regs + reg); +} + +static void sun60i_snps_phy_write_mask(struct sun60i_hdmi *hdmi, u32 reg, + u8 mask, u8 val) +{ + u8 tmp = readb(hdmi->regs + reg); + + tmp &= ~mask; + tmp |= mask & (val << (ffs(mask) - 1)); + writeb(tmp, hdmi->regs + reg); +} + +static void sun60i_snps_phy_set_power(struct sun60i_hdmi *hdmi, bool on) +{ + sun60i_snps_phy_set_bit(hdmi, SUN60I_DW_HDMI_PHY_CONF0, + SUN60I_DW_HDMI_PHY_CONF0_GEN2_PDDQ, !on); + sun60i_snps_phy_set_bit(hdmi, SUN60I_DW_HDMI_PHY_CONF0, + SUN60I_DW_HDMI_PHY_CONF0_GEN2_TXPWRON, on); +} + +static void sun60i_snps_phy_config_svsret(struct sun60i_hdmi *hdmi) +{ + /* + * BSP dw_phy_config_svsret() + dw_hdmi PHY configure path: leave + * SVSRET asserted before I2C/MPLL programming. Without it CONF0 stays + * ~0x4E and internal TX_PHY_LOCK (STAT0 bit0) never sets on A733. + */ + sun60i_snps_phy_set_bit(hdmi, SUN60I_DW_HDMI_PHY_CONF0, + SUN60I_DW_HDMI_PHY_CONF0_SVSRET, false); + udelay(5); + sun60i_snps_phy_set_bit(hdmi, SUN60I_DW_HDMI_PHY_CONF0, + SUN60I_DW_HDMI_PHY_CONF0_SVSRET, true); +} + +static void sun60i_snps_phy_mc_reset(struct sun60i_hdmi *hdmi, bool deassert) +{ + writeb(deassert ? SUN60I_MC_PHYRSTZ_DEASSERT : SUN60I_MC_PHYRSTZ_ASSERT, + hdmi->regs + SUN60I_DW_HDMI_MC_PHYRSTZ); +} + +static void sun60i_snps_phy_prepare_interface(struct sun60i_hdmi *hdmi) +{ + /* + * BSP dw_phy_config_interface(): select internal PHY I2C and program + * slave address directly. Do NOT use dw_hdmi_phy_i2c_set_addr() — its + * hdmi_phy_test_clear() pulse breaks I2CM on sun60i. + */ + writeb(SUN60I_DW_HDMI_JTAG_I2C_MODE, + hdmi->regs + SUN60I_DW_HDMI_JTAG_PHY_CONFIG); + writeb(SUN60I_DW_HDMI_PHY_I2CM_SLAVE_GEN2, + hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_SLAVE); + + sun60i_snps_phy_set_bit(hdmi, SUN60I_DW_HDMI_PHY_CONF0, + SUN60I_DW_HDMI_PHY_CONF0_SELDATAENPOL, true); + sun60i_snps_phy_set_bit(hdmi, SUN60I_DW_HDMI_PHY_CONF0, + SUN60I_DW_HDMI_PHY_CONF0_SELDIPIF, false); +} + +static void sun60i_snps_phy_mute_i2c_irq(struct sun60i_hdmi *hdmi) +{ + /* + * PHY-I2CM is polled locally while the Linux IRQ is disabled. Mask + * only that sub-source. Setting IH_MUTE.MUTE_ALL here also masks the + * external DDC master; its transfer reaches DONE, but dw-hdmi never + * receives the completion IRQ and consequently falls back to no-EDID + * modes such as 1024x768. + */ + writeb(0xff, hdmi->regs + SUN60I_DW_HDMI_IH_MUTE_I2CMPHY_STAT0); +} + +static void sun60i_snps_phy_irq_begin(struct sun60i_hdmi *hdmi) +{ + if (hdmi->irq <= 0 || hdmi->irq_snps_masked) + return; + + disable_irq_nosync(hdmi->irq); + hdmi->irq_snps_masked = true; +} + +static void sun60i_snps_phy_irq_end(struct sun60i_hdmi *hdmi) +{ + if (hdmi->irq <= 0 || !hdmi->irq_snps_masked) + return; + + enable_irq(hdmi->irq); + hdmi->irq_snps_masked = false; +} + +static void sun60i_snps_enable_mc_clocks(struct sun60i_hdmi *hdmi) +{ + writeb(0x00, hdmi->regs + SUN60I_DW_HDMI_MC_CLKDIS); +} + +static void sun60i_snps_phy_gen2_reset(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi) +{ + /* + * sun8i A83T path before internal PHY I2C. BSP sun60i skips snps init + * reset but A733 may still need MC PHY reset to kick I2CM. + */ + if (dw_hdmi) + dw_hdmi_phy_gen2_reset(dw_hdmi); + else if (hdmi && hdmi->regs) + writeb(SUN60I_MC_PHYRSTZ_DEASSERT, hdmi->regs + SUN60I_DW_HDMI_MC_PHYRSTZ); + + udelay(10); +} + +static void sun60i_snps_phy_assert_power(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi, bool on) +{ + sun60i_snps_phy_set_power(hdmi, on); + if (!dw_hdmi) + return; + + dw_hdmi_phy_gen2_txpwron(dw_hdmi, on ? 1 : 0); + dw_hdmi_phy_gen2_pddq(dw_hdmi, on ? 0 : 1); +} + +static void sun60i_snps_phy_config_init(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi) +{ + /* + * BSP _snps_phy_config_init(): MC PHY reset, power down, SVSRET pulse, + * hold PHY in reset, then arm I2C interface for MPLL/drive writes. + */ + sun60i_snps_phy_mc_reset(hdmi, true); + sun60i_snps_phy_assert_power(dw_hdmi, hdmi, false); + sun60i_snps_phy_config_svsret(hdmi); + sun60i_snps_phy_gen2_reset(dw_hdmi, hdmi); + sun60i_snps_phy_prepare_interface(hdmi); +} + +static void sun60i_snps_phy_init_i2cm_hw(struct sun60i_hdmi *hdmi) +{ + /* + * dw_hdmi leaves PHY_I2CM_INT/CTLINT at 0xff after initialize_hdmi_ih_mutes(). + * disp2 dw_phy_initialize() unmasks (mask=0), sets polarity, standard mode. + * Pulse SOFTRSTZ 0->1 to clear a stuck controller; leave deasserted (1). + */ + writeb(0x00, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_DIV); + writeb(0x00, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_SOFTRSTZ); + udelay(10); + writeb(0x01, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_SOFTRSTZ); + + writeb(BIT(3), hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_INT); + writeb(BIT(3) | BIT(7), hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_CTLINT); + sun60i_snps_phy_write_mask(hdmi, SUN60I_DW_HDMI_PHY_I2CM_INT, BIT(2), 0); + sun60i_snps_phy_write_mask(hdmi, SUN60I_DW_HDMI_PHY_I2CM_CTLINT, BIT(2), 0); + sun60i_snps_phy_write_mask(hdmi, SUN60I_DW_HDMI_PHY_I2CM_CTLINT, BIT(6), 0); + sun60i_snps_phy_mute_i2c_irq(hdmi); +} + +static void sun60i_snps_phy_prepare_i2cm(struct sun60i_hdmi *hdmi) +{ + /* + * Poll IH_I2CMPHY_STAT0 directly; keep IH muted so dw_hdmi hardirq + * does not fire during atomic commit (was: IRQ storm + disable #413). + */ + writeb(0xff, hdmi->regs + SUN60I_DW_HDMI_IH_I2CMPHY_STAT0); + sun60i_snps_phy_mute_i2c_irq(hdmi); +} + +static const struct sun60i_snps_mpll_entry * +sun60i_snps_find_mpll(unsigned int pixel_khz) +{ + const struct sun60i_snps_mpll_entry *best = &sun60i_snps_mpll[0]; + unsigned int best_diff = UINT_MAX; + size_t i; + + for (i = 0; i < ARRAY_SIZE(sun60i_snps_mpll); i++) { + unsigned int diff = abs((int)sun60i_snps_mpll[i].pixel_khz - + (int)pixel_khz); + + if (diff < best_diff) { + best = &sun60i_snps_mpll[i]; + best_diff = diff; + } + } + + return best; +} + +#define SUN60I_SNPS_I2C_PROBE_MS 50 +#define SUN60I_SNPS_I2C_XFER_MS 100 +#define SUN60I_SNPS_TXREADY_WAIT_MS 200 +#define SUN60I_SNPS_PIXEL_SETTLE_MS 20 +#define SUN60I_SNPS_I2C_POLL_US 10 + +static bool sun60i_snps_phy_locked(struct sun60i_hdmi *hdmi) +{ + return !!(readb(hdmi->regs + SUN60I_DW_HDMI_PHY_STAT0) & + SUN60I_DW_HDMI_PHY_TX_PHY_LOCK); +} + +static void sun60i_snps_phy_finish_tx(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi) +{ + sun60i_snps_phy_assert_power(dw_hdmi, hdmi, true); + sun60i_hdmi_snps_phy_enable_tmds(hdmi); +} + +static bool sun60i_snps_wait_internal_lock(struct sun60i_hdmi *hdmi, + unsigned int ms) +{ + unsigned int i; + + for (i = 0; i < ms; i++) { + if (sun60i_snps_phy_locked(hdmi)) + return true; + udelay(1000); + } + + return false; +} + +static bool sun60i_snps_wait_txready(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi) +{ + unsigned int i; + + mdelay(SUN60I_SNPS_PIXEL_SETTLE_MS); + sun60i_snps_phy_finish_tx(dw_hdmi, hdmi); + + for (i = 0; i < SUN60I_SNPS_TXREADY_WAIT_MS; i++) { + if (sun60i_hdmi_top_phy_wait_txready(hdmi, 1)) + return true; + + udelay(1000); + } + + return false; +} + +static bool sun60i_snps_phy_i2c_error(struct sun60i_hdmi *hdmi) +{ + u8 ctl = readb(hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_CTLINT); + + return !!(ctl & (SUN60I_PHY_I2CM_NACK_STATUS | + SUN60I_PHY_I2CM_ARB_STATUS)); +} + +static bool sun60i_snps_phy_i2c_done_ms(struct sun60i_hdmi *hdmi, + unsigned int ms) +{ + u8 ih_stat = 0; + u8 phy_int = 0; + unsigned int tries = ms * (1000000 / SUN60I_SNPS_I2C_POLL_US); + unsigned int i; + + for (i = 0; i < tries; i++) { + ih_stat = readb(hdmi->regs + SUN60I_DW_HDMI_IH_I2CMPHY_STAT0); + phy_int = readb(hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_INT); + + if (ih_stat & (SUN60I_I2CMPHY_DONE | SUN60I_I2CMPHY_ERROR)) + break; + if (phy_int & SUN60I_PHY_I2CM_DONE_STATUS) + break; + if (sun60i_snps_phy_i2c_error(hdmi)) + break; + + udelay(SUN60I_SNPS_I2C_POLL_US); + } + + if (sun60i_snps_phy_i2c_error(hdmi)) + return false; + + if (phy_int & SUN60I_PHY_I2CM_DONE_STATUS) { + if (ih_stat) + writeb(ih_stat, hdmi->regs + SUN60I_DW_HDMI_IH_I2CMPHY_STAT0); + return true; + } + + if (!(ih_stat & (SUN60I_I2CMPHY_DONE | SUN60I_I2CMPHY_ERROR))) + return false; + + writeb(ih_stat, hdmi->regs + SUN60I_DW_HDMI_IH_I2CMPHY_STAT0); + return (ih_stat & SUN60I_I2CMPHY_DONE) && !(ih_stat & SUN60I_I2CMPHY_ERROR); +} + +static void sun60i_snps_phy_log_i2cm(struct sun60i_hdmi *hdmi, const char *tag) +{ + dev_warn(hdmi->dev, + "snps-phy: %s slave=0x%02x jtag=0x%02x ih=0x%02x pint=0x%02x ctl=0x%02x op=0x%02x ih_mute=0x%02x conf0=0x%02x\n", + tag, + readb(hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_SLAVE), + readb(hdmi->regs + SUN60I_DW_HDMI_JTAG_PHY_CONFIG), + readb(hdmi->regs + SUN60I_DW_HDMI_IH_I2CMPHY_STAT0), + readb(hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_INT), + readb(hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_CTLINT), + readb(hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_OPERATION), + readb(hdmi->regs + SUN60I_DW_HDMI_IH_MUTE_I2CMPHY_STAT0), + readb(hdmi->regs + SUN60I_DW_HDMI_PHY_CONF0)); +} + +static int sun60i_snps_phy_i2c_write_ms(struct sun60i_hdmi *hdmi, u16 data, + u8 addr, unsigned int wait_ms) +{ + sun60i_snps_phy_prepare_i2cm(hdmi); + writeb(0xff, hdmi->regs + SUN60I_DW_HDMI_IH_I2CMPHY_STAT0); + writeb(addr, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_ADDRESS); + writeb(data >> 8, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_DATAO_1); + writeb(data & 0xff, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_DATAO_0); + writeb(0x10, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_OPERATION); + + if (!sun60i_snps_phy_i2c_done_ms(hdmi, wait_ms)) { + sun60i_snps_phy_log_i2cm(hdmi, "I2C write timeout"); + dev_warn(hdmi->dev, "snps-phy: I2C write 0x%04x @0x%02x failed\n", + data, addr); + return -EIO; + } + + return 0; +} + +static bool sun60i_snps_phy_i2c_probe(struct sun60i_hdmi *hdmi, u16 data, + u8 addr) +{ + sun60i_snps_phy_prepare_i2cm(hdmi); + writeb(0xff, hdmi->regs + SUN60I_DW_HDMI_IH_I2CMPHY_STAT0); + writeb(addr, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_ADDRESS); + writeb(data >> 8, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_DATAO_1); + writeb(data & 0xff, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_DATAO_0); + writeb(0x10, hdmi->regs + SUN60I_DW_HDMI_PHY_I2CM_OPERATION); + + return sun60i_snps_phy_i2c_done_ms(hdmi, SUN60I_SNPS_I2C_PROBE_MS); +} + +static bool sun60i_snps_phy_i2c_probe_mpll(struct sun60i_hdmi *hdmi, + unsigned int pixel_khz) +{ + const struct sun60i_snps_mpll_entry *entry = sun60i_snps_find_mpll(pixel_khz); + + return sun60i_snps_phy_i2c_probe(hdmi, entry->data[0], + SUN60I_SNPS_MPLL_REG1); +} + +static bool sun60i_snps_phy_i2c_alive(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi, + unsigned int pixel_khz) +{ + bool ok; + + /* config_init() already left analog off and I2CM armed. */ + ok = sun60i_snps_phy_i2c_probe_mpll(hdmi, pixel_khz); + if (ok) + return true; + + sun60i_snps_phy_log_i2cm(hdmi, "I2CM probe failed"); + + sun60i_snps_phy_gen2_reset(dw_hdmi, hdmi); + sun60i_snps_phy_init_i2cm_hw(hdmi); + ok = sun60i_snps_phy_i2c_probe_mpll(hdmi, pixel_khz); + if (!ok) + sun60i_snps_phy_log_i2cm(hdmi, "I2CM probe failed retry"); + + return ok; +} + +static int sun60i_snps_program_mpll(struct sun60i_hdmi *hdmi, + unsigned int pixel_khz) +{ + const struct sun60i_snps_mpll_entry *entry = sun60i_snps_find_mpll(pixel_khz); + int ret; + + ret = sun60i_snps_phy_i2c_write_ms(hdmi, entry->data[0], + SUN60I_SNPS_MPLL_REG1, SUN60I_SNPS_I2C_XFER_MS); + if (ret) + return ret; + ret = sun60i_snps_phy_i2c_write_ms(hdmi, entry->data[1], + SUN60I_SNPS_MPLL_REG2, SUN60I_SNPS_I2C_XFER_MS); + if (ret) + return ret; + + return sun60i_snps_phy_i2c_write_ms(hdmi, entry->data[2], + SUN60I_SNPS_MPLL_REG3, SUN60I_SNPS_I2C_XFER_MS); +} + +static int sun60i_snps_program_drive(struct sun60i_hdmi *hdmi, + unsigned int tmds_khz) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(sun60i_snps_drive); i++) { + const struct sun60i_snps_drive_entry *e = &sun60i_snps_drive[i]; + int ret; + + if (tmds_khz < e->min_khz || tmds_khz >= e->max_khz) + continue; + + ret = sun60i_snps_phy_i2c_write_ms(hdmi, e->data[0], + SUN60I_SNPS_DRIVE_REG1, + SUN60I_SNPS_I2C_XFER_MS); + if (ret) + return ret; + ret = sun60i_snps_phy_i2c_write_ms(hdmi, e->data[1], + SUN60I_SNPS_DRIVE_REG2, + SUN60I_SNPS_I2C_XFER_MS); + if (ret) + return ret; + return sun60i_snps_phy_i2c_write_ms(hdmi, e->data[2], + SUN60I_SNPS_DRIVE_REG3, + SUN60I_SNPS_I2C_XFER_MS); + } + + return -EINVAL; +} + +void sun60i_hdmi_snps_phy_probe_init(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi) +{ + if (!dw_hdmi || !hdmi || !hdmi->regs) + return; + + /* + * BSP dw_phy_init(): I2C access + HPD sense. sun60i snps init skips + * reset/power in configure; interface is armed once here at bind. + */ + sun60i_snps_phy_prepare_interface(hdmi); + sun60i_snps_phy_init_i2cm_hw(hdmi); + sun60i_snps_phy_set_bit(hdmi, SUN60I_DW_HDMI_PHY_CONF0, + SUN60I_DW_HDMI_PHY_CONF0_ENHPDRXSENSE, true); +} + +int sun60i_hdmi_snps_phy_configure(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi, + const struct drm_display_mode *mode) +{ + unsigned int pixel_khz = mode->clock; + unsigned int tmds_khz = mode->clock; + bool i2c_ok; + int ret; + + if (!dw_hdmi || !hdmi || !hdmi->regs || !mode->clock) + return -EINVAL; + + sun60i_snps_phy_irq_begin(hdmi); + + /* + * BSP sun60i: skip PHY reset in init; top-PHY already configured. + * Internal PHY I2CM is often dead on A733 — probe once, else MMIO-only. + */ + sun60i_snps_phy_set_bit(hdmi, SUN60I_DW_HDMI_PHY_CONF0, + SUN60I_DW_HDMI_PHY_CONF0_ENTMDS, false); + + sun60i_snps_phy_config_init(dw_hdmi, hdmi); + sun60i_snps_phy_init_i2cm_hw(hdmi); + sun60i_snps_enable_mc_clocks(hdmi); + + i2c_ok = sun60i_snps_phy_i2c_alive(dw_hdmi, hdmi, pixel_khz); + if (i2c_ok) { + /* MPLL/drive while analog off (BSP snps_phy_config order). */ + ret = sun60i_snps_program_mpll(hdmi, pixel_khz); + if (ret) { + dev_warn(hdmi->dev, + "snps-phy: MPLL I2C failed (%d), continuing MMIO-only\n", + ret); + i2c_ok = false; + } + if (i2c_ok) { + ret = sun60i_snps_program_drive(hdmi, tmds_khz); + if (ret) { + dev_warn(hdmi->dev, + "snps-phy: drive I2C failed (%d), continuing MMIO-only\n", + ret); + i2c_ok = false; + } + } + } + + sun60i_snps_phy_config_svsret(hdmi); + sun60i_snps_phy_assert_power(dw_hdmi, hdmi, true); + + if (i2c_ok && !sun60i_snps_wait_internal_lock(hdmi, 500)) + dev_warn(hdmi->dev, + "snps-phy: internal lock timeout (stat0=0x%02x)\n", + readb(hdmi->regs + SUN60I_DW_HDMI_PHY_STAT0)); + + if (!sun60i_snps_wait_txready(dw_hdmi, hdmi)) + dev_warn(hdmi->dev, + "snps-phy: top-PHY txready timeout (conf0=0x%02x stat0=0x%02x)\n", + readb(hdmi->regs + SUN60I_DW_HDMI_PHY_CONF0), + readb(hdmi->regs + SUN60I_DW_HDMI_PHY_STAT0)); + + /* Always leave ENTMDS on even if txready never asserted. */ + sun60i_snps_phy_finish_tx(dw_hdmi, hdmi); + + mdelay(10); + + sun60i_snps_phy_irq_end(hdmi); + + return 0; +} + +void sun60i_hdmi_snps_phy_enable_tmds(struct sun60i_hdmi *hdmi) +{ + if (!hdmi || !hdmi->regs) + return; + + sun60i_snps_phy_set_bit(hdmi, SUN60I_DW_HDMI_PHY_CONF0, + SUN60I_DW_HDMI_PHY_CONF0_ENTMDS, true); +} + +void sun60i_hdmi_snps_phy_disable(struct dw_hdmi *dw_hdmi, + struct sun60i_hdmi *hdmi) +{ + if (!hdmi || !hdmi->regs) + return; + + /* + * BSP sun60i snps_phy_disconfig(): skip dw_phy_standby() — powering + * down the internal PHY between modesets breaks the next top-PHY path. + */ + sun60i_snps_phy_set_bit(hdmi, SUN60I_DW_HDMI_PHY_CONF0, + SUN60I_DW_HDMI_PHY_CONF0_ENTMDS, false); +} diff --git a/drivers/gpu/drm/sun4i/sun60i_hdmi_top_phy.c b/drivers/gpu/drm/sun4i/sun60i_hdmi_top_phy.c new file mode 100644 index 00000000000000..62fc2975280595 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun60i_hdmi_top_phy.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * sun60i top-PHY helper aligned with BSP phy_top.c init/config sequence. + */ + +#include +#include +#include +#include +#include +#include + +#include "sun60i_hdmi.h" + +/* DW-HDMI main controller register (byte); not in public dw_hdmi.h UAPI */ +#define SUN60I_DW_HDMI_MC_CLKDIS 0x4001 +#define SUN60I_DW_HDMI_FC_INVIDCONF 0x1000 +#define SUN60I_DW_HDMI_FC_AVICONF0 0x1005 +#define SUN60I_DW_HDMI_FC_GCP 0x1018 +#define SUN60I_DW_HDMI_MC_FLOWCTRL 0x4004 + +#define SUN60I_TOP_PHY_REG0000 0x0000 +#define SUN60I_TOP_PHY_REG0004 0x0004 +#define SUN60I_TOP_PHY_REG0010 0x0010 +#define SUN60I_TOP_PHY_REG0020 0x0020 +#define SUN60I_TOP_PHY_REG0024 0x0024 +#define SUN60I_TOP_PHY_REG0028 0x0028 +#define SUN60I_TOP_PHY_REG002c 0x002c +#define SUN60I_TOP_PHY_REG0030 0x0030 +#define SUN60I_TOP_PHY_REG0040 0x0040 + +#define SUN60I_RESCAL_RES0_OFFSET 0x4 + +/* BSP top_phy_power(ON): reset, pddq, txpwron, enhpdrxsense */ +#define SUN60I_TOP_PHY_PWR_ON (BIT(0) | BIT(1) | BIT(2) | BIT(4)) +#define SUN60I_TOP_PHY_TXPWRON BIT(2) +#define SUN60I_TOP_PHY_TXREADY BIT(0) +#define SUN60I_TOP_PHY_PLL_OUT_GATE BIT(27) +#define SUN60I_TOP_PHY_LOCK_ENABLE BIT(29) +#define SUN60I_TOP_PHY_PLL_LDO_EN BIT(30) +#define SUN60I_TOP_PHY_PLL_EN BIT(31) +#define SUN60I_TOP_PHY_LEVEL_SHIFTER BIT(7) +#define SUN60I_TOP_PHY_HDMI_OUTCLK_SEL BIT(8) +#define SUN60I_TOP_PHY_PLL_LOCKED BIT(0) + +#define SUN60I_RESCAL_HDMI_RES_SEL BIT(12) +#define SUN60I_RESCAL_MODE BIT(2) +#define SUN60I_RESCAL_ANA_EN BIT(1) +#define SUN60I_RESCAL_EN BIT(0) +#define SUN60I_RESCAL_RES0_MASK GENMASK(31, 24) + +#define SUN60I_PLLVCO_MIN_KHZ 1260000 +#define SUN60I_PLLVCO_MAX_KHZ 2250000 +#define SUN60I_PLL_AUTO_MAX_RESULT 5 + +struct sun60i_top_phy_pll { + unsigned long pixel_clock; + u32 pll_value; + u32 ldo_value; + u32 pll_pattern0; + u32 pll_pattern1; +}; + +struct sun60i_top_phy_pll_cal { + u32 pll_n; + u32 pll_m; + u32 pll_p; + u64 diff; +}; + +static const struct sun60i_top_phy_pll sun60i_top_phy_24m[] = { + { 13500, 0xe85f3500, 0x00035000, 0x00000000, 0x30000000 }, + { 27000, 0xe8576200, 0x00035000, 0x80000000, 0x30000000 }, + { 54000, 0xe82b6200, 0x00035000, 0x80000000, 0x30000000 }, + { 65000, 0xe8246200, 0x00035000, 0x80000000, 0x30000000 }, + { 74250, 0xe81f6200, 0x00035000, 0x80000000, 0x30000000 }, + { 148500, 0xe80f6200, 0x00035000, 0x80000000, 0x30000000 }, + { 185625, 0xe80f6200, 0x00035000, 0x80000000, 0x30000000 }, + { 297000, 0xe8076200, 0x00035000, 0x00000000, 0x30000000 }, + { 371250, 0xe8076200, 0x00035000, 0x00000000, 0x30000000 }, + { 594000, 0xe8036200, 0x00035000, 0x00000000, 0x30000000 }, +}; + +static const struct sun60i_top_phy_pll sun60i_top_phy_26m[] = { + { 13500, 0xe8673500, 0x00035000, 0x00000000, 0x30000000 }, + { 27000, 0xe8595c00, 0x00035000, 0x80000000, 0x30000000 }, + { 54000, 0xe80c1a00, 0x00035000, 0x80000000, 0x30000000 }, + { 65000, 0xe8235a00, 0x00035000, 0x80000000, 0x30000000 }, + { 74250, 0xe81f5a00, 0x00035000, 0x80000000, 0x30000000 }, + { 108000, 0xe80c3500, 0x00035000, 0x00000000, 0x30000000 }, + { 148500, 0xe80f5a00, 0x00035000, 0x80000000, 0x30000000 }, + { 185625, 0xe80f5a00, 0x00035000, 0x80000000, 0x30000000 }, + { 297000, 0xe807b602, 0x00035000, 0x00000000, 0x30000000 }, + { 371250, 0xe807b602, 0x00035000, 0x00000000, 0x30000000 }, + { 594000, 0xe803b602, 0x00035000, 0x00000000, 0x30000000 }, +}; + +static inline void sun60i_top_phy_write(struct sun60i_hdmi *hdmi, + u32 reg, u32 value) +{ + writel(value, hdmi->regs + SUN60I_HDMI_TOP_PHY_OFFSET + reg); +} + +static inline u32 sun60i_top_phy_read(struct sun60i_hdmi *hdmi, u32 reg) +{ + return readl(hdmi->regs + SUN60I_HDMI_TOP_PHY_OFFSET + reg); +} + +static unsigned long sun60i_top_phy_diff(unsigned long a, unsigned long b) +{ + return a > b ? a - b : b - a; +} + +static void sun60i_top_phy_pll_set_output(struct sun60i_hdmi *hdmi, bool on) +{ + u32 value = sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0020); + + if (on) + value |= SUN60I_TOP_PHY_PLL_OUT_GATE; + else + value &= ~SUN60I_TOP_PHY_PLL_OUT_GATE; + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0020, value); +} + +static void sun60i_top_phy_set_clock_select(struct sun60i_hdmi *hdmi, u8 sel) +{ + u32 value = sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0024); + + if (sel) + value |= SUN60I_TOP_PHY_HDMI_OUTCLK_SEL; + else + value &= ~SUN60I_TOP_PHY_HDMI_OUTCLK_SEL; + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0024, value); +} + +static void sun60i_top_phy_apply_pll_fields(struct sun60i_hdmi *hdmi, + u32 pll_value) +{ + u32 value = sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0020); + + value &= ~(BIT(1) | GENMASK(7, 5) | GENMASK(15, 8) | GENMASK(22, 16)); + value |= (pll_value & 0x00000002); + value |= (pll_value & 0x00000020); + value |= (pll_value & 0x000000c0); + value |= (pll_value & 0x0000ff00); + value |= (pll_value & 0x007f0000); + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0020, value); +} + +static int sun60i_top_phy_pll_auto_cal(unsigned long dcxo_khz, + unsigned long pixel_clock, + struct sun60i_top_phy_pll *cfg) +{ + struct sun60i_top_phy_pll_cal temp[SUN60I_PLL_AUTO_MAX_RESULT]; + struct sun60i_top_phy_pll_cal result = {}; + unsigned long min_diff = ULONG_MAX; + unsigned long dcxo_hz = dcxo_khz; + unsigned long target_clk = pixel_clock; + unsigned int temp_count = 0; + bool has_m1 = false, has_result = false; + unsigned long temp_vco_max = 0; + u16 temp_m, temp_n, temp_p; + unsigned int i; + + for (temp_m = 1; temp_m <= 2; temp_m++) { + for (temp_n = 1; temp_n <= 255; temp_n++) { + unsigned long temp_vco = (dcxo_hz * temp_n) / temp_m; + + if (temp_vco < SUN60I_PLLVCO_MIN_KHZ || + temp_vco > SUN60I_PLLVCO_MAX_KHZ) + continue; + + for (temp_p = 1; temp_p <= 128; temp_p++) { + unsigned long temp_clk = + (dcxo_hz * temp_n) / (temp_m * temp_p); + unsigned long temp_diff = + sun60i_top_phy_diff(temp_clk, target_clk); + + if (temp_diff < min_diff) + min_diff = temp_diff; + } + } + } + + for (temp_m = 1; temp_m <= 2; temp_m++) { + for (temp_n = 1; temp_n <= 255; temp_n++) { + unsigned long temp_vco = (dcxo_hz * temp_n) / temp_m; + + if (temp_vco < SUN60I_PLLVCO_MIN_KHZ || + temp_vco > SUN60I_PLLVCO_MAX_KHZ) + continue; + + for (temp_p = 1; temp_p <= 128; temp_p++) { + unsigned long temp_clk = + (dcxo_hz * temp_n) / (temp_m * temp_p); + unsigned long temp_diff = + sun60i_top_phy_diff(temp_clk, target_clk); + + if (temp_diff == min_diff && + temp_count < SUN60I_PLL_AUTO_MAX_RESULT) { + temp[temp_count].pll_n = temp_n; + temp[temp_count].pll_m = temp_m; + temp[temp_count].pll_p = temp_p; + temp[temp_count].diff = temp_diff; + temp_count++; + } + } + } + } + + if (!temp_count) + return -EINVAL; + + for (i = 0; i < temp_count; i++) { + if (temp[i].pll_m == 1) { + has_m1 = true; + break; + } + } + + for (i = 0; i < temp_count; i++) { + unsigned long temp_vco; + + if (has_m1 && temp[i].pll_m != 1) + continue; + + temp_vco = dcxo_hz * temp[i].pll_n; + if (temp_vco > temp_vco_max) { + temp_vco_max = temp_vco; + result = temp[i]; + has_result = true; + } + } + + if (!has_result) + return -EINVAL; + + cfg->pixel_clock = pixel_clock; + cfg->pll_value = 0xe8000000; + cfg->pll_value |= (result.pll_m - 1) << 1; + cfg->pll_value |= (result.pll_n - 1) << 8; + cfg->pll_value |= (result.pll_p - 1) << 16; + cfg->ldo_value = 0x00035000; + cfg->pll_pattern0 = 0x00000000; + cfg->pll_pattern1 = 0x30000000; + return 0; +} + +static int sun60i_top_phy_lookup_pll(const struct sun60i_top_phy_pll *table, + size_t count, unsigned long dcxo_khz, + unsigned long pixel_clock, + struct sun60i_top_phy_pll *cfg) +{ + unsigned long tolerance; + size_t i; + + for (i = 0; i < count; i++) { + tolerance = pixel_clock / 1000; + if (!tolerance) + tolerance = 1; + if (sun60i_top_phy_diff(pixel_clock, table[i].pixel_clock) <= + tolerance) { + *cfg = table[i]; + return 0; + } + } + + if (!sun60i_top_phy_pll_auto_cal(dcxo_khz, pixel_clock, cfg)) + return 0; + + for (i = 0; i + 1 < count; i++) { + if (pixel_clock > table[i].pixel_clock && + pixel_clock < table[i + 1].pixel_clock) { + if ((table[i + 1].pixel_clock - pixel_clock) > + (pixel_clock - table[i].pixel_clock)) + *cfg = table[i]; + else + *cfg = table[i + 1]; + return 0; + } + } + + *cfg = table[0]; + for (i = 1; i < count; i++) { + if (sun60i_top_phy_diff(pixel_clock, table[i].pixel_clock) < + sun60i_top_phy_diff(pixel_clock, cfg->pixel_clock)) + *cfg = table[i]; + } + + return 0; +} + +bool sun60i_hdmi_top_phy_wait_txready(struct sun60i_hdmi *hdmi, + unsigned int ms) +{ + unsigned int i; + + if (!hdmi->regs) + return false; + + for (i = 0; i < ms; i++) { + u32 stat = sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0010); + + if (stat & SUN60I_TOP_PHY_TXREADY) + return true; + udelay(1000); + } + + return false; +} + +int sun60i_hdmi_rescal_board_init(struct sun60i_hdmi *hdmi) +{ + u32 value; + + if (!hdmi->rescal_regs) + return 0; + + if (hdmi->resistor_src_onboard) { + value = readl(hdmi->rescal_regs); + value |= SUN60I_RESCAL_HDMI_RES_SEL; + writel(value, hdmi->rescal_regs); + + value = readl(hdmi->rescal_regs + SUN60I_RESCAL_RES0_OFFSET); + value &= ~SUN60I_RESCAL_RES0_MASK; + writel(value, hdmi->rescal_regs + SUN60I_RESCAL_RES0_OFFSET); + return 0; + } + + value = readl(hdmi->rescal_regs); + value &= ~SUN60I_RESCAL_EN; + writel(value, hdmi->rescal_regs); + + value &= ~SUN60I_RESCAL_HDMI_RES_SEL; + writel(value, hdmi->rescal_regs); + + value |= SUN60I_RESCAL_ANA_EN; + writel(value, hdmi->rescal_regs); + + value |= SUN60I_RESCAL_EN; + writel(value, hdmi->rescal_regs); + + udelay(200); + + value &= ~SUN60I_RESCAL_ANA_EN; + writel(value, hdmi->rescal_regs); + + value &= ~SUN60I_RESCAL_EN; + writel(value, hdmi->rescal_regs); + + return 0; +} + +int sun60i_hdmi_top_phy_init(struct sun60i_hdmi *hdmi) +{ + u32 value; + + if (!hdmi->regs) + return -ENODEV; + + if (hdmi->top_phy_inited) + return 0; + + /* BSP: top_phy_power(ON) */ + value = sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0000); + value |= SUN60I_TOP_PHY_PWR_ON; + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0000, value); + + /* Route TMDS to HDMI pads (not GPIO). */ + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0004, 0); + + /* BSP init: outgate off, then clock source select. */ + sun60i_top_phy_pll_set_output(hdmi, false); + sun60i_top_phy_set_clock_select(hdmi, hdmi->clock_src); + + hdmi->top_phy_inited = true; + return 0; +} + +int sun60i_hdmi_top_phy_config(struct sun60i_hdmi *hdmi, + const struct drm_display_mode *mode) +{ + const struct sun60i_top_phy_pll *table; + struct sun60i_top_phy_pll cfg = {}; + size_t table_count; + unsigned long dcxo_rate, dcxo_khz, pixel_clock; + u32 value; + int i, ret; + + if (!hdmi->clk_dcxo || !hdmi->regs) + return 0; + + /* BSP top_phy_config(): disable output gate first. */ + sun60i_top_phy_pll_set_output(hdmi, false); + + if (hdmi->clock_src) { + dev_dbg(hdmi->dev, "top-phy: CCMU clock source, skip PLL config\n"); + return 0; + } + + dcxo_rate = clk_get_rate(hdmi->clk_dcxo); + switch (dcxo_rate) { + case 24000000: + table = sun60i_top_phy_24m; + table_count = ARRAY_SIZE(sun60i_top_phy_24m); + break; + case 26000000: + table = sun60i_top_phy_26m; + table_count = ARRAY_SIZE(sun60i_top_phy_26m); + break; + default: + dev_warn(hdmi->dev, "unsupported dcxo clock %luHz for top-phy\n", + dcxo_rate); + return -EINVAL; + } + + dcxo_khz = dcxo_rate / 1000; + pixel_clock = mode->clock * (hdmi->pixel_repeat + 1); + + ret = sun60i_top_phy_lookup_pll(table, table_count, dcxo_khz, + pixel_clock, &cfg); + if (ret) + return ret; + + sun60i_top_phy_apply_pll_fields(hdmi, cfg.pll_value); + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0028, cfg.ldo_value); + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG002c, cfg.pll_pattern0); + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0030, cfg.pll_pattern1); + + value = sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0020); + if (!(value & SUN60I_TOP_PHY_PLL_EN)) { + value |= SUN60I_TOP_PHY_PLL_EN; + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0020, value); + } + + value = sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0020); + if (!(value & SUN60I_TOP_PHY_PLL_LDO_EN)) { + value |= SUN60I_TOP_PHY_PLL_LDO_EN; + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0020, value); + } + + value = sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0024); + value |= SUN60I_TOP_PHY_LEVEL_SHIFTER; + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0024, value); + + value = sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0020); + value |= SUN60I_TOP_PHY_LOCK_ENABLE; + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0020, value); + + for (i = 0; i < 200; i++) { + if (sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0040) & + SUN60I_TOP_PHY_PLL_LOCKED) + break; + udelay(100); + } + + if (i == 200) { + dev_warn(hdmi->dev, "top-phy lock timeout for %lu kHz mode\n", + pixel_clock); + return -ETIMEDOUT; + } + + udelay(20); + sun60i_top_phy_pll_set_output(hdmi, true); + + return 0; +} + +void sun60i_hdmi_top_phy_disable(struct sun60i_hdmi *hdmi) +{ + u32 value; + + if (!hdmi->regs) + return; + + sun60i_top_phy_pll_set_output(hdmi, false); + + value = sun60i_top_phy_read(hdmi, SUN60I_TOP_PHY_REG0020); + value &= ~(SUN60I_TOP_PHY_PLL_EN | SUN60I_TOP_PHY_PLL_LDO_EN | + SUN60I_TOP_PHY_LOCK_ENABLE); + sun60i_top_phy_write(hdmi, SUN60I_TOP_PHY_REG0020, value); +} diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c index 96532709c2a7ed..da6fecbc0836f6 100644 --- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c @@ -3,10 +3,16 @@ * Copyright (c) 2018 Jernej Skrabec */ +#include #include +#include +#include #include #include +#include +#include #include +#include #include #include @@ -14,6 +20,7 @@ #include "sun8i_dw_hdmi.h" #include "sun8i_tcon_top.h" +#include "sun60i_hdmi.h" static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, @@ -21,7 +28,16 @@ static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder, { struct sun8i_dw_hdmi *hdmi = encoder_to_sun8i_dw_hdmi(encoder); - clk_set_rate(hdmi->clk_tmds, mode->crtc_clock * 1000); + if (hdmi->quirks->integrated_phy) { + if (hdmi->sun60i.clk_tcon_tv) + clk_set_rate(hdmi->sun60i.clk_tcon_tv, + adj_mode->crtc_clock * 1000); + if (hdmi->sun60i.clk_hdmi) + clk_set_rate(hdmi->sun60i.clk_hdmi, + adj_mode->crtc_clock * 1000); + } else { + clk_set_rate(hdmi->clk_tmds, mode->crtc_clock * 1000); + } } static const struct drm_encoder_helper_funcs @@ -40,6 +56,237 @@ sun8i_dw_hdmi_mode_valid_a83t(struct dw_hdmi *hdmi, void *data, return MODE_OK; } +static enum drm_mode_status +sun8i_dw_hdmi_mode_valid_a733(struct dw_hdmi *hdmi, void *data, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->clock > 594000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static int sun8i_dw_hdmi_tcon_top_gate(struct device *dev, bool enable) +{ + struct device_node *remote; + struct platform_device *top_pdev; + int ret = -ENODEV; + + remote = of_graph_get_remote_node(dev->of_node, 0, -1); + if (!remote) + return ret; + + top_pdev = of_find_device_by_node(remote); + of_node_put(remote); + if (!top_pdev) + return ret; + + ret = sun8i_tcon_top_hdmi_gate_enable(&top_pdev->dev, enable); + put_device(&top_pdev->dev); + + return ret; +} + +static int sun8i_dw_hdmi_bind_sun60i(struct sun8i_dw_hdmi *hdmi, + struct platform_device *pdev, + struct drm_device *drm) +{ + struct device *dev = &pdev->dev; + struct sun60i_hdmi *s60 = &hdmi->sun60i; + struct resource *res; + struct dw_hdmi_plat_data *plat_data = &hdmi->plat_data; + int ret; + + s60->dev = dev; + + /* + * Share the HDMI MMIO window with dw_hdmi_bind(), which claims the + * platform resource via devm_ioremap_resource(). Use a non-exclusive + * map here so Top-PHY/SNPS helpers can reach +0xe0000 without -EBUSY. + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + s60->regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!s60->regs) + return -ENOMEM; + + s60->rescal_regs = devm_ioremap(dev, SUN60I_HDMI_RESCAL_PHYS, + SUN60I_HDMI_RESCAL_SIZE); + + s60->clk_cec = devm_clk_get_optional(dev, "clk_cec"); + if (IS_ERR(s60->clk_cec)) + return dev_err_probe(dev, PTR_ERR(s60->clk_cec), + "Couldn't get cec clock\n"); + + s60->clk_tcon_tv = devm_clk_get_optional(dev, "clk_tcon_tv"); + if (IS_ERR(s60->clk_tcon_tv)) + return dev_err_probe(dev, PTR_ERR(s60->clk_tcon_tv), + "Couldn't get tcon_tv clock\n"); + + s60->clk_hdmi = devm_clk_get(dev, "clk_hdmi"); + if (IS_ERR(s60->clk_hdmi)) + return dev_err_probe(dev, PTR_ERR(s60->clk_hdmi), + "Couldn't get hdmi clock\n"); + + s60->clk_hdmi_24m = devm_clk_get_optional(dev, "clk_hdmi_24M"); + if (IS_ERR(s60->clk_hdmi_24m)) + return dev_err_probe(dev, PTR_ERR(s60->clk_hdmi_24m), + "Couldn't get hdmi 24M clock\n"); + + s60->clk_hdcp = devm_clk_get_optional(dev, "clk_hdcp"); + if (IS_ERR(s60->clk_hdcp)) + return dev_err_probe(dev, PTR_ERR(s60->clk_hdcp), + "Couldn't get hdcp clock\n"); + + s60->clk_dcxo = devm_clk_get_optional(dev, "clk_dcxo"); + if (IS_ERR(s60->clk_dcxo)) + return dev_err_probe(dev, PTR_ERR(s60->clk_dcxo), + "Couldn't get dcxo clock\n"); + + s60->clk_isfr = devm_clk_get(dev, "isfr"); + if (IS_ERR(s60->clk_isfr)) + return dev_err_probe(dev, PTR_ERR(s60->clk_isfr), + "Couldn't get HDMI SFR clock\n"); + + s60->clk_iahb = devm_clk_get(dev, "iahb"); + if (IS_ERR(s60->clk_iahb)) + return dev_err_probe(dev, PTR_ERR(s60->clk_iahb), + "Couldn't get HDMI AHB clock\n"); + + s60->rst_bus_sub = devm_reset_control_get_optional(dev, "rst_bus_sub"); + if (IS_ERR(s60->rst_bus_sub)) + return dev_err_probe(dev, PTR_ERR(s60->rst_bus_sub), + "Couldn't get sub reset\n"); + + s60->rst_bus_main = devm_reset_control_get(dev, "rst_bus_main"); + if (IS_ERR(s60->rst_bus_main)) + return dev_err_probe(dev, PTR_ERR(s60->rst_bus_main), + "Couldn't get main reset\n"); + + s60->rst_bus_hdcp = devm_reset_control_get_optional(dev, "rst_bus_hdcp"); + if (IS_ERR(s60->rst_bus_hdcp)) + return dev_err_probe(dev, PTR_ERR(s60->rst_bus_hdcp), + "Couldn't get hdcp reset\n"); + + hdmi->rst_ctrl = s60->rst_bus_main; + + /* BSP opens the TCON HDMI path before the HDMI clocks and resets. */ + ret = sun8i_dw_hdmi_tcon_top_gate(dev, true); + if (ret) + return dev_err_probe(dev, ret, + "Couldn't enable TCON-top HDMI gate\n"); + + /* Match BSP/opentina: dcxo + clk_tcon_tv before MAIN/SUB reset. */ + ret = clk_prepare_enable(s60->clk_dcxo); + if (ret) + goto err_disable_tcon_gate; + + ret = clk_prepare_enable(s60->clk_tcon_tv); + if (ret) + goto err_disable_dcxo; + + ret = reset_control_deassert(s60->rst_bus_main); + if (ret) + goto err_disable_tcon_tv; + + ret = reset_control_deassert(s60->rst_bus_sub); + if (ret) + goto err_assert_main; + + ret = clk_prepare_enable(s60->clk_hdmi_24m); + if (ret) + goto err_assert_sub; + + ret = clk_prepare_enable(s60->clk_hdmi); + if (ret) + goto err_disable_hdmi24; + + ret = clk_prepare_enable(s60->clk_cec); + if (ret) + goto err_disable_hdmi; + + s60->clock_src = s60->clk_dcxo && + clk_get_rate(s60->clk_dcxo) > 25000000 ? 1 : 0; + + s60->irq = platform_get_irq(pdev, 0); + if (s60->irq < 0) { + ret = s60->irq; + goto err_disable_cec; + } + + /* Prefer the DT-provided DCXO as the HDMI SFR parent. */ + if (s60->clk_dcxo && clk_set_parent(s60->clk_isfr, s60->clk_dcxo)) + dev_warn(dev, "failed to set HDMI SFR clock parent\n"); + + ret = clk_prepare_enable(s60->clk_isfr); + if (ret) + goto err_disable_cec; + + ret = clk_prepare_enable(s60->clk_iahb); + if (ret) + goto err_disable_isfr; + + ret = sun60i_hdmi_rescal_board_init(s60); + if (ret) + goto err_disable_iahb; + + ret = sun60i_hdmi_top_phy_init(s60); + if (ret) + goto err_disable_iahb; + + plat_data->mode_valid = hdmi->quirks->mode_valid; + plat_data->use_drm_infoframe = hdmi->quirks->use_drm_infoframe; + plat_data->hpd_debounce_ms = 100; + plat_data->edid_retry_count = 5; + plat_data->edid_retry_delay_ms = 100; + plat_data->phy_ops = sun60i_hdmi_phy_ops_get(); + plat_data->phy_name = "sun60i_top_snps"; + plat_data->phy_data = s60; + /* CONFIG2_ID reads as vendor PHY on A733; force glue-layer ops. */ + plat_data->phy_force_vendor = true; + + platform_set_drvdata(pdev, hdmi); + + hdmi->hdmi = dw_hdmi_bind(pdev, &hdmi->encoder, plat_data); + if (IS_ERR(hdmi->hdmi)) { + ret = PTR_ERR(hdmi->hdmi); + goto err_disable_iahb; + } + + /* dw-hdmi now owns its runtime isfr/iahb clock references. */ + clk_disable_unprepare(s60->clk_iahb); + clk_disable_unprepare(s60->clk_isfr); + + sun60i_hdmi_snps_phy_probe_init(hdmi->hdmi, s60); + + return 0; + +err_disable_iahb: + clk_disable_unprepare(s60->clk_iahb); +err_disable_isfr: + clk_disable_unprepare(s60->clk_isfr); +err_disable_cec: + clk_disable_unprepare(s60->clk_cec); +err_disable_hdmi: + clk_disable_unprepare(s60->clk_hdmi); +err_disable_hdmi24: + clk_disable_unprepare(s60->clk_hdmi_24m); +err_assert_sub: + reset_control_assert(s60->rst_bus_sub); +err_assert_main: + reset_control_assert(s60->rst_bus_main); +err_disable_tcon_tv: + clk_disable_unprepare(s60->clk_tcon_tv); +err_disable_dcxo: + clk_disable_unprepare(s60->clk_dcxo); +err_disable_tcon_gate: + sun8i_dw_hdmi_tcon_top_gate(dev, false); + + return ret; +} + static enum drm_mode_status sun8i_dw_hdmi_mode_valid_h6(struct dw_hdmi *hdmi, void *data, const struct drm_display_info *info, @@ -128,6 +375,16 @@ static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master, if (encoder->possible_crtcs == 0) return -EPROBE_DEFER; + drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + + if (hdmi->quirks->integrated_phy) { + ret = sun8i_dw_hdmi_bind_sun60i(hdmi, pdev, drm); + if (ret) + drm_encoder_cleanup(encoder); + return ret; + } + hdmi->rst_ctrl = devm_reset_control_get(dev, "ctrl"); if (IS_ERR(hdmi->rst_ctrl)) return dev_err_probe(dev, PTR_ERR(hdmi->rst_ctrl), @@ -179,9 +436,6 @@ static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master, if (ret) goto err_disable_clk_tmds; - drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs); - drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); - plat_data->mode_valid = hdmi->quirks->mode_valid; plat_data->use_drm_infoframe = hdmi->quirks->use_drm_infoframe; sun8i_hdmi_phy_set_ops(hdmi->phy, plat_data); @@ -219,6 +473,22 @@ static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master, struct sun8i_dw_hdmi *hdmi = dev_get_drvdata(dev); dw_hdmi_unbind(hdmi->hdmi); + + if (hdmi->quirks->integrated_phy) { + struct sun60i_hdmi *s60 = &hdmi->sun60i; + + /* HDCP left off intentionally (see bind_sun60i). */ + clk_disable_unprepare(s60->clk_cec); + clk_disable_unprepare(s60->clk_hdmi); + clk_disable_unprepare(s60->clk_hdmi_24m); + reset_control_assert(s60->rst_bus_sub); + reset_control_assert(s60->rst_bus_main); + clk_disable_unprepare(s60->clk_tcon_tv); + clk_disable_unprepare(s60->clk_dcxo); + sun8i_dw_hdmi_tcon_top_gate(dev, false); + return; + } + sun8i_hdmi_phy_deinit(hdmi->phy); clk_disable_unprepare(hdmi->clk_tmds); reset_control_assert(hdmi->rst_ctrl); @@ -249,6 +519,12 @@ static const struct sun8i_dw_hdmi_quirks sun50i_h6_quirks = { .use_drm_infoframe = true, }; +static const struct sun8i_dw_hdmi_quirks sun60i_a733_quirks = { + .mode_valid = sun8i_dw_hdmi_mode_valid_a733, + .use_drm_infoframe = true, + .integrated_phy = true, +}; + static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = { { .compatible = "allwinner,sun8i-a83t-dw-hdmi", @@ -258,6 +534,10 @@ static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = { .compatible = "allwinner,sun50i-h6-dw-hdmi", .data = &sun50i_h6_quirks, }, + { + .compatible = "allwinner,sun60i-a733-dw-hdmi", + .data = &sun60i_a733_quirks, + }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids); diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h index ab80d52a70bbee..8c4d1d5d3f0524 100644 --- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h @@ -13,6 +13,8 @@ #include #include +#include "sun60i_hdmi.h" + #define SUN8I_HDMI_PHY_DBG_CTRL_REG 0x0000 #define SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK BIT(0) #define SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK GENMASK(15, 8) @@ -175,6 +177,7 @@ struct sun8i_dw_hdmi_quirks { const struct drm_display_info *info, const struct drm_display_mode *mode); unsigned int use_drm_infoframe : 1; + unsigned int integrated_phy : 1; }; struct sun8i_dw_hdmi { @@ -187,6 +190,7 @@ struct sun8i_dw_hdmi { struct regulator *regulator; const struct sun8i_dw_hdmi_quirks *quirks; struct reset_control *rst_ctrl; + struct sun60i_hdmi sun60i; }; extern struct platform_driver sun8i_hdmi_phy_driver; diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c index 8adda578c51ba4..be9f531b4a127a 100644 --- a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c +++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c @@ -16,8 +16,10 @@ #include "sun8i_tcon_top.h" struct sun8i_tcon_top_quirks { + bool has_tcon_tv0; bool has_tcon_tv1; bool has_dsi; + bool has_legacy_mux; }; static bool sun8i_tcon_top_node_is_tcon_top(struct device_node *node) @@ -36,6 +38,11 @@ int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon) return -EINVAL; } + /* A733 uses bit 28 as the TV0 HDMI gate, not as a source mux. */ + if (of_device_is_compatible(dev->of_node, + "allwinner,sun60i-a733-tcon-top")) + return sun8i_tcon_top_hdmi_gate_enable(dev, true); + if (tcon < 2 || tcon > 3) { dev_err(dev, "TCON index must be 2 or 3!\n"); return -EINVAL; @@ -54,9 +61,48 @@ int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon) } EXPORT_SYMBOL(sun8i_tcon_top_set_hdmi_src); +int sun8i_tcon_top_hdmi_gate_enable(struct device *dev, bool enable) +{ + struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev); + unsigned long flags; + u32 val, setup; + + if (!tcon_top || !sun8i_tcon_top_node_is_tcon_top(dev->of_node)) + return -EINVAL; + + spin_lock_irqsave(&tcon_top->reg_lock, flags); + + /* Match BSP tcon_top_hdmi_set_gate() for sun60iw2. */ + val = readl(tcon_top->regs + TCON_TOP_GATE_SRC_REG); + if (enable) { + val |= BIT(TCON_TOP_TCON_TV0_GATE); + val |= BIT(TCON_TOP_TV0_HDMI_GATE); + } else { + val &= ~BIT(TCON_TOP_TV0_HDMI_GATE); + } + writel(val, tcon_top->regs + TCON_TOP_GATE_SRC_REG); + + /* + * HDMI path (15.2.9.1): TV0 from HDMI (bit1=0), pixel clk from + * HDMI PHY (bit3=0), CCMU as TV0_CLK_SRC (bit0=0). Matches REF SETUP=0. + */ + setup = readl(tcon_top->regs + TCON_TOP_TCON_TV_SETUP_REG); + if (enable) + setup &= ~(TCON_TOP_TV0_CLK_SRC_CVBS | + TCON_TOP_TV0_EDP_HDMI_CK_SEL | + TCON_TOP_TV0_HDMIPHY_CCU_CK_SEL); + writel(setup, tcon_top->regs + TCON_TOP_TCON_TV_SETUP_REG); + + spin_unlock_irqrestore(&tcon_top->reg_lock, flags); + + return 0; +} +EXPORT_SYMBOL(sun8i_tcon_top_hdmi_gate_enable); + int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon) { struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev); + const struct sun8i_tcon_top_quirks *quirks; unsigned long flags; u32 reg; @@ -65,6 +111,10 @@ int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon) return -EINVAL; } + quirks = of_device_get_match_data(dev); + if (!quirks || !quirks->has_legacy_mux) + return 0; + if (mixer > 1) { dev_err(dev, "Mixer index is too high!\n"); return -EINVAL; @@ -146,41 +196,76 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master, spin_lock_init(&tcon_top->reg_lock); - tcon_top->rst = devm_reset_control_get(dev, NULL); - if (IS_ERR(tcon_top->rst)) { - dev_err(dev, "Couldn't get our reset line\n"); - return PTR_ERR(tcon_top->rst); + /* + * H6/R40 use a single unnamed reset. A733 VO1 also needs + * RST_BUS_VIDEO_OUT1 ("rst_bus_reg") for the HDMI APB fabric. + */ + tcon_top->rst = devm_reset_control_get_optional(dev, "bus"); + if (IS_ERR(tcon_top->rst)) + return dev_err_probe(dev, PTR_ERR(tcon_top->rst), + "Couldn't get bus reset\n"); + if (!tcon_top->rst) { + tcon_top->rst = devm_reset_control_get(dev, NULL); + if (IS_ERR(tcon_top->rst)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(tcon_top->rst); + } } + tcon_top->rst_reg = devm_reset_control_get_optional(dev, "rst_bus_reg"); + if (IS_ERR(tcon_top->rst_reg)) + return PTR_ERR(tcon_top->rst_reg); + tcon_top->bus = devm_clk_get(dev, "bus"); if (IS_ERR(tcon_top->bus)) { dev_err(dev, "Couldn't get the bus clock\n"); return PTR_ERR(tcon_top->bus); } + tcon_top->ahb_gate = devm_clk_get_optional(dev, "ahb-gate"); + if (IS_ERR(tcon_top->ahb_gate)) + return PTR_ERR(tcon_top->ahb_gate); + regs = devm_platform_ioremap_resource(pdev, 0); tcon_top->regs = regs; if (IS_ERR(regs)) return PTR_ERR(regs); + if (tcon_top->rst_reg) { + ret = reset_control_deassert(tcon_top->rst_reg); + if (ret) { + dev_err(dev, "Could not deassert rst_bus_reg\n"); + return ret; + } + } + ret = reset_control_deassert(tcon_top->rst); if (ret) { dev_err(dev, "Could not deassert ctrl reset control\n"); - return ret; + goto err_assert_rst_reg; + } + + ret = clk_prepare_enable(tcon_top->ahb_gate); + if (ret) { + dev_err(dev, "Could not enable ahb-gate clock\n"); + goto err_assert_reset; } ret = clk_prepare_enable(tcon_top->bus); if (ret) { dev_err(dev, "Could not enable bus clock\n"); - goto err_assert_reset; + goto err_disable_ahb_gate; } /* * At least on H6, some registers have some bits set by default - * which may cause issues. Clear them here. + * which may cause issues. Clear them here. A733 VO0/VO1 uses a + * different register layout, so preserve the firmware state there. */ - writel(0, regs + TCON_TOP_PORT_SEL_REG); - writel(0, regs + TCON_TOP_GATE_SRC_REG); + if (quirks->has_legacy_mux) { + writel(0, regs + TCON_TOP_PORT_SEL_REG); + writel(0, regs + TCON_TOP_GATE_SRC_REG); + } /* * TCON TOP has two muxes, which select parent clock for each TCON TV @@ -191,10 +276,11 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master, * to TVE clock parent. */ i = 0; - clk_data->hws[CLK_TCON_TOP_TV0] = - sun8i_tcon_top_register_gate(dev, "tcon-tv0", regs, - &tcon_top->reg_lock, - TCON_TOP_TCON_TV0_GATE, i++); + if (quirks->has_tcon_tv0) + clk_data->hws[CLK_TCON_TOP_TV0] = + sun8i_tcon_top_register_gate(dev, "tcon-tv0", regs, + &tcon_top->reg_lock, + TCON_TOP_TCON_TV0_GATE, i++); if (quirks->has_tcon_tv1) clk_data->hws[CLK_TCON_TOP_TV1] = @@ -214,10 +300,12 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master, goto err_unregister_gates; } - ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, - clk_data); - if (ret) - goto err_unregister_gates; + if (of_property_present(dev->of_node, "#clock-cells")) { + ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, + clk_data); + if (ret) + goto err_unregister_gates; + } dev_set_drvdata(dev, tcon_top); @@ -228,8 +316,13 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master, if (!IS_ERR_OR_NULL(clk_data->hws[i])) clk_hw_unregister_gate(clk_data->hws[i]); clk_disable_unprepare(tcon_top->bus); +err_disable_ahb_gate: + clk_disable_unprepare(tcon_top->ahb_gate); err_assert_reset: reset_control_assert(tcon_top->rst); +err_assert_rst_reg: + if (tcon_top->rst_reg) + reset_control_assert(tcon_top->rst_reg); return ret; } @@ -241,13 +334,17 @@ static void sun8i_tcon_top_unbind(struct device *dev, struct device *master, struct clk_hw_onecell_data *clk_data = tcon_top->clk_data; int i; - of_clk_del_provider(dev->of_node); + if (of_property_present(dev->of_node, "#clock-cells")) + of_clk_del_provider(dev->of_node); for (i = 0; i < CLK_NUM; i++) if (clk_data->hws[i]) clk_hw_unregister_gate(clk_data->hws[i]); clk_disable_unprepare(tcon_top->bus); + clk_disable_unprepare(tcon_top->ahb_gate); reset_control_assert(tcon_top->rst); + if (tcon_top->rst_reg) + reset_control_assert(tcon_top->rst_reg); } static const struct component_ops sun8i_tcon_top_ops = { @@ -266,16 +363,25 @@ static void sun8i_tcon_top_remove(struct platform_device *pdev) } static const struct sun8i_tcon_top_quirks sun8i_r40_tcon_top_quirks = { + .has_tcon_tv0 = true, .has_tcon_tv1 = true, .has_dsi = true, + .has_legacy_mux = true, }; static const struct sun8i_tcon_top_quirks sun20i_d1_tcon_top_quirks = { + .has_tcon_tv0 = true, .has_dsi = true, + .has_legacy_mux = true, }; static const struct sun8i_tcon_top_quirks sun50i_h6_tcon_top_quirks = { - /* Nothing special */ + .has_tcon_tv0 = true, + .has_legacy_mux = true, +}; + +static const struct sun8i_tcon_top_quirks sun60i_a733_tv_top_quirks = { + .has_tcon_tv0 = true, }; /* sun4i_drv uses this list to check if a device node is a TCON TOP */ @@ -292,6 +398,10 @@ const struct of_device_id sun8i_tcon_top_of_table[] = { .compatible = "allwinner,sun50i-h6-tcon-top", .data = &sun50i_h6_tcon_top_quirks }, + { + .compatible = "allwinner,sun60i-a733-tcon-top", + .data = &sun60i_a733_tv_top_quirks + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sun8i_tcon_top_of_table); diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.h b/drivers/gpu/drm/sun4i/sun8i_tcon_top.h index 0390584a330e50..02b04c1e5f49ad 100644 --- a/drivers/gpu/drm/sun4i/sun8i_tcon_top.h +++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.h @@ -9,7 +9,11 @@ #include #include +/* VIDEO_OUT1_SYS @0x00 — A733 User Manual 15.2.9.1 */ #define TCON_TOP_TCON_TV_SETUP_REG 0x00 +#define TCON_TOP_TV0_CLK_SRC_CVBS BIT(0) /* 0=CCMU, 1=CVBS */ +#define TCON_TOP_TV0_EDP_HDMI_CK_SEL BIT(1) /* 0=HDMI, 1=EDP */ +#define TCON_TOP_TV0_HDMIPHY_CCU_CK_SEL BIT(3) /* 0=HDMI PHY, 1=CCU */ #define TCON_TOP_PORT_SEL_REG 0x1C #define TCON_TOP_PORT_DE0_MSK GENMASK(1, 0) @@ -20,14 +24,18 @@ #define TCON_TOP_TCON_TV1_GATE 24 #define TCON_TOP_TCON_TV0_GATE 20 #define TCON_TOP_TCON_DSI_GATE 16 +/* A733/sun60iw2: bit28 is HDMI pixel-path gate, not H6 hdmi_src mux. */ +#define TCON_TOP_TV0_HDMI_GATE 28 #define CLK_NUM 3 struct sun8i_tcon_top { struct clk *bus; + struct clk *ahb_gate; struct clk_hw_onecell_data *clk_data; void __iomem *regs; struct reset_control *rst; + struct reset_control *rst_reg; /* * spinlock is used to synchronize access to same @@ -40,5 +48,6 @@ extern const struct of_device_id sun8i_tcon_top_of_table[]; int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon); int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon); +int sun8i_tcon_top_hdmi_gate_enable(struct device *dev, bool enable); #endif /* _SUN8I_TCON_TOP_H_ */ diff --git a/drivers/gpu/drm/sun4i/sunxi_engine.h b/drivers/gpu/drm/sun4i/sunxi_engine.h index ec0c4932f15cf3..35e6515ec88c4c 100644 --- a/drivers/gpu/drm/sun4i/sunxi_engine.h +++ b/drivers/gpu/drm/sun4i/sunxi_engine.h @@ -7,6 +7,7 @@ #define _SUNXI_ENGINE_H_ struct drm_plane; +struct drm_atomic_state; struct drm_crtc; struct drm_device; struct drm_crtc_state; @@ -123,6 +124,23 @@ struct sunxi_engine_ops { */ void (*mode_set)(struct sunxi_engine *engine, const struct drm_display_mode *mode); + + /** + * @quiesce: + * + * Stop the engine from scanning out memory before the CRTC + * is disabled. Optional. + */ + void (*quiesce)(struct sunxi_engine *engine, struct drm_crtc *crtc); + + /** + * @enable: + * + * (Re)initialize the engine before the TCON is started. + * Optional. + */ + void (*enable)(struct sunxi_engine *engine, + const struct drm_display_mode *mode); }; /** @@ -215,4 +233,29 @@ sunxi_engine_mode_set(struct sunxi_engine *engine, if (engine->ops && engine->ops->mode_set) engine->ops->mode_set(engine, mode); } + +/** + * sunxi_engine_quiesce() - stop scanout before disabling the timing controller + * @engine: engine attached to the CRTC + * @crtc: CRTC being disabled + */ +static inline void +sunxi_engine_quiesce(struct sunxi_engine *engine, struct drm_crtc *crtc) +{ + if (engine->ops && engine->ops->quiesce) + engine->ops->quiesce(engine, crtc); +} + +/** + * sunxi_engine_enable() - prepare scanout before starting the timing controller + * @engine: engine attached to the CRTC + * @mode: adjusted display mode + */ +static inline void +sunxi_engine_enable(struct sunxi_engine *engine, + const struct drm_display_mode *mode) +{ + if (engine->ops && engine->ops->enable) + engine->ops->enable(engine, mode); +} #endif /* _SUNXI_ENGINE_H_ */ diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index 6a46baa0737cd0..597cbd0159859c 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -127,6 +127,11 @@ struct dw_hdmi_plat_data { struct regmap *regm; unsigned int output_port; + /* Optional sink settling delays; zero preserves the default behavior. */ + unsigned int hpd_debounce_ms; + /* Number of additional DDC attempts after the initial EDID read. */ + unsigned int edid_retry_count; + unsigned int edid_retry_delay_ms; unsigned long input_bus_encoding; bool use_drm_infoframe; diff --git a/include/dt-bindings/clock/sun60i-de352.h b/include/dt-bindings/clock/sun60i-de352.h new file mode 100644 index 00000000000000..59dab9de696abb --- /dev/null +++ b/include/dt-bindings/clock/sun60i-de352.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * Clock indices for Allwinner DE352 DETOP (A733): regs at DE+0x8000. + */ + +#ifndef _DT_BINDINGS_CLOCK_SUN60I_DE352_H_ +#define _DT_BINDINGS_CLOCK_SUN60I_DE352_H_ + +#define CLK_MIXER0 0 +#define CLK_BUS_MIXER0 1 +#define CLK_MIXER1 2 + +#endif /* _DT_BINDINGS_CLOCK_SUN60I_DE352_H_ */ diff --git a/include/dt-bindings/reset/sun60i-de352.h b/include/dt-bindings/reset/sun60i-de352.h new file mode 100644 index 00000000000000..05586076c18aaf --- /dev/null +++ b/include/dt-bindings/reset/sun60i-de352.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * Reset indices for Allwinner DE352 DETOP (A733): regs at DE+0x8000. + */ + +#ifndef _DT_BINDINGS_RESET_SUN60I_DE352_H_ +#define _DT_BINDINGS_RESET_SUN60I_DE352_H_ + +#define RST_MIXER0 0 +#define RST_MIXER1 1 + +#endif /* _DT_BINDINGS_RESET_SUN60I_DE352_H_ */